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