Save window when an app died while it's visible

- If an app died visible, keep the dead window and leave it on screen.

- Apply 50% dim over the dead window to indicate it's no longer active.

- Monitor touch inputs on the dead window and restart the app on tap.

bug: 24913379
Change-Id: I911da4e6135f2bffaf3b1bbe6f911ff689a278ff
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index eb79ae7..ee9aa11 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1456,29 +1456,35 @@
                     }
 
                     if (r.app == null || r.app.thread == null) {
-                        // This activity needs to be visible, but isn't even running...
-                        // get it started and resume if no other stack in this stack is resumed.
-                        if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
-                                "Start and freeze screen for " + r);
-                        if (r != starting) {
-                            r.startFreezingScreenLocked(r.app, configChanges);
-                        }
-                        if (!r.visible || r.mLaunchTaskBehind) {
+                        // We need to make sure the app is running if it's the top, or it is
+                        // just made visible from invisible.
+                        // If the app is already visible, it must have died while it was visible.
+                        // In this case, we'll show the dead window but will not restart the app.
+                        // Otherwise we could end up thrashing.
+                        if (r == top || !r.visible) {
+                            // This activity needs to be visible, but isn't even running...
+                            // get it started and resume if no other stack in this stack is resumed.
                             if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
-                                    "Starting and making visible: " + r);
-                            setVisible(r, true);
-                        }
-                        if (r != starting) {
-                            mStackSupervisor.startSpecificActivityLocked(
-                                    r, noStackActivityResumed, false);
-                            if (activityNdx >= activities.size()) {
-                                // Record may be removed if its process needs to restart.
-                                activityNdx = activities.size() - 1;
-                            } else {
-                                noStackActivityResumed = false;
+                                    "Start and freeze screen for " + r);
+                            if (r != starting) {
+                                r.startFreezingScreenLocked(r.app, configChanges);
+                            }
+                            if (!r.visible || r.mLaunchTaskBehind) {
+                                if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
+                                        "Starting and making visible: " + r);
+                                setVisible(r, true);
+                            }
+                            if (r != starting) {
+                                mStackSupervisor.startSpecificActivityLocked(
+                                        r, noStackActivityResumed, false);
+                                if (activityNdx >= activities.size()) {
+                                    // Record may be removed if its process needs to restart.
+                                    activityNdx = activities.size() - 1;
+                                } else {
+                                    noStackActivityResumed = false;
+                                }
                             }
                         }
