Prevent apps to overlay other apps via toast windows

It was possible for apps to put toast type windows
that overlay other apps which toast winodws aren't
removed after a timeout.

Now for apps targeting SDK greater than N MR1 to add a
toast window one needs to have a special token. The token
is added by the notificatoion manager service only for
the lifetime of the shown toast and is then removed
including all windows associated with this token. This
prevents apps to add arbitrary toast windows.

Since legacy apps may rely on the ability to directly
add toasts we mitigate by allowing these apps to still
add such windows for unlimited duration if this app is
the currently focused one, i.e. the user interacts with
it then it can overlay itself, otherwise we make sure
these toast windows are removed after a timeout like
a toast would be.

We don't allow more that one toast window per UID being
added at a time which prevents 1) legacy apps to put the
same toast after a timeout to go around our new policy
of hiding toasts after a while; 2) modern apps to reuse
the passed token to add more than one window; Note that
the notification manager shows toasts one at a time.

bug:30150688

Change-Id: Icc8f8dbd060762ae1a7b1720e96c5afdb8aff3fd
diff --git a/core/java/android/app/ITransientNotification.aidl b/core/java/android/app/ITransientNotification.aidl
index 35b53a4..d5b3ed0 100644
--- a/core/java/android/app/ITransientNotification.aidl
+++ b/core/java/android/app/ITransientNotification.aidl
@@ -19,7 +19,7 @@
 
 /** @hide */
 oneway interface ITransientNotification {
-    void show();
+    void show(IBinder windowToken);
     void hide();
 }
 
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 4818910..b6955a8 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -233,6 +233,7 @@
         mSession = getWindowSession();
         mLayout.token = getWindowToken();
         mLayout.setTitle("SurfaceView - " + getViewRootImpl().getTitle());
+        mLayout.packageName = mContext.getOpPackageName();
         mViewVisibility = getVisibility() == VISIBLE;
 
         if (!mGlobalListenersAdded) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index fe24230..4a20619 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1734,14 +1734,18 @@
         public CharSequence accessibilityTitle;
 
         /**
-         * Sets a timeout in milliseconds before which the window will be removed
+         * Sets a timeout in milliseconds before which the window will be hidden
          * by the window manager. Useful for transient notifications like toasts
          * so we don't have to rely on client cooperation to ensure the window
-         * is removed. Must be specified at window creation time.
+         * is hidden. Must be specified at window creation time. Note that apps
+         * are not prepared to handle their windows being removed without their
+         * explicit request and may try to interact with the removed window
+         * resulting in undefined behavior and crashes. Therefore, we do hide
+         * such windows to prevent them from overlaying other apps.
          *
          * @hide
          */
-        public long removeTimeoutMilliseconds = -1;
+        public long hideTimeoutMilliseconds = -1;
 
         public LayoutParams() {
             super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
@@ -1877,7 +1881,7 @@
             out.writeInt(needsMenuKey);
             out.writeInt(accessibilityIdOfAnchor);
             TextUtils.writeToParcel(accessibilityTitle, out, parcelableFlags);
-            out.writeLong(removeTimeoutMilliseconds);
+            out.writeLong(hideTimeoutMilliseconds);
         }
 
         public static final Parcelable.Creator<LayoutParams> CREATOR
@@ -1931,7 +1935,7 @@
             needsMenuKey = in.readInt();
             accessibilityIdOfAnchor = in.readInt();
             accessibilityTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
-            removeTimeoutMilliseconds = in.readLong();
+            hideTimeoutMilliseconds = in.readLong();
         }
 
         @SuppressWarnings({"PointlessBitwiseExpression"})
@@ -2153,7 +2157,7 @@
             }
 
             // This can't change, it's only set at window creation time.
