New NotificationListenerService.

This is the best and only way for apps to listen for
notifications: create a NotificationListenerService, wait
for the NoMan to bind to you (as a result of the user
checking a box somewhere in Settings and agreeing to a
scary dialog box), and you'll start receiving notification
posted and dismissed callbacks. Your service, while enabled,
will also be able to clear one or all notifications.

Use this power wisely.

This change moves StatusBarNotification out of
com.android.internal into android.service.notification.
[Internal customers, including System UI and early users of
the system-only listener binder API, will need to be
updated.]

Bug: 8199624
Change-Id: I1be46f823d4b3ddc901109ec1e085cd6deb740c2
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 3d9b2ae..92ec3ad 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -17,12 +17,12 @@
 
 package android.app;
 
-import android.app.INotificationListener;
 import android.app.ITransientNotification;
+import android.service.notification.StatusBarNotification;
 import android.app.Notification;
+import android.content.ComponentName;
 import android.content.Intent;
-
-import com.android.internal.statusbar.StatusBarNotification;
+import android.service.notification.INotificationListener;
 
 /** {@hide} */
 interface INotificationManager
@@ -41,7 +41,9 @@
     StatusBarNotification[] getActiveNotifications(String callingPkg);
     StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count);
 
-    void registerListener(in INotificationListener listener, String pkg, int userid);
+    void registerListener(in INotificationListener listener, in ComponentName component, int userid);
     void unregisterListener(in INotificationListener listener, int userid);
-}
 
+    void clearNotificationFromListener(in INotificationListener token, String pkg, String tag, int id);
+    void clearAllNotificationsFromListener(in INotificationListener token);
+}
\ No newline at end of file
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a0473a4..88ee414 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -655,6 +655,22 @@
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
 
