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