-            removeTimeoutMilliseconds = o.removeTimeoutMilliseconds;
+            hideTimeoutMilliseconds = o.hideTimeoutMilliseconds;
 
             return changes;
         }
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index 7762675..eca10cb 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -25,6 +25,8 @@
 import android.content.res.Resources;
 import android.graphics.PixelFormat;
 import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.Log;
@@ -326,13 +328,6 @@
     }
 
     private static class TN extends ITransientNotification.Stub {
-        final Runnable mShow = new Runnable() {
-            @Override
-            public void run() {
-                handleShow();
-            }
-        };
-
         final Runnable mHide = new Runnable() {
             @Override
             public void run() {
@@ -343,7 +338,13 @@
         };
 
         private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
-        final Handler mHandler = new Handler();
+        final Handler mHandler = new Handler() {
+            @Override
+            public void handleMessage(Message msg) {
+                IBinder token = (IBinder) msg.obj;
+                handleShow(token);
+            }
+        };
 
         int mGravity;
         int mX, mY;
@@ -379,9 +380,9 @@
          * schedule handleShow into the right thread
          */
         @Override
-        public void show() {
+        public void show(IBinder windowToken) {
             if (localLOGV) Log.v(TAG, "SHOW: " + this);
-            mHandler.post(mShow);
+            mHandler.obtainMessage(0, windowToken).sendToTarget();
         }
 
         /**
@@ -393,7 +394,7 @@
             mHandler.post(mHide);
         }
 
-        public void handleShow() {
+        public void handleShow(IBinder windowToken) {
             if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
                     + " mNextView=" + mNextView);
             if (mView != mNextView) {
@@ -422,8 +423,9 @@
                 mParams.verticalMargin = mVerticalMargin;
                 mParams.horizontalMargin = mHorizontalMargin;
                 mParams.packageName = packageName;
-                mParams.removeTimeoutMilliseconds = mDuration ==
+                mParams.hideTimeoutMilliseconds = mDuration ==
                     Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
+                mParams.token = windowToken;
                 if (mView.getParent() != null) {
                     if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                     mWM.removeView(mView);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 8b5942c..de1d7a7 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -58,7 +58,6 @@
 import android.app.NotificationManager;
 import android.app.NotificationManager.Policy;
 import android.app.PendingIntent;
-import android.app.RemoteInput;
 import android.app.StatusBarManager;
 import android.app.backup.BackupManager;
 import android.app.usage.UsageEvents;
@@ -93,7 +92,6 @@
 import android.os.IInterface;
 import android.os.Looper;
 import android.os.Message;
-import android.os.Parcelable;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -122,6 +120,8 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.Xml;
+import android.view.WindowManager;
+import android.view.WindowManagerInternal;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.Toast;
@@ -138,6 +138,7 @@
 import com.android.server.lights.Light;
 import com.android.server.lights.LightsManager;
 import com.android.server.notification.ManagedServices.ManagedServiceInfo;
+import com.android.server.policy.PhoneWindowManager;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.vr.VrManagerInternal;
 import com.android.server.notification.ManagedServices.UserProfiles;
@@ -193,7 +194,7 @@
     private static final int MESSAGE_RECONSIDER_RANKING = 1000;
     private static final int MESSAGE_RANKING_SORT = 1001;
 
-    static final int LONG_DELAY = 3500; // 3.5 seconds
+    static final int LONG_DELAY = PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
     static final int SHORT_DELAY = 2000; // 2 seconds
 
     static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250};
@@ -232,6 +233,7 @@
     @Nullable StatusBarManagerInternal mStatusBar;
     Vibrator mVibrator;
     private VrManagerInternal mVrManagerInternal;
+    private WindowManagerInternal mWindowManagerInternal;
 
     final IBinder mForegroundToken = new Binder();
     private Handler mHandler;
@@ -452,13 +454,15 @@
         final String pkg;
         final ITransientNotification callback;
         int duration;
+        Binder token;
 
-        ToastRecord(int pid, String pkg, ITransientNotification callback, int duration)
-        {
+        ToastRecord(int pid, String pkg, ITransientNotification callback, int duration,
+                    Binder token) {
             this.pid = pid;
             this.pkg = pkg;
             this.callback = callback;
             this.duration = duration;
+            this.token = token;
         }
 
         void update(int duration) {
@@ -1125,6 +1129,7 @@
             mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
             mAudioManagerInternal = getLocalService(AudioManagerInternal.class);
             mVrManagerInternal = getLocalService(VrManagerInternal.class);
+            mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
             mZenModeHelper.onSystemReady();
         } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
             // This observer will force an update when observe is called, causing us to
@@ -1325,10 +1330,13 @@
                             }
                         }
 
-                        record = new ToastRecord(callingPid, pkg, callback, duration);
+                        Binder token = new Binder();
+                        mWindowManagerInternal.addWindowToken(token,
+                                WindowManager.LayoutParams.TYPE_TOAST);
+                        record = new ToastRecord(callingPid, pkg, callback, duration, token);
                         mToastQueue.add(record);
                         index = mToastQueue.size() - 1;
-                        keepProcessAliveLocked(callingPid);
+                        keepProcessAliveIfNeededLocked(callingPid);
                     }
                     // If it's at index 0, it's the current toast.  It doesn't matter if it's
                     // new or just been updated.  Call back and tell it to show itself.
@@ -2987,7 +2995,7 @@
         while (record != null) {
             if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
             try {
-                record.callback.show();
+                record.callback.show(record.token);
                 scheduleTimeoutLocked(record);
                 return;
             } catch (RemoteException e) {
@@ -2998,7 +3006,7 @@
                 if (index >= 0) {
                     mToastQueue.remove(index);
                 }
-                keepProcessAliveLocked(record.pid);
+                keepProcessAliveIfNeededLocked(record.pid);
                 if (mToastQueue.size() > 0) {
                     record = mToastQueue.get(0);
                 } else {
@@ -3018,8 +3026,11 @@
             // don't worry about this, we're about to remove it from
             // the list anyway
         }
-        mToastQueue.remove(index);
-        keepProcessAliveLocked(record.pid);
+
+        ToastRecord lastToast = mToastQueue.remove(index);
+        mWindowManagerInternal.removeWindowToken(lastToast.token, true);
+
+        keepProcessAliveIfNeededLocked(record.pid);
         if (mToastQueue.size() > 0) {
             // Show the next one. If the callback fails, this will remove
             // it from the list, so don't assume that the list hasn't changed
@@ -3063,7 +3074,7 @@
     }
 
     // lock on mToastQueue
-    void keepProcessAliveLocked(int pid)
+    void keepProcessAliveIfNeededLocked(int pid)
     {
         int toastCount = 0; // toasts from this pid
         ArrayList<ToastRecord> list = mToastQueue;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 09c2b27..68c4960 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -301,6 +301,9 @@
     /** Amount of time (in milliseconds) to wait for windows drawn before powering on. */
     static final int WAITING_FOR_DRAWN_TIMEOUT = 1000;
 
+    /** Amount of time (in milliseconds) a toast window can be shown. */
+    public static final int TOAST_WINDOW_TIMEOUT = 3500; // 3.5 seconds
+
     /**
      * Lock protecting internal state.  Must not call out into window
      * manager with lock held.  (This lock will be acquired in places
@@ -2229,6 +2232,18 @@
                     attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
                 }
                 break;
+
+            case TYPE_TOAST:
+                // While apps should use the dedicated toast APIs to add such windows
+                // it possible legacy apps to add the window directly. Therefore, we
+                // make windows added directly by the app behave as a toast as much
+                // as possible in terms of timeout and animation.
+                if (attrs.hideTimeoutMilliseconds < 0
+                        || attrs.hideTimeoutMilliseconds > TOAST_WINDOW_TIMEOUT) {
+                    attrs.hideTimeoutMilliseconds = TOAST_WINDOW_TIMEOUT;
+                }
+                attrs.windowAnimations = com.android.internal.R.style.Animation_Toast;
+                break;
         }
 
         if (attrs.type != TYPE_STATUS_BAR) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8c5481d..a4fcbb0 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -35,6 +35,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
@@ -200,6 +201,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION;
 import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static android.view.WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY;
@@ -1867,6 +1869,7 @@
         boolean reportNewConfig = false;
         WindowState attachedWindow = null;
         long origId;
+        final int callingUid = Binder.getCallingUid();
         final int type = attrs.type;
 
         synchronized(mWindowMap) {
@@ -1914,6 +1917,8 @@
             boolean addToken = false;
             WindowToken token = mTokenMap.get(attrs.token);
             AppWindowToken atoken = null;
+            boolean addToastWindowRequiresToken = false;
+
             if (token == null) {
                 if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
                     Slog.w(TAG_WM, "Attempted to add application window with unknown token "
@@ -1950,6 +1955,15 @@
                             + attrs.token + ".  Aborting.");
                     return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                 }
+                if (type == TYPE_TOAST) {
+                    // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
+                    if (doesAddToastWindowRequireToken(attrs.packageName, callingUid,
+                            attachedWindow)) {
+                        Slog.w(TAG_WM, "Attempted to add a toast window with unknown token "
+                                + attrs.token + ".  Aborting.");
+                        return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
+                    }
+                }
                 token = new WindowToken(this, attrs.token, -1, false);
                 addToken = true;
             } else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
@@ -1999,6 +2013,15 @@
                             + attrs.token + ".  Aborting.");
                     return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                 }
+            } else if (type == TYPE_TOAST) {
+                // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
+                addToastWindowRequiresToken = doesAddToastWindowRequireToken(attrs.packageName,
+                        callingUid, attachedWindow);
+                if (addToastWindowRequiresToken && token.windowType != TYPE_TOAST) {
+                    Slog.w(TAG_WM, "Attempted to add a toast window with bad token "
+                            + attrs.token + ".  Aborting.");
+                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
+                }
             } else if (type == TYPE_QS_DIALOG) {
                 if (token.windowType != TYPE_QS_DIALOG) {
                     Slog.w(TAG_WM, "Attempted to add QS dialog window with bad token "
@@ -2043,6 +2066,35 @@
                 win.openInputChannel(outInputChannel);
             }
 
+            // If adding a toast requires a token for this app we always schedule hiding
+            // toast windows to make sure they don't stick around longer then necessary.
+            // We hide instead of remove such windows as apps aren't prepared to handle
+            // windows being removed under them.
+            //   If the app is older it can add toasts without a token and hence overlay
+            // other apps. To be maximally compatible with these apps we will hide the
+            // window after the toast timeout only if the focused window is from another
+            // UID, otherwise we allow unlimited duration. When a UID looses focus we
+            // schedule hiding all of its toast windows.
+            if (type == TYPE_TOAST) {
+                if (!canAddToastWindowForUid(getDefaultDisplayContentLocked(), callingUid)) {
+                    Slog.w(TAG_WM, "Adding more than one toast window for UID at a time.");
+                    return WindowManagerGlobal.ADD_DUPLICATE_ADD;
+                }
+                // Make sure this happens before we moved focus as one can make the
+                // toast focusable to force it not being hidden after the timeout.
+                // Focusable toasts are always timed out to prevent a focused app to
+                // show a focusable toasts while it has focus which will be kept on
+                // the screen after the activity goes away.
+                if (addToastWindowRequiresToken
+                        || (attrs.flags & LayoutParams.FLAG_NOT_FOCUSABLE) == 0
+                        || mCurrentFocus == null
+                        || mCurrentFocus.mOwnerUid != callingUid) {
+                    mH.sendMessageDelayed(
+                            mH.obtainMessage(H.WINDOW_HIDE_TIMEOUT, win),
+                            win.mAttrs.hideTimeoutMilliseconds);
+                }
+            }
+
             // From now on, no exceptions or errors allowed!
 
             res = WindowManagerGlobal.ADD_OKAY;
@@ -2181,11 +2233,6 @@
             if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked(false)) {
                 reportNewConfig = true;
             }
-            if (attrs.removeTimeoutMilliseconds > 0) {
-                mH.sendMessageDelayed(
-                        mH.obtainMessage(H.WINDOW_REMOVE_TIMEOUT, win),
-                        attrs.removeTimeoutMilliseconds);
-            }
         }
 
         if (reportNewConfig) {
@@ -2197,6 +2244,73 @@
         return res;
     }
 
+    private boolean canAddToastWindowForUid(DisplayContent displayContent, int uid) {
+        // We allow one toast window per UID being shown at a time.
+        WindowList windows = displayContent.getWindowList();
+        final int windowCount = windows.size();
+        for (int i = 0; i < windowCount; i++) {
+            WindowState window = windows.get(i);
+            if (window.mAttrs.type == TYPE_TOAST && window.mOwnerUid == uid
+                    && !window.mPermanentlyHidden) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean doesAddToastWindowRequireToken(String packageName, int callingUid,
+            WindowState attachedWindow) {
+        // Try using the target SDK of the root window
+        if (attachedWindow != null) {
+            WindowState currentWindow = attachedWindow;
+            while (currentWindow != null) {
+                if (currentWindow.mAppToken != null
+                        && currentWindow.mAppToken.targetSdk > Build.VERSION_CODES.N_MR1) {
+                    return true;
+                }
+                currentWindow = currentWindow.mAttachedWindow;
+            }
+        } else {
+            // Otherwise, look at the package
+            try {
+                ApplicationInfo appInfo = mContext.getPackageManager()
+                        .getApplicationInfoAsUser(packageName, 0,
+                                UserHandle.getUserId(callingUid));
+                if (appInfo.uid != callingUid) {
+                    throw new SecurityException("Package " + packageName + " not in UID "
+                            + callingUid);
+                }
+                if (appInfo.targetSdkVersion > Build.VERSION_CODES.N_MR1) {
+                    return true;
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                /* ignore */
+            }
+        }
+        return false;
+    }
+
+    private void scheduleToastWindowsTimeoutIfNeededLocked(WindowState oldFocus,
+            WindowState newFocus) {
+        if (oldFocus == null || (newFocus != null && newFocus.mOwnerUid == oldFocus.mOwnerUid)) {
+            return;
+        }
+        final int lostFocusUid = oldFocus.mOwnerUid;
+        DisplayContent displayContent = oldFocus.getDisplayContent();
+        WindowList windows = displayContent.getWindowList();
+        final int windowCount = windows.size();
+        for (int i = 0; i < windowCount; i++) {
+            WindowState window = windows.get(i);
+            if (window.mAttrs.type == TYPE_TOAST && window.mOwnerUid == lostFocusUid) {
+                if (!mH.hasMessages(H.WINDOW_HIDE_TIMEOUT, window)) {
+                    mH.sendMessageDelayed(
+                            mH.obtainMessage(H.WINDOW_HIDE_TIMEOUT, window),
+                            window.mAttrs.hideTimeoutMilliseconds);
+                }
+            }
+        }
+    }
+
     /**
      * Returns true if we're done setting up any transitions.
      */
