1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.service.notification; 18 19 import static java.lang.annotation.RetentionPolicy.SOURCE; 20 21 import android.annotation.IntDef; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.SdkConstant; 25 import android.annotation.SuppressLint; 26 import android.annotation.SystemApi; 27 import android.app.Notification; 28 import android.app.NotificationChannel; 29 import android.app.NotificationManager; 30 import android.app.admin.DevicePolicyManager; 31 import android.content.ComponentName; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.os.Bundle; 35 import android.os.Handler; 36 import android.os.IBinder; 37 import android.os.Looper; 38 import android.os.Message; 39 import android.os.RemoteException; 40 import android.util.Log; 41 import com.android.internal.os.SomeArgs; 42 import java.lang.annotation.Retention; 43 import java.util.List; 44 45 /** 46 * A service that helps the user manage notifications. 47 * <p> 48 * Only one notification assistant can be active at a time. Unlike notification listener services, 49 * assistant services can additionally modify certain aspects about notifications 50 * (see {@link Adjustment}) before they are posted. 51 *<p> 52 * A note about managed profiles: Unlike {@link NotificationListenerService listener services}, 53 * NotificationAssistantServices are allowed to run in managed profiles 54 * (see {@link DevicePolicyManager#isManagedProfile(ComponentName)}), so they can access the 55 * information they need to create good {@link Adjustment adjustments}. To maintain the contract 56 * with {@link NotificationListenerService}, an assistant service will receive all of the 57 * callbacks from {@link NotificationListenerService} for the current user, managed profiles of 58 * that user, and ones that affect all users. However, 59 * {@link #onNotificationEnqueued(StatusBarNotification)} will only be called for notifications 60 * sent to the current user, and {@link Adjustment adjuments} will only be accepted for the 61 * current user. 62 * <p> 63 * All callbacks are called on the main thread. 64 * </p> 65 * @hide 66 */ 67 @SystemApi 68 public abstract class NotificationAssistantService extends NotificationListenerService { 69 private static final String TAG = "NotificationAssistants"; 70 71 /** @hide */ 72 @Retention(SOURCE) 73 @IntDef({SOURCE_FROM_APP, SOURCE_FROM_ASSISTANT}) 74 public @interface Source {} 75 76 /** 77 * To indicate an adjustment is from an app. 78 */ 79 public static final int SOURCE_FROM_APP = 0; 80 /** 81 * To indicate an adjustment is from a {@link NotificationAssistantService}. 82 */ 83 public static final int SOURCE_FROM_ASSISTANT = 1; 84 85 /** 86 * The {@link Intent} that must be declared as handled by the service. 87 */ 88 @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) 89 public static final String SERVICE_INTERFACE 90 = "android.service.notification.NotificationAssistantService"; 91 92 /** 93 * Activity Action: Show notification assistant detail setting page in NAS app. 94 * <p> 95 * In some cases, a matching Activity may not exist, so ensure you 96 * safeguard against this. 97 * <p> 98 * Input: Nothing. 99 * <p> 100 * Output: Nothing. 101 */ 102 @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) 103 public static final String ACTION_NOTIFICATION_ASSISTANT_DETAIL_SETTINGS = 104 "android.service.notification.action.NOTIFICATION_ASSISTANT_DETAIL_SETTINGS"; 105 106 107 /** 108 * Data type: int, the feedback rating score provided by user. The score can be any integer 109 * value depends on the experimental and feedback UX design. 110 */ 111 public static final String FEEDBACK_RATING = "feedback.rating"; 112 113 /** 114 * @hide 115 */ 116 protected Handler mHandler; 117 118 @SuppressLint("OnNameExpected") 119 @Override attachBaseContext(Context base)120 protected void attachBaseContext(Context base) { 121 super.attachBaseContext(base); 122 mHandler = new MyHandler(getContext().getMainLooper()); 123 } 124 125 @Override onBind(@ullable Intent intent)126 public final @NonNull IBinder onBind(@Nullable Intent intent) { 127 if (mWrapper == null) { 128 mWrapper = new NotificationAssistantServiceWrapper(); 129 } 130 return mWrapper; 131 } 132 133 /** 134 * A notification was snoozed until a context. For use with 135 * {@link Adjustment#KEY_SNOOZE_CRITERIA}. When the device reaches the given context, the 136 * assistant should restore the notification with {@link #unsnoozeNotification(String)}. 137 * 138 * @param sbn the notification to snooze 139 * @param snoozeCriterionId the {@link SnoozeCriterion#getId()} representing a device context. 140 */ onNotificationSnoozedUntilContext(@onNull StatusBarNotification sbn, @NonNull String snoozeCriterionId)141 abstract public void onNotificationSnoozedUntilContext(@NonNull StatusBarNotification sbn, 142 @NonNull String snoozeCriterionId); 143 144 /** 145 * A notification was posted by an app. Called before post. 146 * 147 * <p>Note: this method is only called if you don't override 148 * {@link #onNotificationEnqueued(StatusBarNotification, NotificationChannel)} or 149 * {@link #onNotificationEnqueued(StatusBarNotification, NotificationChannel, RankingMap)}.</p> 150 * 151 * @param sbn the new notification 152 * @return an adjustment or null to take no action, within 200ms. 153 */ onNotificationEnqueued(@onNull StatusBarNotification sbn)154 abstract public @Nullable Adjustment onNotificationEnqueued(@NonNull StatusBarNotification sbn); 155 156 /** 157 * A notification was posted by an app. Called before post. 158 * 159 * <p>Note: this method is only called if you don't override 160 * {@link #onNotificationEnqueued(StatusBarNotification, NotificationChannel, RankingMap)}.</p> 161 * 162 * @param sbn the new notification 163 * @param channel the channel the notification was posted to 164 * @return an adjustment or null to take no action, within 200ms. 165 */ onNotificationEnqueued(@onNull StatusBarNotification sbn, @NonNull NotificationChannel channel)166 public @Nullable Adjustment onNotificationEnqueued(@NonNull StatusBarNotification sbn, 167 @NonNull NotificationChannel channel) { 168 return onNotificationEnqueued(sbn); 169 } 170 171 /** 172 * A notification was posted by an app. Called before post. 173 * 174 * @param sbn the new notification 175 * @param channel the channel the notification was posted to 176 * @param rankingMap The current ranking map that can be used to retrieve ranking information 177 * for active notifications. 178 * @return an adjustment or null to take no action, within 200ms. 179 */ onNotificationEnqueued(@onNull StatusBarNotification sbn, @NonNull NotificationChannel channel, @NonNull RankingMap rankingMap)180 public @Nullable Adjustment onNotificationEnqueued(@NonNull StatusBarNotification sbn, 181 @NonNull NotificationChannel channel, @NonNull RankingMap rankingMap) { 182 return onNotificationEnqueued(sbn, channel); 183 } 184 185 /** 186 * Implement this method to learn when notifications are removed, how they were interacted with 187 * before removal, and why they were removed. 188 * <p> 189 * This might occur because the user has dismissed the notification using system UI (or another 190 * notification listener) or because the app has withdrawn the notification. 191 * <p> 192 * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the 193 * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight 194 * fields such as {@link android.app.Notification#contentView} and 195 * {@link android.app.Notification#largeIcon}. However, all other fields on 196 * {@link StatusBarNotification}, sufficient to match this call with a prior call to 197 * {@link #onNotificationPosted(StatusBarNotification)}, will be intact. 198 * 199 ** @param sbn A data structure encapsulating at least the original information (tag and id) 200 * and source (package name) used to post the {@link android.app.Notification} that 201 * was just removed. 202 * @param rankingMap The current ranking map that can be used to retrieve ranking information 203 * for active notifications. 204 * @param stats Stats about how the user interacted with the notification before it was removed. 205 * @param reason see {@link #REASON_LISTENER_CANCEL}, etc. 206 */ 207 @Override onNotificationRemoved(@onNull StatusBarNotification sbn, @NonNull RankingMap rankingMap, @NonNull NotificationStats stats, int reason)208 public void onNotificationRemoved(@NonNull StatusBarNotification sbn, 209 @NonNull RankingMap rankingMap, 210 @NonNull NotificationStats stats, int reason) { 211 onNotificationRemoved(sbn, rankingMap, reason); 212 } 213 214 /** 215 * Implement this to know when a user has seen notifications, as triggered by 216 * {@link #setNotificationsShown(String[])}. 217 */ onNotificationsSeen(@onNull List<String> keys)218 public void onNotificationsSeen(@NonNull List<String> keys) { 219 220 } 221 222 /** 223 * Implement this to know when the notification panel is revealed 224 * 225 * @param items Number of notifications on the panel at time of opening 226 */ onPanelRevealed(int items)227 public void onPanelRevealed(int items) { 228 229 } 230 231 /** 232 * Implement this to know when the notification panel is hidden 233 */ onPanelHidden()234 public void onPanelHidden() { 235 236 } 237 238 /** 239 * Implement this to know when a notification becomes visible or hidden from the user. 240 * 241 * @param key the notification key 242 * @param isVisible whether the notification is visible. 243 */ onNotificationVisibilityChanged(@onNull String key, boolean isVisible)244 public void onNotificationVisibilityChanged(@NonNull String key, boolean isVisible) { 245 246 } 247 248 /** 249 * Implement this to know when a notification change (expanded / collapsed) is visible to user. 250 * 251 * @param key the notification key 252 * @param isUserAction whether the expanded change is caused by user action. 253 * @param isExpanded whether the notification is expanded. 254 */ onNotificationExpansionChanged( @onNull String key, boolean isUserAction, boolean isExpanded)255 public void onNotificationExpansionChanged( 256 @NonNull String key, boolean isUserAction, boolean isExpanded) {} 257 258 /** 259 * Implement this to know when a direct reply is sent from a notification. 260 * @param key the notification key 261 */ onNotificationDirectReplied(@onNull String key)262 public void onNotificationDirectReplied(@NonNull String key) {} 263 264 /** 265 * Implement this to know when a suggested reply is sent. 266 * @param key the notification key 267 * @param reply the reply that is just sent 268 * @param source the source that provided the reply, e.g. SOURCE_FROM_APP 269 */ onSuggestedReplySent(@onNull String key, @NonNull CharSequence reply, @Source int source)270 public void onSuggestedReplySent(@NonNull String key, @NonNull CharSequence reply, 271 @Source int source) { 272 } 273 274 /** 275 * Implement this to know when an action is clicked. 276 * @param key the notification key 277 * @param action the action that is just clicked 278 * @param source the source that provided the action, e.g. SOURCE_FROM_APP 279 */ onActionInvoked(@onNull String key, @NonNull Notification.Action action, @Source int source)280 public void onActionInvoked(@NonNull String key, @NonNull Notification.Action action, 281 @Source int source) { 282 } 283 284 /** 285 * Implement this to know when a notification is clicked by user. 286 * @param key the notification key 287 */ onNotificationClicked(@onNull String key)288 public void onNotificationClicked(@NonNull String key) { 289 } 290 291 /** 292 * Implement this to know when a user has changed which features of 293 * their notifications the assistant can modify. 294 * <p> Query {@link NotificationManager#getAllowedAssistantAdjustments()} to see what 295 * {@link Adjustment adjustments} you are currently allowed to make.</p> 296 * 297 * @deprecated changing allowed adjustments is no longer supported. 298 */ 299 @Deprecated onAllowedAdjustmentsChanged()300 public void onAllowedAdjustmentsChanged() { 301 } 302 303 /** 304 * Implement this to know when user provides a feedback. 305 * @param key the notification key 306 * @param rankingMap The current ranking map that can be used to retrieve ranking information 307 * for active notifications. 308 * @param feedback the received feedback, such as {@link #FEEDBACK_RATING rating score} 309 */ onNotificationFeedbackReceived(@onNull String key, @NonNull RankingMap rankingMap, @NonNull Bundle feedback)310 public void onNotificationFeedbackReceived(@NonNull String key, @NonNull RankingMap rankingMap, 311 @NonNull Bundle feedback) { 312 } 313 314 /** 315 * Updates a notification. N.B. this won’t cause 316 * an existing notification to alert, but might allow a future update to 317 * this notification to alert. 318 * 319 * @param adjustment the adjustment with an explanation 320 */ adjustNotification(@onNull Adjustment adjustment)321 public final void adjustNotification(@NonNull Adjustment adjustment) { 322 if (!isBound()) return; 323 try { 324 setAdjustmentIssuer(adjustment); 325 getNotificationInterface().applyEnqueuedAdjustmentFromAssistant(mWrapper, adjustment); 326 } catch (android.os.RemoteException ex) { 327 Log.v(TAG, "Unable to contact notification manager", ex); 328 throw ex.rethrowFromSystemServer(); 329 } 330 } 331 332 /** 333 * Updates existing notifications. Re-ranking won't occur until all adjustments are applied. 334 * N.B. this won’t cause an existing notification to alert, but might allow a future update to 335 * these notifications to alert. 336 * 337 * @param adjustments a list of adjustments with explanations 338 */ adjustNotifications(@onNull List<Adjustment> adjustments)339 public final void adjustNotifications(@NonNull List<Adjustment> adjustments) { 340 if (!isBound()) return; 341 try { 342 for (Adjustment adjustment : adjustments) { 343 setAdjustmentIssuer(adjustment); 344 } 345 getNotificationInterface().applyAdjustmentsFromAssistant(mWrapper, adjustments); 346 } catch (android.os.RemoteException ex) { 347 Log.v(TAG, "Unable to contact notification manager", ex); 348 throw ex.rethrowFromSystemServer(); 349 } 350 } 351 352 /** 353 * Inform the notification manager about un-snoozing a specific notification. 354 * <p> 355 * This should only be used for notifications snoozed because of a contextual snooze suggestion 356 * you provided via {@link Adjustment#KEY_SNOOZE_CRITERIA}. Once un-snoozed, you will get a 357 * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the 358 * notification. 359 * @param key The key of the notification to snooze 360 */ unsnoozeNotification(@onNull String key)361 public final void unsnoozeNotification(@NonNull String key) { 362 if (!isBound()) return; 363 try { 364 getNotificationInterface().unsnoozeNotificationFromAssistant(mWrapper, key); 365 } catch (android.os.RemoteException ex) { 366 Log.v(TAG, "Unable to contact notification manager", ex); 367 } 368 } 369 370 private class NotificationAssistantServiceWrapper extends NotificationListenerWrapper { 371 @Override onNotificationEnqueuedWithChannel(IStatusBarNotificationHolder sbnHolder, NotificationChannel channel, NotificationRankingUpdate update)372 public void onNotificationEnqueuedWithChannel(IStatusBarNotificationHolder sbnHolder, 373 NotificationChannel channel, NotificationRankingUpdate update) { 374 StatusBarNotification sbn; 375 try { 376 sbn = sbnHolder.get(); 377 } catch (RemoteException e) { 378 Log.w(TAG, "onNotificationEnqueued: Error receiving StatusBarNotification", e); 379 return; 380 } 381 if (sbn == null) { 382 Log.w(TAG, "onNotificationEnqueuedWithChannel: " 383 + "Error receiving StatusBarNotification"); 384 return; 385 } 386 387 applyUpdateLocked(update); 388 SomeArgs args = SomeArgs.obtain(); 389 args.arg1 = sbn; 390 args.arg2 = channel; 391 args.arg3 = getCurrentRanking(); 392 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_ENQUEUED, 393 args).sendToTarget(); 394 } 395 396 @Override onNotificationSnoozedUntilContext( IStatusBarNotificationHolder sbnHolder, String snoozeCriterionId)397 public void onNotificationSnoozedUntilContext( 398 IStatusBarNotificationHolder sbnHolder, String snoozeCriterionId) { 399 StatusBarNotification sbn; 400 try { 401 sbn = sbnHolder.get(); 402 } catch (RemoteException e) { 403 Log.w(TAG, "onNotificationSnoozed: Error receiving StatusBarNotification", e); 404 return; 405 } 406 if (sbn == null) { 407 Log.w(TAG, "onNotificationSnoozed: Error receiving StatusBarNotification"); 408 return; 409 } 410 411 SomeArgs args = SomeArgs.obtain(); 412 args.arg1 = sbn; 413 args.arg2 = snoozeCriterionId; 414 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_SNOOZED, 415 args).sendToTarget(); 416 } 417 418 @Override onNotificationsSeen(List<String> keys)419 public void onNotificationsSeen(List<String> keys) { 420 SomeArgs args = SomeArgs.obtain(); 421 args.arg1 = keys; 422 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATIONS_SEEN, 423 args).sendToTarget(); 424 } 425 426 @Override onPanelRevealed(int items)427 public void onPanelRevealed(int items) { 428 SomeArgs args = SomeArgs.obtain(); 429 args.argi1 = items; 430 mHandler.obtainMessage(MyHandler.MSG_ON_PANEL_REVEALED, 431 args).sendToTarget(); 432 } 433 434 @Override onPanelHidden()435 public void onPanelHidden() { 436 SomeArgs args = SomeArgs.obtain(); 437 mHandler.obtainMessage(MyHandler.MSG_ON_PANEL_HIDDEN, 438 args).sendToTarget(); 439 } 440 441 @Override onNotificationVisibilityChanged(String key, boolean isVisible)442 public void onNotificationVisibilityChanged(String key, boolean isVisible) { 443 SomeArgs args = SomeArgs.obtain(); 444 args.arg1 = key; 445 args.argi1 = isVisible ? 1 : 0; 446 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_VISIBILITY_CHANGED, 447 args).sendToTarget(); 448 } 449 450 @Override onNotificationExpansionChanged(String key, boolean isUserAction, boolean isExpanded)451 public void onNotificationExpansionChanged(String key, boolean isUserAction, 452 boolean isExpanded) { 453 SomeArgs args = SomeArgs.obtain(); 454 args.arg1 = key; 455 args.argi1 = isUserAction ? 1 : 0; 456 args.argi2 = isExpanded ? 1 : 0; 457 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_EXPANSION_CHANGED, args) 458 .sendToTarget(); 459 } 460 461 @Override onNotificationDirectReply(String key)462 public void onNotificationDirectReply(String key) { 463 SomeArgs args = SomeArgs.obtain(); 464 args.arg1 = key; 465 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_DIRECT_REPLY_SENT, args) 466 .sendToTarget(); 467 } 468 469 @Override onSuggestedReplySent(String key, CharSequence reply, int source)470 public void onSuggestedReplySent(String key, CharSequence reply, int source) { 471 SomeArgs args = SomeArgs.obtain(); 472 args.arg1 = key; 473 args.arg2 = reply; 474 args.argi2 = source; 475 mHandler.obtainMessage(MyHandler.MSG_ON_SUGGESTED_REPLY_SENT, args).sendToTarget(); 476 } 477 478 @Override onActionClicked(String key, Notification.Action action, int source)479 public void onActionClicked(String key, Notification.Action action, int source) { 480 SomeArgs args = SomeArgs.obtain(); 481 args.arg1 = key; 482 args.arg2 = action; 483 args.argi2 = source; 484 mHandler.obtainMessage(MyHandler.MSG_ON_ACTION_INVOKED, args).sendToTarget(); 485 } 486 487 @Override onNotificationClicked(String key)488 public void onNotificationClicked(String key) { 489 SomeArgs args = SomeArgs.obtain(); 490 args.arg1 = key; 491 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_CLICKED, args).sendToTarget(); 492 } 493 494 @Override onAllowedAdjustmentsChanged()495 public void onAllowedAdjustmentsChanged() { 496 mHandler.obtainMessage(MyHandler.MSG_ON_ALLOWED_ADJUSTMENTS_CHANGED).sendToTarget(); 497 } 498 499 @Override onNotificationFeedbackReceived(String key, NotificationRankingUpdate update, Bundle feedback)500 public void onNotificationFeedbackReceived(String key, NotificationRankingUpdate update, 501 Bundle feedback) { 502 applyUpdateLocked(update); 503 SomeArgs args = SomeArgs.obtain(); 504 args.arg1 = key; 505 args.arg2 = getCurrentRanking(); 506 args.arg3 = feedback; 507 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_FEEDBACK_RECEIVED, 508 args).sendToTarget(); 509 } 510 } 511 setAdjustmentIssuer(@ullable Adjustment adjustment)512 private void setAdjustmentIssuer(@Nullable Adjustment adjustment) { 513 if (adjustment != null) { 514 adjustment.setIssuer(getOpPackageName() + "/" + getClass().getName()); 515 } 516 } 517 518 private final class MyHandler extends Handler { 519 public static final int MSG_ON_NOTIFICATION_ENQUEUED = 1; 520 public static final int MSG_ON_NOTIFICATION_SNOOZED = 2; 521 public static final int MSG_ON_NOTIFICATIONS_SEEN = 3; 522 public static final int MSG_ON_NOTIFICATION_EXPANSION_CHANGED = 4; 523 public static final int MSG_ON_NOTIFICATION_DIRECT_REPLY_SENT = 5; 524 public static final int MSG_ON_SUGGESTED_REPLY_SENT = 6; 525 public static final int MSG_ON_ACTION_INVOKED = 7; 526 public static final int MSG_ON_ALLOWED_ADJUSTMENTS_CHANGED = 8; 527 public static final int MSG_ON_PANEL_REVEALED = 9; 528 public static final int MSG_ON_PANEL_HIDDEN = 10; 529 public static final int MSG_ON_NOTIFICATION_VISIBILITY_CHANGED = 11; 530 public static final int MSG_ON_NOTIFICATION_CLICKED = 12; 531 public static final int MSG_ON_NOTIFICATION_FEEDBACK_RECEIVED = 13; 532 MyHandler(Looper looper)533 public MyHandler(Looper looper) { 534 super(looper, null, false); 535 } 536 537 @Override handleMessage(Message msg)538 public void handleMessage(Message msg) { 539 switch (msg.what) { 540 case MSG_ON_NOTIFICATION_ENQUEUED: { 541 SomeArgs args = (SomeArgs) msg.obj; 542 StatusBarNotification sbn = (StatusBarNotification) args.arg1; 543 NotificationChannel channel = (NotificationChannel) args.arg2; 544 RankingMap ranking = (RankingMap) args.arg3; 545 args.recycle(); 546 Adjustment adjustment = onNotificationEnqueued(sbn, channel, ranking); 547 setAdjustmentIssuer(adjustment); 548 if (adjustment != null) { 549 if (!isBound()) { 550 Log.w(TAG, "MSG_ON_NOTIFICATION_ENQUEUED: service not bound, skip."); 551 return; 552 } 553 try { 554 getNotificationInterface().applyEnqueuedAdjustmentFromAssistant( 555 mWrapper, adjustment); 556 } catch (android.os.RemoteException ex) { 557 Log.v(TAG, "Unable to contact notification manager", ex); 558 throw ex.rethrowFromSystemServer(); 559 } catch (SecurityException e) { 560 // app cannot catch and recover from this, so do on their behalf 561 Log.w(TAG, "Enqueue adjustment failed; no longer connected", e); 562 } 563 } 564 break; 565 } 566 case MSG_ON_NOTIFICATION_SNOOZED: { 567 SomeArgs args = (SomeArgs) msg.obj; 568 StatusBarNotification sbn = (StatusBarNotification) args.arg1; 569 String snoozeCriterionId = (String) args.arg2; 570 args.recycle(); 571 onNotificationSnoozedUntilContext(sbn, snoozeCriterionId); 572 break; 573 } 574 case MSG_ON_NOTIFICATIONS_SEEN: { 575 SomeArgs args = (SomeArgs) msg.obj; 576 List<String> keys = (List<String>) args.arg1; 577 args.recycle(); 578 onNotificationsSeen(keys); 579 break; 580 } 581 case MSG_ON_NOTIFICATION_EXPANSION_CHANGED: { 582 SomeArgs args = (SomeArgs) msg.obj; 583 String key = (String) args.arg1; 584 boolean isUserAction = args.argi1 == 1; 585 boolean isExpanded = args.argi2 == 1; 586 args.recycle(); 587 onNotificationExpansionChanged(key, isUserAction, isExpanded); 588 break; 589 } 590 case MSG_ON_NOTIFICATION_DIRECT_REPLY_SENT: { 591 SomeArgs args = (SomeArgs) msg.obj; 592 String key = (String) args.arg1; 593 args.recycle(); 594 onNotificationDirectReplied(key); 595 break; 596 } 597 case MSG_ON_SUGGESTED_REPLY_SENT: { 598 SomeArgs args = (SomeArgs) msg.obj; 599 String key = (String) args.arg1; 600 CharSequence reply = (CharSequence) args.arg2; 601 int source = args.argi2; 602 args.recycle(); 603 onSuggestedReplySent(key, reply, source); 604 break; 605 } 606 case MSG_ON_ACTION_INVOKED: { 607 SomeArgs args = (SomeArgs) msg.obj; 608 String key = (String) args.arg1; 609 Notification.Action action = (Notification.Action) args.arg2; 610 int source = args.argi2; 611 args.recycle(); 612 onActionInvoked(key, action, source); 613 break; 614 } 615 case MSG_ON_ALLOWED_ADJUSTMENTS_CHANGED: { 616 onAllowedAdjustmentsChanged(); 617 break; 618 } 619 case MSG_ON_PANEL_REVEALED: { 620 SomeArgs args = (SomeArgs) msg.obj; 621 int items = args.argi1; 622 args.recycle(); 623 onPanelRevealed(items); 624 break; 625 } 626 case MSG_ON_PANEL_HIDDEN: { 627 onPanelHidden(); 628 break; 629 } 630 case MSG_ON_NOTIFICATION_VISIBILITY_CHANGED: { 631 SomeArgs args = (SomeArgs) msg.obj; 632 String key = (String) args.arg1; 633 boolean isVisible = args.argi1 == 1; 634 args.recycle(); 635 onNotificationVisibilityChanged(key, isVisible); 636 break; 637 } 638 case MSG_ON_NOTIFICATION_CLICKED: { 639 SomeArgs args = (SomeArgs) msg.obj; 640 String key = (String) args.arg1; 641 args.recycle(); 642 onNotificationClicked(key); 643 break; 644 } 645 case MSG_ON_NOTIFICATION_FEEDBACK_RECEIVED: { 646 SomeArgs args = (SomeArgs) msg.obj; 647 String key = (String) args.arg1; 648 RankingMap ranking = (RankingMap) args.arg2; 649 Bundle feedback = (Bundle) args.arg3; 650 args.recycle(); 651 onNotificationFeedbackReceived(key, ranking, feedback); 652 break; 653 } 654 } 655 } 656 } 657 } 658