summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/INotificationManager.aidl3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java29
-rwxr-xr-xservices/java/com/android/server/NotificationManagerService.java231
3 files changed, 218 insertions, 45 deletions
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 4d5238c..6f95e26 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -34,5 +34,8 @@ interface INotificationManager
void cancelToast(String pkg, ITransientNotification callback);
void enqueueNotificationWithTag(String pkg, String tag, int id, in Notification notification, inout int[] idReceived);
void cancelNotificationWithTag(String pkg, String tag, int id);
+
+ void setNotificationsEnabledForPackage(String pkg, boolean enabled);
+ boolean areNotificationsEnabledForPackage(String pkg);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index b7becf3..da98c80 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -218,11 +218,6 @@ public class PhoneStatusBar extends BaseStatusBar {
private int mNavigationIconHints = 0;
- // TODO(dsandler): codify this stuff in NotificationManager's header somewhere
- private int mDisplayMinScore = Notification.PRIORITY_LOW * 10;
- private int mIntruderMinScore = Notification.PRIORITY_HIGH * 10;
- private int mIntruderInImmersiveMinScore = Notification.PRIORITY_HIGH * 10 + 5;
-
private class ExpandedDialog extends Dialog {
ExpandedDialog(Context context) {
super(context, com.android.internal.R.style.Theme_Translucent_NoTitleBar);
@@ -2132,30 +2127,6 @@ public class PhoneStatusBar extends BaseStatusBar {
vib.vibrate(250);
}
- public int getScoreThreshold() {
- return mDisplayMinScore;
- }
-
- public void setScoreThreshold(int score) {
- // XXX HAX
- if (mDisplayMinScore != score) {
- this.mDisplayMinScore = score;
- applyScoreThreshold();
- }
- }
-
- private void applyScoreThreshold() {
- int N = mNotificationData.size();
- for (int i=0; i<N; i++) {
- NotificationData.Entry entry = mNotificationData.get(i);
- int vis = (entry.notification.score < mDisplayMinScore)
- ? View.GONE
- : View.VISIBLE;
- entry.row.setVisibility(vis);
- entry.icon.setVisibility(vis);
- }
- }
-
Runnable mStartTracing = new Runnable() {
public void run() {
vibrate();
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index b22be76..1ba7e79 100755
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -16,7 +16,9 @@
package com.android.server;
+import com.android.internal.os.AtomicFile;
import com.android.internal.statusbar.StatusBarNotification;
+import com.android.internal.util.FastXmlSerializer;
import android.app.ActivityManagerNative;
import android.app.IActivityManager;
@@ -37,9 +39,10 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.media.AudioManager;
+import android.net.NetworkPolicy;
+import android.net.NetworkTemplate;
import android.net.Uri;
import android.os.Binder;
-import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -53,14 +56,36 @@ import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
+import android.util.Xml;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;
+import java.io.File;
import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import static android.net.NetworkPolicyManager.POLICY_NONE;
+import static com.android.server.net.NetworkPolicyManagerService.XmlUtils.writeBooleanAttribute;
+import static com.android.server.net.NetworkPolicyManagerService.XmlUtils.writeIntAttribute;
+import static com.android.server.net.NetworkPolicyManagerService.XmlUtils.writeLongAttribute;
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.END_TAG;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
/** {@hide} */
public class NotificationManagerService extends INotificationManager.Stub
@@ -81,6 +106,13 @@ public class NotificationManagerService extends INotificationManager.Stub
private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_NOTIFICATION;
private static final boolean SCORE_ONGOING_HIGHER = false;
+ private static final int JUNK_SCORE = -1000;
+ private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10;
+ private static final int SCORE_DISPLAY_THRESHOLD = Notification.PRIORITY_MIN * NOTIFICATION_PRIORITY_MULTIPLIER;
+
+ private static final boolean ENABLE_BLOCKED_NOTIFICATIONS = true;
+ private static final boolean ENABLE_BLOCKED_TOASTS = true;
+
final Context mContext;
final IActivityManager mAm;
final IBinder mForegroundToken = new Binder();
@@ -115,6 +147,144 @@ public class NotificationManagerService extends INotificationManager.Stub
private ArrayList<NotificationRecord> mLights = new ArrayList<NotificationRecord>();
private NotificationRecord mLedNotification;
+ // Notification control database. For now just contains disabled packages.
+ private AtomicFile mPolicyFile;
+ private HashSet<String> mBlockedPackages = new HashSet<String>();
+
+ private static final int DB_VERSION = 1;
+
+ private static final String TAG_BODY = "notification-policy";
+ private static final String ATTR_VERSION = "version";
+
+ private static final String TAG_BLOCKED_PKGS = "blocked-packages";
+ private static final String TAG_PACKAGE = "package";
+ private static final String ATTR_NAME = "name";
+
+ private void loadBlockDb() {
+ synchronized(mBlockedPackages) {
+ if (mPolicyFile == null) {
+ File dir = new File("/data/system");
+ mPolicyFile = new AtomicFile(new File(dir, "notification_policy.xml"));
+
+ mBlockedPackages.clear();
+
+ FileInputStream infile = null;
+ try {
+ infile = mPolicyFile.openRead();
+ final XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(infile, null);
+
+ int type;
+ String tag;
+ int version = DB_VERSION;
+ while ((type = parser.next()) != END_DOCUMENT) {
+ tag = parser.getName();
+ if (type == START_TAG) {
+ if (TAG_BODY.equals(tag)) {
+ version = Integer.parseInt(parser.getAttributeValue(null, ATTR_VERSION));
+ } else if (TAG_BLOCKED_PKGS.equals(tag)) {
+ while ((type = parser.next()) != END_DOCUMENT) {
+ tag = parser.getName();
+ if (TAG_PACKAGE.equals(tag)) {
+ mBlockedPackages.add(parser.getAttributeValue(null, ATTR_NAME));
+ } else if (TAG_BLOCKED_PKGS.equals(tag) && type == END_TAG) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ } catch (FileNotFoundException e) {
+ // No data yet
+ } catch (IOException e) {
+ Log.wtf(TAG, "Unable to read blocked notifications database", e);
+ } catch (NumberFormatException e) {
+ Log.wtf(TAG, "Unable to parse blocked notifications database", e);
+ } catch (XmlPullParserException e) {
+ Log.wtf(TAG, "Unable to parse blocked notifications database", e);
+ } finally {
+ IoUtils.closeQuietly(infile);
+ }
+ }
+ }
+ }
+
+ private void writeBlockDb() {
+ synchronized(mBlockedPackages) {
+ FileOutputStream outfile = null;
+ try {
+ outfile = mPolicyFile.startWrite();
+
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(outfile, "utf-8");
+
+ out.startDocument(null, true);
+
+ out.startTag(null, TAG_BODY); {
+ out.attribute(null, ATTR_VERSION, String.valueOf(DB_VERSION));
+ out.startTag(null, TAG_BLOCKED_PKGS); {
+ // write all known network policies
+ for (String pkg : mBlockedPackages) {
+ out.startTag(null, TAG_PACKAGE); {
+ out.attribute(null, ATTR_NAME, pkg);
+ } out.endTag(null, TAG_PACKAGE);
+ }
+ } out.endTag(null, TAG_BLOCKED_PKGS);
+ } out.endTag(null, TAG_BODY);
+
+ out.endDocument();
+
+ mPolicyFile.finishWrite(outfile);
+ } catch (IOException e) {
+ if (outfile != null) {
+ mPolicyFile.failWrite(outfile);
+ }
+ }
+ }
+ }
+
+ public boolean areNotificationsEnabledForPackage(String pkg) {
+ checkCallerIsSystem();
+ return areNotificationsEnabledForPackageInt(pkg);
+ }
+
+ // Unchecked. Not exposed via Binder, but can be called in the course of enqueue*().
+ private boolean areNotificationsEnabledForPackageInt(String pkg) {
+ final boolean enabled = !mBlockedPackages.contains(pkg);
+ if (DBG) {
+ Slog.v(TAG, "notifications are " + (enabled?"en":"dis") + "abled for " + pkg);
+ }
+ return enabled;
+ }
+
+ public void setNotificationsEnabledForPackage(String pkg, boolean enabled) {
+ checkCallerIsSystem();
+ if (DBG) {
+ Slog.v(TAG, (enabled?"en":"dis") + "abling notifications for " + pkg);
+ }
+ if (enabled) {
+ mBlockedPackages.remove(pkg);
+ } else {
+ mBlockedPackages.add(pkg);
+
+ // Now, cancel any outstanding notifications that are part of a just-disabled app
+ if (ENABLE_BLOCKED_NOTIFICATIONS) {
+ synchronized (mNotificationList) {
+ final int N = mNotificationList.size();
+ for (int i=0; i<N; i++) {
+ final NotificationRecord r = mNotificationList.get(i);
+ if (r.pkg.equals(pkg)) {
+ cancelNotificationLocked(r, false);
+ }
+ }
+ }
+ }
+ // Don't bother canceling toasts, they'll go away soon enough.
+ }
+ writeBlockDb();
+ }
+
+
private static String idDebugString(Context baseContext, String packageName, int id) {
Context c = null;
@@ -405,6 +575,8 @@ public class NotificationManagerService extends INotificationManager.Stub
mToastQueue = new ArrayList<ToastRecord>();
mHandler = new WorkerHandler();
+ loadBlockDb();
+
mStatusBar = statusBar;
statusBar.setNotificationCallbacks(mNotificationCallbacks);
@@ -465,6 +637,13 @@ public class NotificationManagerService extends INotificationManager.Stub
return ;
}
+ final boolean isSystemToast = ("android".equals(pkg));
+
+ if (ENABLE_BLOCKED_TOASTS && !isSystemToast && !areNotificationsEnabledForPackageInt(pkg)) {
+ Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request.");
+ return;
+ }
+
synchronized (mToastQueue) {
int callingPid = Binder.getCallingPid();
long callingId = Binder.clearCallingIdentity();
@@ -479,7 +658,7 @@ public class NotificationManagerService extends INotificationManager.Stub
} else {
// Limit the number of toasts that any given package except the android
// package can enqueue. Prevents DOS attacks and deals with leaks.
- if (!"android".equals(pkg)) {
+ if (!isSystemToast) {
int count = 0;
final int N = mToastQueue.size();
for (int i=0; i<N; i++) {
@@ -675,11 +854,15 @@ public class NotificationManagerService extends INotificationManager.Stub
public void enqueueNotificationInternal(String pkg, int callingUid, int callingPid,
String tag, int id, Notification notification, int[] idOut)
{
- checkIncomingCall(pkg);
+ if (DBG) {
+ Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification);
+ }
+ checkCallerIsSystemOrSameApp(pkg);
+ final boolean isSystemNotification = ("android".equals(pkg));
// Limit the number of notifications that any given package except the android
// package can enqueue. Prevents DOS attacks and deals with leaks.
- if (!"android".equals(pkg)) {
+ if (!isSystemNotification) {
synchronized (mNotificationList) {
int count = 0;
final int N = mNotificationList.size();
@@ -717,7 +900,7 @@ public class NotificationManagerService extends INotificationManager.Stub
}
// === Scoring ===
-
+
// 0. Sanitize inputs
notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN, Notification.PRIORITY_MAX);
// Migrate notification flags to scores
@@ -726,19 +909,27 @@ public class NotificationManagerService extends INotificationManager.Stub
} else if (SCORE_ONGOING_HIGHER && 0 != (notification.flags & Notification.FLAG_ONGOING_EVENT)) {
if (notification.priority < Notification.PRIORITY_HIGH) notification.priority = Notification.PRIORITY_HIGH;
}
-
+
// 1. initial score: buckets of 10, around the app
- int score = notification.priority * 10; //[-20..20]
+ int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER; //[-20..20]
- // 2. Consult oracles (external heuristics)
- // TODO(dsandler): oracles
+ // 2. Consult external heuristics (TBD)
- // 3. Apply local heuristics & overrides
+ // 3. Apply local rules
// blocked apps
- // TODO(dsandler): add block db
- if (pkg.startsWith("com.test.spammer.")) {
- score = -1000;
+ if (ENABLE_BLOCKED_NOTIFICATIONS && !isSystemNotification && !areNotificationsEnabledForPackageInt(pkg)) {
+ score = JUNK_SCORE;
+ Slog.e(TAG, "Suppressing notification from package " + pkg + " by user request.");
+ }
+
+ if (DBG) {
+ Slog.v(TAG, "Assigned score=" + score + " to " + notification);
+ }
+
+ if (score < SCORE_DISPLAY_THRESHOLD) {
+ // Notification will be blocked because the score is too low.
+ return;
}
synchronized (mNotificationList) {
@@ -1030,7 +1221,7 @@ public class NotificationManagerService extends INotificationManager.Stub
}
public void cancelNotificationWithTag(String pkg, String tag, int id) {
- checkIncomingCall(pkg);
+ checkCallerIsSystemOrSameApp(pkg);
// Don't allow client applications to cancel foreground service notis.
cancelNotification(pkg, tag, id, 0,
Binder.getCallingUid() == Process.SYSTEM_UID
@@ -1038,14 +1229,22 @@ public class NotificationManagerService extends INotificationManager.Stub
}
public void cancelAllNotifications(String pkg) {
- checkIncomingCall(pkg);
+ checkCallerIsSystemOrSameApp(pkg);
// Calling from user space, don't allow the canceling of actively
// running foreground services.
cancelAllNotificationsInt(pkg, 0, Notification.FLAG_FOREGROUND_SERVICE, true);
}
- void checkIncomingCall(String pkg) {
+ void checkCallerIsSystem() {
+ int uid = Binder.getCallingUid();
+ if (uid == Process.SYSTEM_UID || uid == 0) {
+ return;
+ }
+ throw new SecurityException("Disallowed call for uid " + uid);
+ }
+
+ void checkCallerIsSystemOrSameApp(String pkg) {
int uid = Binder.getCallingUid();
if (uid == Process.SYSTEM_UID || uid == 0) {
return;