NotificationListenerService.java revision 85769915e7ef10bef2b5338ed8f04d9b787924fb
152842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk/* 252842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * Copyright (C) 2013 The Android Open Source Project 352842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * 452842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * Licensed under the Apache License, Version 2.0 (the "License"); 552842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * you may not use this file except in compliance with the License. 652842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * You may obtain a copy of the License at 752842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * 852842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * http://www.apache.org/licenses/LICENSE-2.0 952842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * 1052842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * Unless required by applicable law or agreed to in writing, software 1152842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * distributed under the License is distributed on an "AS IS" BASIS, 1252842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1352842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * See the License for the specific language governing permissions and 1452842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * limitations under the License. 1552842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk */ 1652842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk 1752842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkpackage android.service.notification; 1852842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk 1952842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.os.Handler; 2052842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.os.Looper; 2152842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.os.Message; 2252842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk 2352842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.annotation.IntDef; 2452842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.annotation.SystemApi; 2552842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.annotation.SdkConstant; 2652842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.app.INotificationManager; 2752842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.app.Notification; 2852842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.app.Notification.Builder; 2952842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.app.NotificationManager; 3052842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.app.Service; 3152842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.content.ComponentName; 3252842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.content.Context; 3352842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.content.Intent; 3452842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.content.pm.ParceledListSlice; 3552842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.graphics.drawable.BitmapDrawable; 3652842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.graphics.drawable.Drawable; 3752842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.graphics.drawable.Icon; 3852842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.graphics.Bitmap; 3952842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.os.Build; 4052842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.os.Bundle; 4152842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.os.IBinder; 4252842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.os.Parcel; 4352842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.os.Parcelable; 4452842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.os.RemoteException; 4552842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.os.ServiceManager; 4652842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.util.ArrayMap; 4752842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.util.ArraySet; 4852842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.util.Log; 4952842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport android.widget.RemoteViews; 5052842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport com.android.internal.annotations.GuardedBy; 5152842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport com.android.internal.os.SomeArgs; 5252842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport java.lang.annotation.Retention; 5352842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport java.lang.annotation.RetentionPolicy; 5452842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport java.util.ArrayList; 5552842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport java.util.Collections; 5652842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunkimport java.util.List; 5752842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk 5852842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk/** 5952842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * A service that receives calls from the system when new notifications are 6052842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * posted or removed, or their ranking changed. 6152842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * <p>To extend this class, you must declare the service in your manifest file with 6252842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * the {@link android.Manifest.permission#BIND_NOTIFICATION_LISTENER_SERVICE} permission 6352842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p> 6452842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * <pre> 6552842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * <service android:name=".NotificationListener" 6652842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * android:label="@string/service_name" 6752842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"> 6852842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * <intent-filter> 6952842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * <action android:name="android.service.notification.NotificationListenerService" /> 7052842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * </intent-filter> 7152842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * </service></pre> 7252842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * 7352842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * <p>The service should wait for the {@link #onListenerConnected()} event 7452842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * before performing any operations. The {@link #requestRebind(ComponentName)} 7552842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * method is the <i>only</i> one that is safe to call before {@link #onListenerConnected()} 7652842e7a6f0e922185db04ae8b91c776a234acf1Ruben Brunk * or after {@link #onListenerDisconnected()}. 77 * </p> 78 */ 79public 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 330 public void onInterruptionFilterChanged(int interruptionFilter) { 331 // optional 332 } 333 334 /** @hide */ 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 */ 371 @Deprecated 372 public final void cancelNotification(String pkg, String tag, int id) { 373 if (!isBound()) return; 374 try { 375 getNotificationInterface().cancelNotificationFromListener( 376 mWrapper, pkg, tag, id); 377 } catch (android.os.RemoteException ex) { 378 Log.v(TAG, "Unable to contact notification manager", ex); 379 } 380 } 381 382 /** 383 * Inform the notification manager about dismissal of a single notification. 384 * <p> 385 * Use this if your listener has a user interface that allows the user to dismiss individual 386 * notifications, similar to the behavior of Android's status bar and notification panel. 387 * It should be called after the user dismisses a single notification using your UI; 388 * upon being informed, the notification manager will actually remove the notification 389 * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback. 390 * <p> 391 * <b>Note:</b> If your listener allows the user to fire a notification's 392 * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call 393 * this method at that time <i>if</i> the Notification in question has the 394 * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set. 395 * <p> 396 * 397 * <p>The service should wait for the {@link #onListenerConnected()} event 398 * before performing this operation. 399 * 400 * @param key Notification to dismiss from {@link StatusBarNotification#getKey()}. 401 */ 402 public final void cancelNotification(String key) { 403 if (!isBound()) return; 404 try { 405 getNotificationInterface().cancelNotificationsFromListener(mWrapper, 406 new String[] { key }); 407 } catch (android.os.RemoteException ex) { 408 Log.v(TAG, "Unable to contact notification manager", ex); 409 } 410 } 411 412 /** 413 * Inform the notification manager about dismissal of all notifications. 414 * <p> 415 * Use this if your listener has a user interface that allows the user to dismiss all 416 * notifications, similar to the behavior of Android's status bar and notification panel. 417 * It should be called after the user invokes the "dismiss all" function of your UI; 418 * upon being informed, the notification manager will actually remove all active notifications 419 * and you will get multiple {@link #onNotificationRemoved(StatusBarNotification)} callbacks. 420 * 421 * <p>The service should wait for the {@link #onListenerConnected()} event 422 * before performing this operation. 423 * 424 * {@see #cancelNotification(String, String, int)} 425 */ 426 public final void cancelAllNotifications() { 427 cancelNotifications(null /*all*/); 428 } 429 430 /** 431 * Inform the notification manager about dismissal of specific notifications. 432 * <p> 433 * Use this if your listener has a user interface that allows the user to dismiss 434 * multiple notifications at once. 435 * 436 * <p>The service should wait for the {@link #onListenerConnected()} event 437 * before performing this operation. 438 * 439 * @param keys Notifications to dismiss, or {@code null} to dismiss all. 440 * 441 * {@see #cancelNotification(String, String, int)} 442 */ 443 public final void cancelNotifications(String[] keys) { 444 if (!isBound()) return; 445 try { 446 getNotificationInterface().cancelNotificationsFromListener(mWrapper, keys); 447 } catch (android.os.RemoteException ex) { 448 Log.v(TAG, "Unable to contact notification manager", ex); 449 } 450 } 451 452 /** 453 * Inform the notification manager about snoozing a specific notification. 454 * <p> 455 * Use this if your listener has a user interface that allows the user to snooze a notification 456 * until a given time. It should be called after the user snoozes a single notification using 457 * your UI; upon being informed, the notification manager will actually remove the notification 458 * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback. When the 459 * snoozing period expires, you will get a 460 * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the 461 * notification. 462 * @param key The key of the notification to snooze 463 * @param snoozeUntil A time in the future, in milliseconds. 464 */ 465 public final void snoozeNotification(String key, long snoozeUntil) { 466 if (!isBound()) return; 467 try { 468 getNotificationInterface().snoozeNotificationFromListener(mWrapper, key, snoozeUntil); 469 } catch (android.os.RemoteException ex) { 470 Log.v(TAG, "Unable to contact notification manager", ex); 471 } 472 } 473 474 /** 475 * Inform the notification manager that these notifications have been viewed by the 476 * user. This should only be called when there is sufficient confidence that the user is 477 * looking at the notifications, such as when the notifications appear on the screen due to 478 * an explicit user interaction. 479 * 480 * <p>The service should wait for the {@link #onListenerConnected()} event 481 * before performing this operation. 482 * 483 * @param keys Notifications to mark as seen. 484 */ 485 public final void setNotificationsShown(String[] keys) { 486 if (!isBound()) return; 487 try { 488 getNotificationInterface().setNotificationsShownFromListener(mWrapper, keys); 489 } catch (android.os.RemoteException ex) { 490 Log.v(TAG, "Unable to contact notification manager", ex); 491 } 492 } 493 494 /** 495 * Sets the notification trim that will be received via {@link #onNotificationPosted}. 496 * 497 * <p> 498 * Setting a trim other than {@link #TRIM_FULL} enables listeners that don't need access to the 499 * full notification features right away to reduce their memory footprint. Full notifications 500 * can be requested on-demand via {@link #getActiveNotifications(int)}. 501 * 502 * <p> 503 * Set to {@link #TRIM_FULL} initially. 504 * 505 * <p>The service should wait for the {@link #onListenerConnected()} event 506 * before performing this operation. 507 * 508 * @hide 509 * 510 * @param trim trim of the notifications to be passed via {@link #onNotificationPosted}. 511 * See <code>TRIM_*</code> constants. 512 */ 513 @SystemApi 514 public final void setOnNotificationPostedTrim(int trim) { 515 if (!isBound()) return; 516 try { 517 getNotificationInterface().setOnNotificationPostedTrimFromListener(mWrapper, trim); 518 } catch (RemoteException ex) { 519 Log.v(TAG, "Unable to contact notification manager", ex); 520 } 521 } 522 523 /** 524 * Request the list of outstanding notifications (that is, those that are visible to the 525 * current user). Useful when you don't know what's already been posted. 526 * 527 * <p>The service should wait for the {@link #onListenerConnected()} event 528 * before performing this operation. 529 * 530 * @return An array of active notifications, sorted in natural order. 531 */ 532 public StatusBarNotification[] getActiveNotifications() { 533 return getActiveNotifications(null, TRIM_FULL); 534 } 535 536 /** 537 * Request the list of outstanding notifications (that is, those that are visible to the 538 * current user). Useful when you don't know what's already been posted. 539 * 540 * @hide 541 * 542 * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants. 543 * @return An array of active notifications, sorted in natural order. 544 */ 545 @SystemApi 546 public StatusBarNotification[] getActiveNotifications(int trim) { 547 return getActiveNotifications(null, trim); 548 } 549 550 /** 551 * Request one or more notifications by key. Useful if you have been keeping track of 552 * notifications but didn't want to retain the bits, and now need to go back and extract 553 * more data out of those notifications. 554 * 555 * <p>The service should wait for the {@link #onListenerConnected()} event 556 * before performing this operation. 557 * 558 * @param keys the keys of the notifications to request 559 * @return An array of notifications corresponding to the requested keys, in the 560 * same order as the key list. 561 */ 562 public StatusBarNotification[] getActiveNotifications(String[] keys) { 563 return getActiveNotifications(keys, TRIM_FULL); 564 } 565 566 /** 567 * Request one or more notifications by key. Useful if you have been keeping track of 568 * notifications but didn't want to retain the bits, and now need to go back and extract 569 * more data out of those notifications. 570 * 571 * @hide 572 * 573 * @param keys the keys of the notifications to request 574 * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants. 575 * @return An array of notifications corresponding to the requested keys, in the 576 * same order as the key list. 577 */ 578 @SystemApi 579 public StatusBarNotification[] getActiveNotifications(String[] keys, int trim) { 580 if (!isBound()) 581 return null; 582 try { 583 ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface() 584 .getActiveNotificationsFromListener(mWrapper, keys, trim); 585 List<StatusBarNotification> list = parceledList.getList(); 586 ArrayList<StatusBarNotification> corruptNotifications = null; 587 int N = list.size(); 588 for (int i = 0; i < N; i++) { 589 StatusBarNotification sbn = list.get(i); 590 Notification notification = sbn.getNotification(); 591 try { 592 // convert icon metadata to legacy format for older clients 593 createLegacyIconExtras(notification); 594 // populate remote views for older clients. 595 maybePopulateRemoteViews(notification); 596 } catch (IllegalArgumentException e) { 597 if (corruptNotifications == null) { 598 corruptNotifications = new ArrayList<>(N); 599 } 600 corruptNotifications.add(sbn); 601 Log.w(TAG, "onNotificationPosted: can't rebuild notification from " + 602 sbn.getPackageName()); 603 } 604 } 605 if (corruptNotifications != null) { 606 list.removeAll(corruptNotifications); 607 } 608 return list.toArray(new StatusBarNotification[list.size()]); 609 } catch (android.os.RemoteException ex) { 610 Log.v(TAG, "Unable to contact notification manager", ex); 611 } 612 return null; 613 } 614 615 /** 616 * Gets the set of hints representing current state. 617 * 618 * <p> 619 * The current state may differ from the requested state if the hint represents state 620 * shared across all listeners or a feature the notification host does not support or refuses 621 * to grant. 622 * 623 * <p>The service should wait for the {@link #onListenerConnected()} event 624 * before performing this operation. 625 * 626 * @return Zero or more of the HINT_ constants. 627 */ 628 public final int getCurrentListenerHints() { 629 if (!isBound()) return 0; 630 try { 631 return getNotificationInterface().getHintsFromListener(mWrapper); 632 } catch (android.os.RemoteException ex) { 633 Log.v(TAG, "Unable to contact notification manager", ex); 634 return 0; 635 } 636 } 637 638 /** 639 * Gets the current notification interruption filter active on the host. 640 * 641 * <p> 642 * The interruption filter defines which notifications are allowed to interrupt the user 643 * (e.g. via sound & vibration) and is applied globally. Listeners can find out whether 644 * a specific notification matched the interruption filter via 645 * {@link Ranking#matchesInterruptionFilter()}. 646 * <p> 647 * The current filter may differ from the previously requested filter if the notification host 648 * does not support or refuses to apply the requested filter, or if another component changed 649 * the filter in the meantime. 650 * <p> 651 * Listen for updates using {@link #onInterruptionFilterChanged(int)}. 652 * 653 * <p>The service should wait for the {@link #onListenerConnected()} event 654 * before performing this operation. 655 * 656 * @return One of the INTERRUPTION_FILTER_ constants, or INTERRUPTION_FILTER_UNKNOWN when 657 * unavailable. 658 */ 659 public final int getCurrentInterruptionFilter() { 660 if (!isBound()) return INTERRUPTION_FILTER_UNKNOWN; 661 try { 662 return getNotificationInterface().getInterruptionFilterFromListener(mWrapper); 663 } catch (android.os.RemoteException ex) { 664 Log.v(TAG, "Unable to contact notification manager", ex); 665 return INTERRUPTION_FILTER_UNKNOWN; 666 } 667 } 668 669 /** 670 * Sets the desired {@link #getCurrentListenerHints() listener hints}. 671 * 672 * <p> 673 * This is merely a request, the host may or may not choose to take action depending 674 * on other listener requests or other global state. 675 * <p> 676 * Listen for updates using {@link #onListenerHintsChanged(int)}. 677 * 678 * <p>The service should wait for the {@link #onListenerConnected()} event 679 * before performing this operation. 680 * 681 * @param hints One or more of the HINT_ constants. 682 */ 683 public final void requestListenerHints(int hints) { 684 if (!isBound()) return; 685 try { 686 getNotificationInterface().requestHintsFromListener(mWrapper, hints); 687 } catch (android.os.RemoteException ex) { 688 Log.v(TAG, "Unable to contact notification manager", ex); 689 } 690 } 691 692 /** 693 * Sets the desired {@link #getCurrentInterruptionFilter() interruption filter}. 694 * 695 * <p> 696 * This is merely a request, the host may or may not choose to apply the requested 697 * interruption filter depending on other listener requests or other global state. 698 * <p> 699 * Listen for updates using {@link #onInterruptionFilterChanged(int)}. 700 * 701 * <p>The service should wait for the {@link #onListenerConnected()} event 702 * before performing this operation. 703 * 704 * @param interruptionFilter One of the INTERRUPTION_FILTER_ constants. 705 */ 706 public final void requestInterruptionFilter(int interruptionFilter) { 707 if (!isBound()) return; 708 try { 709 getNotificationInterface() 710 .requestInterruptionFilterFromListener(mWrapper, interruptionFilter); 711 } catch (android.os.RemoteException ex) { 712 Log.v(TAG, "Unable to contact notification manager", ex); 713 } 714 } 715 716 /** 717 * Returns current ranking information. 718 * 719 * <p> 720 * The returned object represents the current ranking snapshot and only 721 * applies for currently active notifications. 722 * <p> 723 * Generally you should use the RankingMap that is passed with events such 724 * as {@link #onNotificationPosted(StatusBarNotification, RankingMap)}, 725 * {@link #onNotificationRemoved(StatusBarNotification, RankingMap)}, and 726 * so on. This method should only be used when needing access outside of 727 * such events, for example to retrieve the RankingMap right after 728 * initialization. 729 * 730 * <p>The service should wait for the {@link #onListenerConnected()} event 731 * before performing this operation. 732 * 733 * @return A {@link RankingMap} object providing access to ranking information 734 */ 735 public RankingMap getCurrentRanking() { 736 synchronized (mLock) { 737 return mRankingMap; 738 } 739 } 740 741 /** 742 * This is not the lifecycle event you are looking for. 743 * 744 * <p>The service should wait for the {@link #onListenerConnected()} event 745 * before performing any operations. 746 */ 747 @Override 748 public IBinder onBind(Intent intent) { 749 if (mWrapper == null) { 750 mWrapper = new NotificationListenerWrapper(); 751 } 752 return mWrapper; 753 } 754 755 /** @hide */ 756 protected boolean isBound() { 757 if (mWrapper == null) { 758 Log.w(TAG, "Notification listener service not yet bound."); 759 return false; 760 } 761 return true; 762 } 763 764 @Override 765 public void onDestroy() { 766 onListenerDisconnected(); 767 super.onDestroy(); 768 } 769 770 /** 771 * Directly register this service with the Notification Manager. 772 * 773 * <p>Only system services may use this call. It will fail for non-system callers. 774 * Apps should ask the user to add their listener in Settings. 775 * 776 * @param context Context required for accessing resources. Since this service isn't 777 * launched as a real Service when using this method, a context has to be passed in. 778 * @param componentName the component that will consume the notification information 779 * @param currentUser the user to use as the stream filter 780 * @hide 781 */ 782 @SystemApi 783 public void registerAsSystemService(Context context, ComponentName componentName, 784 int currentUser) throws RemoteException { 785 if (mWrapper == null) { 786 mWrapper = new NotificationListenerWrapper(); 787 } 788 mSystemContext = context; 789 INotificationManager noMan = getNotificationInterface(); 790 mHandler = new MyHandler(context.getMainLooper()); 791 mCurrentUser = currentUser; 792 noMan.registerListener(mWrapper, componentName, currentUser); 793 } 794 795 /** 796 * Directly unregister this service from the Notification Manager. 797 * 798 * <p>This method will fail for listeners that were not registered 799 * with (@link registerAsService). 800 * @hide 801 */ 802 @SystemApi 803 public void unregisterAsSystemService() throws RemoteException { 804 if (mWrapper != null) { 805 INotificationManager noMan = getNotificationInterface(); 806 noMan.unregisterListener(mWrapper, mCurrentUser); 807 } 808 } 809 810 /** 811 * Request that the listener be rebound, after a previous call to (@link requestUnbind). 812 * 813 * <p>This method will fail for listeners that have 814 * not been granted the permission by the user. 815 */ 816 public static void requestRebind(ComponentName componentName) { 817 INotificationManager noMan = INotificationManager.Stub.asInterface( 818 ServiceManager.getService(Context.NOTIFICATION_SERVICE)); 819 try { 820 noMan.requestBindListener(componentName); 821 } catch (RemoteException ex) { 822 throw ex.rethrowFromSystemServer(); 823 } 824 } 825 826 /** 827 * Request that the service be unbound. 828 * 829 * <p>This will no longer receive updates until 830 * {@link #requestRebind(ComponentName)} is called. 831 * The service will likely be kiled by the system after this call. 832 * 833 * <p>The service should wait for the {@link #onListenerConnected()} event 834 * before performing this operation. I know it's tempting, but you must wait. 835 */ 836 public final void requestUnbind() { 837 if (mWrapper != null) { 838 INotificationManager noMan = getNotificationInterface(); 839 try { 840 noMan.requestUnbindListener(mWrapper); 841 // Disable future messages. 842 isConnected = false; 843 } catch (RemoteException ex) { 844 throw ex.rethrowFromSystemServer(); 845 } 846 } 847 } 848 849 /** Convert new-style Icons to legacy representations for pre-M clients. */ 850 private void createLegacyIconExtras(Notification n) { 851 Icon smallIcon = n.getSmallIcon(); 852 Icon largeIcon = n.getLargeIcon(); 853 if (smallIcon != null && smallIcon.getType() == Icon.TYPE_RESOURCE) { 854 n.extras.putInt(Notification.EXTRA_SMALL_ICON, smallIcon.getResId()); 855 n.icon = smallIcon.getResId(); 856 } 857 if (largeIcon != null) { 858 Drawable d = largeIcon.loadDrawable(getContext()); 859 if (d != null && d instanceof BitmapDrawable) { 860 final Bitmap largeIconBits = ((BitmapDrawable) d).getBitmap(); 861 n.extras.putParcelable(Notification.EXTRA_LARGE_ICON, largeIconBits); 862 n.largeIcon = largeIconBits; 863 } 864 } 865 } 866 867 /** 868 * Populates remote views for pre-N targeting apps. 869 */ 870 private void maybePopulateRemoteViews(Notification notification) { 871 if (getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { 872 Builder builder = Builder.recoverBuilder(getContext(), notification); 873 874 // Some styles wrap Notification's contentView, bigContentView and headsUpContentView. 875 // First inflate them all, only then set them to avoid recursive wrapping. 876 RemoteViews content = builder.createContentView(); 877 RemoteViews big = builder.createBigContentView(); 878 RemoteViews headsUp = builder.createHeadsUpContentView(); 879 880 notification.contentView = content; 881 notification.bigContentView = big; 882 notification.headsUpContentView = headsUp; 883 } 884 } 885 886 /** @hide */ 887 protected class NotificationListenerWrapper extends INotificationListener.Stub { 888 @Override 889 public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder, 890 NotificationRankingUpdate update) { 891 StatusBarNotification sbn; 892 try { 893 sbn = sbnHolder.get(); 894 } catch (RemoteException e) { 895 Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification", e); 896 return; 897 } 898 899 try { 900 // convert icon metadata to legacy format for older clients 901 createLegacyIconExtras(sbn.getNotification()); 902 maybePopulateRemoteViews(sbn.getNotification()); 903 } catch (IllegalArgumentException e) { 904 // warn and drop corrupt notification 905 Log.w(TAG, "onNotificationPosted: can't rebuild notification from " + 906 sbn.getPackageName()); 907 sbn = null; 908 } 909 910 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 911 synchronized (mLock) { 912 applyUpdateLocked(update); 913 if (sbn != null) { 914 SomeArgs args = SomeArgs.obtain(); 915 args.arg1 = sbn; 916 args.arg2 = mRankingMap; 917 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_POSTED, 918 args).sendToTarget(); 919 } else { 920 // still pass along the ranking map, it may contain other information 921 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE, 922 mRankingMap).sendToTarget(); 923 } 924 } 925 926 } 927 928 @Override 929 public void onNotificationRemoved(IStatusBarNotificationHolder sbnHolder, 930 NotificationRankingUpdate update) { 931 StatusBarNotification sbn; 932 try { 933 sbn = sbnHolder.get(); 934 } catch (RemoteException e) { 935 Log.w(TAG, "onNotificationRemoved: Error receiving StatusBarNotification", e); 936 return; 937 } 938 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 939 synchronized (mLock) { 940 applyUpdateLocked(update); 941 SomeArgs args = SomeArgs.obtain(); 942 args.arg1 = sbn; 943 args.arg2 = mRankingMap; 944 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_REMOVED, 945 args).sendToTarget(); 946 } 947 948 } 949 950 @Override 951 public void onListenerConnected(NotificationRankingUpdate update) { 952 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 953 synchronized (mLock) { 954 applyUpdateLocked(update); 955 } 956 isConnected = true; 957 mHandler.obtainMessage(MyHandler.MSG_ON_LISTENER_CONNECTED).sendToTarget(); 958 } 959 960 @Override 961 public void onNotificationRankingUpdate(NotificationRankingUpdate update) 962 throws RemoteException { 963 // protect subclass from concurrent modifications of (@link mNotificationKeys}. 964 synchronized (mLock) { 965 applyUpdateLocked(update); 966 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE, 967 mRankingMap).sendToTarget(); 968 } 969 970 } 971 972 @Override 973 public void onListenerHintsChanged(int hints) throws RemoteException { 974 mHandler.obtainMessage(MyHandler.MSG_ON_LISTENER_HINTS_CHANGED, 975 hints, 0).sendToTarget(); 976 } 977 978 @Override 979 public void onInterruptionFilterChanged(int interruptionFilter) throws RemoteException { 980 mHandler.obtainMessage(MyHandler.MSG_ON_INTERRUPTION_FILTER_CHANGED, 981 interruptionFilter, 0).sendToTarget(); 982 } 983 984 @Override 985 public void onNotificationEnqueued(IStatusBarNotificationHolder notificationHolder, 986 int importance, boolean user) throws RemoteException { 987 // no-op in the listener 988 } 989 990 @Override 991 public void onNotificationVisibilityChanged(String key, long time, boolean visible) 992 throws RemoteException { 993 // no-op in the listener 994 } 995 996 @Override 997 public void onNotificationClick(String key, long time) throws RemoteException { 998 // no-op in the listener 999 } 1000 1001 @Override 1002 public void onNotificationActionClick(String key, long time, int actionIndex) 1003 throws RemoteException { 1004 // no-op in the listener 1005 } 1006 1007 @Override 1008 public void onNotificationRemovedReason(String key, long time, int reason) 1009 throws RemoteException { 1010 // no-op in the listener 1011 } 1012 } 1013 1014 private void applyUpdateLocked(NotificationRankingUpdate update) { 1015 mRankingMap = new RankingMap(update); 1016 } 1017 1018 /** @hide */ 1019 protected Context getContext() { 1020 if (mSystemContext != null) { 1021 return mSystemContext; 1022 } 1023 return this; 1024 } 1025 1026 /** 1027 * Stores ranking related information on a currently active notification. 1028 * 1029 * <p> 1030 * Ranking objects aren't automatically updated as notification events 1031 * occur. Instead, ranking information has to be retrieved again via the 1032 * current {@link RankingMap}. 1033 */ 1034 public static class Ranking { 1035 1036 /** Value signifying that the user has not expressed a per-app visibility override value. 1037 * @hide */ 1038 public static final int VISIBILITY_NO_OVERRIDE = NotificationManager.VISIBILITY_NO_OVERRIDE; 1039 1040 private String mKey; 1041 private int mRank = -1; 1042 private boolean mIsAmbient; 1043 private boolean mMatchesInterruptionFilter; 1044 private int mVisibilityOverride; 1045 private int mSuppressedVisualEffects; 1046 private @NotificationManager.Importance int mImportance; 1047 private CharSequence mImportanceExplanation; 1048 // System specified group key. 1049 private String mOverrideGroupKey; 1050 1051 public Ranking() {} 1052 1053 /** 1054 * Returns the key of the notification this Ranking applies to. 1055 */ 1056 public String getKey() { 1057 return mKey; 1058 } 1059 1060 /** 1061 * Returns the rank of the notification. 1062 * 1063 * @return the rank of the notification, that is the 0-based index in 1064 * the list of active notifications. 1065 */ 1066 public int getRank() { 1067 return mRank; 1068 } 1069 1070 /** 1071 * Returns whether the notification is an ambient notification, that is 1072 * a notification that doesn't require the user's immediate attention. 1073 */ 1074 public boolean isAmbient() { 1075 return mIsAmbient; 1076 } 1077 1078 /** 1079 * Returns the user specificed visibility for the package that posted 1080 * this notification, or 1081 * {@link NotificationListenerService.Ranking#VISIBILITY_NO_OVERRIDE} if 1082 * no such preference has been expressed. 1083 * @hide 1084 */ 1085 public int getVisibilityOverride() { 1086 return mVisibilityOverride; 1087 } 1088 1089 /** 1090 * Returns the type(s) of visual effects that should be suppressed for this notification. 1091 * See {@link #SUPPRESSED_EFFECT_SCREEN_OFF}, {@link #SUPPRESSED_EFFECT_SCREEN_ON}. 1092 */ 1093 public int getSuppressedVisualEffects() { 1094 return mSuppressedVisualEffects; 1095 } 1096 1097 /** 1098 * Returns whether the notification matches the user's interruption 1099 * filter. 1100 * 1101 * @return {@code true} if the notification is allowed by the filter, or 1102 * {@code false} if it is blocked. 1103 */ 1104 public boolean matchesInterruptionFilter() { 1105 return mMatchesInterruptionFilter; 1106 } 1107 1108 /** 1109 * Returns the importance of the notification, which dictates its 1110 * modes of presentation, see: {@link NotificationManager#IMPORTANCE_DEFAULT}, etc. 1111 * 1112 * @return the rank of the notification 1113 */ 1114 public @NotificationManager.Importance int getImportance() { 1115 return mImportance; 1116 } 1117 1118 /** 1119 * If the importance has been overriden by user preference, then this will be non-null, 1120 * and should be displayed to the user. 1121 * 1122 * @return the explanation for the importance, or null if it is the natural importance 1123 */ 1124 public CharSequence getImportanceExplanation() { 1125 return mImportanceExplanation; 1126 } 1127 1128 /** 1129 * If the system has overriden the group key, then this will be non-null, and this 1130 * key should be used to bundle notifications. 1131 */ 1132 public String getOverrideGroupKey() { 1133 return mOverrideGroupKey; 1134 } 1135 1136 private void populate(String key, int rank, boolean matchesInterruptionFilter, 1137 int visibilityOverride, int suppressedVisualEffects, int importance, 1138 CharSequence explanation, String overrideGroupKey) { 1139 mKey = key; 1140 mRank = rank; 1141 mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW; 1142 mMatchesInterruptionFilter = matchesInterruptionFilter; 1143 mVisibilityOverride = visibilityOverride; 1144 mSuppressedVisualEffects = suppressedVisualEffects; 1145 mImportance = importance; 1146 mImportanceExplanation = explanation; 1147 mOverrideGroupKey = overrideGroupKey; 1148 } 1149 1150 /** 1151 * {@hide} 1152 */ 1153 public static String importanceToString(int importance) { 1154 switch (importance) { 1155 case NotificationManager.IMPORTANCE_UNSPECIFIED: 1156 return "UNSPECIFIED"; 1157 case NotificationManager.IMPORTANCE_NONE: 1158 return "NONE"; 1159 case NotificationManager.IMPORTANCE_MIN: 1160 return "MIN"; 1161 case NotificationManager.IMPORTANCE_LOW: 1162 return "LOW"; 1163 case NotificationManager.IMPORTANCE_DEFAULT: 1164 return "DEFAULT"; 1165 case NotificationManager.IMPORTANCE_HIGH: 1166 case NotificationManager.IMPORTANCE_MAX: 1167 return "HIGH"; 1168 default: 1169 return "UNKNOWN(" + String.valueOf(importance) + ")"; 1170 } 1171 } 1172 } 1173 1174 /** 1175 * Provides access to ranking information on currently active 1176 * notifications. 1177 * 1178 * <p> 1179 * Note that this object represents a ranking snapshot that only applies to 1180 * notifications active at the time of retrieval. 1181 */ 1182 public static class RankingMap implements Parcelable { 1183 private final NotificationRankingUpdate mRankingUpdate; 1184 private ArrayMap<String,Integer> mRanks; 1185 private ArraySet<Object> mIntercepted; 1186 private ArrayMap<String, Integer> mVisibilityOverrides; 1187 private ArrayMap<String, Integer> mSuppressedVisualEffects; 1188 private ArrayMap<String, Integer> mImportance; 1189 private ArrayMap<String, String> mImportanceExplanation; 1190 private ArrayMap<String, String> mOverrideGroupKeys; 1191 1192 private RankingMap(NotificationRankingUpdate rankingUpdate) { 1193 mRankingUpdate = rankingUpdate; 1194 } 1195 1196 /** 1197 * Request the list of notification keys in their current ranking 1198 * order. 1199 * 1200 * @return An array of active notification keys, in their ranking order. 1201 */ 1202 public String[] getOrderedKeys() { 1203 return mRankingUpdate.getOrderedKeys(); 1204 } 1205 1206 /** 1207 * Populates outRanking with ranking information for the notification 1208 * with the given key. 1209 * 1210 * @return true if a valid key has been passed and outRanking has 1211 * been populated; false otherwise 1212 */ 1213 public boolean getRanking(String key, Ranking outRanking) { 1214 int rank = getRank(key); 1215 outRanking.populate(key, rank, !isIntercepted(key), 1216 getVisibilityOverride(key), getSuppressedVisualEffects(key), 1217 getImportance(key), getImportanceExplanation(key), getOverrideGroupKey(key)); 1218 return rank >= 0; 1219 } 1220 1221 private int getRank(String key) { 1222 synchronized (this) { 1223 if (mRanks == null) { 1224 buildRanksLocked(); 1225 } 1226 } 1227 Integer rank = mRanks.get(key); 1228 return rank != null ? rank : -1; 1229 } 1230 1231 private boolean isIntercepted(String key) { 1232 synchronized (this) { 1233 if (mIntercepted == null) { 1234 buildInterceptedSetLocked(); 1235 } 1236 } 1237 return mIntercepted.contains(key); 1238 } 1239 1240 private int getVisibilityOverride(String key) { 1241 synchronized (this) { 1242 if (mVisibilityOverrides == null) { 1243 buildVisibilityOverridesLocked(); 1244 } 1245 } 1246 Integer override = mVisibilityOverrides.get(key); 1247 if (override == null) { 1248 return Ranking.VISIBILITY_NO_OVERRIDE; 1249 } 1250 return override.intValue(); 1251 } 1252 1253 private int getSuppressedVisualEffects(String key) { 1254 synchronized (this) { 1255 if (mSuppressedVisualEffects == null) { 1256 buildSuppressedVisualEffectsLocked(); 1257 } 1258 } 1259 Integer suppressed = mSuppressedVisualEffects.get(key); 1260 if (suppressed == null) { 1261 return 0; 1262 } 1263 return suppressed.intValue(); 1264 } 1265 1266 private int getImportance(String key) { 1267 synchronized (this) { 1268 if (mImportance == null) { 1269 buildImportanceLocked(); 1270 } 1271 } 1272 Integer importance = mImportance.get(key); 1273 if (importance == null) { 1274 return NotificationManager.IMPORTANCE_DEFAULT; 1275 } 1276 return importance.intValue(); 1277 } 1278 1279 private String getImportanceExplanation(String key) { 1280 synchronized (this) { 1281 if (mImportanceExplanation == null) { 1282 buildImportanceExplanationLocked(); 1283 } 1284 } 1285 return mImportanceExplanation.get(key); 1286 } 1287 1288 private String getOverrideGroupKey(String key) { 1289 synchronized (this) { 1290 if (mOverrideGroupKeys == null) { 1291 buildOverrideGroupKeys(); 1292 } 1293 } 1294 return mOverrideGroupKeys.get(key); 1295 } 1296 1297 // Locked by 'this' 1298 private void buildRanksLocked() { 1299 String[] orderedKeys = mRankingUpdate.getOrderedKeys(); 1300 mRanks = new ArrayMap<>(orderedKeys.length); 1301 for (int i = 0; i < orderedKeys.length; i++) { 1302 String key = orderedKeys[i]; 1303 mRanks.put(key, i); 1304 } 1305 } 1306 1307 // Locked by 'this' 1308 private void buildInterceptedSetLocked() { 1309 String[] dndInterceptedKeys = mRankingUpdate.getInterceptedKeys(); 1310 mIntercepted = new ArraySet<>(dndInterceptedKeys.length); 1311 Collections.addAll(mIntercepted, dndInterceptedKeys); 1312 } 1313 1314 // Locked by 'this' 1315 private void buildVisibilityOverridesLocked() { 1316 Bundle visibilityBundle = mRankingUpdate.getVisibilityOverrides(); 1317 mVisibilityOverrides = new ArrayMap<>(visibilityBundle.size()); 1318 for (String key: visibilityBundle.keySet()) { 1319 mVisibilityOverrides.put(key, visibilityBundle.getInt(key)); 1320 } 1321 } 1322 1323 // Locked by 'this' 1324 private void buildSuppressedVisualEffectsLocked() { 1325 Bundle suppressedBundle = mRankingUpdate.getSuppressedVisualEffects(); 1326 mSuppressedVisualEffects = new ArrayMap<>(suppressedBundle.size()); 1327 for (String key: suppressedBundle.keySet()) { 1328 mSuppressedVisualEffects.put(key, suppressedBundle.getInt(key)); 1329 } 1330 } 1331 // Locked by 'this' 1332 private void buildImportanceLocked() { 1333 String[] orderedKeys = mRankingUpdate.getOrderedKeys(); 1334 int[] importance = mRankingUpdate.getImportance(); 1335 mImportance = new ArrayMap<>(orderedKeys.length); 1336 for (int i = 0; i < orderedKeys.length; i++) { 1337 String key = orderedKeys[i]; 1338 mImportance.put(key, importance[i]); 1339 } 1340 } 1341 1342 // Locked by 'this' 1343 private void buildImportanceExplanationLocked() { 1344 Bundle explanationBundle = mRankingUpdate.getImportanceExplanation(); 1345 mImportanceExplanation = new ArrayMap<>(explanationBundle.size()); 1346 for (String key: explanationBundle.keySet()) { 1347 mImportanceExplanation.put(key, explanationBundle.getString(key)); 1348 } 1349 } 1350 1351 // Locked by 'this' 1352 private void buildOverrideGroupKeys() { 1353 Bundle overrideGroupKeys = mRankingUpdate.getOverrideGroupKeys(); 1354 mOverrideGroupKeys = new ArrayMap<>(overrideGroupKeys.size()); 1355 for (String key: overrideGroupKeys.keySet()) { 1356 mOverrideGroupKeys.put(key, overrideGroupKeys.getString(key)); 1357 } 1358 } 1359 1360 // ----------- Parcelable 1361 1362 @Override 1363 public int describeContents() { 1364 return 0; 1365 } 1366 1367 @Override 1368 public void writeToParcel(Parcel dest, int flags) { 1369 dest.writeParcelable(mRankingUpdate, flags); 1370 } 1371 1372 public static final Creator<RankingMap> CREATOR = new Creator<RankingMap>() { 1373 @Override 1374 public RankingMap createFromParcel(Parcel source) { 1375 NotificationRankingUpdate rankingUpdate = source.readParcelable(null); 1376 return new RankingMap(rankingUpdate); 1377 } 1378 1379 @Override 1380 public RankingMap[] newArray(int size) { 1381 return new RankingMap[size]; 1382 } 1383 }; 1384 } 1385 1386 private final class MyHandler extends Handler { 1387 public static final int MSG_ON_NOTIFICATION_POSTED = 1; 1388 public static final int MSG_ON_NOTIFICATION_REMOVED = 2; 1389 public static final int MSG_ON_LISTENER_CONNECTED = 3; 1390 public static final int MSG_ON_NOTIFICATION_RANKING_UPDATE = 4; 1391 public static final int MSG_ON_LISTENER_HINTS_CHANGED = 5; 1392 public static final int MSG_ON_INTERRUPTION_FILTER_CHANGED = 6; 1393 1394 public MyHandler(Looper looper) { 1395 super(looper, null, false); 1396 } 1397 1398 @Override 1399 public void handleMessage(Message msg) { 1400 if (!isConnected) { 1401 return; 1402 } 1403 switch (msg.what) { 1404 case MSG_ON_NOTIFICATION_POSTED: { 1405 SomeArgs args = (SomeArgs) msg.obj; 1406 StatusBarNotification sbn = (StatusBarNotification) args.arg1; 1407 RankingMap rankingMap = (RankingMap) args.arg2; 1408 args.recycle(); 1409 onNotificationPosted(sbn, rankingMap); 1410 } break; 1411 1412 case MSG_ON_NOTIFICATION_REMOVED: { 1413 SomeArgs args = (SomeArgs) msg.obj; 1414 StatusBarNotification sbn = (StatusBarNotification) args.arg1; 1415 RankingMap rankingMap = (RankingMap) args.arg2; 1416 args.recycle(); 1417 onNotificationRemoved(sbn, rankingMap); 1418 } break; 1419 1420 case MSG_ON_LISTENER_CONNECTED: { 1421 onListenerConnected(); 1422 } break; 1423 1424 case MSG_ON_NOTIFICATION_RANKING_UPDATE: { 1425 RankingMap rankingMap = (RankingMap) msg.obj; 1426 onNotificationRankingUpdate(rankingMap); 1427 } break; 1428 1429 case MSG_ON_LISTENER_HINTS_CHANGED: { 1430 final int hints = msg.arg1; 1431 onListenerHintsChanged(hints); 1432 } break; 1433 1434 case MSG_ON_INTERRUPTION_FILTER_CHANGED: { 1435 final int interruptionFilter = msg.arg1; 1436 onInterruptionFilterChanged(interruptionFilter); 1437 } break; 1438 } 1439 } 1440 } 1441} 1442