NotificationAssistantService.java revision ceecfcf5ccd4790f9ab3a08c3cb7ce4baa2c1eb1
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.NonNull;
20import android.annotation.Nullable;
21import android.annotation.SdkConstant;
22import android.app.NotificationChannel;
23import android.content.Context;
24import android.content.Intent;
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
33import java.util.ArrayList;
34import java.util.List;
35
36/**
37 * A service that helps the user manage notifications.
38 */
39public abstract class NotificationAssistantService extends NotificationListenerService {
40    private static final String TAG = "NotificationAssistants";
41
42    /**
43     * The {@link Intent} that must be declared as handled by the service.
44     */
45    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
46    public static final String SERVICE_INTERFACE
47            = "android.service.notification.NotificationAssistantService";
48
49    private Handler mHandler;
50
51    @Override
52    protected void attachBaseContext(Context base) {
53        super.attachBaseContext(base);
54        mHandler = new MyHandler(getContext().getMainLooper());
55    }
56
57    @Override
58    public final IBinder onBind(Intent intent) {
59        if (mWrapper == null) {
60            mWrapper = new NotificationAssistantServiceWrapper();
61        }
62        return mWrapper;
63    }
64
65    /**
66     * A notification was snoozed until a context. For use with
67     * {@link Adjustment#KEY_SNOOZE_CRITERIA}. When the device reaches the given context, the
68     * assistant should restore the notification with {@link #unsnoozeNotification(String)}.
69     *
70     * @param sbn the notification to snooze
71     * @param snoozeCriterionId the {@link SnoozeCriterion#getId()} representing a device context.
72     */
73    abstract public void onNotificationSnoozedUntilContext(StatusBarNotification sbn,
74            String snoozeCriterionId);
75
76    /**
77     * A notification was posted by an app. Called before alert.
78     *
79     * @param sbn the new notification
80     * @return an adjustment or null to take no action, within 100ms.
81     */
82    abstract public Adjustment onNotificationEnqueued(StatusBarNotification sbn);
83
84    /**
85     * Updates a notification.  N.B. this won’t cause
86     * an existing notification to alert, but might allow a future update to
87     * this notification to alert.
88     *
89     * @param adjustment the adjustment with an explanation
90     */
91    public final void adjustNotification(Adjustment adjustment) {
92        if (!isBound()) return;
93        try {
94            getNotificationInterface().applyAdjustmentFromAssistant(mWrapper, adjustment);
95        } catch (android.os.RemoteException ex) {
96            Log.v(TAG, "Unable to contact notification manager", ex);
97            throw ex.rethrowFromSystemServer();
98        }
99    }
100
101    /**
102     * Updates existing notifications. Re-ranking won't occur until all adjustments are applied.
103     * N.B. this won’t cause an existing notification to alert, but might allow a future update to
104     * these notifications to alert.
105     *
106     * @param adjustments a list of adjustments with explanations
107     */
108    public final void adjustNotifications(List<Adjustment> adjustments) {
109        if (!isBound()) return;
110        try {
111            getNotificationInterface().applyAdjustmentsFromAssistant(mWrapper, adjustments);
112        } catch (android.os.RemoteException ex) {
113            Log.v(TAG, "Unable to contact notification manager", ex);
114            throw ex.rethrowFromSystemServer();
115        }
116    }
117
118    /**
119     * Inform the notification manager about un-snoozing a specific notification.
120     * <p>
121     * This should only be used for notifications snoozed by this listener using
122     * {@link #snoozeNotification(String, String)}. Once un-snoozed, you will get a
123     * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the
124     * notification.
125     * @param key The key of the notification to snooze
126     */
127    public final void unsnoozeNotification(String key) {
128        if (!isBound()) return;
129        try {
130            getNotificationInterface().unsnoozeNotificationFromAssistant(mWrapper, key);
131        } catch (android.os.RemoteException ex) {
132            Log.v(TAG, "Unable to contact notification manager", ex);
133        }
134    }
135
136    /**
137     * Creates a notification channel that notifications can be posted to for a given package.
138     *
139     * @param pkg The package to create a channel for.
140     * @param channel  the channel to attempt to create.
141     */
142    public void createNotificationChannel(@NonNull String pkg,
143            @NonNull NotificationChannel channel) {
144        if (!isBound()) return;
145        try {
146            getNotificationInterface().createNotificationChannelFromAssistant(
147                    mWrapper, pkg, channel);
148        } catch (RemoteException e) {
149            Log.v(TAG, "Unable to contact notification manager", e);
150            throw e.rethrowFromSystemServer();
151        }
152    }
153
154    /**
155     * Updates a notification channel for a given package.
156     *
157     * @param pkg The package to the channel belongs to.
158     * @param channel the channel to attempt to update.
159     */
160    public void updateNotificationChannel(@NonNull String pkg,
161            @NonNull NotificationChannel channel) {
162        if (!isBound()) return;
163        try {
164            getNotificationInterface().updateNotificationChannelFromAssistant(
165                    mWrapper, pkg, channel);
166        } catch (RemoteException e) {
167            Log.v(TAG, "Unable to contact notification manager", e);
168            throw e.rethrowFromSystemServer();
169        }
170    }
171
172    /**
173     * Returns all notification channels belonging to the given package.
174     */
175    public List<NotificationChannel> getNotificationChannels(@NonNull String pkg) {
176        if (!isBound()) return null;
177        try {
178            return getNotificationInterface().getNotificationChannelsFromAssistant(
179                    mWrapper, pkg).getList();
180        } catch (RemoteException e) {
181            Log.v(TAG, "Unable to contact notification manager", e);
182            throw e.rethrowFromSystemServer();
183        }
184    }
185
186    /**
187     * Deletes the given notification channel.
188     */
189    public void deleteNotificationChannel(@NonNull String pkg, @NonNull String channelId) {
190        if (!isBound()) return;
191        try {
192            getNotificationInterface().deleteNotificationChannelFromAssistant(
193                    mWrapper, pkg, channelId);
194        } catch (RemoteException e) {
195            throw e.rethrowFromSystemServer();
196        }
197    }
198
199
200    private class NotificationAssistantServiceWrapper extends NotificationListenerWrapper {
201        @Override
202        public void onNotificationEnqueued(IStatusBarNotificationHolder sbnHolder) {
203            StatusBarNotification sbn;
204            try {
205                sbn = sbnHolder.get();
206            } catch (RemoteException e) {
207                Log.w(TAG, "onNotificationEnqueued: Error receiving StatusBarNotification", e);
208                return;
209            }
210
211            SomeArgs args = SomeArgs.obtain();
212            args.arg1 = sbn;
213            mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_ENQUEUED,
214                    args).sendToTarget();
215        }
216
217        @Override
218        public void onNotificationSnoozedUntilContext(
219                IStatusBarNotificationHolder sbnHolder, String snoozeCriterionId)
220                throws RemoteException {
221            StatusBarNotification sbn;
222            try {
223                sbn = sbnHolder.get();
224            } catch (RemoteException e) {
225                Log.w(TAG, "onNotificationSnoozed: Error receiving StatusBarNotification", e);
226                return;
227            }
228
229            SomeArgs args = SomeArgs.obtain();
230            args.arg1 = sbn;
231            args.arg2 = snoozeCriterionId;
232            mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_SNOOZED,
233                    args).sendToTarget();
234        }
235    }
236
237    private final class MyHandler extends Handler {
238        public static final int MSG_ON_NOTIFICATION_ENQUEUED = 1;
239        public static final int MSG_ON_NOTIFICATION_SNOOZED = 2;
240
241        public MyHandler(Looper looper) {
242            super(looper, null, false);
243        }
244
245        @Override
246        public void handleMessage(Message msg) {
247            switch (msg.what) {
248                case MSG_ON_NOTIFICATION_ENQUEUED: {
249                    SomeArgs args = (SomeArgs) msg.obj;
250                    StatusBarNotification sbn = (StatusBarNotification) args.arg1;
251                    args.recycle();
252                    Adjustment adjustment = onNotificationEnqueued(sbn);
253                    if (adjustment != null) {
254                        if (!isBound()) return;
255                        try {
256                            getNotificationInterface().applyEnqueuedAdjustmentFromAssistant(
257                                    mWrapper, adjustment);
258                        } catch (android.os.RemoteException ex) {
259                            Log.v(TAG, "Unable to contact notification manager", ex);
260                            throw ex.rethrowFromSystemServer();
261                        }
262                    }
263                    break;
264                }
265                case MSG_ON_NOTIFICATION_SNOOZED: {
266                    SomeArgs args = (SomeArgs) msg.obj;
267                    StatusBarNotification sbn = (StatusBarNotification) args.arg1;
268                    String snoozeCriterionId = (String) args.arg2;
269                    args.recycle();
270                    onNotificationSnoozedUntilContext(sbn, snoozeCriterionId);
271                    break;
272                }
273            }
274        }
275    }
276}
277