NotificationListenerService.java revision bdf3376616c276ed18a51185351b44fd16eeae29
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.annotation.SystemApi; 20import android.annotation.SdkConstant; 21import android.app.INotificationManager; 22import android.app.Notification; 23import android.app.Notification.Builder; 24import android.app.NotificationManager; 25import android.app.Service; 26import android.content.ComponentName; 27import android.content.Context; 28import android.content.Intent; 29import android.content.pm.ParceledListSlice; 30import android.graphics.drawable.BitmapDrawable; 31import android.graphics.drawable.Drawable; 32import android.graphics.drawable.Icon; 33import android.graphics.Bitmap; 34import android.os.Build; 35import android.os.Bundle; 36import android.os.IBinder; 37import android.os.Parcel; 38import android.os.Parcelable; 39import android.os.RemoteException; 40import android.os.ServiceManager; 41import android.util.ArrayMap; 42import android.util.ArraySet; 43import android.util.Log; 44 45import java.util.ArrayList; 46import java.util.Collections; 47import java.util.List; 48 49/** 50 * A service that receives calls from the system when new notifications are 51 * posted or removed, or their ranking changed. 52 * <p>To extend this class, you must declare the service in your manifest file with 53 * the {@link android.Manifest.permission#BIND_NOTIFICATION_LISTENER_SERVICE} permission 54 * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p> 55 * <pre> 56 * <service android:name=".NotificationListener" 57 * android:label="@string/service_name" 58 * android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"> 59 * <intent-filter> 60 * <action android:name="android.service.notification.NotificationListenerService" /> 61 * </intent-filter> 62 * </service></pre> 63 */ 64public abstract class NotificationListenerService extends Service { 65 // TAG = "NotificationListenerService[MySubclass]" 66 private final String TAG = NotificationListenerService.class.getSimpleName() 67 + "[" + getClass().getSimpleName() + "]"; 68 69 /** 70 * {@link #getCurrentInterruptionFilter() Interruption filter} constant - 71 * Normal interruption filter. 72 */ 73 public static final int INTERRUPTION_FILTER_ALL 74 = NotificationManager.INTERRUPTION_FILTER_ALL; 75 76 /** 77 * {@link #getCurrentInterruptionFilter() Interruption filter} constant - 78 * Priority interruption filter. 79 */ 80 public static final int INTERRUPTION_FILTER_PRIORITY 81 = NotificationManager.INTERRUPTION_FILTER_PRIORITY; 82 83 /** 84 * {@link #getCurrentInterruptionFilter() Interruption filter} constant - 85 * No interruptions filter. 86 */ 87 public static final int INTERRUPTION_FILTER_NONE 88 = NotificationManager.INTERRUPTION_FILTER_NONE; 89 90 /** 91 * {@link #getCurrentInterruptionFilter() Interruption filter} constant - 92 * Alarms only interruption filter. 93 */ 94 public static final int INTERRUPTION_FILTER_ALARMS 95 = NotificationManager.INTERRUPTION_FILTER_ALARMS; 96 97 /** {@link #getCurrentInterruptionFilter() Interruption filter} constant - returned when 98 * the value is unavailable for any reason. For example, before the notification listener 99 * is connected. 100 * 101 * {@see #onListenerConnected()} 102 */ 103 public static final int INTERRUPTION_FILTER_UNKNOWN 104 = NotificationManager.INTERRUPTION_FILTER_UNKNOWN; 105 106 /** {@link #getCurrentListenerHints() Listener hints} constant - the primary device UI 107 * should disable notification sound, vibrating and other visual or aural effects. 108 * This does not change the interruption filter, only the effects. **/ 109 public static final int HINT_HOST_DISABLE_EFFECTS = 1; 110 111 public static final int SUPPRESSED_EFFECT_LIGHTS = 112 NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; 113 public static final int SUPPRESSED_EFFECT_PEEK = 114 NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; 115 116 /** 117 * The full trim of the StatusBarNotification including all its features. 118 * 119 * @hide 120 */ 121 @SystemApi 122 public static final int TRIM_FULL = 0; 123 124 /** 125 * A light trim of the StatusBarNotification excluding the following features: 126 * 127 * <ol> 128 * <li>{@link Notification#tickerView tickerView}</li> 129 * <li>{@link Notification#contentView contentView}</li> 130 * <li>{@link Notification#largeIcon largeIcon}</li> 131 * <li>{@link Notification#bigContentView bigContentView}</li> 132 * <li>{@link Notification#headsUpContentView headsUpContentView}</li> 133 * <li>{@link Notification#EXTRA_LARGE_ICON extras[EXTRA_LARGE_ICON]}</li> 134 * <li>{@link Notification#EXTRA_LARGE_ICON_BIG extras[EXTRA_LARGE_ICON_BIG]}</li> 135 * <li>{@link Notification#EXTRA_PICTURE extras[EXTRA_PICTURE]}</li> 136 * <li>{@link Notification#EXTRA_BIG_TEXT extras[EXTRA_BIG_TEXT]}</li> 137 * </ol> 138 * 139 * @hide 140 */ 141 @SystemApi 142 public static final int TRIM_LIGHT = 1; 143 144 private INotificationListenerWrapper mWrapper = null; 145 private RankingMap mRankingMap; 146 147 private INotificationManager mNoMan; 148 149 /** Only valid after a successful call to (@link registerAsService}. */ 150 private int mCurrentUser; 151 152 153 // This context is required for system services since NotificationListenerService isn't 154 // started as a real Service and hence no context is available. 155 private Context mSystemContext; 156 157 /** 158 * The {@link Intent} that must be declared as handled by the service. 159 */ 160 @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) 161 public static final String SERVICE_INTERFACE 162 = "android.service.notification.NotificationListenerService"; 163 164 /** 165 * Implement this method to learn about new notifications as they are posted by apps. 166 * 167 * @param sbn A data structure encapsulating the original {@link android.app.Notification} 168 * object as well as its identifying information (tag and id) and source 169 * (package name). 170 */ 171 public void onNotificationPosted(StatusBarNotification sbn) { 172 // optional 173 } 174 175 /** 176 * Implement this method to learn about new notifications as they are posted by apps. 177 * 178 * @param sbn A data structure encapsulating the original {@link android.app.Notification} 179 * object as well as its identifying information (tag and id) and source 180 * (package name). 181 * @param rankingMap The current ranking map that can be used to retrieve ranking information 182 * for active notifications, including the newly posted one. 183 */ 184 public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) { 185 onNotificationPosted(sbn); 186 } 187 188 /** 189 * Implement this method to learn when notifications are removed. 190 * <P> 191 * This might occur because the user has dismissed the notification using system UI (or another 192 * notification listener) or because the app has withdrawn the notification. 193 * <P> 194 * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the 195 * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight 196 * fields such as {@link android.app.Notification#contentView} and 197 * {@link android.app.Notification#largeIcon}. However, all other fields on 198 * {@link StatusBarNotification}, sufficient to match this call with a prior call to 199 * {@link #onNotificationPosted(StatusBarNotification)}, will be intact. 200 * 201 * @param sbn A data structure encapsulating at least the original information (tag and id) 202 * and source (package name) used to post the {@link android.app.Notification} that 203 * was just removed. 204 */ 205 public void onNotificationRemoved(StatusBarNotification sbn) { 206 // optional 207 } 208 209 /** 210 * Implement this method to learn when notifications are removed. 211 * <P> 212 * This might occur because the user has dismissed the notification using system UI (or another 213 * notification listener) or because the app has withdrawn the notification. 214 * <P> 215 * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the 216 * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight 217 * fields such as {@link android.app.Notification#contentView} and 218 * {@link android.app.Notification#largeIcon}. However, all other fields on 219 * {@link StatusBarNotification}, sufficient to match this call with a prior call to 220 * {@link #onNotificationPosted(StatusBarNotification)}, will be intact. 221 * 222 * @param sbn A data structure encapsulating at least the original information (tag and id) 223 * and source (package name) used to post the {@link android.app.Notification} that 224 * was just removed. 225 * @param rankingMap The current ranking map that can be used to retrieve ranking information 226 * for active notifications. 227 * 228 */ 229 public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) { 230 onNotificationRemoved(sbn); 231 } 232 233 /** 234 * Implement this method to learn about when the listener is enabled and connected to 235 * the notification manager. You are safe to call {@link #getActiveNotifications()} 236 * at this time. 237 */ 238 public void onListenerConnected() { 239 // optional 240 } 241 242 /** 243 * Implement this method to be notified when the notification ranking changes. 244 * 245 * @param rankingMap The current ranking map that can be used to retrieve ranking information 246 * for active notifications. 247 */ 248 public void onNotificationRankingUpdate(RankingMap rankingMap) { 249 // optional 250 } 251 252 /** 253 * Implement this method to be notified when the 254 * {@link #getCurrentListenerHints() Listener hints} change. 255 * 256 * @param hints The current {@link #getCurrentListenerHints() listener hints}. 257 */ 258 public void onListenerHintsChanged(int hints) { 259 // optional 260 } 261 262 /** 263 * Implement this method to be notified when the 264 * {@link #getCurrentInterruptionFilter() interruption filter} changed. 265 * 266 * @param interruptionFilter The current 267 * {@link #getCurrentInterruptionFilter() interruption filter}. 268 */ 269 public void onInterruptionFilterChanged(int interruptionFilter) { 270 // optional 271 } 272 273 private final INotificationManager getNotificationInterface() { 274 if (mNoMan == null) { 275 mNoMan = INotificationManager.Stub.asInterface( 276 ServiceManager.getService(Context.NOTIFICATION_SERVICE)); 277 } 278 return mNoMan; 279 } 280 281 /** 282 * Inform the notification manager about dismissal of a single notification. 283 * <p> 284 * Use this if your listener has a user interface that allows the user to dismiss individual 285 * notifications, similar to the behavior of Android's status bar and notification panel. 286 * It should be called after the user dismisses a single notification using your UI; 287 * upon being informed, the notification manager will actually remove the notification 288 * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback. 289 * <P> 290 * <b>Note:</b> If your listener allows the user to fire a notification's 291 * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call 292 * this method at that time <i>if</i> the Notification in question has the 293 * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set. 294 * 295 * @param pkg Package of the notifying app. 296 * @param tag Tag of the notification as specified by the notifying app in 297 * {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}. 298 * @param id ID of the notification as specified by the notifying app in 299 * {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}. 300 * <p> 301 * @deprecated Use {@link #cancelNotification(String key)} 302 * instead. Beginning with {@link android.os.Build.VERSION_CODES#LOLLIPOP} this method will no longer 303 * cancel the notification. It will continue to cancel the notification for applications 304 * whose {@code targetSdkVersion} is earlier than {@link android.os.Build.VERSION_CODES#LOLLIPOP}. 305 */ 306 public final void cancelNotification(String pkg, String tag, int id) { 307 if (!isBound()) return; 308 try { 309 getNotificationInterface().cancelNotificationFromListener( 310 mWrapper, pkg, tag, id); 311 } catch (android.os.RemoteException ex) { 312 Log.v(TAG, "Unable to contact notification manager", ex); 313 } 314 } 315 316 /** 317 * Inform the notification manager about dismissal of a single notification. 318 * <p> 319 * Use this if your listener has a user interface that allows the user to dismiss individual 320 * notifications, similar to the behavior of Android's status bar and notification panel. 321 * It should be called after the user dismisses a single notification using your UI; 322 * upon being informed, the notification manager will actually remove the notification 323 * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback. 324 * <P> 325 * <b>Note:</b> If your listener allows the user to fire a notification's 326 * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call 327 * this method at that time <i>if</i> the Notification in question has the 328 * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set. 329 * <p> 330 * @param key Notification to dismiss from {@link StatusBarNotification#getKey()}. 331 */ 332 public final void cancelNotification(String key) { 333 if (!isBound()) return; 334 try { 335 getNotificationInterface().cancelNotificationsFromListener(mWrapper, 336 new String[] { key }); 337 } catch (android.os.RemoteException ex) { 338 Log.v(TAG, "Unable to contact notification manager", ex); 339 } 340 } 341 342 /** 343 * Inform the notification manager about dismissal of all notifications. 344 * <p> 345 * Use this if your listener has a user interface that allows the user to dismiss all 346 * notifications, similar to the behavior of Android's status bar and notification panel. 347 * It should be called after the user invokes the "dismiss all" function of your UI; 348 * upon being informed, the notification manager will actually remove all active notifications 349 * and you will get multiple {@link #onNotificationRemoved(StatusBarNotification)} callbacks. 350 * 351 * {@see #cancelNotification(String, String, int)} 352 */ 353 public final void cancelAllNotifications() { 354 cancelNotifications(null /*all*/); 355 } 356 357 /** 358 * Inform the notification manager about dismissal of specific notifications. 359 * <p> 360 * Use this if your listener has a user interface that allows the user to dismiss 361 * multiple notifications at once. 362 * 363 * @param keys Notifications to dismiss, or {@code null} to dismiss all. 364 * 365 * {@see #cancelNotification(String, String, int)} 366 */ 367 public final void cancelNotifications(String[] keys) { 368 if (!isBound()) return; 369 try { 370 getNotificationInterface().cancelNotificationsFromListener(mWrapper, keys); 371 } catch (android.os.RemoteException ex) { 372 Log.v(TAG, "Unable to contact notification manager", ex); 373 } 374 } 375 376 /** 377 * Inform the notification manager that these notifications have been viewed by the 378 * user. This should only be called when there is sufficient confidence that the user is 379 * looking at the notifications, such as when the notifications appear on the screen due to 380 * an explicit user interaction. 381 * @param keys Notifications to mark as seen. 382 */ 383 public final void setNotificationsShown(String[] keys) { 384 if (!isBound()) return; 385 try { 386 getNotificationInterface().setNotificationsShownFromListener(mWrapper, keys); 387 } catch (android.os.RemoteException ex) { 388 Log.v(TAG, "Unable to contact notification manager", ex); 389 } 390 } 391 392 /** 393 * Sets the notification trim that will be received via {@link #onNotificationPosted}. 394 * 395 * <p> 396 * Setting a trim other than {@link #TRIM_FULL} enables listeners that don't need access to the 397 * full notification features right away to reduce their memory footprint. Full notifications 398 * can be requested on-demand via {@link #getActiveNotifications(int)}. 399 * 400 * <p> 401 * Set to {@link #TRIM_FULL} initially. 402 * 403 * @hide 404 * 405 * @param trim trim of the notifications to be passed via {@link #onNotificationPosted}. 406 * See <code>TRIM_*</code> constants. 407 */ 408 @SystemApi 409 public final void setOnNotificationPostedTrim(int trim) { 410 if (!isBound()) return; 411 try { 412 getNotificationInterface().setOnNotificationPostedTrimFromListener(mWrapper, trim); 413 } catch (RemoteException ex) { 414 Log.v(TAG, "Unable to contact notification manager", ex); 415 } 416 } 417 418 /** 419 * Request the list of outstanding notifications (that is, those that are visible to the 420 * current user). Useful when you don't know what's already been posted. 421 * 422 * @return An array of active notifications, sorted in natural order. 423 */ 424 public StatusBarNotification[] getActiveNotifications() { 425 return getActiveNotifications(null, TRIM_FULL); 426 } 427 428 /** 429 * Request the list of outstanding notifications (that is, those that are visible to the 430 * current user). Useful when you don't know what's already been posted. 431 * 432 * @hide 433 * 434 * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants. 435 * @return An array of active notifications, sorted in natural order. 436 */ 437 @SystemApi 438 public StatusBarNotification[] getActiveNotifications(int trim) { 439 return getActiveNotifications(null, trim); 440 } 441 442 /** 443 * Request one or more notifications by key. Useful if you have been keeping track of 444 * notifications but didn't want to retain the bits, and now need to go back and extract 445 * more data out of those notifications. 446 * 447 * @param keys the keys of the notifications to request 448 * @return An array of notifications corresponding to the requested keys, in the 449 * same order as the key list. 450 */ 451 public StatusBarNotification[] getActiveNotifications(String[] keys) { 452 return getActiveNotifications(keys, TRIM_FULL); 453 } 454 455 /** 456 * Request one or more notifications by key. Useful if you have been keeping track of 457 * notifications but didn't want to retain the bits, and now need to go back and extract 458 * more data out of those notifications. 459 * 460 * @hide 461 * 462 * @param keys the keys of the notifications to request 463 * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants. 464 * @return An array of notifications corresponding to the requested keys, in the 465 * same order as the key list. 466 */ 467 @SystemApi 468 public StatusBarNotification[] getActiveNotifications(String[] keys, int trim) { 469 if (!isBound()) 470 return null; 471 try { 472 ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface() 473 .getActiveNotificationsFromListener(mWrapper, keys, trim); 474 List<StatusBarNotification> list = parceledList.getList(); 475 ArrayList<StatusBarNotification> corruptNotifications = null; 476 int N = list.size(); 477 for (int i = 0; i < N; i++) { 478 StatusBarNotification sbn = list.get(i); 479 Notification notification = sbn.getNotification(); 480 try { 481 // convert icon metadata to legacy format for older clients 482 createLegacyIconExtras(notification); 483 // populate remote views for older clients. 484 maybePopulateRemoteViews(notification); 485 } catch (IllegalArgumentException e) { 486 if (corruptNotifications == null) { 487 corruptNotifications = new ArrayList<>(N); 488 } 489 corruptNotifications.add(sbn); 490 Log.w(TAG, "onNotificationPosted: can't rebuild notification from " + 491 sbn.getPackageName()); 492 } 493 } 494 if (corruptNotifications != null) { 495 list.removeAll(corruptNotifications); 496 } 497 return list.toArray(new StatusBarNotification[list.size()]); 498 } catch (android.os.RemoteException ex) { 499 Log.v(TAG, "Unable to contact notification manager", ex); 500 } 501 return null; 502 } 503 504 /** 505 * Gets the set of hints representing current state. 506 * 507 * <p> 508 * The current state may differ from the requested state if the hint represents state 509 * shared across all listeners or a feature the notification host does not support or refuses 510 * to grant. 511 * 512 * @return Zero or more of the HINT_ constants. 513 */ 514 public final int getCurrentListenerHints() { 515 if (!isBound()) return 0; 516 try { 517 return getNotificationInterface().getHintsFromListener(mWrapper); 518 } catch (android.os.RemoteException ex) { 519 Log.v(TAG, "Unable to contact notification manager", ex); 520 return 0; 521 } 522 } 523 524 /** 525 * Gets the current notification interruption filter active on the host. 526 * 527 * <p> 528 * The interruption filter defines which notifications are allowed to interrupt the user 529 * (e.g. via sound & vibration) and is applied globally. Listeners can find out whether 530 * a specific notification matched the interruption filter via 531 * {@link Ranking#matchesInterruptionFilter()}. 532 * <p> 533 * The current filter may differ from the previously requested filter if the notification host 534 * does not support or refuses to apply the requested filter, or if another component changed 535 * the filter in the meantime. 536 * <p> 537 * Listen for updates using {@link #onInterruptionFilterChanged(int)}. 538 * 539 * @return One of the INTERRUPTION_FILTER_ constants, or INTERRUPTION_FILTER_UNKNOWN when 540 * unavailable. 541 */ 542 public final int getCurrentInterruptionFilter() { 543 if (!isBound()) return INTERRUPTION_FILTER_UNKNOWN; 544 try { 545 return getNotificationInterface().getInterruptionFilterFromListener(mWrapper); 546 } catch (android.os.RemoteException ex) { 547 Log.v(TAG, "Unable to contact notification manager", ex); 548 return INTERRUPTION_FILTER_UNKNOWN; 549 } 550 } 551 552 /** 553 * Sets the desired {@link #getCurrentListenerHints() listener hints}. 554 * 555 * <p> 556 * This is merely a request, the host may or may not choose to take action depending 557 * on other listener requests or other global state. 558 * <p> 559 * Listen for updates using {@link #onListenerHintsChanged(int)}. 560 * 561 * @param hints One or more of the HINT_ constants. 562 */ 563 public final void requestListenerHints(int hints) { 564 if (!isBound()) return; 565 try { 566 getNotificationInterface().requestHintsFromListener(mWrapper, hints); 567 } catch (android.os.RemoteException ex) { 568 Log.v(TAG, "Unable to contact notification manager", ex); 569 } 570 } 571 572 /** 573 * Sets the desired {@link #getCurrentInterruptionFilter() interruption filter}. 574 * 575 * <p> 576 * This is merely a request, the host may or may not choose to apply the requested 577 * interruption filter depending on other listener requests or other global state. 578 * <p> 579 * Listen for updates using {@link #onInterruptionFilterChanged(int)}. 580 * 581 * @param interruptionFilter One of the INTERRUPTION_FILTER_ constants. 582 */ 583 public final void requestInterruptionFilter(int interruptionFilter) { 584 if (!isBound()) return; 585 try { 586 getNotificationInterface() 587 .requestInterruptionFilterFromListener(mWrapper, interruptionFilter); 588 } catch (android.os.RemoteException ex) { 589 Log.v(TAG, "Unable to contact notification manager", ex); 590 } 591 } 592 593 /** 594 * Returns current ranking information. 595 * 596 * <p> 597 * The returned object represents the current ranking snapshot and only 598 * applies for currently active notifications. 599 * <p> 600 * Generally you should use the RankingMap that is passed with events such 601 * as {@link #onNotificationPosted(StatusBarNotification, RankingMap)}, 602 * {@link #onNotificationRemoved(StatusBarNotification, RankingMap)}, and 603 * so on. This method should only be used when needing access outside of 604 * such events, for example to retrieve the RankingMap right after 605 * initialization. 606 * 607 * @return A {@link RankingMap} object providing access to ranking information 608 */ 609 public RankingMap getCurrentRanking() { 610 return mRankingMap; 611 } 612 613 @Override 614 public IBinder onBind(Intent intent) { 615 if (mWrapper == null) { 616 mWrapper = new INotificationListenerWrapper(); 617 } 618 return mWrapper; 619 } 620 621 private boolean isBound() { 622 if (mWrapper == null) { 623 Log.w(TAG, "Notification listener service not yet bound."); 624 return false; 625 } 626 return true; 627 } 628 629 /** 630 * Directly register this service with the Notification Manager. 631 * 632 * <p>Only system services may use this call. It will fail for non-system callers. 633 * Apps should ask the user to add their listener in Settings. 634 * 635 * @param context Context required for accessing resources. Since this service isn't 636 * launched as a real Service when using this method, a context has to be passed in. 637 * @param componentName the component that will consume the notification information 638 * @param currentUser the user to use as the stream filter 639 * @hide 640 */ 641 @SystemApi 642 public void registerAsSystemService(Context context, ComponentName componentName, 643 int currentUser) throws RemoteException { 644 mSystemContext = context; 645 if (mWrapper == null) { 646 mWrapper = new INotificationListenerWrapper(); 647 } 648 INotificationManager noMan = getNotificationInterface(); 649 noMan.registerListener(mWrapper, componentName, currentUser); 650 mCurrentUser = currentUser; 651 } 652 653 /** 654 * Directly unregister this service from the Notification Manager. 655 * 656 * <P>This method will fail for listeners that were not registered 657 * with (@link registerAsService). 658 * @hide 659 */ 660 @SystemApi 661 public void unregisterAsSystemService() throws RemoteException { 662 if (mWrapper != null) { 663 INotificationManager noMan = getNotificationInterface(); 664 noMan.unregisterListener(mWrapper, mCurrentUser); 665 } 666 } 667 668 /** Convert new-style Icons to legacy representations for pre-M clients. */ 669 private void createLegacyIconExtras(Notification n) { 670 Icon smallIcon = n.getSmallIcon(); 671 Icon largeIcon = n.getLargeIcon(); 672 if (smallIcon != null && smallIcon.getType() == Icon.TYPE_RESOURCE) { 673 n.extras.putInt(Notification.EXTRA_SMALL_ICON, smallIcon.getResId()); 674 n.icon = smallIcon.getResId(); 675 } 676 if (largeIcon != null) { 677 Drawable d = largeIcon.loadDrawable(getContext()); 678 if (d != null && d instanceof BitmapDrawable) { 679 final Bitmap largeIconBits = ((BitmapDrawable) d).getBitmap(); 680 n.extras.putParcelable(Notification.EXTRA_LARGE_ICON, largeIconBits); 681 n.largeIcon = largeIconBits; 682 } 683 } 684 } 685 686 /** 687 * Populates remote views for pre-N targeting apps. 688 */ 689 private void maybePopulateRemoteViews(Notification notification) { 690 if (getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { 691 Builder builder = Builder.recoverBuilder(getContext(), notification); 692 notification.contentView = builder.makeContentView(); 693 notification.bigContentView = builder.makeBigContentView(); 694 notification.headsUpContentView = builder.makeHeadsUpContentView(); 695 } 696 } 697 698 private class INotificationListenerWrapper extends INotificationListener.Stub { 699 @Override 700 public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder, 701 NotificationRankingUpdate update) { 702 StatusBarNotification sbn; 703 try { 704 sbn = sbnHolder.get(); 705 } catch (RemoteException e) { 706 Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification", e); 707 return; 708 } 709 710 try { 711 Notification notification = sbn.getNotification(); 712 // convert icon metadata to legacy format for older clients 713 createLegacyIconExtras(sbn.getNotification()); 714 maybePopulateRemoteViews(sbn.getNotification()); 715 } catch (IllegalArgumentException e) { 716 // drop corrupt notification 717 sbn = null; 718 Log.w(TAG, "onNotificationPosted: can't rebuild notification from " + 719 sbn.getPackageName()); 720 } 721 722 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 723 synchronized (mWrapper) { 724 applyUpdate(update); 725 try { 726 if (sbn != null) { 727 NotificationListenerService.this.onNotificationPosted(sbn, mRankingMap); 728 } else { 729 // still pass along the ranking map, it may contain other information 730 NotificationListenerService.this.onNotificationRankingUpdate(mRankingMap); 731 } 732 } catch (Throwable t) { 733 Log.w(TAG, "Error running onNotificationPosted", t); 734 } 735 } 736 } 737 @Override 738 public void onNotificationRemoved(IStatusBarNotificationHolder sbnHolder, 739 NotificationRankingUpdate update) { 740 StatusBarNotification sbn; 741 try { 742 sbn = sbnHolder.get(); 743 } catch (RemoteException e) { 744 Log.w(TAG, "onNotificationRemoved: Error receiving StatusBarNotification", e); 745 return; 746 } 747 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 748 synchronized (mWrapper) { 749 applyUpdate(update); 750 try { 751 NotificationListenerService.this.onNotificationRemoved(sbn, mRankingMap); 752 } catch (Throwable t) { 753 Log.w(TAG, "Error running onNotificationRemoved", t); 754 } 755 } 756 } 757 @Override 758 public void onListenerConnected(NotificationRankingUpdate update) { 759 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 760 synchronized (mWrapper) { 761 applyUpdate(update); 762 try { 763 NotificationListenerService.this.onListenerConnected(); 764 } catch (Throwable t) { 765 Log.w(TAG, "Error running onListenerConnected", t); 766 } 767 } 768 } 769 @Override 770 public void onNotificationRankingUpdate(NotificationRankingUpdate update) 771 throws RemoteException { 772 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 773 synchronized (mWrapper) { 774 applyUpdate(update); 775 try { 776 NotificationListenerService.this.onNotificationRankingUpdate(mRankingMap); 777 } catch (Throwable t) { 778 Log.w(TAG, "Error running onNotificationRankingUpdate", t); 779 } 780 } 781 } 782 @Override 783 public void onListenerHintsChanged(int hints) throws RemoteException { 784 try { 785 NotificationListenerService.this.onListenerHintsChanged(hints); 786 } catch (Throwable t) { 787 Log.w(TAG, "Error running onListenerHintsChanged", t); 788 } 789 } 790 791 @Override 792 public void onInterruptionFilterChanged(int interruptionFilter) throws RemoteException { 793 try { 794 NotificationListenerService.this.onInterruptionFilterChanged(interruptionFilter); 795 } catch (Throwable t) { 796 Log.w(TAG, "Error running onInterruptionFilterChanged", t); 797 } 798 } 799 } 800 801 private void applyUpdate(NotificationRankingUpdate update) { 802 mRankingMap = new RankingMap(update); 803 } 804 805 private Context getContext() { 806 if (mSystemContext != null) { 807 return mSystemContext; 808 } 809 return this; 810 } 811 812 /** 813 * Stores ranking related information on a currently active notification. 814 * 815 * <p> 816 * Ranking objects aren't automatically updated as notification events 817 * occur. Instead, ranking information has to be retrieved again via the 818 * current {@link RankingMap}. 819 */ 820 public static class Ranking { 821 /** Value signifying that the user has not expressed a per-app visibility override value. 822 * @hide */ 823 public static final int VISIBILITY_NO_OVERRIDE = -1000; 824 825 /** 826 * Value signifying that the user has not expressed an importance. 827 * 828 * This value is for persisting preferences, and should never be associated with 829 * an actual notification. 830 */ 831 public static final int IMPORTANCE_UNSPECIFIED = -1000; 832 833 /** 834 * A notification with no importance: shows nowhere, is blocked. 835 */ 836 public static final int IMPORTANCE_NONE = -2; 837 838 /** 839 * Low notification importance: only shows in the shade, below the fold. 840 */ 841 public static final int IMPORTANCE_LOW = -1; 842 843 /** 844 * Default notification importance: shows everywhere, but is not intrusive. 845 */ 846 public static final int IMPORTANCE_DEFAULT = 0; 847 848 /** 849 * Higher notification importance: shows everywhere, makes noise, 850 * but does not visually intrude. 851 */ 852 public static final int IMPORTANCE_HIGH = 1; 853 854 /** 855 * Highest notification importance: shows everywhere, makes noise, 856 * and also visually intrudes. 857 */ 858 public static final int IMPORTANCE_MAX = 2; 859 860 private String mKey; 861 private int mRank = -1; 862 private boolean mIsAmbient; 863 private boolean mMatchesInterruptionFilter; 864 private int mVisibilityOverride; 865 private int mSuppressedVisualEffects; 866 private int mImportance; 867 private CharSequence mImportanceExplanation; 868 869 public Ranking() {} 870 871 /** 872 * Returns the key of the notification this Ranking applies to. 873 */ 874 public String getKey() { 875 return mKey; 876 } 877 878 /** 879 * Returns the rank of the notification. 880 * 881 * @return the rank of the notification, that is the 0-based index in 882 * the list of active notifications. 883 */ 884 public int getRank() { 885 return mRank; 886 } 887 888 /** 889 * Returns whether the notification is an ambient notification, that is 890 * a notification that doesn't require the user's immediate attention. 891 */ 892 public boolean isAmbient() { 893 return mIsAmbient; 894 } 895 896 /** 897 * Returns the user specificed visibility for the package that posted 898 * this notification, or 899 * {@link NotificationListenerService.Ranking#VISIBILITY_NO_OVERRIDE} if 900 * no such preference has been expressed. 901 * @hide 902 */ 903 public int getVisibilityOverride() { 904 return mVisibilityOverride; 905 } 906 907 /** 908 * Returns the type(s) of visual effects that should be suppressed for this notification. 909 * See {@link #SUPPRESSED_EFFECT_LIGHTS}, {@link #SUPPRESSED_EFFECT_PEEK}}. 910 */ 911 public int getSuppressedVisualEffects() { 912 return mSuppressedVisualEffects; 913 } 914 915 /** 916 * Returns whether the notification matches the user's interruption 917 * filter. 918 * 919 * @return {@code true} if the notification is allowed by the filter, or 920 * {@code false} if it is blocked. 921 */ 922 public boolean matchesInterruptionFilter() { 923 return mMatchesInterruptionFilter; 924 } 925 926 /** 927 * Returns the importance of the notification, which dictates its 928 * modes of presentation, see: {@link #IMPORTANCE_DEFAULT}, etc. 929 * 930 * @return the rank of the notification 931 */ 932 public int getImportance() { 933 return mImportance; 934 } 935 936 /** 937 * If the importance has been overriden by user preference, or by a 938 * {@link NotificationAssistantService}, then this will be non-null, 939 * and should be displayed to the user. 940 * 941 * @return the explanation for the importance, or null if it is the natural importance 942 */ 943 public CharSequence getImportanceExplanation() { 944 return mImportanceExplanation; 945 } 946 947 private void populate(String key, int rank, boolean isAmbient, 948 boolean matchesInterruptionFilter, int visibilityOverride, 949 int suppressedVisualEffects, int importance, 950 CharSequence explanation) { 951 mKey = key; 952 mRank = rank; 953 mIsAmbient = isAmbient; 954 mMatchesInterruptionFilter = matchesInterruptionFilter; 955 mVisibilityOverride = visibilityOverride; 956 mSuppressedVisualEffects = suppressedVisualEffects; 957 mImportance = importance; 958 mImportanceExplanation = explanation; 959 } 960 961 /** 962 * {@hide} 963 */ 964 public static String importanceToString(int importance) { 965 switch (importance) { 966 case IMPORTANCE_UNSPECIFIED: 967 return "UNSPECIFIED"; 968 case IMPORTANCE_NONE: 969 return "NONE"; 970 case IMPORTANCE_LOW: 971 return "LOW"; 972 case IMPORTANCE_DEFAULT: 973 return "DEFAULT"; 974 case IMPORTANCE_HIGH: 975 return "HIGH"; 976 case IMPORTANCE_MAX: 977 return "MAX"; 978 default: 979 return "UNKNOWN(" + String.valueOf(importance) + ")"; 980 } 981 } 982 } 983 984 /** 985 * Provides access to ranking information on currently active 986 * notifications. 987 * 988 * <p> 989 * Note that this object represents a ranking snapshot that only applies to 990 * notifications active at the time of retrieval. 991 */ 992 public static class RankingMap implements Parcelable { 993 private final NotificationRankingUpdate mRankingUpdate; 994 private ArrayMap<String,Integer> mRanks; 995 private ArraySet<Object> mIntercepted; 996 private ArrayMap<String, Integer> mVisibilityOverrides; 997 private ArrayMap<String, Integer> mSuppressedVisualEffects; 998 private ArrayMap<String, Integer> mImportance; 999 private ArrayMap<String, String> mImportanceExplanation; 1000 1001 private RankingMap(NotificationRankingUpdate rankingUpdate) { 1002 mRankingUpdate = rankingUpdate; 1003 } 1004 1005 /** 1006 * Request the list of notification keys in their current ranking 1007 * order. 1008 * 1009 * @return An array of active notification keys, in their ranking order. 1010 */ 1011 public String[] getOrderedKeys() { 1012 return mRankingUpdate.getOrderedKeys(); 1013 } 1014 1015 /** 1016 * Populates outRanking with ranking information for the notification 1017 * with the given key. 1018 * 1019 * @return true if a valid key has been passed and outRanking has 1020 * been populated; false otherwise 1021 */ 1022 public boolean getRanking(String key, Ranking outRanking) { 1023 int rank = getRank(key); 1024 outRanking.populate(key, rank, isAmbient(key), !isIntercepted(key), 1025 getVisibilityOverride(key), getSuppressedVisualEffects(key), 1026 getImportance(key), getImportanceExplanation(key)); 1027 return rank >= 0; 1028 } 1029 1030 private int getRank(String key) { 1031 synchronized (this) { 1032 if (mRanks == null) { 1033 buildRanksLocked(); 1034 } 1035 } 1036 Integer rank = mRanks.get(key); 1037 return rank != null ? rank : -1; 1038 } 1039 1040 private boolean isAmbient(String key) { 1041 int firstAmbientIndex = mRankingUpdate.getFirstAmbientIndex(); 1042 if (firstAmbientIndex < 0) { 1043 return false; 1044 } 1045 int rank = getRank(key); 1046 return rank >= 0 && rank >= firstAmbientIndex; 1047 } 1048 1049 private boolean isIntercepted(String key) { 1050 synchronized (this) { 1051 if (mIntercepted == null) { 1052 buildInterceptedSetLocked(); 1053 } 1054 } 1055 return mIntercepted.contains(key); 1056 } 1057 1058 private int getVisibilityOverride(String key) { 1059 synchronized (this) { 1060 if (mVisibilityOverrides == null) { 1061 buildVisibilityOverridesLocked(); 1062 } 1063 } 1064 Integer override = mVisibilityOverrides.get(key); 1065 if (override == null) { 1066 return Ranking.VISIBILITY_NO_OVERRIDE; 1067 } 1068 return override.intValue(); 1069 } 1070 1071 private int getSuppressedVisualEffects(String key) { 1072 synchronized (this) { 1073 if (mSuppressedVisualEffects == null) { 1074 buildSuppressedVisualEffectsLocked(); 1075 } 1076 } 1077 Integer suppressed = mSuppressedVisualEffects.get(key); 1078 if (suppressed == null) { 1079 return 0; 1080 } 1081 return suppressed.intValue(); 1082 } 1083 1084 private int getImportance(String key) { 1085 synchronized (this) { 1086 if (mImportance == null) { 1087 buildImportanceLocked(); 1088 } 1089 } 1090 Integer importance = mImportance.get(key); 1091 if (importance == null) { 1092 return Ranking.IMPORTANCE_DEFAULT; 1093 } 1094 return importance.intValue(); 1095 } 1096 1097 private String getImportanceExplanation(String key) { 1098 synchronized (this) { 1099 if (mImportanceExplanation == null) { 1100 buildImportanceExplanationLocked(); 1101 } 1102 } 1103 return mImportanceExplanation.get(key); 1104 } 1105 1106 // Locked by 'this' 1107 private void buildRanksLocked() { 1108 String[] orderedKeys = mRankingUpdate.getOrderedKeys(); 1109 mRanks = new ArrayMap<>(orderedKeys.length); 1110 for (int i = 0; i < orderedKeys.length; i++) { 1111 String key = orderedKeys[i]; 1112 mRanks.put(key, i); 1113 } 1114 } 1115 1116 // Locked by 'this' 1117 private void buildInterceptedSetLocked() { 1118 String[] dndInterceptedKeys = mRankingUpdate.getInterceptedKeys(); 1119 mIntercepted = new ArraySet<>(dndInterceptedKeys.length); 1120 Collections.addAll(mIntercepted, dndInterceptedKeys); 1121 } 1122 1123 // Locked by 'this' 1124 private void buildVisibilityOverridesLocked() { 1125 Bundle visibilityBundle = mRankingUpdate.getVisibilityOverrides(); 1126 mVisibilityOverrides = new ArrayMap<>(visibilityBundle.size()); 1127 for (String key: visibilityBundle.keySet()) { 1128 mVisibilityOverrides.put(key, visibilityBundle.getInt(key)); 1129 } 1130 } 1131 1132 // Locked by 'this' 1133 private void buildSuppressedVisualEffectsLocked() { 1134 Bundle suppressedBundle = mRankingUpdate.getSuppressedVisualEffects(); 1135 mSuppressedVisualEffects = new ArrayMap<>(suppressedBundle.size()); 1136 for (String key: suppressedBundle.keySet()) { 1137 mSuppressedVisualEffects.put(key, suppressedBundle.getInt(key)); 1138 } 1139 } 1140 // Locked by 'this' 1141 private void buildImportanceLocked() { 1142 String[] orderedKeys = mRankingUpdate.getOrderedKeys(); 1143 int[] importance = mRankingUpdate.getImportance(); 1144 mImportance = new ArrayMap<>(orderedKeys.length); 1145 for (int i = 0; i < orderedKeys.length; i++) { 1146 String key = orderedKeys[i]; 1147 mImportance.put(key, importance[i]); 1148 } 1149 } 1150 1151 // Locked by 'this' 1152 private void buildImportanceExplanationLocked() { 1153 Bundle explanationBundle = mRankingUpdate.getImportanceExplanation(); 1154 mImportanceExplanation = new ArrayMap<>(explanationBundle.size()); 1155 for (String key: explanationBundle.keySet()) { 1156 mImportanceExplanation.put(key, explanationBundle.getString(key)); 1157 } 1158 } 1159 1160 // ----------- Parcelable 1161 1162 @Override 1163 public int describeContents() { 1164 return 0; 1165 } 1166 1167 @Override 1168 public void writeToParcel(Parcel dest, int flags) { 1169 dest.writeParcelable(mRankingUpdate, flags); 1170 } 1171 1172 public static final Creator<RankingMap> CREATOR = new Creator<RankingMap>() { 1173 @Override 1174 public RankingMap createFromParcel(Parcel source) { 1175 NotificationRankingUpdate rankingUpdate = source.readParcelable(null); 1176 return new RankingMap(rankingUpdate); 1177 } 1178 1179 @Override 1180 public RankingMap[] newArray(int size) { 1181 return new RankingMap[size]; 1182 } 1183 }; 1184 } 1185} 1186