Fix lack of dim layer behind "Power off" window.

This enables dim behind functionality for both tasks and stacks and
groups it inside a controller that manages the dim layers (including
shared dim layers for full screen tasks/stacks).

Bug: 24331704

Change-Id: I0d1ba996d9e00d05e0203166b82268da00fbb35e
diff --git a/services/core/java/com/android/server/wm/DimBehindController.java b/services/core/java/com/android/server/wm/DimBehindController.java
new file mode 100644
index 0000000..c56af39
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DimBehindController.java
@@ -0,0 +1,291 @@
+package com.android.server.wm;
+
+import static com.android.server.wm.WindowManagerService.DEBUG_DIM_LAYER;
+
+import android.graphics.Rect;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.TypedValue;
+
+import java.io.PrintWriter;
+
+/**
+ * Centralizes the control of dim layers used for
+ * {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND}.
+ */
+class DimBehindController {
+    private static final String TAG = "DimBehindController";
+
+    /** 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
+    // instead of creating a new object per fullscreen task on a display.
+    private DimLayer mSharedFullScreenDimLayer;
+
+    private ArrayMap<DimLayer.DimLayerUser, DimBehindState> mState = new ArrayMap<>();
+
+    private DisplayContent mDisplayContent;
+
+    private Rect mTmpBounds = new Rect();
+
+    DimBehindController(DisplayContent displayContent) {
+        mDisplayContent = displayContent;
+    }
+
+    /** Updates the dim layer bounds, recreating it if needed. */
+    void updateDimLayer(DimLayer.DimLayerUser dimLayerUser) {
+        DimBehindState state = getOrCreateDimBehindState(dimLayerUser);
+        final boolean previousFullscreen = state.dimLayer != null
+                && state.dimLayer == mSharedFullScreenDimLayer;
+        DimLayer newDimLayer;
+        final int displayId = mDisplayContent.getDisplayId();
+        if (dimLayerUser.isFullscreen()) {
+            if (previousFullscreen) {
+                // Nothing to do here...
+                return;
+            }
+            // Use shared fullscreen dim layer
+            newDimLayer = mSharedFullScreenDimLayer;
+            if (newDimLayer == null) {
+                if (state.dimLayer != null) {
+                    // Re-purpose the previous dim layer.
+                    newDimLayer = state.dimLayer;
+                } else {
+                    // Create new full screen dim layer.
+                    newDimLayer = new DimLayer(mDisplayContent.mService, dimLayerUser, displayId);
+                }
+                dimLayerUser.getBounds(mTmpBounds);
+                newDimLayer.setBounds(mTmpBounds);
+                mSharedFullScreenDimLayer = newDimLayer;
+            } else if (state.dimLayer != null) {
+                state.dimLayer. destroySurface();
+            }
+        } else {
+            newDimLayer = (state.dimLayer == null || previousFullscreen)
+                    ? new DimLayer(mDisplayContent.mService, dimLayerUser, displayId)
+                    : state.dimLayer;
+            dimLayerUser.getBounds(mTmpBounds);
+            newDimLayer.setBounds(mTmpBounds);
+        }
+        state.dimLayer = newDimLayer;
+    }
+
+    private DimBehindState getOrCreateDimBehindState(DimLayer.DimLayerUser dimLayerUser) {
+        if (DEBUG_DIM_LAYER) Slog.v(TAG, "getDimBehindState, dimLayerUser="
+                + dimLayerUser.toShortString());
+        DimBehindState state = mState.get(dimLayerUser);
+        if (state == null) {
+            state = new DimBehindState();
+            mState.put(dimLayerUser, state);
+        }
+        return state;
+    }
+
+    private void setContinueDimming(DimLayer.DimLayerUser dimLayerUser) {
+        DimBehindState state = mState.get(dimLayerUser);
+        if (state == null) {
+            if (DEBUG_DIM_LAYER) Slog.w(TAG, "setContinueDimming, no state for: "
+                    + dimLayerUser.toShortString());
+            return;
+        }
+        state.continueDimming = true;
+    }
+
+    boolean isDimming() {
+        for (int i = mState.size() - 1; i >= 0; i--) {
+            DimBehindState state = mState.valueAt(i);
+            if (state.dimLayer != null && state.dimLayer.isDimming()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    void resetDimming() {
+        for (int i = mState.size() - 1; i >= 0; i--) {
+            mState.valueAt(i).continueDimming = false;
+        }
+    }
+
+    private boolean getContinueDimming(DimLayer.DimLayerUser dimLayerUser) {
+        DimBehindState state = mState.get(dimLayerUser);
+        return state != null && state.continueDimming;
+    }
+
+    void startDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser,
+            WindowStateAnimator newWinAnimator) {
+        // 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);
+        if (DEBUG_DIM_LAYER) Slog.v(TAG, "startDimmingIfNeeded,"
+                + " dimLayerUser=" + dimLayerUser.toShortString()
+                + " newWinAnimator=" + newWinAnimator
+                + " state.animator=" + state.animator);
+        if (newWinAnimator.mSurfaceShown && (state.animator == null
+                || !state.animator.mSurfaceShown
+                || state.animator.mAnimLayer <= newWinAnimator.mAnimLayer)) {
+            state.animator = newWinAnimator;
+            if (state.animator.mWin.mAppToken == null && !dimLayerUser.isFullscreen()) {
+                // Dim should cover the entire screen for system windows.
+                mDisplayContent.getLogicalDisplayRect(mTmpBounds);
+                state.dimLayer.setBounds(mTmpBounds);
+            }
+        }
+    }
+
+    void stopDimmingIfNeeded() {
+        if (DEBUG_DIM_LAYER) Slog.v(TAG, "stopDimmingIfNeeded, mState.size()=" + mState.size());
+        for (int i = mState.size() - 1; i >= 0; i--) {
+            DimLayer.DimLayerUser dimLayerUser = mState.keyAt(i);
+            stopDimmingIfNeeded(dimLayerUser);
+        }
+    }
+
+    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);
+        if (DEBUG_DIM_LAYER) Slog.v(TAG, "stopDimmingIfNeeded,"
+                + " dimLayerUser=" + dimLayerUser.toShortString()
+                + " state.continueDimming=" + state.continueDimming
+                + " state.dimLayer.isDimming=" + state.dimLayer.isDimming());
+        if (!state.continueDimming && state.dimLayer.isDimming()) {
+            state.animator = null;
+            dimLayerUser.getBounds(mTmpBounds);
+            state.dimLayer.setBounds(mTmpBounds);
+        }
+    }
+
+    boolean animateDimLayers() {
+        int fullScreen = -1;
+        for (int i = mState.size() - 1; i >= 0; i--) {
+            DimLayer.DimLayerUser dimLayerUser = mState.keyAt(i);
+            if (dimLayerUser.isFullscreen()) {
+                fullScreen = i;
+                if (mState.valueAt(i).continueDimming) {
+                    break;
+                }
+            }
+        }
+        if (fullScreen != -1) {
+            return animateDimLayers(mState.keyAt(fullScreen));
+        } else {
+            boolean result = false;
+            for (int i = mState.size() - 1; i >= 0; i--) {
+                result |= animateDimLayers(mState.keyAt(i));
+            }
+            return result;
+        }
+    }
+
+    private boolean animateDimLayers(DimLayer.DimLayerUser dimLayerUser) {
+        DimBehindState state = mState.get(dimLayerUser);
+        if (DEBUG_DIM_LAYER) Slog.v(TAG, "animateDimLayers,"
+                + " dimLayerUser=" + dimLayerUser.toShortString()
+                + " state.animator=" + state.animator
+                + " state.continueDimming=" + state.continueDimming);
+        final int dimLayer;
+        final float dimAmount;
+        if (state.animator == null) {
+            dimLayer = state.dimLayer.getLayer();
+            dimAmount = 0;
+        } else {
+            dimLayer = state.animator.mAnimLayer - WindowManagerService.LAYER_OFFSET_DIM;
+            dimAmount = state.animator.mWin.mAttrs.dimAmount;
+        }
+        final float targetAlpha = state.dimLayer.getTargetAlpha();
+        if (targetAlpha != dimAmount) {
+            if (state.animator == null) {
+                state.dimLayer.hide(DEFAULT_DIM_DURATION);
+            } else {
+                long duration = (state.animator.mAnimating && state.animator.mAnimation != null)
+                        ? state.animator.mAnimation.computeDurationHint()
+                        : DEFAULT_DIM_DURATION;
+                if (targetAlpha > dimAmount) {
+                    duration = getDimBehindFadeDuration(duration);
+                }
+                state.dimLayer.show(dimLayer, dimAmount, duration);
+            }
+        } else if (state.dimLayer.getLayer() != dimLayer) {
+            state.dimLayer.setLayer(dimLayer);
+        }
+        if (state.dimLayer.isAnimating()) {
+            if (!mDisplayContent.mService.okToDisplay()) {
+                // Jump to the end of the animation.
+                state.dimLayer.show();
+            } else {
+                return state.dimLayer.stepAnimation();
+            }
+        }
+        return false;
+    }
+
+    boolean isDimming(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator winAnimator) {
+        DimBehindState state = mState.get(dimLayerUser);
+        return state != null && state.animator == winAnimator && state.dimLayer.isDimming();
+    }
+
+    private long getDimBehindFadeDuration(long duration) {
+        TypedValue tv = new TypedValue();
+        mDisplayContent.mService.mContext.getResources().getValue(
+                com.android.internal.R.fraction.config_dimBehindFadeDuration, tv, true);
+        if (tv.type == TypedValue.TYPE_FRACTION) {
+            duration = (long) tv.getFraction(duration, duration);
+        } else if (tv.type >= TypedValue.TYPE_FIRST_INT && tv.type <= TypedValue.TYPE_LAST_INT) {
+            duration = tv.data;
+        }
+        return duration;
+    }
+
+    void close() {
+        for (int i = mState.size() - 1; i >= 0; i--) {
+            DimBehindState state = mState.valueAt(i);
+            state.dimLayer.destroySurface();
+        }
+        mState.clear();
+        mSharedFullScreenDimLayer = null;
+    }
+
+    void removeDimLayerUser(DimLayer.DimLayerUser dimLayerUser) {
+        mState.remove(dimLayerUser);
+    }
+
+    void applyDimBehind(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
+        if (dimLayerUser == null) {
+            Slog.e(TAG, "Trying to apply dim layer for: " + this
+                    + ", but no dim layer user found.");
+            return;
+        }
+        if (!getContinueDimming(dimLayerUser)) {
+            setContinueDimming(dimLayerUser);
+            if (!isDimming(dimLayerUser, animator)) {
+                if (DEBUG_DIM_LAYER) Slog.v(TAG, "Win " + this + " start dimming.");
+                startDimmingIfNeeded(dimLayerUser, animator);
+            }
+        }
+    }
+
+    private static class DimBehindState {
+        // The particular window with FLAG_DIM_BEHIND set. 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;
+    }
+
+    void dump(String prefix, PrintWriter pw) {
+        pw.println(prefix + "DimBehindController");
+        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);
+            pw.print("dimLayer=" + (state.dimLayer == mSharedFullScreenDimLayer ? "shared" :
+                    state.dimLayer));
+            pw.print(", animator=" + state.animator);
+            pw.println(", continueDimming=" + state.continueDimming + "}");
+
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DimLayer.java b/services/core/java/com/android/server/wm/DimLayer.java
index 538d6b84..8c479d8 100644
--- a/services/core/java/com/android/server/wm/DimLayer.java
+++ b/services/core/java/com/android/server/wm/DimLayer.java
@@ -65,6 +65,9 @@
         boolean isFullscreen();
         /** Returns the display info. of the dim layer user. */
         DisplayInfo getDisplayInfo();
+        /** Gets the bounds of the dim layer user. */
+        void getBounds(Rect outBounds);
+        String toShortString();
     }
     /** The user of this dim layer. */
     final DimLayerUser mUser;
