Make bad notifications crash their application.

Implement notification manager handling of bad notifications, to
call a new activity manager to have the owner's process crashed
(if there is one).

Change-Id: Ib15e8d0c598756f3b39c99cc2045c18e054daf6b
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 63b2f08..e56fee9 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1293,6 +1293,17 @@
             return true;
         }
 
+        case CRASH_APPLICATION_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            int uid = data.readInt();
+            int initialPid = data.readInt();
+            String packageName = data.readString();
+            String message = data.readString();
+            crashApplication(uid, initialPid, packageName, message);
+            reply.writeNoException();
+            return true;
+        }
+
         }
         
         return super.onTransact(code, data, reply, flags);
@@ -2867,5 +2878,20 @@
         return res;
     }
 
+    public void crashApplication(int uid, int initialPid, String packageName,
+            String message) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeInt(uid);
+        data.writeInt(initialPid);
+        data.writeString(packageName);
+        data.writeString(message);
+        mRemote.transact(CRASH_APPLICATION_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    
     private IBinder mRemote;
 }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 6599096..03bb858 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -99,6 +99,12 @@
     }
 }
 
+final class RemoteServiceException extends AndroidRuntimeException {
+    public RemoteServiceException(String msg) {
+        super(msg);
+    }
+}
+
 /**
  * This manages the execution of the main thread in an
  * application process, scheduling and executing activities,
@@ -644,6 +650,10 @@
         public void dispatchPackageBroadcast(int cmd, String[] packages) {
             queueOrSendMessage(H.DISPATCH_PACKAGE_BROADCAST, packages, cmd);
         }
+
+        public void scheduleCrash(String msg) {
+            queueOrSendMessage(H.SCHEDULE_CRASH, msg);
+        }
         
         @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -870,6 +880,7 @@
         public static final int REMOVE_PROVIDER         = 131;
         public static final int ENABLE_JIT              = 132;
         public static final int DISPATCH_PACKAGE_BROADCAST = 133;
+        public static final int SCHEDULE_CRASH          = 134;
         String codeToString(int code) {
             if (localLOGV) {
                 switch (code) {
@@ -907,6 +918,7 @@
                     case REMOVE_PROVIDER: return "REMOVE_PROVIDER";
                     case ENABLE_JIT: return "ENABLE_JIT";
                     case DISPATCH_PACKAGE_BROADCAST: return "DISPATCH_PACKAGE_BROADCAST";
+                    case SCHEDULE_CRASH: return "SCHEDULE_CRASH";
                 }
             }
             return "(unknown)";
@@ -1030,6 +1042,8 @@
                 case DISPATCH_PACKAGE_BROADCAST:
                     handleDispatchPackageBroadcast(msg.arg1, (String[])msg.obj);
                     break;
+                case SCHEDULE_CRASH:
+                    throw new RemoteServiceException((String)msg.obj);
             }
         }
 
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index da26a78..360959d 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -402,6 +402,14 @@
             dispatchPackageBroadcast(cmd, packages);
             return true;
         }
+
+        case SCHEDULE_CRASH_TRANSACTION:
+        {
+            data.enforceInterface(IApplicationThread.descriptor);
+            String msg = data.readString();
+            scheduleCrash(msg);
+            return true;
+        }
         }
 
         return super.onTransact(code, data, reply, flags);
@@ -826,5 +834,15 @@
         data.recycle();
         
     }
+    
+    public void scheduleCrash(String msg) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        data.writeString(msg);
+        mRemote.transact(SCHEDULE_CRASH_TRANSACTION, data, null,
+                IBinder.FLAG_ONEWAY);
+        data.recycle();
+        
+    }
 }
 
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 3a86ead..bf02d5a 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -316,6 +316,9 @@
     public boolean isImmersive(IBinder token) throws RemoteException;
     public boolean isTopActivityImmersive() throws RemoteException;
     
+    public void crashApplication(int uid, int initialPid, String packageName,
+            String message) throws RemoteException;
+    
     /*
      * Private non-Binder interfaces
      */
@@ -531,4 +534,5 @@
     int IS_IMMERSIVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+110;
     int SET_IMMERSIVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+111;
     int IS_TOP_ACTIVITY_IMMERSIVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+112;
+    int CRASH_APPLICATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+113;
 }
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index c917e81..ffb8651 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -103,6 +103,7 @@
     static final int PACKAGE_REMOVED = 0;
     static final int EXTERNAL_STORAGE_UNAVAILABLE = 1;
     void dispatchPackageBroadcast(int cmd, String[] packages) throws RemoteException;
