Merge "Fix toast lifetime"
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index d378f22..ddd0656 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -45,6 +45,8 @@
     void clearData(String pkg, int uid, boolean fromApp);
     void enqueueToast(String pkg, ITransientNotification callback, int duration);
     void cancelToast(String pkg, ITransientNotification callback);
+    void finishToken(String pkg, ITransientNotification callback);
+
     void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
             in Notification notification, int userId);
     void cancelNotificationWithTag(String pkg, String tag, int id, int userId);
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index edcf209..d74a60e 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -531,6 +531,14 @@
                     mWM.removeViewImmediate(mView);
                 }
 
+
+                // Now that we've removed the view it's safe for the server to release
+                // the resources.
+                try {
+                    getService().finishToken(mPackageName, this);
+                } catch (RemoteException e) {
+                }
+
                 mView = null;
             }
         }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b1bc462..efc948b 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -235,11 +235,12 @@
     static final float DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE = 5f;
 
     // message codes
-    static final int MESSAGE_TIMEOUT = 2;
+    static final int MESSAGE_DURATION_REACHED = 2;
     static final int MESSAGE_SAVE_POLICY_FILE = 3;
     static final int MESSAGE_SEND_RANKING_UPDATE = 4;
     static final int MESSAGE_LISTENER_HINTS_CHANGED = 5;
     static final int MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED = 6;
+    static final int MESSAGE_FINISH_TOKEN_TIMEOUT = 7;
 
     // ranking thread messages
     private static final int MESSAGE_RECONSIDER_RANKING = 1000;
@@ -1897,6 +1898,25 @@
         }
 
         @Override
+        public void finishToken(String pkg, ITransientNotification callback) {
+            synchronized (mToastQueue) {
+                long callingId = Binder.clearCallingIdentity();
+                try {
+                    int index = indexOfToastLocked(pkg, callback);
+                    if (index >= 0) {
+                        ToastRecord record = mToastQueue.get(index);
+                        finishTokenLocked(record.token);
+                    } else {
+                        Slog.w(TAG, "Toast already killed. pkg=" + pkg
+                                + " callback=" + callback);
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(callingId);
+                }
+            }
+        }
+
+        @Override
         public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
                 Notification notification, int userId) throws RemoteException {
             enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(),
@@ -4597,7 +4617,7 @@
             if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
             try {
                 record.callback.show(record.token);
-                scheduleTimeoutLocked(record);
+                scheduleDurationReachedLocked(record);
                 return;
             } catch (RemoteException e) {
                 Slog.w(TAG, "Object died trying to show notification " + record.callback
@@ -4630,7 +4650,15 @@
         }
 
         ToastRecord lastToast = mToastQueue.remove(index);
-        mWindowManagerInternal.removeWindowToken(lastToast.token, true, DEFAULT_DISPLAY);
+
+        mWindowManagerInternal.removeWindowToken(lastToast.token, false /* removeWindows */,
+                DEFAULT_DISPLAY);
+        // We passed 'false' for 'removeWindows' so that the client has time to stop
+        // rendering (as hide above is a one-way message), otherwise we could crash
+        // a client which was actively using a surface made from the token. However
+        // we need to schedule a timeout to make sure the token is eventually killed
+        // one way or another.
+        scheduleKillTokenTimeout(lastToast.token);
 
         keepProcessAliveIfNeededLocked(record.pid);
         if (mToastQueue.size() > 0) {
@@ -4641,16 +4669,26 @@
         }
     }
 
+    void finishTokenLocked(IBinder t) {
+        mHandler.removeCallbacksAndMessages(t);
+        // We pass 'true' for 'removeWindows' to let the WindowManager destroy any
+        // remaining surfaces as either the client has called finishToken indicating
+        // it has successfully removed the views, or the client has timed out
+        // at which point anything goes.
+        mWindowManagerInternal.removeWindowToken(t, true /* removeWindows */,
+                DEFAULT_DISPLAY);
+    }
+
     @GuardedBy("mToastQueue")
-    private void scheduleTimeoutLocked(ToastRecord r)
+    private void scheduleDurationReachedLocked(ToastRecord r)
     {
         mHandler.removeCallbacksAndMessages(r);
-        Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
+        Message m = Message.obtain(mHandler, MESSAGE_DURATION_REACHED, r);
         long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
         mHandler.sendMessageDelayed(m, delay);
     }
 
-    private void handleTimeout(ToastRecord record)
+    private void handleDurationReached(ToastRecord record)
     {
         if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);
         synchronized (mToastQueue) {
@@ -4662,6 +4700,22 @@
     }
 
     @GuardedBy("mToastQueue")
+    private void scheduleKillTokenTimeout(IBinder token)
+    {
+        mHandler.removeCallbacksAndMessages(token);
+        Message m = Message.obtain(mHandler, MESSAGE_FINISH_TOKEN_TIMEOUT, token);
+        mHandler.sendMessageDelayed(m, 5);
+    }
+
+    private void handleKillTokenTimeout(IBinder token)
+    {
+        if (DBG) Slog.d(TAG, "Kill Token Timeout token=" + token);
+        synchronized (mToastQueue) {
+            finishTokenLocked(token);
+        }
+    }
+
+    @GuardedBy("mToastQueue")
     int indexOfToastLocked(String pkg, ITransientNotification callback)
     {
         IBinder cbak = callback.asBinder();
@@ -4858,8 +4912,11 @@
         {
             switch (msg.what)
             {
-                case MESSAGE_TIMEOUT:
-                    handleTimeout((ToastRecord)msg.obj);
+                case MESSAGE_DURATION_REACHED:
+                    handleDurationReached((ToastRecord)msg.obj);
+                    break;
+                case MESSAGE_FINISH_TOKEN_TIMEOUT:
+                    handleKillTokenTimeout((IBinder)msg.obj);
                     break;
                 case MESSAGE_SAVE_POLICY_FILE:
                     handleSavePolicyFile();