@@ -239,8 +242,9 @@
                 mDuration = duration;
             }
         }
-        if (DEBUG) Slog.v(TAG, "show: mStartAlpha=" + mStartAlpha + " mStartTime=" + mStartTime);
         mTargetAlpha = alpha;
+        if (DEBUG) Slog.v(TAG, "show: mStartAlpha=" + mStartAlpha + " mStartTime=" + mStartTime
+                + " mTargetAlpha=" + mTargetAlpha);
     }
 
     /** Immediate hide.
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 6b5ecdc..f13f350 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -115,6 +115,8 @@
 
     final DockedStackDividerController mDividerControllerLocked;
 
+    final DimBehindController mDimBehindController;
+
     /**
      * @param display May not be null.
      * @param service You know.
@@ -128,6 +130,7 @@
         mService = service;
         initializeDisplayBaseInfo();
         mDividerControllerLocked = new DockedStackDividerController(service.mContext, this);
+        mDimBehindController = new DimBehindController(this);
     }
 
     int getDisplayId() {
@@ -246,6 +249,7 @@
     }
 
     void detachStack(TaskStack stack) {
+        mDimBehindController.removeDimLayerUser(stack);
         mStacks.remove(stack);
     }
 
@@ -382,54 +386,23 @@
     }
 
     boolean animateDimLayers() {
-        boolean result = false;
-        for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
-            final ArrayList<Task> tasks = mStacks.get(stackNdx).getTasks();
-            for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
-                final Task task = tasks.get(taskNdx);
-                result |= task.animateDimLayers();
-                if (task.isFullscreen()) {
-                    // No point in continuing as this task covers the entire screen.
-                    // Also, fullscreen tasks all share the same dim layer, so we don't want
-                    // processing of fullscreen task below this one affecting the dim layer state.
-                    return result;
-                }
-            }
-        }
-        return result;
+        return mDimBehindController.animateDimLayers();
     }
 
     void resetDimming() {
-        for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
-            final ArrayList<Task> tasks = mStacks.get(stackNdx).getTasks();
-            for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
-                tasks.get(taskNdx).clearContinueDimming();
-            }
-        }
+        mDimBehindController.resetDimming();
     }
 
     boolean isDimming() {
-        for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
-            final ArrayList<Task> tasks = mStacks.get(stackNdx).getTasks();
-            for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
-                if (tasks.get(taskNdx).isDimming()) {
-                    return true;
-                }
-            }
-        }
-        return false;
+        return mDimBehindController.isDimming();
     }
 
     void stopDimmingIfNeeded() {
-        for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
-            final ArrayList<Task> tasks = mStacks.get(stackNdx).getTasks();
-            for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
-                tasks.get(taskNdx).stopDimmingIfNeeded();
-            }
-        }
+        mDimBehindController.stopDimmingIfNeeded();
     }
 
     void close() {
+        mDimBehindController.close();
         for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
             mStacks.get(stackNdx).close();
         }
@@ -576,6 +549,7 @@
             }
         }
         pw.println();
+        mDimBehindController.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 1f986dd..af33880 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -29,7 +29,6 @@
 import android.util.EventLog;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.TypedValue;
 import android.view.DisplayInfo;
 import android.view.Surface;
 
@@ -39,10 +38,6 @@
 import java.util.ArrayList;
 
 class Task implements DimLayer.DimLayerUser {
-    /** 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;
-
     // Return value from {@link setBounds} indicating no change was made to the Task bounds.
     static final int BOUNDS_CHANGE_NONE = 0;
     // Return value from {@link setBounds} indicating the position of the Task bounds changed.
@@ -78,17 +73,6 @@
     // Whether the task is currently being drag-resized
     private boolean mDragResizing;
 
-    // The particular window with FLAG_DIM_BEHIND set. If null, hide mDimLayer.
-    WindowStateAnimator mDimWinAnimator;
-    // Used to support {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND}
-    private DimLayer mDimLayer;
-    // Set to false at the start of performLayoutAndPlaceSurfaces. If it is still false by the end
-    // then stop any dimming.
-    private boolean mContinueDimming;
-    // Shared dim layer for fullscreen tasks. {@link #mDimLayer} will point to this instead
-    // of creating a new object per fullscreen task on a display.
-    private static final SparseArray<DimLayer> sSharedFullscreenDimLayers = new SparseArray<>();
-
     Task(int taskId, TaskStack stack, int userId, WindowManagerService service, Rect bounds,
             Configuration config) {
         mTaskId = taskId;
@@ -128,6 +112,10 @@
         if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing taskId=" + mTaskId);
         EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId, "removeTask");
         mDeferRemoval = false;
+        DisplayContent content = getDisplayContent();
+        if (content != null) {
+            content.mDimBehindController.removeDimLayerUser(this);
+        }
         mStack.removeTask(this);
         mService.mTaskIdToTask.delete(mTaskId);
     }
@@ -228,7 +216,9 @@
 
         mBounds.set(bounds);
         mRotation = rotation;
-        updateDimLayer();
+        if (displayContent != null) {
+            displayContent.mDimBehindController.updateDimLayer(this);
+        }
         mOverrideConfig = mFullscreen ? Configuration.EMPTY : config;
         return boundsChange;
     }
@@ -261,7 +251,8 @@
     }
 
     /** Bounds of the task with other system factors taken into consideration. */