-
                     } else if (r.visible) {
                         // If this activity is already visible, then there is nothing
                         // else to do here.
@@ -3731,10 +3737,13 @@
                         // Don't currently have state for the activity, or
                         // it is finishing -- always remove it.
                         remove = true;
-                    } else if (r.launchCount > 2 &&
-                            r.lastLaunchTime > (SystemClock.uptimeMillis()-60000)) {
+                    } else if (!r.visible && r.launchCount > 2 &&
+                            r.lastLaunchTime > (SystemClock.uptimeMillis() - 60000)) {
                         // We have launched this activity too many times since it was
                         // able to run, so give up and remove it.
+                        // (Note if the activity is visible, we don't remove the record.
+                        // We leave the dead window on the screen but the process will
+                        // not be restarted unless user explicitly tap on it.)
                         remove = true;
                     } else {
                         // The process may be gone, but the activity lives on!
@@ -3764,7 +3773,11 @@
                         if (DEBUG_APP) Slog.v(TAG_APP,
                                 "Clearing app during removeHistory for activity " + r);
                         r.app = null;
-                        r.nowVisible = false;
+                        // Set nowVisible to previous visible state. If the app was visible while
+                        // it died, we leave the dead window on screen so it's basically visible.
+                        // This is needed when user later tap on the dead window, we need to stop
+                        // other apps when user transfers focus to the restarted activity.
+                        r.nowVisible = r.visible;
                         if (!r.haveState) {
                             if (DEBUG_SAVED_STATE) Slog.i(TAG_SAVED_STATE,
                                     "App died, clearing saved state of " + r);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 0afc715..b025ce2 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2872,25 +2872,25 @@
             return;
         }
 
-        int stackId = task.stack.mStackId;
         if (task.mResizeable && options != null) {
             ActivityOptions opts = new ActivityOptions(options);
             if (opts.hasBounds()) {
                 Rect bounds = opts.getBounds();
                 task.updateOverrideConfiguration(bounds);
+                final int stackId = task.getLaunchStackId();
+                if (stackId != task.stack.mStackId) {
+                    moveTaskToStackUncheckedLocked(task, stackId, ON_TOP, !FORCE_FOCUS, reason);
+                    // moveTaskToStackUncheckedLocked() should already placed the task on top,
+                    // still need moveTaskToFrontLocked() below for any transition settings.
+                }
+                // WM resizeTask must be done after the task is moved to the correct stack,
+                // because Task's setBounds() also updates dim layer's bounds, but that has
+                // dependency on the stack.
                 mWindowManager.resizeTask(task.taskId, bounds, task.mOverrideConfig,
                         false /*relayout*/, false /*forced*/);
-                stackId = task.getLaunchStackId();
             }
         }
 
-        if (stackId != task.stack.mStackId) {
-            moveTaskToStackUncheckedLocked(task, stackId, ON_TOP, !FORCE_FOCUS, reason);
-
-            // moveTaskToStackUncheckedLocked() should already placed the task on top,
-            // still need moveTaskToFrontLocked() below for any transition settings.
-        }
-
         final ActivityRecord r = task.getTopActivity();
         task.stack.moveTaskToFrontLocked(task, false /* noAnimation */, options,
                 r == null ? null : r.appTimeTracker, reason);
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index c5bd3a7..9143097 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -102,6 +102,7 @@
     // Set to true when the token has been removed from the window mgr.
     boolean removed;
 
+    boolean appDied;
     // Information about an application starting window if displayed.
     StartingData startingData;
     WindowState startingWindow;
@@ -365,6 +366,26 @@
         windows.clear();
     }
 
