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