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