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