NotificationListenerService.java revision 1941fc718690d6b87db68b6a133c71fb470599d0
1/*
2 * Copyright (C) 2013 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.PrivateApi;
20import android.annotation.SdkConstant;
21import android.app.INotificationManager;
22import android.app.Service;
23import android.content.ComponentName;
24import android.content.Context;
25import android.content.Intent;
26import android.os.IBinder;
27import android.os.RemoteException;
28import android.os.ServiceManager;
29import android.util.Log;
30
31/**
32 * A service that receives calls from the system when new notifications are posted or removed.
33 * <p>To extend this class, you must declare the service in your manifest file with
34 * the {@link android.Manifest.permission#BIND_NOTIFICATION_LISTENER_SERVICE} permission
35 * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
36 * <pre>
37 * &lt;service android:name=".NotificationListener"
38 *          android:label="&#64;string/service_name"
39 *          android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
40 *     &lt;intent-filter>
41 *         &lt;action android:name="android.service.notification.NotificationListenerService" />
42 *     &lt;/intent-filter>
43 * &lt;/service></pre>
44 */
45public abstract class NotificationListenerService extends Service {
46    // TAG = "NotificationListenerService[MySubclass]"
47    private final String TAG = NotificationListenerService.class.getSimpleName()
48            + "[" + getClass().getSimpleName() + "]";
49
50    private INotificationListenerWrapper mWrapper = null;
51    private String[] mNotificationKeys;
52
53    private INotificationManager mNoMan;
54
55    /** Only valid after a successful call to (@link registerAsService}. */
56    private int mCurrentUser;
57
58    /**
59     * The {@link Intent} that must be declared as handled by the service.
60     */
61    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
62    public static final String SERVICE_INTERFACE
63            = "android.service.notification.NotificationListenerService";
64
65    /**
66     * Implement this method to learn about new notifications as they are posted by apps.
67     *
68     * @param sbn A data structure encapsulating the original {@link android.app.Notification}
69     *            object as well as its identifying information (tag and id) and source
70     *            (package name).
71     */
72    public abstract void onNotificationPosted(StatusBarNotification sbn);
73
74    /**
75     * Implement this method to learn when notifications are removed.
76     * <P>
77     * This might occur because the user has dismissed the notification using system UI (or another
78     * notification listener) or because the app has withdrawn the notification.
79     * <P>
80     * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the
81     * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight
82     * fields such as {@link android.app.Notification#contentView} and
83     * {@link android.app.Notification#largeIcon}. However, all other fields on
84     * {@link StatusBarNotification}, sufficient to match this call with a prior call to
85     * {@link #onNotificationPosted(StatusBarNotification)}, will be intact.
86     *
87     * @param sbn A data structure encapsulating at least the original information (tag and id)
88     *            and source (package name) used to post the {@link android.app.Notification} that
89     *            was just removed.
90     */
91    public abstract void onNotificationRemoved(StatusBarNotification sbn);
92
93    /**
94     * Implement this method to learn about when the listener is enabled and connected to
95     * the notification manager.  You are safe to call {@link #getActiveNotifications(String[])
96     * at this time.
97     *
98     * @param notificationKeys The notification keys for all currently posted notifications.
99     */
100    public void onListenerConnected(String[] notificationKeys) {
101        // optional
102    }
103
104    /**
105     * Implement this method to be notified when the notification order cahnges.
106     *
107     * Call {@link #getOrderedNotificationKeys()} to retrieve the new order.
108     */
109    public void onNotificationOrderUpdate() {
110        // optional
111    }
112
113    private final INotificationManager getNotificationInterface() {
114        if (mNoMan == null) {
115            mNoMan = INotificationManager.Stub.asInterface(
116                    ServiceManager.getService(Context.NOTIFICATION_SERVICE));
117        }
118        return mNoMan;
119    }
120
121    /**
122     * Inform the notification manager about dismissal of a single notification.
123     * <p>
124     * Use this if your listener has a user interface that allows the user to dismiss individual
125     * notifications, similar to the behavior of Android's status bar and notification panel.
126     * It should be called after the user dismisses a single notification using your UI;
127     * upon being informed, the notification manager will actually remove the notification
128     * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback.
129     * <P>
130     * <b>Note:</b> If your listener allows the user to fire a notification's
131     * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call
132     * this method at that time <i>if</i> the Notification in question has the
133     * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set.
134     *
135     * @param pkg Package of the notifying app.
136     * @param tag Tag of the notification as specified by the notifying app in
137     *     {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}.
138     * @param id  ID of the notification as specified by the notifying app in
139     *     {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}.
140     * <p>
141     * @deprecated Use {@link #cancelNotification(String key)}
142     * instead. Beginning with {@link android.os.Build.VERSION_CODES#L} this method will no longer
143     * cancel the notification. It will continue to cancel the notification for applications
144     * whose {@code targetSdkVersion} is earlier than {@link android.os.Build.VERSION_CODES#L}.
145     */
146    public final void cancelNotification(String pkg, String tag, int id) {
147        if (!isBound()) return;
148        try {
149            getNotificationInterface().cancelNotificationFromListener(
150                    mWrapper, pkg, tag, id);
151        } catch (android.os.RemoteException ex) {
152            Log.v(TAG, "Unable to contact notification manager", ex);
153        }
154    }
155
156    /**
157     * Inform the notification manager about dismissal of a single notification.
158     * <p>
159     * Use this if your listener has a user interface that allows the user to dismiss individual
160     * notifications, similar to the behavior of Android's status bar and notification panel.
161     * It should be called after the user dismisses a single notification using your UI;
162     * upon being informed, the notification manager will actually remove the notification
163     * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback.
164     * <P>
165     * <b>Note:</b> If your listener allows the user to fire a notification's
166     * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call
167     * this method at that time <i>if</i> the Notification in question has the
168     * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set.
169     * <p>
170     * @param key Notification to dismiss from {@link StatusBarNotification#getKey()}.
171     */
172    public final void cancelNotification(String key) {
173        if (!isBound()) return;
174        try {
175            getNotificationInterface().cancelNotificationsFromListener(mWrapper,
176                    new String[] {key});
177        } catch (android.os.RemoteException ex) {
178            Log.v(TAG, "Unable to contact notification manager", ex);
179        }
180    }
181
182    /**
183     * Inform the notification manager about dismissal of all notifications.
184     * <p>
185     * Use this if your listener has a user interface that allows the user to dismiss all
186     * notifications, similar to the behavior of Android's status bar and notification panel.
187     * It should be called after the user invokes the "dismiss all" function of your UI;
188     * upon being informed, the notification manager will actually remove all active notifications
189     * and you will get multiple {@link #onNotificationRemoved(StatusBarNotification)} callbacks.
190     *
191     * {@see #cancelNotification(String, String, int)}
192     */
193    public final void cancelAllNotifications() {
194        cancelNotifications(null /*all*/);
195    }
196
197    /**
198     * Inform the notification manager about dismissal of specific notifications.
199     * <p>
200     * Use this if your listener has a user interface that allows the user to dismiss
201     * multiple notifications at once.
202     *
203     * @param keys Notifications to dismiss, or {@code null} to dismiss all.
204     *
205     * {@see #cancelNotification(String, String, int)}
206     */
207    public final void cancelNotifications(String[] keys) {
208        if (!isBound()) return;
209        try {
210            getNotificationInterface().cancelNotificationsFromListener(mWrapper, keys);
211        } catch (android.os.RemoteException ex) {
212            Log.v(TAG, "Unable to contact notification manager", ex);
213        }
214    }
215
216    /**
217     * Request the list of outstanding notifications (that is, those that are visible to the
218     * current user). Useful when you don't know what's already been posted.
219     *
220     * @return An array of active notifications, sorted in natural order.
221     */
222    public StatusBarNotification[] getActiveNotifications() {
223        return getActiveNotifications(null /*all*/);
224    }
225
226    /**
227     * Request the list of outstanding notifications (that is, those that are visible to the
228     * current user). Useful when you don't know what's already been posted.
229     *
230     * @param keys A specific list of notification keys, or {@code null} for all.
231     * @return An array of active notifications, sorted in natural order
232     *   if {@code keys} is {@code null}.
233     */
234    public StatusBarNotification[] getActiveNotifications(String[] keys) {
235        if (!isBound()) return null;
236        try {
237            return getNotificationInterface().getActiveNotificationsFromListener(mWrapper, keys);
238        } catch (android.os.RemoteException ex) {
239            Log.v(TAG, "Unable to contact notification manager", ex);
240        }
241        return null;
242    }
243
244    /**
245     * Request the list of notification keys in their current natural order.
246     * You can use the notification keys for subsequent retrieval via
247     * {@link #getActiveNotifications(String[]) or dismissal via
248     * {@link #cancelNotifications(String[]).
249     *
250     * @return An array of active notification keys, in their natural order.
251     */
252    public String[] getOrderedNotificationKeys() {
253        return mNotificationKeys;
254    }
255
256    @Override
257    public IBinder onBind(Intent intent) {
258        if (mWrapper == null) {
259            mWrapper = new INotificationListenerWrapper();
260        }
261        return mWrapper;
262    }
263
264    private boolean isBound() {
265        if (mWrapper == null) {
266            Log.w(TAG, "Notification listener service not yet bound.");
267            return false;
268        }
269        return true;
270    }
271
272    /**
273     * Directly register this service with the Notification Manager.
274     *
275     * <p>Only system services may use this call. It will fail for non-system callers.
276     * Apps should ask the user to add their listener in Settings.
277     *
278     * @param componentName the component that will consume the notification information
279     * @param currentUser the user to use as the stream filter
280     * @hide
281     */
282    @PrivateApi
283    public void registerAsSystemService(ComponentName componentName, int currentUser)
284            throws RemoteException {
285        if (mWrapper == null) {
286            mWrapper = new INotificationListenerWrapper();
287        }
288        INotificationManager noMan = getNotificationInterface();
289        noMan.registerListener(mWrapper, componentName, currentUser);
290        mCurrentUser = currentUser;
291    }
292
293    /**
294     * Directly unregister this service from the Notification Manager.
295     *
296     * <P>This method will fail for listeners that were not registered
297     * with (@link registerAsService).
298     * @hide
299     */
300    @PrivateApi
301    public void unregisterAsSystemService() throws RemoteException {
302        if (mWrapper != null) {
303            INotificationManager noMan = getNotificationInterface();
304            noMan.unregisterListener(mWrapper, mCurrentUser);
305        }
306    }
307
308    private class INotificationListenerWrapper extends INotificationListener.Stub {
309        @Override
310        public void onNotificationPosted(StatusBarNotification sbn,
311                NotificationOrderUpdate update) {
312            try {
313                // protect subclass from concurrent modifications of (@link mNotificationKeys}.
314                synchronized (mWrapper) {
315                    updateNotificationKeys(update);
316                    NotificationListenerService.this.onNotificationPosted(sbn);
317                }
318            } catch (Throwable t) {
319                Log.w(TAG, "Error running onOrderedNotificationPosted", t);
320            }
321        }
322        @Override
323        public void onNotificationRemoved(StatusBarNotification sbn,
324                NotificationOrderUpdate update) {
325            try {
326                // protect subclass from concurrent modifications of (@link mNotificationKeys}.
327                synchronized (mWrapper) {
328                    updateNotificationKeys(update);
329                    NotificationListenerService.this.onNotificationRemoved(sbn);
330                }
331            } catch (Throwable t) {
332                Log.w(TAG, "Error running onNotificationRemoved", t);
333            }
334        }
335        @Override
336        public void onListenerConnected(NotificationOrderUpdate update) {
337            try {
338                // protect subclass from concurrent modifications of (@link mNotificationKeys}.
339                synchronized (mWrapper) {
340                    updateNotificationKeys(update);
341                    NotificationListenerService.this.onListenerConnected(mNotificationKeys);
342                }
343            } catch (Throwable t) {
344                Log.w(TAG, "Error running onListenerConnected", t);
345            }
346        }
347        @Override
348        public void onNotificationOrderUpdate(NotificationOrderUpdate update)
349                throws RemoteException {
350            try {
351                // protect subclass from concurrent modifications of (@link mNotificationKeys}.
352                synchronized (mWrapper) {
353                    updateNotificationKeys(update);
354                    NotificationListenerService.this.onNotificationOrderUpdate();
355                }
356            } catch (Throwable t) {
357                Log.w(TAG, "Error running onNotificationOrderUpdate", t);
358            }
359        }
360    }
361
362    private void updateNotificationKeys(NotificationOrderUpdate update) {
363        // TODO: avoid garbage by comparing the lists
364        mNotificationKeys = update.getOrderedKeys();
365    }
366}
367