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