+    void removeAllDeadWindows() {
+        for (int winNdx = allAppWindows.size() - 1; winNdx >= 0;
+                // removeWindowLocked at bottom of loop may remove multiple entries from
+                // allAppWindows if the window to be removed has child windows. It also may
+                // not remove any windows from allAppWindows at all if win is exiting and
+                // currently animating away. This ensures that winNdx is monotonically decreasing
+                // and never beyond allAppWindows bounds.
+                winNdx = Math.min(winNdx - 1, allAppWindows.size() - 1)) {
+            WindowState win = allAppWindows.get(winNdx);
+            if (win.mAppDied) {
+                if (WindowManagerService.DEBUG_WINDOW_MOVEMENT) {
+                    Slog.w(WindowManagerService.TAG, "removeAllDeadWindows: " + win);
+                }
+                // Set mDestroying, we don't want any animation or delayed removal here.
+                win.mDestroying = true;
+                service.removeWindowLocked(win);
+            }
+        }
+    }
+
     @Override
     void dump(PrintWriter pw, String prefix) {
         super.dump(pw, prefix);
diff --git a/services/core/java/com/android/server/wm/DimBehindController.java b/services/core/java/com/android/server/wm/DimLayerController.java
similarity index 78%
rename from services/core/java/com/android/server/wm/DimBehindController.java
rename to services/core/java/com/android/server/wm/DimLayerController.java
index 8870dd1..f9aca00 100644
--- a/services/core/java/com/android/server/wm/DimBehindController.java
+++ b/services/core/java/com/android/server/wm/DimLayerController.java
@@ -1,6 +1,7 @@
 package com.android.server.wm;
 
 import static com.android.server.wm.WindowManagerService.DEBUG_DIM_LAYER;
+import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;
 
 import android.graphics.Rect;
 import android.util.ArrayMap;
@@ -11,32 +12,38 @@
 
 /**
  * Centralizes the control of dim layers used for
- * {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND}.
+ * {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND}
+ * as well as other use cases (such as dimming above a dead window).
  */
-class DimBehindController {
-    private static final String TAG = "DimBehindController";
+class DimLayerController {
+    private static final String TAG = "DimLayerController";
 
     /** Amount of time in milliseconds to animate the dim surface from one value to another,
      * when no window animation is driving it. */
     private static final int DEFAULT_DIM_DURATION = 200;
 
-    // Shared dim layer for fullscreen users. {@link DimBehindState#dimLayer} will point to this
+    /**
+     * The default amount of dim applied over a dead window
+     */
+    private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
+
+    // Shared dim layer for fullscreen users. {@link DimLayerState#dimLayer} will point to this
     // instead of creating a new object per fullscreen task on a display.
     private DimLayer mSharedFullScreenDimLayer;
 
-    private ArrayMap<DimLayer.DimLayerUser, DimBehindState> mState = new ArrayMap<>();
+    private ArrayMap<DimLayer.DimLayerUser, DimLayerState> mState = new ArrayMap<>();
 
     private DisplayContent mDisplayContent;
 
     private Rect mTmpBounds = new Rect();
 
-    DimBehindController(DisplayContent displayContent) {
+    DimLayerController(DisplayContent displayContent) {
         mDisplayContent = displayContent;
     }
 
     /** Updates the dim layer bounds, recreating it if needed. */
     void updateDimLayer(DimLayer.DimLayerUser dimLayerUser) {
-        DimBehindState state = getOrCreateDimBehindState(dimLayerUser);
+        DimLayerState state = getOrCreateDimLayerState(dimLayerUser, false);
         final boolean previousFullscreen = state.dimLayer != null
                 && state.dimLayer == mSharedFullScreenDimLayer;
         DimLayer newDimLayer;
@@ -60,7 +67,7 @@
                 newDimLayer.setBounds(mTmpBounds);
                 mSharedFullScreenDimLayer = newDimLayer;
             } else if (state.dimLayer != null) {
-                state.dimLayer. destroySurface();
+                state.dimLayer.destroySurface();
             }
         } else {
             newDimLayer = (state.dimLayer == null || previousFullscreen)
@@ -72,19 +79,21 @@
         state.dimLayer = newDimLayer;
     }
 
-    private DimBehindState getOrCreateDimBehindState(DimLayer.DimLayerUser dimLayerUser) {
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "getDimBehindState, dimLayerUser="
+    private DimLayerState getOrCreateDimLayerState(
+            DimLayer.DimLayerUser dimLayerUser, boolean aboveApp) {
+        if (DEBUG_DIM_LAYER) Slog.v(TAG, "getOrCreateDimLayerState, dimLayerUser="
                 + dimLayerUser.toShortString());
-        DimBehindState state = mState.get(dimLayerUser);
+        DimLayerState state = mState.get(dimLayerUser);
         if (state == null) {
-            state = new DimBehindState();
+            state = new DimLayerState();
             mState.put(dimLayerUser, state);
         }
+        state.dimAbove = aboveApp;
         return state;
     }
 
     private void setContinueDimming(DimLayer.DimLayerUser dimLayerUser) {
-        DimBehindState state = mState.get(dimLayerUser);
+        DimLayerState state = mState.get(dimLayerUser);
         if (state == null) {
             if (DEBUG_DIM_LAYER) Slog.w(TAG, "setContinueDimming, no state for: "
                     + dimLayerUser.toShortString());
@@ -95,7 +104,7 @@
 
     boolean isDimming() {
         for (int i = mState.size() - 1; i >= 0; i--) {
-            DimBehindState state = mState.valueAt(i);
+            DimLayerState state = mState.valueAt(i);
             if (state.dimLayer != null && state.dimLayer.isDimming()) {
                 return true;
             }
@@ -110,15 +119,15 @@
     }
 
     private boolean getContinueDimming(DimLayer.DimLayerUser dimLayerUser) {
-        DimBehindState state = mState.get(dimLayerUser);
+        DimLayerState state = mState.get(dimLayerUser);
         return state != null && state.continueDimming;
     }
 
     void startDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser,
-            WindowStateAnimator newWinAnimator) {
+            WindowStateAnimator newWinAnimator, boolean aboveApp) {
         // Only set dim params on the highest dimmed layer.
         // Don't turn on for an unshown surface, or for any layer but the highest dimmed layer.
-        DimBehindState state = getOrCreateDimBehindState(dimLayerUser);
+        DimLayerState state = getOrCreateDimLayerState(dimLayerUser, aboveApp);
         if (DEBUG_DIM_LAYER) Slog.v(TAG, "startDimmingIfNeeded,"
                 + " dimLayerUser=" + dimLayerUser.toShortString()
                 + " newWinAnimator=" + newWinAnimator
@@ -145,7 +154,7 @@
 
     private void stopDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser) {
         // No need to check if state is null, we know the key has a value.
-        DimBehindState state = mState.get(dimLayerUser);
+        DimLayerState state = mState.get(dimLayerUser);
         if (DEBUG_DIM_LAYER) Slog.v(TAG, "stopDimmingIfNeeded,"
                 + " dimLayerUser=" + dimLayerUser.toShortString()
                 + " state.continueDimming=" + state.continueDimming
@@ -188,7 +197,7 @@
     }
 
     private boolean animateDimLayers(DimLayer.DimLayerUser dimLayerUser) {
-        DimBehindState state = mState.get(dimLayerUser);
+        DimLayerState state = mState.get(dimLayerUser);
         if (DEBUG_DIM_LAYER) Slog.v(TAG, "animateDimLayers,"
                 + " dimLayerUser=" + dimLayerUser.toShortString()
                 + " state.animator=" + state.animator
@@ -199,8 +208,13 @@
             dimLayer = state.dimLayer.getLayer();
             dimAmount = 0;
         } else {
-            dimLayer = state.animator.mAnimLayer - WindowManagerService.LAYER_OFFSET_DIM;
-            dimAmount = state.animator.mWin.mAttrs.dimAmount;
+            if (state.dimAbove) {
+                dimLayer = state.animator.mAnimLayer + LAYER_OFFSET_DIM;
+                dimAmount = DEFAULT_DIM_AMOUNT_DEAD_WINDOW;
+            } else {
+                dimLayer = state.animator.mAnimLayer - LAYER_OFFSET_DIM;
+                dimAmount = state.animator.mWin.mAttrs.dimAmount;
+            }
         }
         final float targetAlpha = state.dimLayer.getTargetAlpha();
         if (targetAlpha != dimAmount) {
@@ -211,7 +225,7 @@
                         ? state.animator.mAnimation.computeDurationHint()
                         : DEFAULT_DIM_DURATION;
                 if (targetAlpha > dimAmount) {
-                    duration = getDimBehindFadeDuration(duration);
+                    duration = getDimLayerFadeDuration(duration);
                 }
                 state.dimLayer.show(dimLayer, dimAmount, duration);
             }
@@ -230,11 +244,11 @@
     }
 
     boolean isDimming(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator winAnimator) {
-        DimBehindState state = mState.get(dimLayerUser);
+        DimLayerState state = mState.get(dimLayerUser);
         return state != null && state.animator == winAnimator && state.dimLayer.isDimming();
     }
 
-    private long getDimBehindFadeDuration(long duration) {
+    private long getDimLayerFadeDuration(long duration) {
         TypedValue tv = new TypedValue();
         mDisplayContent.mService.mContext.getResources().getValue(
                 com.android.internal.R.fraction.config_dimBehindFadeDuration, tv, true);
@@ -248,7 +262,7 @@
 
     void close() {
         for (int i = mState.size() - 1; i >= 0; i--) {
-            DimBehindState state = mState.valueAt(i);
+            DimLayerState state = mState.valueAt(i);
             state.dimLayer.destroySurface();
         }
         mState.clear();
@@ -256,10 +270,23 @@
     }
 
     void removeDimLayerUser(DimLayer.DimLayerUser dimLayerUser) {
-        mState.remove(dimLayerUser);
+        DimLayerState state = mState.get(dimLayerUser);
+        if (state != null) {
+            state.dimLayer.destroySurface();
+            mState.remove(dimLayerUser);
+        }
     }
 
     void applyDimBehind(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
+        applyDim(dimLayerUser, animator, false /* aboveApp */);
+    }
+
+    void applyDimAbove(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
+        applyDim(dimLayerUser, animator, true /* aboveApp */);
+    }
+
+    private void applyDim(
+            DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator, boolean aboveApp) {
         if (dimLayerUser == null) {
             Slog.e(TAG, "Trying to apply dim layer for: " + this
                     + ", but no dim layer user found.");
@@ -269,26 +296,27 @@
             setContinueDimming(dimLayerUser);
             if (!isDimming(dimLayerUser, animator)) {
                 if (DEBUG_DIM_LAYER) Slog.v(TAG, "Win " + this + " start dimming.");
-                startDimmingIfNeeded(dimLayerUser, animator);
+                startDimmingIfNeeded(dimLayerUser, animator, aboveApp);
             }
         }
     }
 
-    private static class DimBehindState {
-        // The particular window with FLAG_DIM_BEHIND set. If null, hide dimLayer.
+    private static class DimLayerState {
+        // The particular window requesting a dim layer. If null, hide dimLayer.
         WindowStateAnimator animator;
         // Set to false at the start of performLayoutAndPlaceSurfaces. If it is still false by the
         // end then stop any dimming.
         boolean continueDimming;
         DimLayer dimLayer;
+        boolean dimAbove;
     }
 
     void dump(String prefix, PrintWriter pw) {
-        pw.println(prefix + "DimBehindController");
+        pw.println(prefix + "DimLayerController");
         for (int i = 0, n = mState.size(); i < n; i++) {
             pw.println(prefix + "  " + mState.keyAt(i).toShortString());
             pw.print(prefix + "    ");
-            DimBehindState state = mState.valueAt(i);
+            DimLayerState state = mState.valueAt(i);
             pw.print("dimLayer=" + (state.dimLayer == mSharedFullScreenDimLayer ? "shared" :
                     state.dimLayer));
             pw.print(", animator=" + state.animator);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index d6f807e..53f8bbd 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -116,7 +116,7 @@
 
     final DockedStackDividerController mDividerControllerLocked;
 
-    final DimBehindController mDimBehindController;
+    final DimLayerController mDimLayerController;
 
     /**
      * @param display May not be null.
@@ -131,7 +131,7 @@
         mService = service;
         initializeDisplayBaseInfo();
         mDividerControllerLocked = new DockedStackDividerController(service.mContext, this);
-        mDimBehindController = new DimBehindController(this);
+        mDimLayerController = new DimLayerController(this);
     }
 
     int getDisplayId() {
@@ -271,7 +271,7 @@
     }
 
     void detachStack(TaskStack stack) {
-        mDimBehindController.removeDimLayerUser(stack);
+        mDimLayerController.removeDimLayerUser(stack);
         mStacks.remove(stack);
     }
 
@@ -415,23 +415,23 @@
     }
 
     boolean animateDimLayers() {
-        return mDimBehindController.animateDimLayers();
+        return mDimLayerController.animateDimLayers();
     }
 
     void resetDimming() {
-        mDimBehindController.resetDimming();
+        mDimLayerController.resetDimming();
     }
 
     boolean isDimming() {
-        return mDimBehindController.isDimming();
+        return mDimLayerController.isDimming();
     }
 
     void stopDimmingIfNeeded() {
-        mDimBehindController.stopDimmingIfNeeded();
+        mDimLayerController.stopDimmingIfNeeded();
     }
 
     void close() {
-        mDimBehindController.close();
+        mDimLayerController.close();
         for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
             mStacks.get(stackNdx).close();
         }
@@ -578,7 +578,7 @@
             }
         }
         pw.println();
-        mDimBehindController.dump(prefix + "  ", pw);
+        mDimLayerController.dump(prefix + "  ", pw);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index dbf13fe..5864b25 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -116,7 +116,7 @@
         mDeferRemoval = false;
         DisplayContent content = getDisplayContent();
         if (content != null) {
-            content.mDimBehindController.removeDimLayerUser(this);
+            content.mDimLayerController.removeDimLayerUser(this);
         }
         mStack.removeTask(this);
         mService.mTaskIdToTask.delete(mTaskId);
@@ -220,7 +220,7 @@
         mBounds.set(bounds);
         mRotation = rotation;
         if (displayContent != null) {
-            displayContent.mDimBehindController.updateDimLayer(this);
+            displayContent.mDimLayerController.updateDimLayer(this);
         }
         mOverrideConfig = mFullscreen ? Configuration.EMPTY : config;
         return boundsChange;
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 95e2391..f72384c 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -176,7 +176,7 @@
         }
 
         if (mDisplayContent != null) {
-            mDisplayContent.mDimBehindController.updateDimLayer(this);
+            mDisplayContent.mDimLayerController.updateDimLayer(this);
             mAnimationBackgroundSurface.setBounds(bounds);
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 230e81b..d5304c2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1924,6 +1924,11 @@
                 return WindowManagerGlobal.ADD_INVALID_DISPLAY;
             }
 
+            if (atoken != null && atoken.appDied) {
+                Slog.d(TAG, "App is now revived: " + atoken);
+                atoken.appDied = false;
+            }
+
             mPolicy.adjustWindowParamsLw(win.mAttrs);
             win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));
 
@@ -1935,11 +1940,7 @@
             final boolean openInputChannels = (outInputChannel != null
                     && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
             if  (openInputChannels) {
-                String name = win.makeInputChannelName();
-                InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
-                win.setInputChannel(inputChannels[0]);
-                inputChannels[1].transferTo(outInputChannel);
-                mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
+                win.openInputChannel(outInputChannel);
             }
 
             // From now on, no exceptions or errors allowed!
@@ -2183,13 +2184,31 @@
                         + "added");
                 win.mExiting = true;
                 appToken.mReplacingRemoveRequested = true;
+                Binder.restoreCallingIdentity(origId);
                 return;
             }
             // If we are not currently running the exit animation, we
             // need to see about starting one.
             wasVisible = win.isWinVisibleLw();
-            if (wasVisible) {
 
+            if (wasVisible && appToken != null && appToken.appDied) {
+                if (DEBUG_ADD_REMOVE) Slog.v(TAG,
+                        "Not removing " + win + " because app died while it's visible");
+
+                win.mAppDied = true;
+                win.setDisplayLayoutNeeded();
+                mWindowPlacerLocked.performSurfacePlacement();
+
+                // Set up a replacement input channel since the app is now dead.
+                // We need to catch tapping on the dead window to restart the app.
+                win.openInputChannel(null);
+                mInputMonitor.updateInputWindowsLw(true /*force*/);
+
+                Binder.restoreCallingIdentity(origId);
+                return;
+            }
+
+            if (wasVisible) {
                 final int transit = (!startingWindow)
                         ? WindowManagerPolicy.TRANSIT_EXIT
                         : WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
@@ -2240,10 +2259,6 @@
     }
 
     void removeWindowInnerLocked(WindowState win) {
-        removeWindowInnerLocked(win, true);
-    }
-
-    private void removeWindowInnerLocked(WindowState win, boolean performLayout) {
         if (win.mRemoved) {
             // Nothing to do.
             return;
@@ -2338,9 +2353,7 @@
             if (!mWindowPlacerLocked.isInLayout()) {
                 assignLayersLocked(windows);
                 win.setDisplayLayoutNeeded();
-                if (performLayout) {
-                    mWindowPlacerLocked.performSurfacePlacement();
-                }
+                mWindowPlacerLocked.performSurfacePlacement();
                 if (win.mAppToken != null) {
                     win.mAppToken.updateReportedVisibilityLocked();
                 }
@@ -4118,6 +4131,14 @@
             wtoken.waitingToShow = false;
             wtoken.hiddenRequested = !visible;
 
+            if (!visible && wtoken.appDied) {
+                // This app is dead while it was visible, we kept its dead window on screen.
+                // Now that the app is going invisible, we can remove it. It will be restarted
+                // if made visible again.
+                wtoken.appDied = false;
+                wtoken.removeAllWindows();
+            }
+
             // If we are preparing an app transition, then delay changing
             // the visibility of this token until we execute that transition.
             if (okToDisplay() && mAppTransition.isTransitionSet()) {
@@ -8548,7 +8569,7 @@
             final DimLayer.DimLayerUser dimLayerUser = w.getDimLayerUser();
             final DisplayContent displayContent = w.getDisplayContent();
             if (layerChanged && dimLayerUser != null && displayContent != null &&
-                    displayContent.mDimBehindController.isDimming(dimLayerUser, winAnimator)) {
+                    displayContent.mDimLayerController.isDimming(dimLayerUser, winAnimator)) {
                 // Force an animation pass just to update the mDimLayer layer.
                 scheduleAnimationLocked();
             }
@@ -8667,6 +8688,13 @@
                             + " dragResizingChanged=" + dragResizingChanged);
                 }
 
+                // If it's a dead window left on screen, and the configuration changed,
+                // there is nothing we can do about it. Remove the window now.
+                if (w.mAppToken != null && w.mAppDied) {
+                    w.mAppToken.removeAllDeadWindows();
+                    return;
+                }
+
                 w.mLastOverscanInsets.set(w.mOverscanInsets);
                 w.mLastContentInsets.set(w.mContentInsets);
                 w.mLastVisibleInsets.set(w.mVisibleInsets);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index c1fa78a..984a353 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -340,6 +340,12 @@
     boolean mRemoveOnExit;
 
     /**
+     * Whether the app died while it was visible, if true we might need
+     * to continue to show it until it's restarted.
+     */
+    boolean mAppDied;
+
+    /**
      * Set when the orientation is changing and this window has not yet
      * been updated for the new orientation.
      */
@@ -362,6 +368,7 @@
     // Input channel and input window handle used by the input dispatcher.
     final InputWindowHandle mInputWindowHandle;
     InputChannel mInputChannel;
+    InputChannel mClientChannel;
 
     // Used to improve performance of toString()
     String mStringNameCache;
@@ -1274,16 +1281,28 @@
         mConfigHasChanged = false;
     }
 
-    void setInputChannel(InputChannel inputChannel) {
+    void openInputChannel(InputChannel outInputChannel) {
         if (mInputChannel != null) {
             throw new IllegalStateException("Window already has an input channel.");
         }
-
-        mInputChannel = inputChannel;
-        mInputWindowHandle.inputChannel = inputChannel;
+        String name = makeInputChannelName();
+        InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
+        mInputChannel = inputChannels[0];
+        mClientChannel = inputChannels[1];
+        mInputWindowHandle.inputChannel = inputChannels[0];
+        if (outInputChannel != null) {
+            mClientChannel.transferTo(outInputChannel);
+            mClientChannel.dispose();
+            mClientChannel = null;
+        }
+        mService.mInputManager.registerInputChannel(mInputChannel, mInputWindowHandle);
     }
 
     void disposeInputChannel() {
+        if (mClientChannel != null) {
+            mClientChannel.dispose();
+            mClientChannel = null;
+        }
         if (mInputChannel != null) {
             mService.mInputManager.unregisterInputChannel(mInputChannel);
 
@@ -1294,10 +1313,13 @@
         mInputWindowHandle.inputChannel = null;
     }
 
-    void handleFlagDimBehind() {
-        if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0 && mDisplayContent != null && !mExiting
-                && isDisplayedLw()) {
-            mDisplayContent.mDimBehindController.applyDimBehind(getDimLayerUser(), mWinAnimator);
+    void applyDimLayerIfNeeded() {
+        if (!mExiting && mAppDied) {
+            // If app died visible, apply a dim over the window to indicate that it's inactive
+            mDisplayContent.mDimLayerController.applyDimAbove(getDimLayerUser(), mWinAnimator);
+        } else if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0
+                && mDisplayContent != null && !mExiting && isDisplayedLw()) {
+            mDisplayContent.mDimLayerController.applyDimBehind(getDimLayerUser(), mWinAnimator);
         }
     }
 
@@ -1375,6 +1397,9 @@
                     WindowState win = mService.windowForClientLocked(mSession, mClient, false);
                     Slog.i(TAG, "WIN DEATH: " + win);
                     if (win != null) {
+                        if (win.mAppToken != null && !win.mAppToken.clientHidden) {
+                            win.mAppToken.appDied = true;
+                        }
                         mService.removeWindowLocked(win);
                     } else if (mHasSurface) {
                         Slog.e(TAG, "!!! LEAK !!! Window removed but surface still valid.");
@@ -1574,7 +1599,7 @@
     public boolean isDimming() {
         final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
         return dimLayerUser != null && mDisplayContent != null &&
-                mDisplayContent.mDimBehindController.isDimming(dimLayerUser, mWinAnimator);
+                mDisplayContent.mDimLayerController.isDimming(dimLayerUser, mWinAnimator);
     }
 
     public void setShowToOwnerOnlyLocked(boolean showToOwnerOnly) {
@@ -1833,7 +1858,8 @@
             pw.print(prefix); pw.print("mToken="); pw.println(mToken);
             pw.print(prefix); pw.print("mRootToken="); pw.println(mRootToken);
             if (mAppToken != null) {
-                pw.print(prefix); pw.print("mAppToken="); pw.println(mAppToken);
+                pw.print(prefix); pw.print("mAppToken="); pw.print(mAppToken);
+                pw.print(" mAppDied=");pw.println(mAppDied);
             }
             if (mTargetAppToken != null) {
                 pw.print(prefix); pw.print("mTargetAppToken="); pw.println(mTargetAppToken);
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 8dddbd1..e9be2dd 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1426,12 +1426,13 @@
 
     private void adjustCropToStackBounds(WindowState w, Rect clipRect) {
         final AppWindowToken appToken = w.mAppToken;
+        final Task task = w.getTask();
         // We don't apply the the stack bounds to the window that is being replaced, because it was
         // living in a different stack. If we suddenly crop it to the new stack bounds, it might
         // get cut off. We don't want it to happen, so we let it ignore the stack bounds until it
         // gets removed. The window that will replace it will abide them.
-        if (appToken != null && appToken.mCropWindowsToStack && !appToken.mWillReplaceWindow) {
-            TaskStack stack = w.getTask().mStack;
+        if (task != null && appToken.mCropWindowsToStack && !appToken.mWillReplaceWindow) {
+            TaskStack stack = task.mStack;
             stack.getBounds(mTmpStackBounds);
             // When we resize we use the big surface approach, which means we can't trust the
             // window frame bounds anymore. Instead, the window will be placed at 0, 0, but to avoid
@@ -1543,7 +1544,7 @@
                         mDsDy * w.mHScale, mDtDy * w.mVScale);
                 mAnimator.setPendingLayoutChanges(w.getDisplayId(),
                         WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
-                w.handleFlagDimBehind();
+                w.applyDimLayerIfNeeded();
             } catch (RuntimeException e) {
                 // If something goes wrong with the surface (such
                 // as running out of memory), don't take down the
@@ -1859,6 +1860,9 @@
             if (mWin.mAttrs.type != TYPE_APPLICATION_STARTING && mWin.mAppToken != null) {
                 mWin.mAppToken.firstWindowDrawn = true;
 
+                // We now have a good window to show, remove dead placeholders
+                mWin.mAppToken.removeAllDeadWindows();
+
                 if (mWin.mAppToken.startingData != null) {
                     if (WindowManagerService.DEBUG_STARTING_WINDOW ||
                             WindowManagerService.DEBUG_ANIM) Slog.v(TAG,
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index aca0f5b..eef8e17 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -670,7 +670,7 @@
                     handleNotObscuredLocked(w, innerDw, innerDh);
                 }
 
-                w.handleFlagDimBehind();
+                w.applyDimLayerIfNeeded();
 
                 if (isDefaultDisplay && obscuredChanged
                         && mWallpaperControllerLocked.isWallpaperTarget(w) && w.isVisibleLw()) {
@@ -781,7 +781,7 @@
                                         + " a=" + winAnimator.mAnimating);
                             }
                         }
-                        if (w != atoken.startingWindow) {
+                        if (w != atoken.startingWindow && !w.mAppDied) {
                             if (!atoken.mAppAnimator.freezingScreen || !w.mAppFreezing) {
                                 atoken.numInterestingWindows++;
                                 if (w.isDrawnLw()) {