From 0da673f02e542c644c300568807c80e196296860 Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Wed, 11 Apr 2012 12:33:16 -0400 Subject: [PATCH] Notifications may now be disabled on a per-package basis. When a package's ability to post notifications is disabled, all outstanding notifications from that package are immediately canceled, and the score of any future notification from that package is set so low that the notification manager won't even send it to the status bar. No UI for this yet, but you can try it out: adb shell service call notification 8 s16 $PKG i32 (1|0) Bug: 5547401 Change-Id: Ieccac5746b40f60debd902a45d1dedbc91dcdc89 --- .../android/app/INotificationManager.aidl | 3 + .../statusbar/phone/PhoneStatusBar.java | 29 --- .../server/NotificationManagerService.java | 231 ++++++++++++++++-- 3 files changed, 218 insertions(+), 45 deletions(-) diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 4d5238c582f7b..6f95e26ae79bc 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 b7becf33d009d..da98c8075a030 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 mLights = new ArrayList(); private NotificationRecord mLedNotification; + // Notification control database. For now just contains disabled packages. + private AtomicFile mPolicyFile; + private HashSet mBlockedPackages = new HashSet(); + + 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(); 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