+    void scheduleCrash(String msg) throws RemoteException;
     
     String descriptor = "android.app.IApplicationThread";
 
@@ -139,4 +140,5 @@
     int GET_MEMORY_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+31;
     int SCHEDULE_SUICIDE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+32;
     int DISPATCH_PACKAGE_BROADCAST_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+33;
+    int SCHEDULE_CRASH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+34;
 }
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 045c24f..852630d 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -37,6 +37,7 @@
             out List<IBinder> notificationKeys, out List<StatusBarNotification> notifications);
     void onPanelRevealed();
     void onNotificationClick(String pkg, String tag, int id);
-    void onNotificationError(String pkg, String tag, int id, String message);
+    void onNotificationError(String pkg, String tag, int id,
+            int uid, int initialPid, String message);
     void onClearAllNotifications();
 }
diff --git a/core/java/com/android/internal/statusbar/StatusBarNotification.java b/core/java/com/android/internal/statusbar/StatusBarNotification.java
index 5499676..aa340fb 100644
--- a/core/java/com/android/internal/statusbar/StatusBarNotification.java
+++ b/core/java/com/android/internal/statusbar/StatusBarNotification.java
@@ -38,18 +38,23 @@
     public String pkg;
     public int id;
     public String tag;
+    public int uid;
+    public int initialPid;
     public Notification notification;
 
     public StatusBarNotification() {
     }
 
-    public StatusBarNotification(String pkg, int id, String tag, Notification notification) {
+    public StatusBarNotification(String pkg, int id, String tag,
+            int uid, int initialPid, Notification notification) {
         if (pkg == null) throw new NullPointerException();
         if (notification == null) throw new NullPointerException();
 
         this.pkg = pkg;
         this.id = id;
         this.tag = tag;
+        this.uid = uid;
+        this.initialPid = initialPid;
         this.notification = notification;
     }
 
@@ -65,6 +70,8 @@
         } else {
             this.tag = null;
         }
+        this.uid = in.readInt();
+        this.initialPid = in.readInt();
         this.notification = new Notification(in);
     }
 
@@ -77,6 +84,8 @@
         } else {
             out.writeInt(0);
         }
+        out.writeInt(this.uid);
+        out.writeInt(this.initialPid);
         this.notification.writeToParcel(out, flags);
     }
 
@@ -99,7 +108,8 @@
     };
 
     public StatusBarNotification clone() {
-        return new StatusBarNotification(this.pkg, this.id, this.tag, this.notification.clone());
+        return new StatusBarNotification(this.pkg, this.id, this.tag,
+                this.uid, this.initialPid, this.notification.clone());
     }
 
     public String toString() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java b/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java
index a7e5e31..801cb91 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java
@@ -989,7 +989,7 @@
     void handleNotificationError(IBinder key, StatusBarNotification n, String message) {
         removeNotification(key);
         try {
-            mBarService.onNotificationError(n.pkg, n.tag, n.id, message);
+            mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message);
         } catch (RemoteException ex) {
             // The end is nigh.
         }
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index 6f44e8e..3e2c122 100755
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -164,16 +164,21 @@
         final String pkg;
         final String tag;
         final int id;
+        final int uid;
+        final int initialPid;
         ITransientNotification callback;
         int duration;
         final Notification notification;
         IBinder statusBarKey;
 
-        NotificationRecord(String pkg, String tag, int id, Notification notification)
+        NotificationRecord(String pkg, String tag, int id, int uid, int initialPid,
+                Notification notification)
         {
             this.pkg = pkg;
             this.tag = tag;
             this.id = id;
+            this.uid = uid;
+            this.initialPid = initialPid;
             this.notification = notification;
         }
 
@@ -304,10 +309,18 @@
             }
         }
 
-        public void onNotificationError(String pkg, String tag, int id, String message) {
+        public void onNotificationError(String pkg, String tag, int id,
+                int uid, int initialPid, String message) {
             Slog.d(TAG, "onNotification error pkg=" + pkg + " tag=" + tag + " id=" + id);
             cancelNotification(pkg, tag, id, 0, 0);
-            // TODO: Tell the activity manager.
+            long ident = Binder.clearCallingIdentity();
+            try {
+                ActivityManagerNative.getDefault().crashApplication(uid, initialPid, pkg,
+                        "Bad notification posted from package " + pkg
+                        + ": " + message);
+            } catch (RemoteException e) {
+            }
+            Binder.restoreCallingIdentity(ident);
         }
     };
 
