am 5feceebb: New NotificationListenerService.

* commit '5feceebb892d4cb5777cea3c6174b206705d456b':
  New NotificationListenerService.
This commit is contained in:
Daniel Sandler
2013-04-05 12:58:43 -07:00
committed by Android Git Automerger
26 changed files with 573 additions and 93 deletions

View File

@ -69,7 +69,6 @@ LOCAL_SRC_FILES += \
core/java/android/app/IAlarmManager.aidl \
core/java/android/app/IBackupAgent.aidl \
core/java/android/app/IInstrumentationWatcher.aidl \
core/java/android/app/INotificationListener.aidl \
core/java/android/app/INotificationManager.aidl \
core/java/android/app/IProcessObserver.aidl \
core/java/android/app/ISearchManager.aidl \
@ -148,6 +147,7 @@ LOCAL_SRC_FILES += \
core/java/android/os/IUpdateLock.aidl \
core/java/android/os/IUserManager.aidl \
core/java/android/os/IVibratorService.aidl \
core/java/android/service/notification/INotificationListener.aidl \
core/java/android/service/dreams/IDreamManager.aidl \
core/java/android/service/dreams/IDreamService.aidl \
core/java/android/service/wallpaper/IWallpaperConnection.aidl \

View File

@ -159,6 +159,7 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framew
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/com/android/internal/view/IInputMethodCallback.*)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/com/android/internal/view/IInputMethodSession.*)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/com/android/internal/view/IInputMethodCallback.*)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates)
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
# ************************************************

View File