+    /**
+     * Activity Action: Show Notification listener settings.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     * @see android.service.notification.NotificationListenerService
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_NOTIFICATION_LISTENER_SETTINGS
+            = "android.settings.NOTIFICATION_LISTENER_SETTINGS";
+
     // End of Intent actions for Settings
 
     /**
diff --git a/core/java/android/app/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl
similarity index 89%
rename from core/java/android/app/INotificationListener.aidl
rename to core/java/android/service/notification/INotificationListener.aidl
index f010a2a..425fdc1 100644
--- a/core/java/android/app/INotificationListener.aidl
+++ b/core/java/android/service/notification/INotificationListener.aidl
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package android.app;
+package android.service.notification;
 
-import com.android.internal.statusbar.StatusBarNotification;
+import android.service.notification.StatusBarNotification;
 
 /** @hide */
 oneway interface INotificationListener
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
new file mode 100644
index 0000000..86bab2a
--- /dev/null
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.notification;
+
+import android.annotation.SdkConstant;
+import android.app.INotificationManager;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.ServiceManager;
+import android.util.Log;
+
+public abstract class NotificationListenerService extends Service {
+    // TAG = "NotificationListenerService[MySubclass]"
+    private final String TAG = NotificationListenerService.class.getSimpleName()
+            + "[" + getClass().getSimpleName() + "]";
+
+    private INotificationListenerWrapper mWrapper = null;
+
+    private INotificationManager mNoMan;
+
+    /**
+     * The {@link Intent} that must be declared as handled by the service.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+    public static final String SERVICE_INTERFACE
+            = "android.service.notification.NotificationListenerService";
+
+    /**
+     * Implement this method to learn about new notifications as they are posted by apps.
+     *
+     * @param sbn A data structure encapsulating the original {@link android.app.Notification}
+     *            object as well as its identifying information (tag and id) and source
+     *            (package name).
+     */
+    public abstract void onNotificationPosted(StatusBarNotification sbn);
+
+    /**
+     * Implement this method to learn when notifications are removed.
+     * <P>
+     * This might occur because the user has dismissed the notification using system UI (or another
+     * notification listener) or because the app has withdrawn the notification.
+     *
+     * @param sbn A data structure encapsulating the original {@link android.app.Notification}
+     *            object as well as its identifying information (tag and id) and source
+     *            (package name).
+     */
+    public abstract void onNotificationRemoved(StatusBarNotification sbn);
+
+    private final INotificationManager getNotificationInterface() {
+        if (mNoMan == null) {
+            mNoMan = INotificationManager.Stub.asInterface(
+                    ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+        }
+        return mNoMan;
+    }
+
+    /**
+     * Inform the notification manager about dismissal of a single notification.
+     * <p>
+     * Use this if your listener has a user interface that allows the user to dismiss individual
+     * notifications, similar to the behavior of Android's status bar and notification panel.
+     * It should be called after the user dismisses a single notification using your UI;
+     * upon being informed, the notification manager will actually remove the notification
+     * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback.
+     * <P>
+     * <b>Note:</b> If your listener allows the user to fire a notification's
+     * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call
+     * this method at that time <i>if</i> the Notification in question has the
+     * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set.
+     *
+     * @param pkg Package of the notifying app.
+     * @param tag Tag of the notification as specified by the notifying app in
+     *     {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}.
+     * @param id  ID of the notification as specified by the notifying app in
+     *     {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}.
+     */
+    public final void clearNotification(String pkg, String tag, int id) {
+        try {
+            getNotificationInterface().clearNotificationFromListener(mWrapper, pkg, tag, id);
+        } catch (android.os.RemoteException ex) {
+            Log.v(TAG, "Unable to contact notification manager", ex);
+        }
+    }
+
+    /**
+     * Inform the notification manager about dismissal of all notifications.
+     * <p>
+     * Use this if your listener has a user interface that allows the user to dismiss all
+     * notifications, similar to the behavior of Android's status bar and notification panel.
+     * It should be called after the user invokes the "dismiss all" function of your UI;
+     * upon being informed, the notification manager will actually remove all active notifications
+     * and you will get multiple {@link #onNotificationRemoved(StatusBarNotification)} callbacks.
+     *
+     * {@see #clearNotification(String, String, int)}
+     */
+    public final void clearAllNotifications() {
+        try {
+            getNotificationInterface().clearAllNotificationsFromListener(mWrapper);
+        } catch (android.os.RemoteException ex) {
+            Log.v(TAG, "Unable to contact notification manager", ex);
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (mWrapper == null) {
+            mWrapper = new INotificationListenerWrapper();
+        }
+        return mWrapper;
+    }
+
+    private class INotificationListenerWrapper extends INotificationListener.Stub {
+        @Override
+        public void onNotificationPosted(StatusBarNotification sbn) {
+            NotificationListenerService.this.onNotificationPosted(sbn);
+        }
+        @Override
+        public void onNotificationRemoved(StatusBarNotification sbn) {
+            NotificationListenerService.this.onNotificationRemoved(sbn);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/statusbar/StatusBarNotification.aidl b/core/java/android/service/notification/StatusBarNotification.aidl
similarity index 93%
rename from core/java/com/android/internal/statusbar/StatusBarNotification.aidl
rename to core/java/android/service/notification/StatusBarNotification.aidl
index bd9e89c..ba81972 100644
--- a/core/java/com/android/internal/statusbar/StatusBarNotification.aidl
+++ b/core/java/android/service/notification/StatusBarNotification.aidl
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.internal.statusbar;
+package android.service.notification;
 
 parcelable StatusBarNotification;
 
diff --git a/core/java/com/android/internal/statusbar/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
similarity index 80%
rename from core/java/com/android/internal/statusbar/StatusBarNotification.java
rename to core/java/android/service/notification/StatusBarNotification.java
index 23e87fc..ef5f8c4 100644
--- a/core/java/com/android/internal/statusbar/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.internal.statusbar;
+package android.service.notification;
 
 import android.app.Notification;
 import android.os.Parcel;
@@ -23,34 +23,54 @@
 
 /**
  * Class encapsulating a Notification. Sent by the NotificationManagerService to clients including
- * the IStatusBar (in System UI).
+ * the status bar and any {@link android.service.notification.NotificationListenerService}s.
  */
 public class StatusBarNotification implements Parcelable {
+    /** The package of the app that posted the notification. */
     public final String pkg;
-    public final String basePkg;
+    /** The id supplied to {@link android.app.NotificationManager#notify}. */
     public final int id;
+    /** The tag supplied to {@link android.app.NotificationManager#notify}, or null if no tag
+     * was specified. */
     public final String tag;
+
+    /** The notifying app's calling uid. @hide */
     public final int uid;
+    /** The notifying app's base package. @hide */
+    public final String basePkg;
+    /** @hide */
     public final int initialPid;
     // TODO: make this field private and move callers to an accessor that
     // ensures sourceUser is applied.
+
+    /** The {@link android.app.Notification} supplied to
+     * {@link android.app.NotificationManager#notify}. */
     public final Notification notification;
-    public final int score;
+    /** The {@link android.os.UserHandle} for whom this notification is intended. */
     public final UserHandle user;
+    /** The time (in {@link System#currentTimeMillis} time) the notification was posted,
+     * which may be different than {@link android.app.Notification#when}.
+     */
     public final long postTime;
 
-    /** This is temporarily needed for the JB MR1 PDK. */
+    /** @hide */
+    public final int score;
+
+    /** This is temporarily needed for the JB MR1 PDK.
+     * @hide */
     @Deprecated
     public StatusBarNotification(String pkg, int id, String tag, int uid, int initialPid, int score,
             Notification notification) {
         this(pkg, id, tag, uid, initialPid, score, notification, UserHandle.OWNER);
     }
 
+    /** @hide */
     public StatusBarNotification(String pkg, int id, String tag, int uid, int initialPid, int score,
             Notification notification, UserHandle user) {
         this(pkg, null, id, tag, uid, initialPid, score, notification, user);
     }
 
+    /** @hide */
     public StatusBarNotification(String pkg, String basePkg, int id, String tag, int uid,
             int initialPid, int score, Notification notification, UserHandle user) {
         this(pkg, basePkg, id, tag, uid, initialPid, score, notification, user,
@@ -147,10 +167,17 @@
                 this.score, this.notification);
     }
 
+    /** Convenience method to check the notification's flags for
+     * {@link Notification#FLAG_ONGOING_EVENT}.
+     */
     public boolean isOngoing() {
         return (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0;
     }
 
+    /** Convenience method to check the notification's flags for
+     * either {@link Notification#FLAG_ONGOING_EVENT} or
+     * {@link Notification#FLAG_NO_CLEAR}.
+     */
     public boolean isClearable() {
         return ((notification.flags & Notification.FLAG_ONGOING_EVENT) == 0)
                 && ((notification.flags & Notification.FLAG_NO_CLEAR) == 0);
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 780f5b3..58b15e2 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -17,7 +17,7 @@
 package com.android.internal.statusbar;
 
 import com.android.internal.statusbar.StatusBarIcon;
-import com.android.internal.statusbar.StatusBarNotification;
+import android.service.notification.StatusBarNotification;
 
 /** @hide */
 oneway interface IStatusBar
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 04e5bc9..c98ba8d 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -19,7 +19,7 @@
 import com.android.internal.statusbar.IStatusBar;
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.statusbar.StatusBarIconList;
-import com.android.internal.statusbar.StatusBarNotification;
+import android.service.notification.StatusBarNotification;
 
 /** @hide */
 interface IStatusBarService
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ffceb68..90e3b8d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2193,6 +2193,14 @@
         android:description="@string/permdesc_accessNotifications"
         android:protectionLevel="signature|system" />
 
+    <!-- Must be required by an {@link
+         android.service.notification.NotificationListenerService},
+         to ensure that only the system can bind to it. -->
+    <permission android:name="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
+        android:label="@string/permlab_bindNotificationListenerService"
+        android:description="@string/permdesc_bindNotificationListenerService"
+        android:protectionLevel="signature" />
+
     <!-- The system process is explicitly the only one allowed to launch the
          confirmation UI for full backup/restore -->
     <uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 4a15967..6bf6403 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1816,6 +1816,11 @@
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_accessNotifications">Allows the app to retrieve, examine, and clear notifications, including those posted by other apps.</string>
 
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_bindNotificationListenerService">bind to a notification listener service</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_bindNotificationListenerService">Allows the holder to bind to the top-level interface of a notification listener service. Should never be needed for normal apps.</string>
+
     <!-- Policy administration -->
 
     <!-- Title of policy access to limiting the user's password choices -->
@@ -3508,6 +3513,9 @@
     <string name="wallpaper_binding_label">Wallpaper</string>
     <!-- Dialog title for user to select a different wallpaper from service list -->
     <string name="chooser_wallpaper">Change wallpaper</string>
+    <!-- Label to show for a service that is running because it is observing
+         the user's notifications. -->
+    <string name="notification_listener_binding_label">Notification listener</string>
 
     <!-- Do Not Translate: Alternate eri.xml -->
     <string name="alternate_eri_file">/data/eri.xml</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ced0851..bb35bab 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1638,6 +1638,7 @@
   <java-symbol type="string" name="launch_warning_title" />
   <java-symbol type="string" name="low_internal_storage_view_text" />
   <java-symbol type="string" name="low_internal_storage_view_title" />
+  <java-symbol type="string" name="notification_listener_binding_label" />
   <java-symbol type="string" name="report" />
   <java-symbol type="string" name="select_input_method" />
   <java-symbol type="string" name="select_keyboard_layout_notification_title" />