@@ -663,6 +676,9 @@
     public void enqueueNotificationWithTag(String pkg, String tag, int id,
             Notification notification, int[] idOut)
     {
+        final int callingUid = Binder.getCallingUid();
+        final int callingPid = Binder.getCallingPid();
+        
         checkIncomingCall(pkg);
 
         // Limit the number of notifications that any given package except the android
@@ -708,7 +724,8 @@
         }
 
         synchronized (mNotificationList) {
-            NotificationRecord r = new NotificationRecord(pkg, tag, id, notification);
+            NotificationRecord r = new NotificationRecord(pkg, tag, id,
+                    callingUid, callingPid, notification);
             NotificationRecord old = null;
 
             int index = indexOfNotificationLocked(pkg, tag, id);
@@ -732,7 +749,8 @@
             }
 
             if (notification.icon != 0) {
-                StatusBarNotification n = new StatusBarNotification(pkg, id, tag, notification);
+                StatusBarNotification n = new StatusBarNotification(pkg, id, tag,
+                        r.uid, r.initialPid, notification);
                 if (old != null && old.statusBarKey != null) {
                     r.statusBarKey = old.statusBarKey;
                     long identity = Binder.clearCallingIdentity();
diff --git a/services/java/com/android/server/StatusBarManagerService.java b/services/java/com/android/server/StatusBarManagerService.java
index 1a16387..4177432 100644
--- a/services/java/com/android/server/StatusBarManagerService.java
+++ b/services/java/com/android/server/StatusBarManagerService.java
@@ -85,7 +85,8 @@
         void onClearAll();
         void onNotificationClick(String pkg, String tag, int id);
         void onPanelRevealed();
-        void onNotificationError(String pkg, String tag, int id, String message);
+        void onNotificationError(String pkg, String tag, int id,
+                int uid, int initialPid, String message);
     }
 
     /**
@@ -293,11 +294,12 @@
         mNotificationCallbacks.onNotificationClick(pkg, tag, id);
     }
 
-    public void onNotificationError(String pkg, String tag, int id, String message) {
+    public void onNotificationError(String pkg, String tag, int id,
+            int uid, int initialPid, String message) {
         enforceStatusBarService();
 
         // WARNING: this will call back into us to do the remove.  Don't hold any locks.
-        mNotificationCallbacks.onNotificationError(pkg, tag, id, message);
+        mNotificationCallbacks.onNotificationError(pkg, tag, id, uid, initialPid, message);
     }
 
     public void onClearAllNotifications() {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 0e1eb6f..252392b 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -4562,6 +4562,60 @@
         }
     }
     
+    public void crashApplication(int uid, int initialPid, String packageName,
+            String message) {
+        if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
+                != PackageManager.PERMISSION_GRANTED) {
+            String msg = "Permission Denial: crashApplication() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + android.Manifest.permission.FORCE_STOP_PACKAGES;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+        
+        synchronized(this) {
+            ProcessRecord proc = null;
+            
+            // Figure out which process to kill.  We don't trust that initialPid
+            // still has any relation to current pids, so must scan through the
+            // list.
+            synchronized (mPidsSelfLocked) {
+                for (int i=0; i<mPidsSelfLocked.size(); i++) {
+                    ProcessRecord p = mPidsSelfLocked.valueAt(i);
+                    if (p.info.uid != uid) {
+                        continue;
+                    }
+                    if (p.pid == initialPid) {
+                        proc = p;
+                        break;
+                    }
+                    for (String str : p.pkgList) {
+                        if (str.equals(packageName)) {
+                            proc = p;
+                        }
+                    }
+                }
+            }
+            
+            if (proc == null) {
+                Log.w(TAG, "crashApplication: nothing for uid=" + uid
+                        + " initialPid=" + initialPid
+                        + " packageName=" + packageName);
+                return;
+            }
+            
+            if (proc.thread != null) {
+                long ident = Binder.clearCallingIdentity();
+                try {
+                    proc.thread.scheduleCrash(message);
+                } catch (RemoteException e) {
+                }
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+    
     void sendActivityResultLocked(int callingUid, ActivityRecord r,
             String resultWho, int requestCode, int resultCode, Intent data) {