-    void getBounds(Rect out) {
+    @Override
+    public void getBounds(Rect out) {
         if (useCurrentBounds()) {
             // No need to adjust the output bounds if fullscreen or the docked stack is visible
             // since it is already what we want to represent to the rest of the system.
@@ -309,139 +300,6 @@
         }
     }
 
-    /** Updates the dim layer bounds, recreating it if needed. */
-    private void updateDimLayer() {
-        DimLayer newDimLayer;
-        final boolean previousFullscreen =
-                mDimLayer != null && sSharedFullscreenDimLayers.indexOfValue(mDimLayer) > -1;
-        final int displayId = mStack.getDisplayContent().getDisplayId();
-        if (mFullscreen) {
-            if (previousFullscreen) {
-                // Nothing to do here...
-                return;
-            }
-            // Use shared fullscreen dim layer
-            newDimLayer = sSharedFullscreenDimLayers.get(displayId);
-            if (newDimLayer == null) {
-                if (mDimLayer != null) {
-                    // Re-purpose the previous dim layer.
-                    newDimLayer = mDimLayer;
-                } else {
-                    // Create new full screen dim layer.
-                    newDimLayer = new DimLayer(mService, this, displayId);
-                }
-                newDimLayer.setBounds(mBounds);
-                sSharedFullscreenDimLayers.put(displayId, newDimLayer);
-            } else if (mDimLayer != null) {
-                mDimLayer.destroySurface();
-            }
-        } else {
-            newDimLayer = (mDimLayer == null || previousFullscreen)
-                    ? new DimLayer(mService, this, displayId) : mDimLayer;
-            newDimLayer.setBounds(mBounds);
-        }
-        mDimLayer = newDimLayer;
-    }
-
-    boolean animateDimLayers() {
-        final int dimLayer;
-        final float dimAmount;
-        if (mDimWinAnimator == null) {
-            dimLayer = mDimLayer.getLayer();
-            dimAmount = 0;
-        } else {
-            dimLayer = mDimWinAnimator.mAnimLayer - WindowManagerService.LAYER_OFFSET_DIM;
-            dimAmount = mDimWinAnimator.mWin.mAttrs.dimAmount;
-        }
-        final float targetAlpha = mDimLayer.getTargetAlpha();
-        if (targetAlpha != dimAmount) {
-            if (mDimWinAnimator == null) {
-                mDimLayer.hide(DEFAULT_DIM_DURATION);
-            } else {
-                long duration = (mDimWinAnimator.mAnimating && mDimWinAnimator.mAnimation != null)
-                        ? mDimWinAnimator.mAnimation.computeDurationHint()
-                        : DEFAULT_DIM_DURATION;
-                if (targetAlpha > dimAmount) {
-                    duration = getDimBehindFadeDuration(duration);
-                }
-                mDimLayer.show(dimLayer, dimAmount, duration);
-            }
-        } else if (mDimLayer.getLayer() != dimLayer) {
-            mDimLayer.setLayer(dimLayer);
-        }
-        if (mDimLayer.isAnimating()) {
-            if (!mService.okToDisplay()) {
-                // Jump to the end of the animation.
-                mDimLayer.show();
-            } else {
-                return mDimLayer.stepAnimation();
-            }
-        }
-        return false;
-    }
-
-    private long getDimBehindFadeDuration(long duration) {
-        TypedValue tv = new TypedValue();
-        mService.mContext.getResources().getValue(
-                com.android.internal.R.fraction.config_dimBehindFadeDuration, tv, true);
-        if (tv.type == TypedValue.TYPE_FRACTION) {
-            duration = (long)tv.getFraction(duration, duration);
-        } else if (tv.type >= TypedValue.TYPE_FIRST_INT && tv.type <= TypedValue.TYPE_LAST_INT) {
-            duration = tv.data;
-        }
-        return duration;
-    }
-
-    void clearContinueDimming() {
-        mContinueDimming = false;
-    }
-
-    void setContinueDimming() {
-        mContinueDimming = true;
-    }
-
-    boolean getContinueDimming() {
-        return mContinueDimming;
-    }
-
-    boolean isDimming() {
-        return mDimLayer.isDimming();
-    }
-
-    boolean isDimming(WindowStateAnimator winAnimator) {
-        return mDimWinAnimator == winAnimator && isDimming();
-    }
-
-    void startDimmingIfNeeded(WindowStateAnimator newWinAnimator) {
-        // 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.
-        if (newWinAnimator.mSurfaceShown && (mDimWinAnimator == null
-                || !mDimWinAnimator.mSurfaceShown
-                || mDimWinAnimator.mAnimLayer < newWinAnimator.mAnimLayer)) {
-            mDimWinAnimator = newWinAnimator;
-            if (mDimWinAnimator.mWin.mAppToken == null
-                    && !mFullscreen && mStack.getDisplayContent() != null) {
-                // Dim should cover the entire screen for system windows.
-                mStack.getDisplayContent().getLogicalDisplayRect(mTmpRect);
-                mDimLayer.setBounds(mTmpRect);
-            }
-        }
-    }
-
-    void stopDimmingIfNeeded() {
-        if (!mContinueDimming && isDimming()) {
-            mDimWinAnimator = null;
-            mDimLayer.setBounds(mBounds);
-        }
-    }
-
-    void close() {
-        if (mDimLayer != null) {
-            mDimLayer.destroySurface();
-            mDimLayer = null;
-        }
-    }
-
     void resizeWindows() {
         final ArrayList<WindowState> resizingWindows = mService.mResizingWindows;
         for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
@@ -491,16 +349,14 @@
         return "{taskId=" + mTaskId + " appTokens=" + mAppTokens + " mdr=" + mDeferRemoval + "}";
     }
 
+    @Override
+    public String toShortString() {
+        return "Task=" + mTaskId;
+    }
+
     public void printTo(String prefix, PrintWriter pw) {
         pw.print(prefix); pw.print("taskId="); pw.print(mTaskId);
                 pw.print(prefix); pw.print("appTokens="); pw.print(mAppTokens);
                 pw.print(prefix); pw.print("mdr="); pw.println(mDeferRemoval);
-        if (mDimLayer.isDimming()) {
-            pw.print(prefix); pw.println("mDimLayer:");
-            mDimLayer.printTo(prefix + " ", pw);
-            pw.print(prefix); pw.print("mDimWinAnimator="); pw.println(mDimWinAnimator);
-        } else {
-            pw.println();
-        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 290c2ea..207da47 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -468,6 +468,16 @@
         return mTask.mStack.getDisplayInfo();
     }
 
+    @Override
+    public void getBounds(Rect out) {
+        // This dim layer user doesn't need this.
+    }
+
+    @Override
+    public String toShortString() {
+        return TAG;
+    }
+
     private int getDragLayerLocked() {
         return mService.mPolicy.windowTypeToLayerLw(WindowManager.LayoutParams.TYPE_DRAG)
                 * WindowManagerService.TYPE_LAYER_MULTIPLIER
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index f030b9a..a630993 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -175,6 +175,10 @@
             return false;
         }
 
+        if (mDisplayContent != null) {
+            mDisplayContent.mDimBehindController.updateDimLayer(this);
+        }
+
         mAnimationBackgroundSurface.setBounds(bounds);
         mBounds.set(bounds);
         mRotation = rotation;
@@ -201,7 +205,8 @@
     }
 
     /** Bounds of the stack with other system factors taken into consideration. */
-    void getBounds(Rect out) {
+    @Override
+    public void getBounds(Rect out) {
         if (useCurrentBounds()) {
             // No need to adjust the output bounds if fullscreen or the docked stack is visible
             // since it is already what we want to represent to the rest of the system.
@@ -550,9 +555,6 @@
             mAnimationBackgroundSurface.destroySurface();
             mAnimationBackgroundSurface = null;
         }
-        for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
-            mTasks.get(taskNdx).close();
-        }
         mDisplayContent = null;
     }
 
@@ -609,6 +611,11 @@
         return "{stackId=" + mStackId + " tasks=" + mTasks + "}";
     }
 