@@ -8121,7 +8235,7 @@
         public static final int NOTIFY_APP_TRANSITION_FINISHED = 49;
         public static final int NOTIFY_STARTING_WINDOW_DRAWN = 50;
         public static final int UPDATE_ANIMATION_SCALE = 51;
-        public static final int WINDOW_REMOVE_TIMEOUT = 52;
+        public static final int WINDOW_HIDE_TIMEOUT = 52;
         public static final int NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED = 53;
         public static final int SEAMLESS_ROTATION_TIMEOUT = 54;
 
@@ -8741,7 +8855,7 @@
                     mAmInternal.notifyStartingWindowDrawn();
                 }
                 break;
-                case WINDOW_REMOVE_TIMEOUT: {
+                case WINDOW_HIDE_TIMEOUT: {
                     final WindowState window = (WindowState) msg.obj;
                     synchronized(mWindowMap) {
                         // TODO: This is all about fixing b/21693547
@@ -8752,8 +8866,11 @@
                         // running under debugger) to crash (b/29105388). The windows will
                         // eventually be removed when the client process finishes.
                         // The best we can do for now is remove the FLAG_KEEP_SCREEN_ON
-                        // and prevent the symptoms of b/21693547.
+                        // and prevent the symptoms of b/21693547. Since apps don't
+                        // support windows being removed under them we hide the window
+                        // and it will be removed when the app dies.
                         window.mAttrs.flags &= ~FLAG_KEEP_SCREEN_ON;
+                        window.markPermanentlyHiddenLw();
                         window.setDisplayLayoutNeeded();
                         mWindowPlacerLocked.performSurfacePlacement();
                     }
@@ -9783,6 +9900,11 @@
 
             adjustForImeIfNeeded(displayContent);
 
+            // We may need to schedule some toast windows to be removed. The
+            // toasts for an app that does not have input focus are removed
+            // within a timeout to prevent apps to redress other apps' UI.
+            scheduleToastWindowsTimeoutIfNeededLocked(oldFocus, newFocus);
+
             Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
             return true;
         }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 25ad07e..7a3c07c 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -165,6 +165,7 @@
     boolean mPolicyVisibility = true;
     boolean mPolicyVisibilityAfterAnim = true;
     boolean mAppOpVisibility = true;
+    boolean mPermanentlyHidden;
     boolean mAppFreezing;
     boolean mAttachedHidden;    // is our parent window hidden?
     boolean mWallpaperVisible;  // for wallpaper, what was last vis report?
@@ -1866,6 +1867,11 @@
             // Being hidden due to app op request.
             return false;
         }
