• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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