1616
1717package com .android .server ;
1818
19+ import com .android .internal .os .AtomicFile ;
1920import com .android .internal .statusbar .StatusBarNotification ;
21+ import com .android .internal .util .FastXmlSerializer ;
2022
2123import android .app .ActivityManagerNative ;
2224import android .app .IActivityManager ;
3739import android .content .res .Resources ;
3840import android .database .ContentObserver ;
3941import android .media .AudioManager ;
42+ import android .net .NetworkPolicy ;
43+ import android .net .NetworkTemplate ;
4044import android .net .Uri ;
4145import android .os .Binder ;
42- import android .os .Bundle ;
4346import android .os .Handler ;
4447import android .os .IBinder ;
4548import android .os .Message ;
5356import android .util .EventLog ;
5457import android .util .Log ;
5558import android .util .Slog ;
59+ import android .util .Xml ;
5660import android .view .accessibility .AccessibilityEvent ;
5761import android .view .accessibility .AccessibilityManager ;
5862import android .widget .Toast ;
5963
64+ import java .io .File ;
6065import java .io .FileDescriptor ;
66+ import java .io .FileInputStream ;
67+ import java .io .FileNotFoundException ;
68+ import java .io .FileOutputStream ;
69+ import java .io .IOException ;
6170import java .io .PrintWriter ;
6271import java .util .ArrayList ;
6372import 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} */
6691public 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