+        if (mPermanentlyHidden) {
+            // Permanently hidden until the app exists as apps aren't prepared
+            // to handle their windows being removed from under them.
+            return false;
+        }
         if (mPolicyVisibility && mPolicyVisibilityAfterAnim) {
             // Already showing.
             return false;
@@ -1956,6 +1962,13 @@
         }
     }
 
+    public void markPermanentlyHiddenLw() {
+        if (!mPermanentlyHidden) {
+            mPermanentlyHidden = true;
+            hideLw(true, true);
+        }
+    }
+
     public void pokeDrawLockLw(long timeout) {
         if (isVisibleOrAdding()) {
             if (mDrawLock == null) {
@@ -2593,7 +2606,7 @@
             pw.println(Integer.toHexString(mSystemUiVisibility));
         }
         if (!mPolicyVisibility || !mPolicyVisibilityAfterAnim || !mAppOpVisibility
-                || mAttachedHidden) {
+                || mAttachedHidden || mPermanentlyHidden) {
             pw.print(prefix); pw.print("mPolicyVisibility=");
                     pw.print(mPolicyVisibility);
                     pw.print(" mPolicyVisibilityAfterAnim=");
@@ -2601,6 +2614,7 @@
                     pw.print(" mAppOpVisibility=");
                     pw.print(mAppOpVisibility);
                     pw.print(" mAttachedHidden="); pw.println(mAttachedHidden);
+                    pw.print(" mPermanentlyHidden="); pw.println(mPermanentlyHidden);
         }
         if (!mRelayoutCalled || mLayoutNeeded) {
             pw.print(prefix); pw.print("mRelayoutCalled="); pw.print(mRelayoutCalled);