NotificationAssistantService.java revision b8f53ee812b75b526c3b481b62334e45609fa70e
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
17package android.service.notification;
18
19import android.annotation.SdkConstant;
20import android.annotation.SystemApi;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.net.Uri;
25import android.os.Handler;
26import android.os.IBinder;
27import android.os.Looper;
28import android.os.Message;
29import android.os.RemoteException;
30import android.util.Log;
31import com.android.internal.os.SomeArgs;
32
33/**
34 * A service that helps the user manage notifications by modifying the
35 * relative importance of notifications.
36 * <p>To extend this class, you must declare the service in your manifest file with
37 * the {@link android.Manifest.permission#BIND_NOTIFICATION_ASSISTANT_SERVICE} permission
38 * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
39 * <pre>
40 * &lt;service android:name=".NotificationAssistant"
41 *          android:label="&#64;string/service_name"
42 *          android:permission="android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE">
43 *     &lt;intent-filter>
44 *         &lt;action android:name="android.service.notification.NotificationAssistantService" />
45 *     &lt;/intent-filter>
46 * &lt;/service></pre>
47 * @hide
48 */
49@SystemApi
50public abstract class NotificationAssistantService extends NotificationListenerService {
51    private static final String TAG = "NotificationAssistant";
52
53    /**
54     * The {@link Intent} that must be declared as handled by the service.
55     */
56    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
57    public static final String SERVICE_INTERFACE
58            = "android.service.notification.NotificationAssistantService";
59
60    /** Notification was canceled by the status bar reporting a click. */
61    public static final int REASON_DELEGATE_CLICK = 1;
62
63    /** Notification was canceled by the status bar reporting a user dismissal. */
64    public static final int REASON_DELEGATE_CANCEL = 2;
65
66    /** Notification was canceled by the status bar reporting a user dismiss all. */
67    public static final int REASON_DELEGATE_CANCEL_ALL = 3;
68
69    /** Notification was canceled by the status bar reporting an inflation error. */
70    public static final int REASON_DELEGATE_ERROR = 4;
71
72    /** Notification was canceled by the package manager modifying the package. */
73    public static final int REASON_PACKAGE_CHANGED = 5;
74
75    /** Notification was canceled by the owning user context being stopped. */
76    public static final int REASON_USER_STOPPED = 6;
77
78    /** Notification was canceled by the user banning the package. */
79    public static final int REASON_PACKAGE_BANNED = 7;
80
81    /** Notification was canceled by the app canceling this specific notification. */
82    public static final int REASON_APP_CANCEL = 8;
83
84    /** Notification was canceled by the app cancelling all its notifications. */
85    public static final int REASON_APP_CANCEL_ALL = 9;
86
87    /** Notification was canceled by a listener reporting a user dismissal. */
88    public static final int REASON_LISTENER_CANCEL = 10;
89
90    /** Notification was canceled by a listener reporting a user dismiss all. */
91    public static final int REASON_LISTENER_CANCEL_ALL = 11;
92
93    /** Notification was canceled because it was a member of a canceled group. */
94    public static final int REASON_GROUP_SUMMARY_CANCELED = 12;
95
96    /** Notification was canceled because it was an invisible member of a group. */
97    public static final int REASON_GROUP_OPTIMIZATION = 13;
98
99    /** Notification was canceled by the device administrator suspending the package. */
100    public static final int REASON_PACKAGE_SUSPENDED = 14;
101
102    /** Notification was canceled by the owning managed profile being turned off. */
103    public static final int REASON_PROFILE_TURNED_OFF = 15;
104
105    public class Adjustment {
106        int mImportance;
107        CharSequence mExplanation;
108        Uri mReference;
109
110        /**
111         * Create a notification importance adjustment.
112         *
113         * @param importance The final importance of the notification.
114         * @param explanation A human-readable justification for the adjustment.
115         * @param reference A reference to an external object that augments the
116         *                  explanation, such as a
117         *                  {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI},
118         *                  or null.
119         */
120        public Adjustment(int importance, CharSequence explanation, Uri reference) {
121            mImportance = importance;
122            mExplanation = explanation;
123            mReference = reference;
124        }
125    }
126
127    private Handler mHandler;
128
129    /** @hide */
130    @Override
131    public void registerAsSystemService(Context context, ComponentName componentName,
132            int currentUser) throws RemoteException {
133        super.registerAsSystemService(context, componentName, currentUser);
134        mHandler = new MyHandler(getContext().getMainLooper());
135    }
136
137    @Override
138    protected void attachBaseContext(Context base) {
139        super.attachBaseContext(base);
140        mHandler = new MyHandler(getContext().getMainLooper());
141    }
142
143    @Override
144    public final IBinder onBind(Intent intent) {
145        if (mWrapper == null) {
146            mWrapper = new NotificationAssistantWrapper();
147        }
148        return mWrapper;
149    }
150
151    /**
152     * A notification was posted by an app. Called before alert.
153     *
154     * @param sbn the new notification
155     * @param importance the initial importance of the notification.
156     * @param user true if the initial importance reflects an explicit user preference.
157     * @return an adjustment or null to take no action, within 100ms.
158     */
159    abstract public Adjustment onNotificationEnqueued(StatusBarNotification sbn,
160          int importance, boolean user);
161
162    /**
163     * The visibility of a notification has changed.
164     *
165     * @param key the notification key
166     * @param time milliseconds since midnight, January 1, 1970 UTC.
167     * @param visible true if the notification became visible, false if hidden.
168     */
169    public void onNotificationVisibilityChanged(String key, long time, boolean visible)
170    {
171        // Do nothing, Override this to collect visibility statistics.
172    }
173
174    /**
175     * The user clicked on a notification.
176     *
177     * @param key the notification key
178     * @param time milliseconds since midnight, January 1, 1970 UTC.
179     */
180    public void onNotificationClick(String key, long time)
181    {
182        // Do nothing, Override this to collect click statistics
183    }
184
185    /**
186     * The user clicked on a notification action.
187     *
188     * @param key the notification key
189     * @param time milliseconds since midnight, January 1, 1970 UTC.
190     * @param actionIndex the index of the action button that was pressed.
191     */
192    public void onNotificationActionClick(String key, long time, int actionIndex)
193    {
194        // Do nothing, Override this to collect action button click statistics
195    }
196
197    /**
198     * A notification was removed.
199
200     * @param key the notification key
201     * @param time milliseconds since midnight, January 1, 1970 UTC.
202     * @param reason see {@link #REASON_LISTENER_CANCEL}, etc.
203     */
204    public void onNotificationRemoved(String key, long time, int reason) {
205        // Do nothing, Override this to collect dismissal statistics
206    }
207
208    /**
209     * Change the importance of an existing notification.  N.B. this won’t cause
210     * an existing notification to alert, but might allow a future update to
211     * this notification to alert.
212     *
213     * @param key the notification key
214     * @param adjustment the new importance with an explanation
215     */
216    public final void adjustImportance(String key, Adjustment adjustment) {
217        if (!isBound()) return;
218        try {
219            getNotificationInterface().setImportanceFromAssistant(mWrapper, key,
220                    adjustment.mImportance, adjustment.mExplanation);
221        } catch (android.os.RemoteException ex) {
222            Log.v(TAG, "Unable to contact notification manager", ex);
223        }
224    }
225
226    private class NotificationAssistantWrapper extends NotificationListenerWrapper {
227        @Override
228        public void onNotificationEnqueued(IStatusBarNotificationHolder sbnHolder,
229                int importance, boolean user) {
230            StatusBarNotification sbn;
231            try {
232                sbn = sbnHolder.get();
233            } catch (RemoteException e) {
234                Log.w(TAG, "onNotificationEnqueued: Error receiving StatusBarNotification", e);
235                return;
236            }
237
238            SomeArgs args = SomeArgs.obtain();
239            args.arg1 = sbn;
240            args.argi1 = importance;
241            args.argi2 = user ? 1 : 0;
242            mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_ENQUEUED,
243                    args).sendToTarget();
244        }
245
246        @Override
247        public void onNotificationVisibilityChanged(String key, long time, boolean visible) {
248            SomeArgs args = SomeArgs.obtain();
249            args.arg1 = key;
250            args.arg2 = time;
251            args.argi1 = visible ? 1 : 0;
252            mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_VISIBILITY_CHANGED,
253                    args).sendToTarget();
254        }
255
256        @Override
257        public void onNotificationClick(String key, long time) {
258            SomeArgs args = SomeArgs.obtain();
259            args.arg1 = key;
260            args.arg2 = time;
261            mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_CLICK,
262                    args).sendToTarget();
263        }
264
265        @Override
266        public void onNotificationActionClick(String key, long time, int actionIndex) {
267            SomeArgs args = SomeArgs.obtain();
268            args.arg1 = key;
269            args.arg2 = time;
270            args.argi1 = actionIndex;
271            mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_ACTION_CLICK,
272                    args).sendToTarget();
273        }
274
275        @Override
276        public void onNotificationRemovedReason(String key, long time, int reason) {
277            SomeArgs args = SomeArgs.obtain();
278            args.arg1 = key;
279            args.arg2 = time;
280            args.argi1 = reason;
281            mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_REMOVED_REASON,
282                    args).sendToTarget();
283        }
284    }
285
286    private final class MyHandler extends Handler {
287        public static final int MSG_ON_NOTIFICATION_ENQUEUED = 1;
288        public static final int MSG_ON_NOTIFICATION_VISIBILITY_CHANGED = 2;
289        public static final int MSG_ON_NOTIFICATION_CLICK = 3;
290        public static final int MSG_ON_NOTIFICATION_ACTION_CLICK = 4;
291        public static final int MSG_ON_NOTIFICATION_REMOVED_REASON = 5;
292
293        public MyHandler(Looper looper) {
294            super(looper, null, false);
295        }
296
297        @Override
298        public void handleMessage(Message msg) {
299            switch (msg.what) {
300                case MSG_ON_NOTIFICATION_ENQUEUED: {
301                    SomeArgs args = (SomeArgs) msg.obj;
302                    StatusBarNotification sbn = (StatusBarNotification) args.arg1;
303                    final int importance = args.argi1;
304                    final boolean user = args.argi2 == 1;
305                    args.recycle();
306                    Adjustment adjustment = onNotificationEnqueued(sbn, importance, user);
307                    if (adjustment != null) {
308                        adjustImportance(sbn.getKey(), adjustment);
309                    }
310                } break;
311
312                case MSG_ON_NOTIFICATION_VISIBILITY_CHANGED: {
313                    SomeArgs args = (SomeArgs) msg.obj;
314                    final String key = (String) args.arg1;
315                    final long time = (long) args.arg2;
316                    final boolean visible = args.argi1 == 1;
317                    args.recycle();
318                    onNotificationVisibilityChanged(key, time, visible);
319                } break;
320
321                case MSG_ON_NOTIFICATION_CLICK: {
322                    SomeArgs args = (SomeArgs) msg.obj;
323                    final String key = (String) args.arg1;
324                    final long time = (long) args.arg2;
325                    args.recycle();
326                    onNotificationClick(key, time);
327                } break;
328
329                case MSG_ON_NOTIFICATION_ACTION_CLICK: {
330                    SomeArgs args = (SomeArgs) msg.obj;
331                    final String key = (String) args.arg1;
332                    final long time = (long) args.arg2;
333                    final int actionIndex = args.argi1;
334                    args.recycle();
335                    onNotificationActionClick(key, time, actionIndex);
336                } break;
337
338                case MSG_ON_NOTIFICATION_REMOVED_REASON: {
339                    SomeArgs args = (SomeArgs) msg.obj;
340                    final String key = (String) args.arg1;
341                    final long time = (long) args.arg2;
342                    final int reason = args.argi1;
343                    args.recycle();
344                    onNotificationRemoved(key, time, reason);
345                } break;
346            }
347        }
348    }
349}
350