Skip to content

Commit 9afbfb5

Browse files
dsandlerAndroid (Google) Code Review
authored andcommitted
Merge "Notifications may now be disabled on a per-package basis."
2 parents a46f768 + 0da673f commit 9afbfb5

File tree

3 files changed

+218
-45
lines changed

3 files changed

+218
-45
lines changed

core/java/android/app/INotificationManager.aidl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,8 @@ interface INotificationManager
3434
void cancelToast(String pkg, ITransientNotification callback);
3535
void enqueueNotificationWithTag(String pkg, String tag, int id, in Notification notification, inout int[] idReceived);
3636
void cancelNotificationWithTag(String pkg, String tag, int id);
37+
38+
void setNotificationsEnabledForPackage(String pkg, boolean enabled);
39+
boolean areNotificationsEnabledForPackage(String pkg);
3740
}
3841

packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -218,11 +218,6 @@ public class PhoneStatusBar extends BaseStatusBar {
218218

219219
private int mNavigationIconHints = 0;
220220

221-
// TODO(dsandler): codify this stuff in NotificationManager's header somewhere
222-
private int mDisplayMinScore = Notification.PRIORITY_LOW * 10;
223-
private int mIntruderMinScore = Notification.PRIORITY_HIGH * 10;
224-
private int mIntruderInImmersiveMinScore = Notification.PRIORITY_HIGH * 10 + 5;
225-
226221
private class ExpandedDialog extends Dialog {
227222
ExpandedDialog(Context context) {
228223
super(context, com.android.internal.R.style.Theme_Translucent_NoTitleBar);
@@ -2132,30 +2127,6 @@ void vibrate() {
21322127
vib.vibrate(250);
21332128
}
21342129

2135-
public int getScoreThreshold() {
2136-
return mDisplayMinScore;
2137-
}
2138-
2139-
public void setScoreThreshold(int score) {
2140-
// XXX HAX
2141-
if (mDisplayMinScore != score) {
2142-
this.mDisplayMinScore = score;
2143-
applyScoreThreshold();
2144-
}
2145-
}
2146-
2147-
private void applyScoreThreshold() {
2148-
int N = mNotificationData.size();
2149-
for (int i=0; i<N; i++) {
2150-
NotificationData.Entry entry = mNotificationData.get(i);
2151-
int vis = (entry.notification.score < mDisplayMinScore)
2152-
? View.GONE
2153-
: View.VISIBLE;
2154-
entry.row.setVisibility(vis);
2155-
entry.icon.setVisibility(vis);
2156-
}
2157-
}
2158-
21592130
Runnable mStartTracing = new Runnable() {
21602131
public void run() {
21612132
vibrate();

services/java/com/android/server/NotificationManagerService.java

Lines changed: 215 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
package com.android.server;
1818

19+
import com.android.internal.os.AtomicFile;
1920
import com.android.internal.statusbar.StatusBarNotification;
21+
import com.android.internal.util.FastXmlSerializer;
2022

2123
import android.app.ActivityManagerNative;
2224
import android.app.IActivityManager;
@@ -37,9 +39,10 @@
3739
import android.content.res.Resources;
3840
import android.database.ContentObserver;
3941
import android.media.AudioManager;
42+
import android.net.NetworkPolicy;
43+
import android.net.NetworkTemplate;
4044
import android.net.Uri;
4145
import android.os.Binder;
42-
import android.os.Bundle;
4346
import android.os.Handler;
4447
import android.os.IBinder;
4548
import android.os.Message;
@@ -53,14 +56,36 @@
5356
import android.util.EventLog;
5457
import android.util.Log;
5558
import android.util.Slog;
59+
import android.util.Xml;
5660
import android.view.accessibility.AccessibilityEvent;
5761
import android.view.accessibility.AccessibilityManager;
5862
import android.widget.Toast;
5963

64+
import java.io.File;
6065
import java.io.FileDescriptor;
66+
import java.io.FileInputStream;
67+
import java.io.FileNotFoundException;
68+
import java.io.FileOutputStream;
69+
import java.io.IOException;
6170
import java.io.PrintWriter;
6271
import java.util.ArrayList;
6372
import java.util.Arrays;
73+
import java.util.HashSet;
74+
75+
import libcore.io.IoUtils;
76+
77+
import org.xmlpull.v1.XmlPullParser;
78+
import org.xmlpull.v1.XmlPullParserException;
79+
import org.xmlpull.v1.XmlSerializer;
80+
81+
import static android.net.NetworkPolicyManager.POLICY_NONE;
82+
import static com.android.server.net.NetworkPolicyManagerService.XmlUtils.writeBooleanAttribute;
83+
import static com.android.server.net.NetworkPolicyManagerService.XmlUtils.writeIntAttribute;
84+
import static com.android.server.net.NetworkPolicyManagerService.XmlUtils.writeLongAttribute;
85+
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
86+
import static org.xmlpull.v1.XmlPullParser.END_TAG;
87+
import static org.xmlpull.v1.XmlPullParser.START_TAG;
88+
6489

6590
/** {@hide} */
6691
public class NotificationManagerService extends INotificationManager.Stub
@@ -81,6 +106,13 @@ public class NotificationManagerService extends INotificationManager.Stub
81106
private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_NOTIFICATION;
82107
private static final boolean SCORE_ONGOING_HIGHER = false;
83108

109+
private static final int JUNK_SCORE = -1000;
110+
private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10;
111+
private static final int SCORE_DISPLAY_THRESHOLD = Notification.PRIORITY_MIN * NOTIFICATION_PRIORITY_MULTIPLIER;
112+
113+
private static final boolean ENABLE_BLOCKED_NOTIFICATIONS = true;
114+
private static final boolean ENABLE_BLOCKED_TOASTS = true;
115+
84116
final Context mContext;
85117
final IActivityManager mAm;
86118
final IBinder mForegroundToken = new Binder();
@@ -115,6 +147,144 @@ public class NotificationManagerService extends INotificationManager.Stub
115147
private ArrayList<NotificationRecord> mLights = new ArrayList<NotificationRecord>();
116148
private NotificationRecord mLedNotification;
117149

150+
// Notification control database. For now just contains disabled packages.
151+
private AtomicFile mPolicyFile;
152+
private HashSet<String> mBlockedPackages = new HashSet<String>();
153+
154+
private static final int DB_VERSION = 1;
155+
156+
private static final String TAG_BODY = "notification-policy";
157+
private static final String ATTR_VERSION = "version";
158+
159+
private static final String TAG_BLOCKED_PKGS = "blocked-packages";
160+
private static final String TAG_PACKAGE = "package";
161+
private static final String ATTR_NAME = "name";
162+
163+
private void loadBlockDb() {
164+
synchronized(mBlockedPackages) {
165+
if (mPolicyFile == null) {
166+
File dir = new File("/data/system");
167+
mPolicyFile = new AtomicFile(new File(dir, "notification_policy.xml"));
168+
169+
mBlockedPackages.clear();
170+
171+
FileInputStream infile = null;
172+
try {
173+
infile = mPolicyFile.openRead();
174+
final XmlPullParser parser = Xml.newPullParser();
175+
parser.setInput(infile, null);
176+
177+
int type;
178+
String tag;
179+
int version = DB_VERSION;
180+
while ((type = parser.next()) != END_DOCUMENT) {
181+
tag = parser.getName();
182+
if (type == START_TAG) {
183+
if (TAG_BODY.equals(tag)) {
184+
version = Integer.parseInt(parser.getAttributeValue(null, ATTR_VERSION));
185+
} else if (TAG_BLOCKED_PKGS.equals(tag)) {
186+
while ((type = parser.next()) != END_DOCUMENT) {
187+
tag = parser.getName();
188+
if (TAG_PACKAGE.equals(tag)) {
189+
mBlockedPackages.add(parser.getAttributeValue(null, ATTR_NAME));
190+
} else if (TAG_BLOCKED_PKGS.equals(tag) && type == END_TAG) {
191+
break;
192+
}
193+
}
194+
}
195+
}
196+
}
197+
} catch (FileNotFoundException e) {
198+
// No data yet
199+
} catch (IOException e) {
200+
Log.wtf(TAG, "Unable to read blocked notifications database", e);
201+
} catch (NumberFormatException e) {
202+
Log.wtf(TAG, "Unable to parse blocked notifications database", e);
203+
} catch (XmlPullParserException e) {
204+
Log.wtf(TAG, "Unable to parse blocked notifications database", e);
205+
} finally {
206+
IoUtils.closeQuietly(infile);
207+
}
208+
}
209+
}
210+
}
211+
212+
private void writeBlockDb() {
213+
synchronized(mBlockedPackages) {
214+
FileOutputStream outfile = null;
215+
try {
216+
outfile = mPolicyFile.startWrite();
217+
218+
XmlSerializer out = new FastXmlSerializer();
219+
out.setOutput(outfile, "utf-8");
220+
221+
out.startDocument(null, true);
222+
223+
out.startTag(null, TAG_BODY); {
224+
out.attribute(null, ATTR_VERSION, String.valueOf(DB_VERSION));
225+
out.startTag(null, TAG_BLOCKED_PKGS); {
226+
// write all known network policies
227+
for (String pkg : mBlockedPackages) {
228+
out.startTag(null, TAG_PACKAGE); {
229+
out.attribute(null, ATTR_NAME, pkg);
230+
} out.endTag(null, TAG_PACKAGE);
231+
}
232+
} out.endTag(null, TAG_BLOCKED_PKGS);
233+
} out.endTag(null, TAG_BODY);
234+
235+
out.endDocument();
236+
237+
mPolicyFile.finishWrite(outfile);
238+
} catch (IOException e) {
239+
if (outfile != null) {
240+
mPolicyFile.failWrite(outfile);
241+
}
242+
}
243+
}
244+
}
245+
246+
public boolean areNotificationsEnabledForPackage(String pkg) {
247+
checkCallerIsSystem();
248+
return areNotificationsEnabledForPackageInt(pkg);
249+
}
250+
251+
// Unchecked. Not exposed via Binder, but can be called in the course of enqueue*().
252+
private boolean areNotificationsEnabledForPackageInt(String pkg) {
253+
final boolean enabled = !mBlockedPackages.contains(pkg);
254+
if (DBG) {
255+
Slog.v(TAG, "notifications are " + (enabled?"en":"dis") + "abled for " + pkg);
256+
}
257+
return enabled;
258+
}
259+
260+
public void setNotificationsEnabledForPackage(String pkg, boolean enabled) {
261+
checkCallerIsSystem();
262+
if (DBG) {
263+
Slog.v(TAG, (enabled?"en":"dis") + "abling notifications for " + pkg);
264+
}
265+
if (enabled) {
266+
mBlockedPackages.remove(pkg);
267+
} else {
268+
mBlockedPackages.add(pkg);
269+
270+
// Now, cancel any outstanding notifications that are part of a just-disabled app
271+
if (ENABLE_BLOCKED_NOTIFICATIONS) {
272+
synchronized (mNotificationList) {
273+
final int N = mNotificationList.size();
274+
for (int i=0; i<N; i++) {
275+
final NotificationRecord r = mNotificationList.get(i);
276+
if (r.pkg.equals(pkg)) {
277+
cancelNotificationLocked(r, false);
278+
}
279+
}
280+
}
281+
}
282+
// Don't bother canceling toasts, they'll go away soon enough.
283+
}
284+
writeBlockDb();
285+
}
286+
287+
118288
private static String idDebugString(Context baseContext, String packageName, int id) {
119289
Context c = null;
120290

@@ -405,6 +575,8 @@ public void update() {
405575
mToastQueue = new ArrayList<ToastRecord>();
406576
mHandler = new WorkerHandler();
407577

578+
loadBlockDb();
579+
408580
mStatusBar = statusBar;
409581
statusBar.setNotificationCallbacks(mNotificationCallbacks);
410582

@@ -465,6 +637,13 @@ public void enqueueToast(String pkg, ITransientNotification callback, int durati
465637
return ;
466638
}
467639

640+
final boolean isSystemToast = ("android".equals(pkg));
641+
642+
if (ENABLE_BLOCKED_TOASTS && !isSystemToast && !areNotificationsEnabledForPackageInt(pkg)) {
643+
Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request.");
644+
return;
645+
}
646+
468647
synchronized (mToastQueue) {
469648
int callingPid = Binder.getCallingPid();
470649
long callingId = Binder.clearCallingIdentity();
@@ -479,7 +658,7 @@ record = mToastQueue.get(index);
479658
} else {
480659
// Limit the number of toasts that any given package except the android
481660
// package can enqueue. Prevents DOS attacks and deals with leaks.
482-
if (!"android".equals(pkg)) {
661+
if (!isSystemToast) {
483662
int count = 0;
484663
final int N = mToastQueue.size();
485664
for (int i=0; i<N; i++) {
@@ -675,11 +854,15 @@ private final static int clamp(int x, int low, int high) {
675854
public void enqueueNotificationInternal(String pkg, int callingUid, int callingPid,
676855
String tag, int id, Notification notification, int[] idOut)
677856
{
678-
checkIncomingCall(pkg);
857+
if (DBG) {
858+
Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification);
859+
}
860+
checkCallerIsSystemOrSameApp(pkg);
861+
final boolean isSystemNotification = ("android".equals(pkg));
679862

680863
// Limit the number of notifications that any given package except the android
681864
// package can enqueue. Prevents DOS attacks and deals with leaks.
682-
if (!"android".equals(pkg)) {
865+
if (!isSystemNotification) {
683866
synchronized (mNotificationList) {
684867
int count = 0;
685868
final int N = mNotificationList.size();
@@ -717,7 +900,7 @@ public void enqueueNotificationInternal(String pkg, int callingUid, int callingP
717900
}
718901

719902
// === Scoring ===
720-
903+
721904
// 0. Sanitize inputs
722905
notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN, Notification.PRIORITY_MAX);
723906
// Migrate notification flags to scores
@@ -726,19 +909,27 @@ public void enqueueNotificationInternal(String pkg, int callingUid, int callingP
726909
} else if (SCORE_ONGOING_HIGHER && 0 != (notification.flags & Notification.FLAG_ONGOING_EVENT)) {
727910
if (notification.priority < Notification.PRIORITY_HIGH) notification.priority = Notification.PRIORITY_HIGH;
728911
}
729-
912+
730913
// 1. initial score: buckets of 10, around the app
731-
int score = notification.priority * 10; //[-20..20]
914+
int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER; //[-20..20]
732915

733-
// 2. Consult oracles (external heuristics)
734-
// TODO(dsandler): oracles
916+
// 2. Consult external heuristics (TBD)
735917

736-
// 3. Apply local heuristics & overrides
918+
// 3. Apply local rules
737919

738920
// blocked apps
739-
// TODO(dsandler): add block db
740-
if (pkg.startsWith("com.test.spammer.")) {
741-
score = -1000;
921+
if (ENABLE_BLOCKED_NOTIFICATIONS && !isSystemNotification && !areNotificationsEnabledForPackageInt(pkg)) {
922+
score = JUNK_SCORE;
923+
Slog.e(TAG, "Suppressing notification from package " + pkg + " by user request.");
924+
}
925+
926+
if (DBG) {
927+
Slog.v(TAG, "Assigned score=" + score + " to " + notification);
928+
}
929+
930+
if (score < SCORE_DISPLAY_THRESHOLD) {
931+
// Notification will be blocked because the score is too low.
932+
return;
742933
}
743934

744935
synchronized (mNotificationList) {
@@ -1030,22 +1221,30 @@ public void cancelNotification(String pkg, int id) {
10301221
}
10311222

10321223
public void cancelNotificationWithTag(String pkg, String tag, int id) {
1033-
checkIncomingCall(pkg);
1224+
checkCallerIsSystemOrSameApp(pkg);
10341225
// Don't allow client applications to cancel foreground service notis.
10351226
cancelNotification(pkg, tag, id, 0,
10361227
Binder.getCallingUid() == Process.SYSTEM_UID
10371228
? 0 : Notification.FLAG_FOREGROUND_SERVICE, false);
10381229
}
10391230

10401231
public void cancelAllNotifications(String pkg) {
1041-
checkIncomingCall(pkg);
1232+
checkCallerIsSystemOrSameApp(pkg);
10421233

10431234
// Calling from user space, don't allow the canceling of actively
10441235
// running foreground services.
10451236
cancelAllNotificationsInt(pkg, 0, Notification.FLAG_FOREGROUND_SERVICE, true);
10461237
}
10471238

1048-
void checkIncomingCall(String pkg) {
1239+
void checkCallerIsSystem() {
1240+
int uid = Binder.getCallingUid();
1241+
if (uid == Process.SYSTEM_UID || uid == 0) {
1242+
return;
1243+
}
1244+
throw new SecurityException("Disallowed call for uid " + uid);
1245+
}
1246+
1247+
void checkCallerIsSystemOrSameApp(String pkg) {
10491248
int uid = Binder.getCallingUid();
10501249
if (uid == Process.SYSTEM_UID || uid == 0) {
10511250
return;

0 commit comments

Comments
 (0)