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