@ -22,6 +22,7 @@ package android {
field public static final java.lang.String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET";
field public static final java.lang.String BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN";
field public static final java.lang.String BIND_INPUT_METHOD = "android.permission.BIND_INPUT_METHOD";
field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE";
field public static final java.lang.String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS";
field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
field public static final java.lang.String BIND_VPN_SERVICE = "android.permission.BIND_VPN_SERVICE";
@ -20839,6 +20840,38 @@ package android.service.dreams {
}
package android.service.notification {
public abstract class NotificationListenerService extends android.app.Service {
ctor public NotificationListenerService();
method public final void clearAllNotifications();
method public final void clearNotification(java.lang.String, java.lang.String, int);
method public android.os.IBinder onBind(android.content.Intent);
method public abstract void onNotificationPosted(android.service.notification.StatusBarNotification);
method public abstract void onNotificationRemoved(android.service.notification.StatusBarNotification);
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService";
}
public class StatusBarNotification implements android.os.Parcelable {
ctor public StatusBarNotification(java.lang.String, java.lang.String, int, java.lang.String, int, int, int, android.app.Notification, android.os.UserHandle, long);
ctor public StatusBarNotification(android.os.Parcel);
method public android.service.notification.StatusBarNotification clone();
method public int describeContents();
method public int getUserId();
method public boolean isClearable();
method public boolean isOngoing();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
field public final int id;
field public final android.app.Notification notification;
field public final java.lang.String pkg;
field public final long postTime;
field public final java.lang.String tag;
field public final android.os.UserHandle user;
}
}
package android.service.textservice {
public abstract class SpellCheckerService extends android.app.Service {

View File

@ -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 @@ interface INotificationManager
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);
}

View File

@ -655,6 +655,22 @@ public final class Settings {
@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
/**

View File

@ -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

View File

@ -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);
}
}
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.internal.statusbar;
package android.service.notification;
parcelable StatusBarNotification;

View File

@ -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 @@ import android.os.UserHandle;
/**
* 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 @@ public class StatusBarNotification implements Parcelable {
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);

View File

@ -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

View File

@ -19,7 +19,7 @@ package com.android.internal.statusbar;
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

View File

@ -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"/>

View File

@ -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>

View File

@ -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" />

View File

@ -17,11 +17,11 @@
package com.android.systemui.statusbar;
import android.service.notification.StatusBarNotification;
import android.content.res.Configuration;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.statusbar.StatusBarIconList;
import com.android.internal.statusbar.StatusBarNotification;
import com.android.internal.widget.SizeAdaptiveLayout;
import com.android.systemui.R;
import com.android.systemui.SearchPanelView;

View File

@ -20,10 +20,10 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.service.notification.StatusBarNotification;
import com.android.internal.statusbar.IStatusBar;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.statusbar.StatusBarIconList;
import com.android.internal.statusbar.StatusBarNotification;
/**
* This class takes the functions from IStatusBar that come in on

View File

@ -16,12 +16,11 @@
package com.android.systemui.statusbar;
import android.app.Notification;
import android.service.notification.StatusBarNotification;
import android.os.IBinder;
import android.view.View;
import android.widget.ImageView;
import com.android.internal.statusbar.StatusBarNotification;
import com.android.systemui.R;
import java.util.Comparator;

View File

@ -26,6 +26,7 @@ import android.app.ActivityManagerNative;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.service.notification.StatusBarNotification;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@ -76,7 +77,6 @@ import android.widget.ScrollView;
import android.widget.TextView;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.statusbar.StatusBarNotification;
import com.android.systemui.EventLogTags;
import com.android.systemui.R;
import com.android.systemui.statusbar.BaseStatusBar;

View File

@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
import android.service.notification.StatusBarNotification;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
@ -23,10 +24,7 @@ import android.os.Handler;
import android.text.StaticLayout;
import android.text.Layout.Alignment;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.Slog;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.TextSwitcher;
import android.widget.TextView;
@ -35,7 +33,6 @@ import android.widget.ImageSwitcher;
import java.util.ArrayList;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.statusbar.StatusBarNotification;
import com.android.internal.util.CharSequences;
import com.android.systemui.R;

View File

@ -28,16 +28,11 @@ import android.content.IntentFilter;
import android.location.LocationManager;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
import android.view.View;
import android.widget.ImageView;
// private NM API
import android.app.INotificationManager;
import com.android.internal.statusbar.StatusBarNotification;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
public class LocationController extends BroadcastReceiver {
private static final String TAG = "StatusBar.LocationController";

View File

@ -23,6 +23,7 @@ import android.app.ActivityManagerNative;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.service.notification.StatusBarNotification;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@ -58,7 +59,6 @@ import android.widget.ScrollView;
import android.widget.TextView;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.statusbar.StatusBarNotification;
import com.android.systemui.R;
import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.CommandQueue;

View File

@ -21,9 +21,9 @@ import java.util.Arrays;
import android.animation.LayoutTransition;
import android.app.Notification;
import android.app.PendingIntent;
import android.service.notification.StatusBarNotification;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.os.Handler;
@ -37,11 +37,9 @@ import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.FrameLayout;
import android.widget.TextView;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.statusbar.StatusBarNotification;
import com.android.systemui.R;
import com.android.systemui.statusbar.StatusBarIconView;

View File

@ -16,8 +16,8 @@
package com.android.systemui.statusbar.tv;
import android.service.notification.StatusBarNotification;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.statusbar.StatusBarNotification;
import com.android.systemui.statusbar.BaseStatusBar;
import android.os.IBinder;

View File

@ -26,16 +26,17 @@ import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.INotificationManager;
import android.app.INotificationListener;
import android.app.ITransientNotification;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@ -57,6 +58,9 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.os.Vibrator;
import android.provider.Settings;
import android.service.notification.INotificationListener;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.AtomicFile;
@ -68,8 +72,6 @@ import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;
import com.android.internal.statusbar.StatusBarNotification;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@ -121,6 +123,8 @@ public class NotificationManagerService extends INotificationManager.Stub
private static final boolean ENABLE_BLOCKED_NOTIFICATIONS = true;
private static final boolean ENABLE_BLOCKED_TOASTS = true;
private static final String ENABLED_NOTIFICATION_LISTENERS_SEPARATOR = ":";
final Context mContext;
final IActivityManager mAm;
final UserManager mUserManager;
@ -163,8 +167,18 @@ public class NotificationManagerService extends INotificationManager.Stub
private final AppOpsManager mAppOps;
private ArrayList<NotificationListenerInfo> mListeners = new ArrayList<NotificationListenerInfo>();
private ArrayList<String> mEnabledListenersForCurrentUser = new ArrayList<String>();
// contains connections to all connected listeners, including app services
// and system listeners
private ArrayList<NotificationListenerInfo> mListeners
= new ArrayList<NotificationListenerInfo>();
// things that will be put into mListeners as soon as they're ready
private ArrayList<String> mServicesBinding = new ArrayList<String>();
// lists the component names of all enabled (and therefore connected) listener
// app services for the current user only
private HashSet<ComponentName> mEnabledListenersForCurrentUser
= new HashSet<ComponentName>();
// Just the packages from mEnabledListenersForCurrentUser
private HashSet<String> mEnabledListenerPackageNames = new HashSet<String>();
// Notification control database. For now just contains disabled packages.
private AtomicFile mPolicyFile;
@ -181,27 +195,42 @@ public class NotificationManagerService extends INotificationManager.Stub
private class NotificationListenerInfo implements DeathRecipient {
INotificationListener listener;
String pkg;
ComponentName component;
int userid;
boolean isSystem;
ServiceConnection connection;
public NotificationListenerInfo(INotificationListener listener, String pkg, int userid,
boolean isSystem) {
public NotificationListenerInfo(INotificationListener listener, ComponentName component,
int userid, boolean isSystem) {
this.listener = listener;
this.pkg = pkg;
this.component = component;
this.userid = userid;
this.isSystem = isSystem;
this.connection = null;
}
public NotificationListenerInfo(INotificationListener listener, ComponentName component,
int userid, ServiceConnection connection) {
this.listener = listener;
this.component = component;
this.userid = userid;
this.isSystem = false;
this.connection = connection;
}
boolean enabledAndUserMatches(StatusBarNotification sbn) {
final int nid = sbn.getUserId();
if (!(isSystem || isEnabledForUser(nid))) return false;
if (!isEnabledForCurrentUser()) {
return false;
}
if (this.userid == UserHandle.USER_ALL) return true;
return (nid == UserHandle.USER_ALL || nid == this.userid);
}
public void notifyPostedIfUserMatch(StatusBarNotification sbn) {
if (!enabledAndUserMatches(sbn)) return;
if (!enabledAndUserMatches(sbn)) {
return;
}
try {
listener.onNotificationPosted(sbn);
} catch (RemoteException ex) {
@ -220,15 +249,17 @@ public class NotificationManagerService extends INotificationManager.Stub
@Override
public void binderDied() {
unregisterListener(this.listener, this.userid);
if (connection == null) {
// This is not a service; it won't be recreated. We can give up this connection.
unregisterListener(this.listener, this.userid);
}
}
/** convenience method for looking in mEnabledListenersForCurrentUser */
public boolean isEnabledForUser(int userid) {
for (int i=0; i<mEnabledListenersForCurrentUser.size(); i++) {
if (this.pkg.equals(mEnabledListenersForCurrentUser.get(i))) return true;
}
return false;
public boolean isEnabledForCurrentUser() {
if (this.isSystem) return true;
if (this.connection == null) return false;
return mEnabledListenersForCurrentUser.contains(this.component);
}
}
@ -434,6 +465,12 @@ public class NotificationManagerService extends INotificationManager.Stub
}
}
/**
* System-only API for getting a list of current (i.e. not cleared) notifications.
*
* Requires ACCESS_NOTIFICATIONS which is signature|system.
*/
@Override
public StatusBarNotification[] getActiveNotifications(String callingPkg) {
// enforce() will ensure the calling uid has the correct permission
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS,
@ -456,6 +493,12 @@ public class NotificationManagerService extends INotificationManager.Stub
return tmp;
}
/**
* System-only API for getting a list of recent (cleared, no longer shown) notifications.
*
* Requires ACCESS_NOTIFICATIONS which is signature|system.
*/
@Override
public StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count) {
// enforce() will ensure the calling uid has the correct permission
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS,
@ -474,27 +517,76 @@ public class NotificationManagerService extends INotificationManager.Stub
return tmp;
}
boolean packageCanTapNotificationsForUser(final int uid, final String pkg) {
// Make sure the package and uid match, and that the package is allowed access
return (AppOpsManager.MODE_ALLOWED
== mAppOps.checkOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, pkg));
/**
* Called whenever packages change, the user switches, or ENABLED_NOTIFICATION_LISTENERS
* is altered. (For example in response to USER_SWITCHED in our broadcast receiver)
*/
void rebindListenerServices() {
String flat = Settings.Secure.getString(
mContext.getContentResolver(),
Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
NotificationListenerInfo[] toRemove = new NotificationListenerInfo[mListeners.size()];
final ArrayList<ComponentName> toAdd;
final int currentUser = ActivityManager.getCurrentUser();
synchronized (mNotificationList) {
// unbind and remove all existing listeners
toRemove = mListeners.toArray(toRemove);
toAdd = new ArrayList<ComponentName>();
final HashSet<ComponentName> newEnabled = new HashSet<ComponentName>();
final HashSet<String> newPackages = new HashSet<String>();
// decode the list of components
if (flat != null) {
String[] components = flat.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR);
for (int i=0; i<components.length; i++) {
final ComponentName component
= ComponentName.unflattenFromString(components[i]);
if (component != null) {
newEnabled.add(component);
toAdd.add(component);
newPackages.add(component.getPackageName());
}
}
mEnabledListenersForCurrentUser = newEnabled;
mEnabledListenerPackageNames = newPackages;
}
}
for (NotificationListenerInfo info : toRemove) {
final ComponentName component = info.component;
final int oldUser = info.userid;
Slog.v(TAG, "disabling notification listener for user " + oldUser + ": " + component);
unregisterListenerService(component, info.userid);
}
final int N = toAdd.size();
for (int i=0; i<N; i++) {
final ComponentName component = toAdd.get(i);
Slog.v(TAG, "enabling notification listener for user " + currentUser + ": "
+ component);
registerListenerService(component, currentUser);
}
}
/**
* Register a listener binder directly with the notification manager.
*
* Only works with system callers. Apps should extend
* {@link android.service.notification.NotificationListenerService}.
*/
@Override
public void registerListener(final INotificationListener listener,
final String pkg, final int userid) {
// ensure system or allowed pkg
int uid = Binder.getCallingUid();
boolean isSystem = (UserHandle.getAppId(uid) == Process.SYSTEM_UID || uid == 0);
if (!(isSystem || packageCanTapNotificationsForUser(uid, pkg))) {
throw new SecurityException("Package " + pkg
+ " may not listen for notifications");
}
final ComponentName component, final int userid) {
checkCallerIsSystem();
synchronized (mNotificationList) {
try {
NotificationListenerInfo info
= new NotificationListenerInfo(listener, pkg, userid, isSystem);
= new NotificationListenerInfo(listener, component, userid, true);
listener.asBinder().linkToDeath(info, 0);
mListeners.add(info);
} catch (RemoteException e) {
@ -503,6 +595,90 @@ public class NotificationManagerService extends INotificationManager.Stub
}
}
/**
* Version of registerListener that takes the name of a
* {@link android.service.notification.NotificationListenerService} to bind to.
*
* This is the mechanism by which third parties may subscribe to notifications.
*/
private void registerListenerService(final ComponentName name, final int userid) {
checkCallerIsSystem();
if (DBG) Slog.v(TAG, "registerListenerService: " + name + " u=" + userid);
synchronized (mNotificationList) {
final String servicesBindingTag = name.toString() + "/" + userid;
if (mServicesBinding.contains(servicesBindingTag)) {
// stop registering this thing already! we're working on it
return;
}
mServicesBinding.add(servicesBindingTag);
final int N = mListeners.size();
for (int i=N-1; i>=0; i--) {
final NotificationListenerInfo info = mListeners.get(i);
if (name.equals(info.component)
&& info.userid == userid) {
// cut old connections
if (DBG) Slog.v(TAG, " disconnecting old listener: " + info.listener);
mListeners.remove(i);
if (info.connection != null) {
mContext.unbindService(info.connection);
}
}
}
Intent intent = new Intent(NotificationListenerService.SERVICE_INTERFACE);
intent.setComponent(name);
intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
com.android.internal.R.string.notification_listener_binding_label);
intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
mContext, 0, new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS), 0));
try {
if (DBG) Slog.v(TAG, "binding: " + intent);
if (!mContext.bindServiceAsUser(intent,
new ServiceConnection() {
INotificationListener mListener;
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mNotificationList) {
mServicesBinding.remove(servicesBindingTag);
try {
mListener = INotificationListener.Stub.asInterface(service);
NotificationListenerInfo info = new NotificationListenerInfo(
mListener, name, userid, this);
service.linkToDeath(info, 0);
mListeners.add(info);
} catch (RemoteException e) {
// already dead
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Slog.v(TAG, "notification listener connection lost: " + name);
}
},
Context.BIND_AUTO_CREATE,
new UserHandle(userid)))
{
mServicesBinding.remove(servicesBindingTag);
Slog.w(TAG, "Unable to bind listener service: " + intent);
return;
}
} catch (SecurityException ex) {
Slog.e(TAG, "Unable to bind listener service: " + intent, ex);
return;
}
}
}
/**
* Remove a listener binder directly
*/
@Override
public void unregisterListener(INotificationListener listener, int userid) {
// no need to check permissions; if your listener binder is in the list,
@ -513,12 +689,39 @@ public class NotificationManagerService extends INotificationManager.Stub
for (int i=N-1; i>=0; i--) {
final NotificationListenerInfo info = mListeners.get(i);
if (info.listener == listener && info.userid == userid) {
mListeners.remove(listener);
mListeners.remove(i);
if (info.connection != null) {
mContext.unbindService(info.connection);
}
}
}
}
}
/**
* Remove a listener service for the given user by ComponentName
*/
private void unregisterListenerService(ComponentName name, int userid) {
checkCallerIsSystem();
synchronized (mNotificationList) {
final int N = mListeners.size();
for (int i=N-1; i>=0; i--) {
final NotificationListenerInfo info = mListeners.get(i);
if (name.equals(info.component)
&& info.userid == userid) {
mListeners.remove(i);
if (info.connection != null) {
mContext.unbindService(info.connection);
}
}
}
}
}
/**
* asynchronously notify all listeners about a new notification
*/
private void notifyPostedLocked(NotificationRecord n) {
final StatusBarNotification sbn = n.sbn;
for (final NotificationListenerInfo info : mListeners) {
@ -530,6 +733,9 @@ public class NotificationManagerService extends INotificationManager.Stub
}
}
/**
* asynchronously notify all listeners about a removed notification
*/
private void notifyRemovedLocked(NotificationRecord n) {
final StatusBarNotification sbn = n.sbn;
for (final NotificationListenerInfo info : mListeners) {
@ -541,6 +747,57 @@ public class NotificationManagerService extends INotificationManager.Stub
}
}
// -- APIs to support listeners clicking/clearing notifications --
private NotificationListenerInfo checkListenerToken(INotificationListener listener) {
final IBinder token = listener.asBinder();
final int N = mListeners.size();
for (int i=0; i<N; i++) {
final NotificationListenerInfo info = mListeners.get(i);
if (info.listener.asBinder() == token) return info;
}
throw new SecurityException("Disallowed call from unknown listener: " + listener);
}
/**
* Allow an INotificationListener to simulate a "clear all" operation.
*
* {@see com.android.server.StatusBarManagerService.NotificationCallbacks#onClearAllNotifications}
*
* @param token The binder for the listener, to check that the caller is allowed
*/
public void clearAllNotificationsFromListener(INotificationListener token) {
NotificationListenerInfo info = checkListenerToken(token);
long identity = Binder.clearCallingIdentity();
try {
cancelAll(info.userid);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
* Allow an INotificationListener to simulate clearing (dismissing) a single notification.
*
* {@see com.android.server.StatusBarManagerService.NotificationCallbacks#onNotificationClear}
*
* @param token The binder for the listener, to check that the caller is allowed
*/
public void clearNotificationFromListener(INotificationListener token, String pkg, String tag, int id) {
NotificationListenerInfo info = checkListenerToken(token);
long identity = Binder.clearCallingIdentity();
try {
cancelNotification(pkg, tag, id, 0,
Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE,
true,
info.userid);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
// -- end of listener APIs --
public static final class NotificationRecord
{
final StatusBarNotification sbn;
@ -759,12 +1016,23 @@ public class NotificationManagerService extends INotificationManager.Stub
}
pkgList = new String[]{pkgName};
}
boolean anyListenersInvolved = false;
if (pkgList != null && (pkgList.length > 0)) {
for (String pkgName : pkgList) {
cancelAllNotificationsInt(pkgName, 0, 0, !queryRestart,
UserHandle.USER_ALL);
if (mEnabledListenerPackageNames.contains(pkgName)) {
anyListenersInvolved = true;
}
}
}
if (anyListenersInvolved) {
// make sure we're still bound to any of our
// listeners who may have just upgraded
rebindListenerServices();
}
} else if (action.equals(Intent.ACTION_SCREEN_ON)) {
// Keep track of screen on/off state, but do not turn off the notification light
// until user passes through the lock screen or views the notification.
@ -795,7 +1063,7 @@ public class NotificationManagerService extends INotificationManager.Stub
= Settings.System.getUriFor(Settings.System.NOTIFICATION_LIGHT_PULSE);
private final Uri ENABLED_NOTIFICATION_LISTENERS_URI
= Settings.System.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
= Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
SettingsObserver(Handler handler) {
super(handler);
@ -804,9 +1072,9 @@ public class NotificationManagerService extends INotificationManager.Stub
void observe() {
ContentResolver resolver = mContext.getContentResolver();
resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI,
false, this);
false, this, UserHandle.USER_ALL);
resolver.registerContentObserver(ENABLED_NOTIFICATION_LISTENERS_URI,
false, this);
false, this, UserHandle.USER_ALL);
update(null);
}
@ -825,19 +1093,7 @@ public class NotificationManagerService extends INotificationManager.Stub
}
}
if (uri == null || ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri)) {
String pkglist = Settings.Secure.getString(
mContext.getContentResolver(),
Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
mEnabledListenersForCurrentUser.clear();
if (pkglist != null) {
String[] pkgs = pkglist.split(";");
for (int i=0; i<pkgs.length; i++) {
final String pkg = pkgs[i];
if (pkg != null && ! "".equals(pkg)) {
mEnabledListenersForCurrentUser.add(pkgs[i]);
}
}
}
rebindListenerServices();
}
}
}
@ -956,6 +1212,9 @@ public class NotificationManagerService extends INotificationManager.Stub
// no beeping until we're basically done booting
mSystemReady = true;
// make sure our listener services are properly bound
rebindListenerServices();
}
// Toasts
@ -1781,16 +2040,17 @@ public class NotificationManagerService extends INotificationManager.Stub
pw.println("Current Notification Manager state:");
pw.print(" Enabled listeners: [");
for (String pkg : mEnabledListenersForCurrentUser) {
pw.print(" " + pkg);
pw.println(" Listeners (" + mEnabledListenersForCurrentUser.size()
+ ") enabled for current user:");
for (ComponentName cmpt : mEnabledListenersForCurrentUser) {
pw.println(" " + cmpt);
}
pw.println(" ]");
pw.println(" Live listeners:");
pw.println(" Live listeners (" + mListeners.size() + "):");
for (NotificationListenerInfo info : mListeners) {
pw.println(" " + info.pkg + " (user " + info.userid + "): " + info.listener
+ (info.isSystem?" SYSTEM":""));
pw.println(" " + info.component
+ " (user " + info.userid + "): " + info.listener
+ (info.isSystem?" SYSTEM":""));
}
int N;

View File

@ -17,6 +17,7 @@
package com.android.server;
import android.app.StatusBarManager;
import android.service.notification.StatusBarNotification;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@ -33,7 +34,6 @@ import com.android.internal.statusbar.IStatusBar;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.statusbar.StatusBarIconList;
import com.android.internal.statusbar.StatusBarNotification;
import com.android.server.wm.WindowManagerService;
import java.io.FileDescriptor;

View File

@ -33,13 +33,10 @@ import android.util.Log;
import android.net.Uri;
import android.os.SystemClock;
import android.widget.RemoteViews;
import android.widget.TextView;
import android.widget.ProgressBar;
import android.os.PowerManager;
// private NM API
import android.app.INotificationManager;
import com.android.internal.statusbar.StatusBarNotification;
public class NotificationTestList extends TestActivity
{