+    @Override
+    public String toShortString() {
+        return "Stack=" + mStackId;
+    }
+
     /**
      * For docked workspace provides information which side of the screen was the dock anchored.
      */
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 74572cf..c4201d9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -216,6 +216,7 @@
     static final boolean DEBUG_STACK = false;
     static final boolean DEBUG_DISPLAY = false;
     static final boolean DEBUG_POWER = false;
+    static final boolean DEBUG_DIM_LAYER = false;
     static final boolean SHOW_SURFACE_ALLOC = false;
     static final boolean SHOW_TRANSACTIONS = false;
     static final boolean SHOW_LIGHT_TRANSACTIONS = false || SHOW_TRANSACTIONS;
@@ -8356,8 +8357,10 @@
                 layerChanged = true;
                 anyLayerChanged = true;
             }
-            final Task task = w.getTask();
-            if (layerChanged && task != null && task.isDimming(winAnimator)) {
+            final DimLayer.DimLayerUser dimLayerUser = w.getDimLayerUser();
+            final DisplayContent displayContent = w.getDisplayContent();
+            if (layerChanged && dimLayerUser != null && displayContent != null &&
+                    displayContent.mDimBehindController.isDimming(dimLayerUser, winAnimator)) {
                 // Force an animation pass just to update the mDimLayer layer.
                 scheduleAnimationLocked();
             }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 55ddbc0..8ace990 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -29,6 +29,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static com.android.server.wm.WindowManagerService.DEBUG_CONFIGURATION;
+import static com.android.server.wm.WindowManagerService.DEBUG_DIM_LAYER;
 import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT;
 import static com.android.server.wm.WindowManagerService.DEBUG_ORIENTATION;
 import static com.android.server.wm.WindowManagerService.DEBUG_POWER;
@@ -698,10 +699,10 @@
             final int height = Math.min(mFrame.height(), mContentFrame.height());
             final int width = Math.min(mContentFrame.width(), mFrame.width());
             final DisplayMetrics displayMetrics = getDisplayContent().getDisplayMetrics();
-            final int minVisibleHeight =
-                    mService.dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, displayMetrics);
-            final int minVisibleWidth =
-                    mService.dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, displayMetrics);
+            final int minVisibleHeight = WindowManagerService.dipToPixel(
+                    MINIMUM_VISIBLE_HEIGHT_IN_DP, displayMetrics);
+            final int minVisibleWidth = WindowManagerService.dipToPixel(
+                    MINIMUM_VISIBLE_WIDTH_IN_DP, displayMetrics);
             final int top = Math.max(mContentFrame.top,
                     Math.min(mFrame.top, mContentFrame.bottom - minVisibleHeight));
             final int left = Math.max(mContentFrame.left + minVisibleWidth - width,
@@ -889,7 +890,7 @@
 
     @Override
     public boolean isVoiceInteraction() {
-        return mAppToken != null ? mAppToken.voiceInteraction : false;
+        return mAppToken != null && mAppToken.voiceInteraction;
     }
 
     boolean setInsetsChanged() {
@@ -934,7 +935,10 @@
                 return task.mStack;
             }
         }
