Merge "Notifications may now be disabled on a per-package basis."

This commit is contained in:
Daniel Sandler
2012-04-19 08:53:36 -07:00
committed by Android (Google) Code Review
3 changed files with 218 additions and 45 deletions

View File

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

View File

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

View File

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