-        return null;
+        // Some system windows (e.g. "Power off" dialog) don't have a task, but we would still
+        // associate them with some stack to enable dimming.
+        return mAttrs.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW
+                && mDisplayContent != null ? mDisplayContent.getHomeStack() : null;
     }
 
     /**
@@ -970,7 +974,8 @@
         }
         if (forTouch && inFreeformWorkspace()) {
             final DisplayMetrics displayMetrics = getDisplayContent().getDisplayMetrics();
-            final int delta = mService.dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, displayMetrics);
+            final int delta = WindowManagerService.dipToPixel(
+                    RESIZE_HANDLE_WIDTH_IN_DP, displayMetrics);
             bounds.inset(-delta, -delta);
         }
     }
@@ -1031,8 +1036,7 @@
             return false;
         }
         final AppWindowToken atoken = mAppToken;
-        final boolean animating = atoken != null
-                ? (atoken.mAppAnimator.animation != null) : false;
+        final boolean animating = atoken != null && atoken.mAppAnimator.animation != null;
         return mHasSurface && !mDestroying && !mExiting
                 && (atoken == null ? mPolicyVisibility : !atoken.hiddenRequested)
                 && ((!mAttachedHidden && mViewVisibility == View.VISIBLE
@@ -1114,8 +1118,7 @@
      * of a transition that has not yet been started.
      */
     boolean isReadyForDisplay() {
-        if (mRootToken.waitingToShow &&
-                mService.mAppTransition.isTransitionSet()) {
+        if (mRootToken.waitingToShow && mService.mAppTransition.isTransitionSet()) {
             return false;
         }
         return mHasSurface && mPolicyVisibility && !mDestroying
@@ -1295,19 +1298,20 @@
     }
 
     void handleFlagDimBehind() {
-        if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0 && isDisplayedLw() && !mExiting) {
-            final Task task = getTask();
-            if (task == null) {
-                return;
-            }
-            task.setContinueDimming();
-            if (!task.isDimming(mWinAnimator)) {
-                if (WindowManagerService.localLOGV) Slog.v(TAG, "Win " + this + " start dimming.");
-                task.startDimmingIfNeeded(mWinAnimator);
-            }
+        if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0 && mDisplayContent != null && !mExiting
+                && isDisplayedLw()) {
+            mDisplayContent.mDimBehindController.applyDimBehind(getDimLayerUser(), mWinAnimator);
         }
     }
 
+    DimLayer.DimLayerUser getDimLayerUser() {
+        Task task = getTask();
+        if (task != null) {
+            return task;
+        }
+        return getStack();
+    }
+
     void maybeRemoveReplacedWindow() {
         AppWindowToken token = mAppToken;
         if (token != null && token.mReplacingWindow) {
@@ -1512,11 +1516,9 @@
 
     @Override
     public boolean isDimming() {
-        Task task = getTask();
-        if (task == null) {
-            return false;
-        }
-        return task.isDimming(mWinAnimator);
+        final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
+        return dimLayerUser != null && mDisplayContent != null &&
+                mDisplayContent.mDimBehindController.isDimming(dimLayerUser, mWinAnimator);
     }
 
     public void setShowToOwnerOnlyLocked(boolean showToOwnerOnly) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 60bf571..ed53501 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1516,12 +1516,7 @@
                         mDsDy * w.mHScale, mDtDy * w.mVScale);
                 mAnimator.setPendingLayoutChanges(w.getDisplayId(),
                         WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
-                if ((w.mAttrs.flags & LayoutParams.FLAG_DIM_BEHIND) != 0) {
-                    final Task task = w.getTask();
-                    if (task != null) {
-                        task.startDimmingIfNeeded(this);
-                    }
-                }
+                w.handleFlagDimBehind();
             } catch (RuntimeException e) {
                 // If something goes wrong with the surface (such
                 // as running out of memory), don't take down the
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index df0a1c9..db9fcf1 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -642,9 +642,7 @@
                     handleNotObscuredLocked(w, innerDw, innerDh);
                 }
 
-                if (task != null && !task.getContinueDimming()) {
-                    w.handleFlagDimBehind();
-                }
+                w.handleFlagDimBehind();
 
                 if (isDefaultDisplay && obscuredChanged
                         && mWallpaperControllerLocked.isWallpaperTarget(w) && w.isVisibleLw()) {