Import Android SDK Platform PI [4335822]

/google/data/ro/projects/android/fetch_artifact \
    --bid 4335822 \
    --target sdk_phone_armv7-win_sdk \
    sdk-repo-linux-sources-4335822.zip

AndroidVersion.ApiLevel has been modified to appear as 28

Change-Id: Ic8f04be005a71c2b9abeaac754d8da8d6f9a2c32
diff --git a/com/android/server/wm/AccessibilityController.java b/com/android/server/wm/AccessibilityController.java
new file mode 100644
index 0000000..6484a13
--- /dev/null
+++ b/com/android/server/wm/AccessibilityController.java
@@ -0,0 +1,1349 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
+
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.app.Service;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TypedValue;
+import android.view.MagnificationSpec;
+import android.view.Surface;
+import android.view.Surface.OutOfResourcesException;
+import android.view.SurfaceControl;
+import android.view.ViewConfiguration;
+import android.view.WindowInfo;
+import android.view.WindowManager;
+import android.view.WindowManagerInternal.MagnificationCallbacks;
+import android.view.WindowManagerInternal.WindowsForAccessibilityCallback;
+import android.view.WindowManagerPolicy;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+import com.android.internal.R;
+import com.android.internal.os.SomeArgs;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This class contains the accessibility related logic of the window manger.
+ */
+final class AccessibilityController {
+
+    private final WindowManagerService mWindowManagerService;
+
+    private static final float[] sTempFloats = new float[9];
+
+    public AccessibilityController(WindowManagerService service) {
+        mWindowManagerService = service;
+    }
+
+    private DisplayMagnifier mDisplayMagnifier;
+
+    private WindowsForAccessibilityObserver mWindowsForAccessibilityObserver;
+
+    public void setMagnificationCallbacksLocked(MagnificationCallbacks callbacks) {
+        if (callbacks != null) {
+            if (mDisplayMagnifier != null) {
+                throw new IllegalStateException("Magnification callbacks already set!");
+            }
+            mDisplayMagnifier = new DisplayMagnifier(mWindowManagerService, callbacks);
+        } else {
+            if  (mDisplayMagnifier == null) {
+                throw new IllegalStateException("Magnification callbacks already cleared!");
+            }
+            mDisplayMagnifier.destroyLocked();
+            mDisplayMagnifier = null;
+        }
+    }
+
+    public void setWindowsForAccessibilityCallback(WindowsForAccessibilityCallback callback) {
+        if (callback != null) {
+            if (mWindowsForAccessibilityObserver != null) {
+                throw new IllegalStateException(
+                        "Windows for accessibility callback already set!");
+            }
+            mWindowsForAccessibilityObserver = new WindowsForAccessibilityObserver(
+                    mWindowManagerService, callback);
+        } else {
+            if (mWindowsForAccessibilityObserver == null) {
+                throw new IllegalStateException(
+                        "Windows for accessibility callback already cleared!");
+            }
+            mWindowsForAccessibilityObserver = null;
+        }
+    }
+
+    public void performComputeChangedWindowsNotLocked() {
+        WindowsForAccessibilityObserver observer = null;
+        synchronized (mWindowManagerService) {
+            observer = mWindowsForAccessibilityObserver;
+        }
+        if (observer != null) {
+            observer.performComputeChangedWindowsNotLocked();
+        }
+    }
+
+    public void setMagnificationSpecLocked(MagnificationSpec spec) {
+        if (mDisplayMagnifier != null) {
+            mDisplayMagnifier.setMagnificationSpecLocked(spec);
+        }
+        if (mWindowsForAccessibilityObserver != null) {
+            mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
+        }
+    }
+
+    public void getMagnificationRegionLocked(Region outMagnificationRegion) {
+        if (mDisplayMagnifier != null) {
+            mDisplayMagnifier.getMagnificationRegionLocked(outMagnificationRegion);
+        }
+    }
+
+    public void onRectangleOnScreenRequestedLocked(Rect rectangle) {
+        if (mDisplayMagnifier != null) {
+            mDisplayMagnifier.onRectangleOnScreenRequestedLocked(rectangle);
+        }
+        // Not relevant for the window observer.
+    }
+
+    public void onWindowLayersChangedLocked() {
+        if (mDisplayMagnifier != null) {
+            mDisplayMagnifier.onWindowLayersChangedLocked();
+        }
+        if (mWindowsForAccessibilityObserver != null) {
+            mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
+        }
+    }
+
+    public void onRotationChangedLocked(DisplayContent displayContent) {
+        if (mDisplayMagnifier != null) {
+            mDisplayMagnifier.onRotationChangedLocked(displayContent);
+        }
+        if (mWindowsForAccessibilityObserver != null) {
+            mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
+        }
+    }
+
+    public void onAppWindowTransitionLocked(WindowState windowState, int transition) {
+        if (mDisplayMagnifier != null) {
+            mDisplayMagnifier.onAppWindowTransitionLocked(windowState, transition);
+        }
+        // Not relevant for the window observer.
+    }
+
+    public void onWindowTransitionLocked(WindowState windowState, int transition) {
+        if (mDisplayMagnifier != null) {
+            mDisplayMagnifier.onWindowTransitionLocked(windowState, transition);
+        }
+        if (mWindowsForAccessibilityObserver != null) {
+            mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
+        }
+    }
+
+    public void onWindowFocusChangedNotLocked() {
+        // Not relevant for the display magnifier.
+
+        WindowsForAccessibilityObserver observer = null;
+        synchronized (mWindowManagerService) {
+            observer = mWindowsForAccessibilityObserver;
+        }
+        if (observer != null) {
+            observer.performComputeChangedWindowsNotLocked();
+        }
+    }
+
+
+    public void onSomeWindowResizedOrMovedLocked() {
+        // Not relevant for the display magnifier.
+
+        if (mWindowsForAccessibilityObserver != null) {
+            mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
+        }
+    }
+
+    /** NOTE: This has to be called within a surface transaction. */
+    public void drawMagnifiedRegionBorderIfNeededLocked() {
+        if (mDisplayMagnifier != null) {
+            mDisplayMagnifier.drawMagnifiedRegionBorderIfNeededLocked();
+        }
+        // Not relevant for the window observer.
+    }
+
+    public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) {
+        if (mDisplayMagnifier != null) {
+            return mDisplayMagnifier.getMagnificationSpecForWindowLocked(windowState);
+        }
+        return null;
+    }
+
+    public boolean hasCallbacksLocked() {
+        return (mDisplayMagnifier != null
+                || mWindowsForAccessibilityObserver != null);
+    }
+
+    public void setForceShowMagnifiableBoundsLocked(boolean show) {
+        if (mDisplayMagnifier != null) {
+            mDisplayMagnifier.setForceShowMagnifiableBoundsLocked(show);
+            mDisplayMagnifier.showMagnificationBoundsIfNeeded();
+        }
+    }
+
+    private static void populateTransformationMatrixLocked(WindowState windowState,
+            Matrix outMatrix) {
+        sTempFloats[Matrix.MSCALE_X] = windowState.mWinAnimator.mDsDx;
+        sTempFloats[Matrix.MSKEW_Y] = windowState.mWinAnimator.mDtDx;
+        sTempFloats[Matrix.MSKEW_X] = windowState.mWinAnimator.mDtDy;
+        sTempFloats[Matrix.MSCALE_Y] = windowState.mWinAnimator.mDsDy;
+        sTempFloats[Matrix.MTRANS_X] = windowState.mShownPosition.x;
+        sTempFloats[Matrix.MTRANS_Y] = windowState.mShownPosition.y;
+        sTempFloats[Matrix.MPERSP_0] = 0;
+        sTempFloats[Matrix.MPERSP_1] = 0;
+        sTempFloats[Matrix.MPERSP_2] = 1;
+        outMatrix.setValues(sTempFloats);
+    }
+
+    /**
+     * This class encapsulates the functionality related to display magnification.
+     */
+    private static final class DisplayMagnifier {
+
+        private static final String LOG_TAG = TAG_WITH_CLASS_NAME ? "DisplayMagnifier" : TAG_WM;
+
+        private static final boolean DEBUG_WINDOW_TRANSITIONS = false;
+        private static final boolean DEBUG_ROTATION = false;
+        private static final boolean DEBUG_LAYERS = false;
+        private static final boolean DEBUG_RECTANGLE_REQUESTED = false;
+        private static final boolean DEBUG_VIEWPORT_WINDOW = false;
+
+        private final Rect mTempRect1 = new Rect();
+        private final Rect mTempRect2 = new Rect();
+
+        private final Region mTempRegion1 = new Region();
+        private final Region mTempRegion2 = new Region();
+        private final Region mTempRegion3 = new Region();
+        private final Region mTempRegion4 = new Region();
+
+        private final Context mContext;
+        private final WindowManagerService mWindowManagerService;
+        private final MagnifiedViewport mMagnifedViewport;
+        private final Handler mHandler;
+
+        private final MagnificationCallbacks mCallbacks;
+
+        private final long mLongAnimationDuration;
+
+        private boolean mForceShowMagnifiableBounds = false;
+
+        public DisplayMagnifier(WindowManagerService windowManagerService,
+                MagnificationCallbacks callbacks) {
+            mContext = windowManagerService.mContext;
+            mWindowManagerService = windowManagerService;
+            mCallbacks = callbacks;
+            mHandler = new MyHandler(mWindowManagerService.mH.getLooper());
+            mMagnifedViewport = new MagnifiedViewport();
+            mLongAnimationDuration = mContext.getResources().getInteger(
+                    com.android.internal.R.integer.config_longAnimTime);
+        }
+
+        public void setMagnificationSpecLocked(MagnificationSpec spec) {
+            mMagnifedViewport.updateMagnificationSpecLocked(spec);
+            mMagnifedViewport.recomputeBoundsLocked();
+            mWindowManagerService.scheduleAnimationLocked();
+        }
+
+        public void setForceShowMagnifiableBoundsLocked(boolean show) {
+            mForceShowMagnifiableBounds = show;
+            mMagnifedViewport.setMagnifiedRegionBorderShownLocked(show, true);
+        }
+
+        public boolean isForceShowingMagnifiableBoundsLocked() {
+            return mForceShowMagnifiableBounds;
+        }
+
+        public void onRectangleOnScreenRequestedLocked(Rect rectangle) {
+            if (DEBUG_RECTANGLE_REQUESTED) {
+                Slog.i(LOG_TAG, "Rectangle on screen requested: " + rectangle);
+            }
+            if (!mMagnifedViewport.isMagnifyingLocked()) {
+                return;
+            }
+            Rect magnifiedRegionBounds = mTempRect2;
+            mMagnifedViewport.getMagnifiedFrameInContentCoordsLocked(magnifiedRegionBounds);
+            if (magnifiedRegionBounds.contains(rectangle)) {
+                return;
+            }
+            SomeArgs args = SomeArgs.obtain();
+            args.argi1 = rectangle.left;
+            args.argi2 = rectangle.top;
+            args.argi3 = rectangle.right;
+            args.argi4 = rectangle.bottom;
+            mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED,
+                    args).sendToTarget();
+        }
+
+        public void onWindowLayersChangedLocked() {
+            if (DEBUG_LAYERS) {
+                Slog.i(LOG_TAG, "Layers changed.");
+            }
+            mMagnifedViewport.recomputeBoundsLocked();
+            mWindowManagerService.scheduleAnimationLocked();
+        }
+
+        public void onRotationChangedLocked(DisplayContent displayContent) {
+            if (DEBUG_ROTATION) {
+                final int rotation = displayContent.getRotation();
+                Slog.i(LOG_TAG, "Rotation: " + Surface.rotationToString(rotation)
+                        + " displayId: " + displayContent.getDisplayId());
+            }
+            mMagnifedViewport.onRotationChangedLocked();
+            mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_ROTATION_CHANGED);
+        }
+
+        public void onAppWindowTransitionLocked(WindowState windowState, int transition) {
+            if (DEBUG_WINDOW_TRANSITIONS) {
+                Slog.i(LOG_TAG, "Window transition: "
+                        + AppTransition.appTransitionToString(transition)
+                        + " displayId: " + windowState.getDisplayId());
+            }
+            final boolean magnifying = mMagnifedViewport.isMagnifyingLocked();
+            if (magnifying) {
+                switch (transition) {
+                    case AppTransition.TRANSIT_ACTIVITY_OPEN:
+                    case AppTransition.TRANSIT_TASK_OPEN:
+                    case AppTransition.TRANSIT_TASK_TO_FRONT:
+                    case AppTransition.TRANSIT_WALLPAPER_OPEN:
+                    case AppTransition.TRANSIT_WALLPAPER_CLOSE:
+                    case AppTransition.TRANSIT_WALLPAPER_INTRA_OPEN: {
+                        mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_USER_CONTEXT_CHANGED);
+                    }
+                }
+            }
+        }
+
+        public void onWindowTransitionLocked(WindowState windowState, int transition) {
+            if (DEBUG_WINDOW_TRANSITIONS) {
+                Slog.i(LOG_TAG, "Window transition: "
+                        + AppTransition.appTransitionToString(transition)
+                        + " displayId: " + windowState.getDisplayId());
+            }
+            final boolean magnifying = mMagnifedViewport.isMagnifyingLocked();
+            final int type = windowState.mAttrs.type;
+            switch (transition) {
+                case WindowManagerPolicy.TRANSIT_ENTER:
+                case WindowManagerPolicy.TRANSIT_SHOW: {
+                    if (!magnifying) {
+                        break;
+                    }
+                    switch (type) {
+                        case WindowManager.LayoutParams.TYPE_APPLICATION:
+                        case WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION:
+                        case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL:
+                        case WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA:
+                        case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL:
+                        case WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL:
+                        case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG:
+                        case WindowManager.LayoutParams.TYPE_SEARCH_BAR:
+                        case WindowManager.LayoutParams.TYPE_PHONE:
+                        case WindowManager.LayoutParams.TYPE_SYSTEM_ALERT:
+                        case WindowManager.LayoutParams.TYPE_TOAST:
+                        case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY:
+                        case WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY:
+                        case WindowManager.LayoutParams.TYPE_PRIORITY_PHONE:
+                        case WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG:
+                        case WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG:
+                        case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR:
+                        case WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY:
+                        case WindowManager.LayoutParams.TYPE_QS_DIALOG:
+                        case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL: {
+                            Rect magnifiedRegionBounds = mTempRect2;
+                            mMagnifedViewport.getMagnifiedFrameInContentCoordsLocked(
+                                    magnifiedRegionBounds);
+                            Rect touchableRegionBounds = mTempRect1;
+                            windowState.getTouchableRegion(mTempRegion1);
+                            mTempRegion1.getBounds(touchableRegionBounds);
+                            if (!magnifiedRegionBounds.intersect(touchableRegionBounds)) {
+                                mCallbacks.onRectangleOnScreenRequested(
+                                        touchableRegionBounds.left,
+                                        touchableRegionBounds.top,
+                                        touchableRegionBounds.right,
+                                        touchableRegionBounds.bottom);
+                            }
+                        } break;
+                    } break;
+                }
+            }
+        }
+
+        public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) {
+            MagnificationSpec spec = mMagnifedViewport.getMagnificationSpecLocked();
+            if (spec != null && !spec.isNop()) {
+                if (!mWindowManagerService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) {
+                    return null;
+                }
+            }
+            return spec;
+        }
+
+        public void getMagnificationRegionLocked(Region outMagnificationRegion) {
+            // Make sure we're working with the most current bounds
+            mMagnifedViewport.recomputeBoundsLocked();
+            mMagnifedViewport.getMagnificationRegionLocked(outMagnificationRegion);
+        }
+
+        public void destroyLocked() {
+            mMagnifedViewport.destroyWindow();
+        }
+
+        // Can be called outside of a surface transaction
+        public void showMagnificationBoundsIfNeeded() {
+            mHandler.obtainMessage(MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)
+                    .sendToTarget();
+        }
+
+        /** NOTE: This has to be called within a surface transaction. */
+        public void drawMagnifiedRegionBorderIfNeededLocked() {
+            mMagnifedViewport.drawWindowIfNeededLocked();
+        }
+
+        private final class MagnifiedViewport {
+
+            private final SparseArray<WindowState> mTempWindowStates =
+                    new SparseArray<WindowState>();
+
+            private final RectF mTempRectF = new RectF();
+
+            private final Point mTempPoint = new Point();
+
+            private final Matrix mTempMatrix = new Matrix();
+
+            private final Region mMagnificationRegion = new Region();
+            private final Region mOldMagnificationRegion = new Region();
+
+            private final Path mCircularPath;
+
+            private final MagnificationSpec mMagnificationSpec = MagnificationSpec.obtain();
+
+            private final WindowManager mWindowManager;
+
+            private final float mBorderWidth;
+            private final int mHalfBorderWidth;
+            private final int mDrawBorderInset;
+
+            private final ViewportWindow mWindow;
+
+            private boolean mFullRedrawNeeded;
+
+            public MagnifiedViewport() {
+                mWindowManager = (WindowManager) mContext.getSystemService(Service.WINDOW_SERVICE);
+                mBorderWidth = mContext.getResources().getDimension(
+                        com.android.internal.R.dimen.accessibility_magnification_indicator_width);
+                mHalfBorderWidth = (int) Math.ceil(mBorderWidth / 2);
+                mDrawBorderInset = (int) mBorderWidth / 2;
+                mWindow = new ViewportWindow(mContext);
+
+                if (mContext.getResources().getConfiguration().isScreenRound()) {
+                    mCircularPath = new Path();
+                    mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
+                    final int centerXY = mTempPoint.x / 2;
+                    mCircularPath.addCircle(centerXY, centerXY, centerXY, Path.Direction.CW);
+                } else {
+                    mCircularPath = null;
+                }
+
+                recomputeBoundsLocked();
+            }
+
+            public void getMagnificationRegionLocked(@NonNull Region outMagnificationRegion) {
+                outMagnificationRegion.set(mMagnificationRegion);
+            }
+
+            public void updateMagnificationSpecLocked(MagnificationSpec spec) {
+                if (spec != null) {
+                    mMagnificationSpec.initialize(spec.scale, spec.offsetX, spec.offsetY);
+                } else {
+                    mMagnificationSpec.clear();
+                }
+                // If this message is pending we are in a rotation animation and do not want
+                // to show the border. We will do so when the pending message is handled.
+                if (!mHandler.hasMessages(
+                        MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)) {
+                    setMagnifiedRegionBorderShownLocked(
+                            isMagnifyingLocked() || isForceShowingMagnifiableBoundsLocked(), true);
+                }
+            }
+
+            public void recomputeBoundsLocked() {
+                mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
+                final int screenWidth = mTempPoint.x;
+                final int screenHeight = mTempPoint.y;
+
+                mMagnificationRegion.set(0, 0, 0, 0);
+                final Region availableBounds = mTempRegion1;
+                availableBounds.set(0, 0, screenWidth, screenHeight);
+
+                if (mCircularPath != null) {
+                    availableBounds.setPath(mCircularPath, availableBounds);
+                }
+
+                Region nonMagnifiedBounds = mTempRegion4;
+                nonMagnifiedBounds.set(0, 0, 0, 0);
+
+                SparseArray<WindowState> visibleWindows = mTempWindowStates;
+                visibleWindows.clear();
+                populateWindowsOnScreenLocked(visibleWindows);
+
+                final int visibleWindowCount = visibleWindows.size();
+                for (int i = visibleWindowCount - 1; i >= 0; i--) {
+                    WindowState windowState = visibleWindows.valueAt(i);
+                    if ((windowState.mAttrs.type == TYPE_MAGNIFICATION_OVERLAY)
+                            || ((windowState.mAttrs.privateFlags
+                            & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0)) {
+                        continue;
+                    }
+
+                    // Consider the touchable portion of the window
+                    Matrix matrix = mTempMatrix;
+                    populateTransformationMatrixLocked(windowState, matrix);
+                    Region touchableRegion = mTempRegion3;
+                    windowState.getTouchableRegion(touchableRegion);
+                    Rect touchableFrame = mTempRect1;
+                    touchableRegion.getBounds(touchableFrame);
+                    RectF windowFrame = mTempRectF;
+                    windowFrame.set(touchableFrame);
+                    windowFrame.offset(-windowState.mFrame.left, -windowState.mFrame.top);
+                    matrix.mapRect(windowFrame);
+                    Region windowBounds = mTempRegion2;
+                    windowBounds.set((int) windowFrame.left, (int) windowFrame.top,
+                            (int) windowFrame.right, (int) windowFrame.bottom);
+                    // Only update new regions
+                    Region portionOfWindowAlreadyAccountedFor = mTempRegion3;
+                    portionOfWindowAlreadyAccountedFor.set(mMagnificationRegion);
+                    portionOfWindowAlreadyAccountedFor.op(nonMagnifiedBounds, Region.Op.UNION);
+                    windowBounds.op(portionOfWindowAlreadyAccountedFor, Region.Op.DIFFERENCE);
+
+                    if (mWindowManagerService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) {
+                        mMagnificationRegion.op(windowBounds, Region.Op.UNION);
+                        mMagnificationRegion.op(availableBounds, Region.Op.INTERSECT);
+                    } else {
+                        nonMagnifiedBounds.op(windowBounds, Region.Op.UNION);
+                        availableBounds.op(windowBounds, Region.Op.DIFFERENCE);
+                    }
+
+                    // Update accounted bounds
+                    Region accountedBounds = mTempRegion2;
+                    accountedBounds.set(mMagnificationRegion);
+                    accountedBounds.op(nonMagnifiedBounds, Region.Op.UNION);
+                    accountedBounds.op(0, 0, screenWidth, screenHeight, Region.Op.INTERSECT);
+
+                    if (accountedBounds.isRect()) {
+                        Rect accountedFrame = mTempRect1;
+                        accountedBounds.getBounds(accountedFrame);
+                        if (accountedFrame.width() == screenWidth
+                                && accountedFrame.height() == screenHeight) {
+                            break;
+                        }
+                    }
+                }
+
+                visibleWindows.clear();
+
+                mMagnificationRegion.op(mDrawBorderInset, mDrawBorderInset,
+                        screenWidth - mDrawBorderInset, screenHeight - mDrawBorderInset,
+                        Region.Op.INTERSECT);
+
+                final boolean magnifiedChanged =
+                        !mOldMagnificationRegion.equals(mMagnificationRegion);
+                if (magnifiedChanged) {
+                    mWindow.setBounds(mMagnificationRegion);
+                    final Rect dirtyRect = mTempRect1;
+                    if (mFullRedrawNeeded) {
+                        mFullRedrawNeeded = false;
+                        dirtyRect.set(mDrawBorderInset, mDrawBorderInset,
+                                screenWidth - mDrawBorderInset,
+                                screenHeight - mDrawBorderInset);
+                        mWindow.invalidate(dirtyRect);
+                    } else {
+                        final Region dirtyRegion = mTempRegion3;
+                        dirtyRegion.set(mMagnificationRegion);
+                        dirtyRegion.op(mOldMagnificationRegion, Region.Op.UNION);
+                        dirtyRegion.op(nonMagnifiedBounds, Region.Op.INTERSECT);
+                        dirtyRegion.getBounds(dirtyRect);
+                        mWindow.invalidate(dirtyRect);
+                    }
+
+                    mOldMagnificationRegion.set(mMagnificationRegion);
+                    final SomeArgs args = SomeArgs.obtain();
+                    args.arg1 = Region.obtain(mMagnificationRegion);
+                    mHandler.obtainMessage(
+                            MyHandler.MESSAGE_NOTIFY_MAGNIFICATION_REGION_CHANGED, args)
+                            .sendToTarget();
+                }
+            }
+
+            public void onRotationChangedLocked() {
+                // If we are showing the magnification border, hide it immediately so
+                // the user does not see strange artifacts during rotation. The screenshot
+                // used for rotation already has the border. After the rotation is complete
+                // we will show the border.
+                if (isMagnifyingLocked() || isForceShowingMagnifiableBoundsLocked()) {
+                    setMagnifiedRegionBorderShownLocked(false, false);
+                    final long delay = (long) (mLongAnimationDuration
+                            * mWindowManagerService.getWindowAnimationScaleLocked());
+                    Message message = mHandler.obtainMessage(
+                            MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED);
+                    mHandler.sendMessageDelayed(message, delay);
+                }
+                recomputeBoundsLocked();
+                mWindow.updateSize();
+            }
+
+            public void setMagnifiedRegionBorderShownLocked(boolean shown, boolean animate) {
+                if (shown) {
+                    mFullRedrawNeeded = true;
+                    mOldMagnificationRegion.set(0, 0, 0, 0);
+                }
+                mWindow.setShown(shown, animate);
+            }
+
+            public void getMagnifiedFrameInContentCoordsLocked(Rect rect) {
+                MagnificationSpec spec = mMagnificationSpec;
+                mMagnificationRegion.getBounds(rect);
+                rect.offset((int) -spec.offsetX, (int) -spec.offsetY);
+                rect.scale(1.0f / spec.scale);
+            }
+
+            public boolean isMagnifyingLocked() {
+                return mMagnificationSpec.scale > 1.0f;
+            }
+
+            public MagnificationSpec getMagnificationSpecLocked() {
+                return mMagnificationSpec;
+            }
+
+            /** NOTE: This has to be called within a surface transaction. */
+            public void drawWindowIfNeededLocked() {
+                recomputeBoundsLocked();
+                mWindow.drawIfNeeded();
+            }
+
+            public void destroyWindow() {
+                mWindow.releaseSurface();
+            }
+
+            private void populateWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
+                final DisplayContent dc = mWindowManagerService.getDefaultDisplayContentLocked();
+                dc.forAllWindows((w) -> {
+                    if (w.isOnScreen() && w.isVisibleLw()
+                            && !w.mWinAnimator.mEnterAnimationPending) {
+                        outWindows.put(w.mLayer, w);
+                    }
+                }, false /* traverseTopToBottom */ );
+            }
+
+            private final class ViewportWindow {
+                private static final String SURFACE_TITLE = "Magnification Overlay";
+
+                private final Region mBounds = new Region();
+                private final Rect mDirtyRect = new Rect();
+                private final Paint mPaint = new Paint();
+
+                private final SurfaceControl mSurfaceControl;
+                private final Surface mSurface = new Surface();
+
+                private final AnimationController mAnimationController;
+
+                private boolean mShown;
+                private int mAlpha;
+
+                private boolean mInvalidated;
+
+                public ViewportWindow(Context context) {
+                    SurfaceControl surfaceControl = null;
+                    try {
+                        mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
+                        surfaceControl = new SurfaceControl(mWindowManagerService.mFxSession,
+                                SURFACE_TITLE, mTempPoint.x, mTempPoint.y, PixelFormat.TRANSLUCENT,
+                                SurfaceControl.HIDDEN);
+                    } catch (OutOfResourcesException oore) {
+                        /* ignore */
+                    }
+                    mSurfaceControl = surfaceControl;
+                    mSurfaceControl.setLayerStack(mWindowManager.getDefaultDisplay()
+                            .getLayerStack());
+                    mSurfaceControl.setLayer(mWindowManagerService.mPolicy.getWindowLayerFromTypeLw(
+                            TYPE_MAGNIFICATION_OVERLAY)
+                            * WindowManagerService.TYPE_LAYER_MULTIPLIER);
+                    mSurfaceControl.setPosition(0, 0);
+                    mSurface.copyFrom(mSurfaceControl);
+
+                    mAnimationController = new AnimationController(context,
+                            mWindowManagerService.mH.getLooper());
+
+                    TypedValue typedValue = new TypedValue();
+                    context.getTheme().resolveAttribute(R.attr.colorActivatedHighlight,
+                            typedValue, true);
+                    final int borderColor = context.getColor(typedValue.resourceId);
+
+                    mPaint.setStyle(Paint.Style.STROKE);
+                    mPaint.setStrokeWidth(mBorderWidth);
+                    mPaint.setColor(borderColor);
+
+                    mInvalidated = true;
+                }
+
+                public void setShown(boolean shown, boolean animate) {
+                    synchronized (mWindowManagerService.mWindowMap) {
+                        if (mShown == shown) {
+                            return;
+                        }
+                        mShown = shown;
+                        mAnimationController.onFrameShownStateChanged(shown, animate);
+                        if (DEBUG_VIEWPORT_WINDOW) {
+                            Slog.i(LOG_TAG, "ViewportWindow shown: " + mShown);
+                        }
+                    }
+                }
+
+                @SuppressWarnings("unused")
+                // Called reflectively from an animator.
+                public int getAlpha() {
+                    synchronized (mWindowManagerService.mWindowMap) {
+                        return mAlpha;
+                    }
+                }
+
+                public void setAlpha(int alpha) {
+                    synchronized (mWindowManagerService.mWindowMap) {
+                        if (mAlpha == alpha) {
+                            return;
+                        }
+                        mAlpha = alpha;
+                        invalidate(null);
+                        if (DEBUG_VIEWPORT_WINDOW) {
+                            Slog.i(LOG_TAG, "ViewportWindow set alpha: " + alpha);
+                        }
+                    }
+                }
+
+                public void setBounds(Region bounds) {
+                    synchronized (mWindowManagerService.mWindowMap) {
+                        if (mBounds.equals(bounds)) {
+                            return;
+                        }
+                        mBounds.set(bounds);
+                        invalidate(mDirtyRect);
+                        if (DEBUG_VIEWPORT_WINDOW) {
+                            Slog.i(LOG_TAG, "ViewportWindow set bounds: " + bounds);
+                        }
+                    }
+                }
+
+                public void updateSize() {
+                    synchronized (mWindowManagerService.mWindowMap) {
+                        mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
+                        mSurfaceControl.setSize(mTempPoint.x, mTempPoint.y);
+                        invalidate(mDirtyRect);
+                    }
+                }
+
+                public void invalidate(Rect dirtyRect) {
+                    if (dirtyRect != null) {
+                        mDirtyRect.set(dirtyRect);
+                    } else {
+                        mDirtyRect.setEmpty();
+                    }
+                    mInvalidated = true;
+                    mWindowManagerService.scheduleAnimationLocked();
+                }
+
+                /** NOTE: This has to be called within a surface transaction. */
+                public void drawIfNeeded() {
+                    synchronized (mWindowManagerService.mWindowMap) {
+                        if (!mInvalidated) {
+                            return;
+                        }
+                        mInvalidated = false;
+                        Canvas canvas = null;
+                        try {
+                            // Empty dirty rectangle means unspecified.
+                            if (mDirtyRect.isEmpty()) {
+                                mBounds.getBounds(mDirtyRect);
+                            }
+                            mDirtyRect.inset(- mHalfBorderWidth, - mHalfBorderWidth);
+                            canvas = mSurface.lockCanvas(mDirtyRect);
+                            if (DEBUG_VIEWPORT_WINDOW) {
+                                Slog.i(LOG_TAG, "Dirty rect: " + mDirtyRect);
+                            }
+                        } catch (IllegalArgumentException iae) {
+                            /* ignore */
+                        } catch (Surface.OutOfResourcesException oore) {
+                            /* ignore */
+                        }
+                        if (canvas == null) {
+                            return;
+                        }
+                        if (DEBUG_VIEWPORT_WINDOW) {
+                            Slog.i(LOG_TAG, "Bounds: " + mBounds);
+                        }
+                        canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
+                        mPaint.setAlpha(mAlpha);
+                        Path path = mBounds.getBoundaryPath();
+                        canvas.drawPath(path, mPaint);
+
+                        mSurface.unlockCanvasAndPost(canvas);
+
+                        if (mAlpha > 0) {
+                            mSurfaceControl.show();
+                        } else {
+                            mSurfaceControl.hide();
+                        }
+                    }
+                }
+
+                public void releaseSurface() {
+                    mSurfaceControl.release();
+                    mSurface.release();
+                }
+
+                private final class AnimationController extends Handler {
+                    private static final String PROPERTY_NAME_ALPHA = "alpha";
+
+                    private static final int MIN_ALPHA = 0;
+                    private static final int MAX_ALPHA = 255;
+
+                    private static final int MSG_FRAME_SHOWN_STATE_CHANGED = 1;
+
+                    private final ValueAnimator mShowHideFrameAnimator;
+
+                    public AnimationController(Context context, Looper looper) {
+                        super(looper);
+                        mShowHideFrameAnimator = ObjectAnimator.ofInt(ViewportWindow.this,
+                                PROPERTY_NAME_ALPHA, MIN_ALPHA, MAX_ALPHA);
+
+                        Interpolator interpolator = new DecelerateInterpolator(2.5f);
+                        final long longAnimationDuration = context.getResources().getInteger(
+                                com.android.internal.R.integer.config_longAnimTime);
+
+                        mShowHideFrameAnimator.setInterpolator(interpolator);
+                        mShowHideFrameAnimator.setDuration(longAnimationDuration);
+                    }
+
+                    public void onFrameShownStateChanged(boolean shown, boolean animate) {
+                        obtainMessage(MSG_FRAME_SHOWN_STATE_CHANGED,
+                                shown ? 1 : 0, animate ? 1 : 0).sendToTarget();
+                    }
+
+                    @Override
+                    public void handleMessage(Message message) {
+                        switch (message.what) {
+                            case MSG_FRAME_SHOWN_STATE_CHANGED: {
+                                final boolean shown = message.arg1 == 1;
+                                final boolean animate = message.arg2 == 1;
+
+                                if (animate) {
+                                    if (mShowHideFrameAnimator.isRunning()) {
+                                        mShowHideFrameAnimator.reverse();
+                                    } else {
+                                        if (shown) {
+                                            mShowHideFrameAnimator.start();
+                                        } else {
+                                            mShowHideFrameAnimator.reverse();
+                                        }
+                                    }
+                                } else {
+                                    mShowHideFrameAnimator.cancel();
+                                    if (shown) {
+                                        setAlpha(MAX_ALPHA);
+                                    } else {
+                                        setAlpha(MIN_ALPHA);
+                                    }
+                                }
+                            } break;
+                        }
+                    }
+                }
+            }
+        }
+
+        private class MyHandler extends Handler {
+            public static final int MESSAGE_NOTIFY_MAGNIFICATION_REGION_CHANGED = 1;
+            public static final int MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED = 2;
+            public static final int MESSAGE_NOTIFY_USER_CONTEXT_CHANGED = 3;
+            public static final int MESSAGE_NOTIFY_ROTATION_CHANGED = 4;
+            public static final int MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED = 5;
+
+            public MyHandler(Looper looper) {
+                super(looper);
+            }
+
+            @Override
+            public void handleMessage(Message message) {
+                switch (message.what) {
+                    case MESSAGE_NOTIFY_MAGNIFICATION_REGION_CHANGED: {
+                        final SomeArgs args = (SomeArgs) message.obj;
+                        final Region magnifiedBounds = (Region) args.arg1;
+                        mCallbacks.onMagnificationRegionChanged(magnifiedBounds);
+                        magnifiedBounds.recycle();
+                    } break;
+
+                    case MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED: {
+                        SomeArgs args = (SomeArgs) message.obj;
+                        final int left = args.argi1;
+                        final int top = args.argi2;
+                        final int right = args.argi3;
+                        final int bottom = args.argi4;
+                        mCallbacks.onRectangleOnScreenRequested(left, top, right, bottom);
+                        args.recycle();
+                    } break;
+
+                    case MESSAGE_NOTIFY_USER_CONTEXT_CHANGED: {
+                        mCallbacks.onUserContextChanged();
+                    } break;
+
+                    case MESSAGE_NOTIFY_ROTATION_CHANGED: {
+                        final int rotation = message.arg1;
+                        mCallbacks.onRotationChanged(rotation);
+                    } break;
+
+                    case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : {
+                        synchronized (mWindowManagerService.mWindowMap) {
+                            if (mMagnifedViewport.isMagnifyingLocked()
+                                    || isForceShowingMagnifiableBoundsLocked()) {
+                                mMagnifedViewport.setMagnifiedRegionBorderShownLocked(true, true);
+                                mWindowManagerService.scheduleAnimationLocked();
+                            }
+                        }
+                    } break;
+                }
+            }
+        }
+    }
+
+    /**
+     * This class encapsulates the functionality related to computing the windows
+     * reported for accessibility purposes. These windows are all windows a sighted
+     * user can see on the screen.
+     */
+    private static final class WindowsForAccessibilityObserver {
+        private static final String LOG_TAG = TAG_WITH_CLASS_NAME ?
+                "WindowsForAccessibilityObserver" : TAG_WM;
+
+        private static final boolean DEBUG = false;
+
+        private final SparseArray<WindowState> mTempWindowStates =
+                new SparseArray<WindowState>();
+
+        private final List<WindowInfo> mOldWindows = new ArrayList<WindowInfo>();
+
+        private final Set<IBinder> mTempBinderSet = new ArraySet<IBinder>();
+
+        private final RectF mTempRectF = new RectF();
+
+        private final Matrix mTempMatrix = new Matrix();
+
+        private final Point mTempPoint = new Point();
+
+        private final Rect mTempRect = new Rect();
+
+        private final Region mTempRegion = new Region();
+
+        private final Region mTempRegion1 = new Region();
+
+        private final Context mContext;
+
+        private final WindowManagerService mWindowManagerService;
+
+        private final Handler mHandler;
+
+        private final WindowsForAccessibilityCallback mCallback;
+
+        private final long mRecurringAccessibilityEventsIntervalMillis;
+
+        public WindowsForAccessibilityObserver(WindowManagerService windowManagerService,
+                WindowsForAccessibilityCallback callback) {
+            mContext = windowManagerService.mContext;
+            mWindowManagerService = windowManagerService;
+            mCallback = callback;
+            mHandler = new MyHandler(mWindowManagerService.mH.getLooper());
+            mRecurringAccessibilityEventsIntervalMillis = ViewConfiguration
+                    .getSendRecurringAccessibilityEventsInterval();
+            computeChangedWindows();
+        }
+
+        public void performComputeChangedWindowsNotLocked() {
+            mHandler.removeMessages(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS);
+            computeChangedWindows();
+        }
+
+        public void scheduleComputeChangedWindowsLocked() {
+            if (!mHandler.hasMessages(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS)) {
+                mHandler.sendEmptyMessageDelayed(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS,
+                        mRecurringAccessibilityEventsIntervalMillis);
+            }
+        }
+
+        public void computeChangedWindows() {
+            if (DEBUG) {
+                Slog.i(LOG_TAG, "computeChangedWindows()");
+            }
+
+            boolean windowsChanged = false;
+            List<WindowInfo> windows = new ArrayList<WindowInfo>();
+
+            synchronized (mWindowManagerService.mWindowMap) {
+                // Do not send the windows if there is no current focus as
+                // the window manager is still looking for where to put it.
+                // We will do the work when we get a focus change callback.
+                if (mWindowManagerService.mCurrentFocus == null) {
+                    return;
+                }
+
+                WindowManager windowManager = (WindowManager)
+                        mContext.getSystemService(Context.WINDOW_SERVICE);
+                windowManager.getDefaultDisplay().getRealSize(mTempPoint);
+                final int screenWidth = mTempPoint.x;
+                final int screenHeight = mTempPoint.y;
+
+                Region unaccountedSpace = mTempRegion;
+                unaccountedSpace.set(0, 0, screenWidth, screenHeight);
+
+                final SparseArray<WindowState> visibleWindows = mTempWindowStates;
+                populateVisibleWindowsOnScreenLocked(visibleWindows);
+                Set<IBinder> addedWindows = mTempBinderSet;
+                addedWindows.clear();
+
+                boolean focusedWindowAdded = false;
+
+                final int visibleWindowCount = visibleWindows.size();
+                HashSet<Integer> skipRemainingWindowsForTasks = new HashSet<>();
+                for (int i = visibleWindowCount - 1; i >= 0; i--) {
+                    final WindowState windowState = visibleWindows.valueAt(i);
+                    final int flags = windowState.mAttrs.flags;
+                    final Task task = windowState.getTask();
+
+                    // If the window is part of a task that we're finished with - ignore.
+                    if (task != null && skipRemainingWindowsForTasks.contains(task.mTaskId)) {
+                        continue;
+                    }
+
+                    // If the window is not touchable - ignore.
+                    if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) {
+                        continue;
+                    }
+
+                    // Compute the bounds in the screen.
+                    final Rect boundsInScreen = mTempRect;
+                    computeWindowBoundsInScreen(windowState, boundsInScreen);
+
+                    // If the window is completely covered by other windows - ignore.
+                    if (unaccountedSpace.quickReject(boundsInScreen)) {
+                        continue;
+                    }
+
+                    // Add windows of certain types not covered by modal windows.
+                    if (isReportedWindowType(windowState.mAttrs.type)) {
+                        // Add the window to the ones to be reported.
+                        WindowInfo window = obtainPopulatedWindowInfo(windowState, boundsInScreen);
+                        addedWindows.add(window.token);
+                        windows.add(window);
+                        if (windowState.isFocused()) {
+                            focusedWindowAdded = true;
+                        }
+                    }
+
+                    // Account for the space this window takes if the window
+                    // is not an accessibility overlay which does not change
+                    // the reported windows.
+                    if (windowState.mAttrs.type !=
+                            WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) {
+                        unaccountedSpace.op(boundsInScreen, unaccountedSpace,
+                                Region.Op.REVERSE_DIFFERENCE);
+                    }
+
+                    // If a window is modal it prevents other windows from being touched
+                    if ((flags & (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                            | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)) == 0) {
+                        // Account for all space in the task, whether the windows in it are
+                        // touchable or not. The modal window blocks all touches from the task's
+                        // area.
+                        unaccountedSpace.op(windowState.getDisplayFrameLw(), unaccountedSpace,
+                                Region.Op.REVERSE_DIFFERENCE);
+
+                        if (task != null) {
+                            // If the window is associated with a particular task, we can skip the
+                            // rest of the windows for that task.
+                            skipRemainingWindowsForTasks.add(task.mTaskId);
+                            continue;
+                        } else {
+                            // If the window is not associated with a particular task, then it is
+                            // globally modal. In this case we can skip all remaining windows.
+                            break;
+                        }
+                    }
+                    // We figured out what is touchable for the entire screen - done.
+                    if (unaccountedSpace.isEmpty()) {
+                        break;
+                    }
+                }
+
+                // Always report the focused window.
+                if (!focusedWindowAdded) {
+                    for (int i = visibleWindowCount - 1; i >= 0; i--) {
+                        WindowState windowState = visibleWindows.valueAt(i);
+                        if (windowState.isFocused()) {
+                            // Compute the bounds in the screen.
+                            Rect boundsInScreen = mTempRect;
+                            computeWindowBoundsInScreen(windowState, boundsInScreen);
+
+                            // Add the window to the ones to be reported.
+                            WindowInfo window = obtainPopulatedWindowInfo(windowState,
+                                    boundsInScreen);
+                            addedWindows.add(window.token);
+                            windows.add(window);
+                            break;
+                        }
+                    }
+                }
+
+                // Remove child/parent references to windows that were not added.
+                final int windowCount = windows.size();
+                for (int i = 0; i < windowCount; i++) {
+                    WindowInfo window = windows.get(i);
+                    if (!addedWindows.contains(window.parentToken)) {
+                        window.parentToken = null;
+                    }
+                    if (window.childTokens != null) {
+                        final int childTokenCount = window.childTokens.size();
+                        for (int j = childTokenCount - 1; j >= 0; j--) {
+                            if (!addedWindows.contains(window.childTokens.get(j))) {
+                                window.childTokens.remove(j);
+                            }
+                        }
+                        // Leave the child token list if empty.
+                    }
+                }
+
+                visibleWindows.clear();
+                addedWindows.clear();
+
+                // We computed the windows and if they changed notify the client.
+                if (mOldWindows.size() != windows.size()) {
+                    // Different size means something changed.
+                    windowsChanged = true;
+                } else if (!mOldWindows.isEmpty() || !windows.isEmpty()) {
+                    // Since we always traverse windows from high to low layer
+                    // the old and new windows at the same index should be the
+                    // same, otherwise something changed.
+                    for (int i = 0; i < windowCount; i++) {
+                        WindowInfo oldWindow = mOldWindows.get(i);
+                        WindowInfo newWindow = windows.get(i);
+                        // We do not care for layer changes given the window
+                        // order does not change. This brings no new information
+                        // to the clients.
+                        if (windowChangedNoLayer(oldWindow, newWindow)) {
+                            windowsChanged = true;
+                            break;
+                        }
+                    }
+                }
+
+                if (windowsChanged) {
+                    cacheWindows(windows);
+                }
+            }
+
+            // Now we do not hold the lock, so send the windows over.
+            if (windowsChanged) {
+                if (DEBUG) {
+                    Log.i(LOG_TAG, "Windows changed:" + windows);
+                }
+                mCallback.onWindowsForAccessibilityChanged(windows);
+            } else {
+                if (DEBUG) {
+                    Log.i(LOG_TAG, "No windows changed.");
+                }
+            }
+
+            // Recycle the windows as we do not need them.
+            clearAndRecycleWindows(windows);
+        }
+
+        private void computeWindowBoundsInScreen(WindowState windowState, Rect outBounds) {
+            // Get the touchable frame.
+            Region touchableRegion = mTempRegion1;
+            windowState.getTouchableRegion(touchableRegion);
+            Rect touchableFrame = mTempRect;
+            touchableRegion.getBounds(touchableFrame);
+
+            // Move to origin as all transforms are captured by the matrix.
+            RectF windowFrame = mTempRectF;
+            windowFrame.set(touchableFrame);
+            windowFrame.offset(-windowState.mFrame.left, -windowState.mFrame.top);
+
+            // Map the frame to get what appears on the screen.
+            Matrix matrix = mTempMatrix;
+            populateTransformationMatrixLocked(windowState, matrix);
+            matrix.mapRect(windowFrame);
+
+            // Got the bounds.
+            outBounds.set((int) windowFrame.left, (int) windowFrame.top,
+                    (int) windowFrame.right, (int) windowFrame.bottom);
+        }
+
+        private static WindowInfo obtainPopulatedWindowInfo(
+                WindowState windowState, Rect boundsInScreen) {
+            final WindowInfo window = windowState.getWindowInfo();
+            window.boundsInScreen.set(boundsInScreen);
+            return window;
+        }
+
+        private void cacheWindows(List<WindowInfo> windows) {
+            final int oldWindowCount = mOldWindows.size();
+            for (int i = oldWindowCount - 1; i >= 0; i--) {
+                mOldWindows.remove(i).recycle();
+            }
+            final int newWindowCount = windows.size();
+            for (int i = 0; i < newWindowCount; i++) {
+                WindowInfo newWindow = windows.get(i);
+                mOldWindows.add(WindowInfo.obtain(newWindow));
+            }
+        }
+
+        private boolean windowChangedNoLayer(WindowInfo oldWindow, WindowInfo newWindow) {
+            if (oldWindow == newWindow) {
+                return false;
+            }
+            if (oldWindow == null) {
+                return true;
+            }
+            if (newWindow == null) {
+                return true;
+            }
+            if (oldWindow.type != newWindow.type) {
+                return true;
+            }
+            if (oldWindow.focused != newWindow.focused) {
+                return true;
+            }
+            if (oldWindow.token == null) {
+                if (newWindow.token != null) {
+                    return true;
+                }
+            } else if (!oldWindow.token.equals(newWindow.token)) {
+                return true;
+            }
+            if (oldWindow.parentToken == null) {
+                if (newWindow.parentToken != null) {
+                    return true;
+                }
+            } else if (!oldWindow.parentToken.equals(newWindow.parentToken)) {
+                return true;
+            }
+            if (!oldWindow.boundsInScreen.equals(newWindow.boundsInScreen)) {
+                return true;
+            }
+            if (oldWindow.childTokens != null && newWindow.childTokens != null
+                    && !oldWindow.childTokens.equals(newWindow.childTokens)) {
+                return true;
+            }
+            if (!TextUtils.equals(oldWindow.title, newWindow.title)) {
+                return true;
+            }
+            if (oldWindow.accessibilityIdOfAnchor != newWindow.accessibilityIdOfAnchor) {
+                return true;
+            }
+            return false;
+        }
+
+        private static void clearAndRecycleWindows(List<WindowInfo> windows) {
+            final int windowCount = windows.size();
+            for (int i = windowCount - 1; i >= 0; i--) {
+                windows.remove(i).recycle();
+            }
+        }
+
+        private static boolean isReportedWindowType(int windowType) {
+            return (windowType != WindowManager.LayoutParams.TYPE_WALLPAPER
+                    && windowType != WindowManager.LayoutParams.TYPE_BOOT_PROGRESS
+                    && windowType != WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY
+                    && windowType != WindowManager.LayoutParams.TYPE_DRAG
+                    && windowType != WindowManager.LayoutParams.TYPE_INPUT_CONSUMER
+                    && windowType != WindowManager.LayoutParams.TYPE_POINTER
+                    && windowType != TYPE_MAGNIFICATION_OVERLAY
+                    && windowType != WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY
+                    && windowType != WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY
+                    && windowType != WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
+        }
+
+        private void populateVisibleWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
+            final DisplayContent dc = mWindowManagerService.getDefaultDisplayContentLocked();
+            dc.forAllWindows((w) -> {
+                if (w.isVisibleLw()) {
+                    outWindows.put(w.mLayer, w);
+                }
+            }, false /* traverseTopToBottom */ );
+        }
+
+        private class MyHandler extends Handler {
+            public static final int MESSAGE_COMPUTE_CHANGED_WINDOWS = 1;
+
+            public MyHandler(Looper looper) {
+                super(looper, null, false);
+            }
+
+            @Override
+            @SuppressWarnings("unchecked")
+            public void handleMessage(Message message) {
+                switch (message.what) {
+                    case MESSAGE_COMPUTE_CHANGED_WINDOWS: {
+                        computeChangedWindows();
+                    } break;
+                }
+            }
+        }
+    }
+}
diff --git a/com/android/server/wm/AlertWindowNotification.java b/com/android/server/wm/AlertWindowNotification.java
new file mode 100644
index 0000000..3f32079
--- /dev/null
+++ b/com/android/server/wm/AlertWindowNotification.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static android.app.NotificationManager.IMPORTANCE_MIN;
+import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
+import static android.content.Context.NOTIFICATION_SERVICE;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.provider.Settings.ACTION_MANAGE_OVERLAY_PERMISSION;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+
+import android.net.Uri;
+import com.android.internal.R;
+import com.android.server.policy.IconUtilities;
+
+/** Displays an ongoing notification for a process displaying an alert window */
+class AlertWindowNotification {
+    private static final String CHANNEL_PREFIX = "com.android.server.wm.AlertWindowNotification - ";
+    private static final int NOTIFICATION_ID = 0;
+
+    private static int sNextRequestCode = 0;
+    private static NotificationChannelGroup sChannelGroup;
+    private final int mRequestCode;
+    private final WindowManagerService mService;
+    private String mNotificationTag;
+    private final NotificationManager mNotificationManager;
+    private final String mPackageName;
+    private boolean mPosted;
+    private IconUtilities mIconUtilities;
+
+    AlertWindowNotification(WindowManagerService service, String packageName) {
+        mService = service;
+        mPackageName = packageName;
+        mNotificationManager =
+                (NotificationManager) mService.mContext.getSystemService(NOTIFICATION_SERVICE);
+        mNotificationTag = CHANNEL_PREFIX + mPackageName;
+        mRequestCode = sNextRequestCode++;
+        mIconUtilities = new IconUtilities(mService.mContext);
+    }
+
+    void post() {
+        // We can't create/post the notification while the window manager lock is held since it will
+        // end up calling into activity manager. So, we post a message to do it later.
+        mService.mH.post(this::onPostNotification);
+    }
+
+    /** Cancels the notification */
+    void cancel() {
+        // We can't call into NotificationManager with WM lock held since it might call into AM.
+        // So, we post a message to do it later.
+        mService.mH.post(this::onCancelNotification);
+    }
+
+    /** Don't call with the window manager lock held! */
+    private void onCancelNotification() {
+        if (!mPosted) {
+            // Notification isn't currently posted...
+            return;
+        }
+        mPosted = false;
+        mNotificationManager.cancel(mNotificationTag, NOTIFICATION_ID);
+    }
+
+    /** Don't call with the window manager lock held! */
+    private void onPostNotification() {
+        if (mPosted) {
+            // Notification already posted...
+            return;
+        }
+        mPosted = true;
+
+        final Context context = mService.mContext;
+        final PackageManager pm = context.getPackageManager();
+        final ApplicationInfo aInfo = getApplicationInfo(pm, mPackageName);
+        final String appName = (aInfo != null)
+                ? pm.getApplicationLabel(aInfo).toString() : mPackageName;
+
+        createNotificationChannel(context, appName);
+
+        final String message = context.getString(R.string.alert_windows_notification_message,
+                appName);
+        final Notification.Builder builder = new Notification.Builder(context, mNotificationTag)
+                .setOngoing(true)
+                .setContentTitle(
+                        context.getString(R.string.alert_windows_notification_title, appName))
+                .setContentText(message)
+                .setSmallIcon(R.drawable.alert_window_layer)
+                .setColor(context.getColor(R.color.system_notification_accent_color))
+                .setStyle(new Notification.BigTextStyle().bigText(message))
+                .setLocalOnly(true)
+                .setContentIntent(getContentIntent(context, mPackageName));
+
+        if (aInfo != null) {
+            final Drawable drawable = pm.getApplicationIcon(aInfo);
+            if (drawable != null) {
+                final Bitmap bitmap = mIconUtilities.createIconBitmap(drawable);
+                builder.setLargeIcon(bitmap);
+            }
+        }
+
+        mNotificationManager.notify(mNotificationTag, NOTIFICATION_ID, builder.build());
+    }
+
+    private PendingIntent getContentIntent(Context context, String packageName) {
+        final Intent intent = new Intent(ACTION_MANAGE_OVERLAY_PERMISSION,
+                Uri.fromParts("package", packageName, null));
+        intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+        // Calls into activity manager...
+        return PendingIntent.getActivity(context, mRequestCode, intent, FLAG_CANCEL_CURRENT);
+    }
+
+    private void createNotificationChannel(Context context, String appName) {
+        if (sChannelGroup == null) {
+            sChannelGroup = new NotificationChannelGroup(CHANNEL_PREFIX,
+                    mService.mContext.getString(
+                            R.string.alert_windows_notification_channel_group_name));
+            mNotificationManager.createNotificationChannelGroup(sChannelGroup);
+        }
+
+        final String nameChannel =
+                context.getString(R.string.alert_windows_notification_channel_name, appName);
+        final NotificationChannel channel =
+                new NotificationChannel(mNotificationTag, nameChannel, IMPORTANCE_MIN);
+        channel.enableLights(false);
+        channel.enableVibration(false);
+        channel.setBlockableSystem(true);
+        channel.setGroup(sChannelGroup.getId());
+        mNotificationManager.createNotificationChannel(channel);
+    }
+
+
+    private ApplicationInfo getApplicationInfo(PackageManager pm, String packageName) {
+        try {
+            return pm.getApplicationInfo(packageName, 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            return null;
+        }
+    }
+}
diff --git a/com/android/server/wm/AppTransition.java b/com/android/server/wm/AppTransition.java
new file mode 100644
index 0000000..c19ede0
--- /dev/null
+++ b/com/android/server/wm/AppTransition.java
@@ -0,0 +1,2110 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.WindowManagerInternal.AppTransitionListener;
+import static com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_activityOpenEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_activityOpenExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_launchTaskBehindSourceAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_launchTaskBehindTargetAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskCloseEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskCloseExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskOpenEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskOpenExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskToBackEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskToBackExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskToFrontEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskToFrontExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperCloseEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperCloseExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
+import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
+import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE;
+import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
+import static com.android.server.wm.proto.AppTransitionProto.APP_TRANSITION_STATE;
+import static com.android.server.wm.proto.AppTransitionProto.LAST_USED_APP_TRANSITION;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.GraphicBuffer;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.Debug;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
+import android.view.AppTransitionAnimationSpec;
+import android.view.IAppTransitionAnimationSpecsFuture;
+import android.view.WindowManager;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.AnimationUtils;
+import android.view.animation.ClipRectAnimation;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+import android.view.animation.ScaleAnimation;
+import android.view.animation.TranslateAnimation;
+
+import com.android.internal.util.DumpUtils.Dump;
+import com.android.server.AttributeCache;
+import com.android.server.wm.WindowManagerService.H;
+import com.android.server.wm.animation.ClipRectLRAnimation;
+import com.android.server.wm.animation.ClipRectTBAnimation;
+import com.android.server.wm.animation.CurvedTranslateAnimation;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+// State management of app transitions.  When we are preparing for a
+// transition, mNextAppTransition will be the kind of transition to
+// perform or TRANSIT_NONE if we are not waiting.  If we are waiting,
+// mOpeningApps and mClosingApps are the lists of tokens that will be
+// made visible or hidden at the next transition.
+public class AppTransition implements Dump {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "AppTransition" : TAG_WM;
+    private static final int CLIP_REVEAL_TRANSLATION_Y_DP = 8;
+
+    /** Not set up for a transition. */
+    public static final int TRANSIT_UNSET = -1;
+    /** No animation for transition. */
+    public static final int TRANSIT_NONE = 0;
+    /** A window in a new activity is being opened on top of an existing one in the same task. */
+    public static final int TRANSIT_ACTIVITY_OPEN = 6;
+    /** The window in the top-most activity is being closed to reveal the
+     * previous activity in the same task. */
+    public static final int TRANSIT_ACTIVITY_CLOSE = 7;
+    /** A window in a new task is being opened on top of an existing one
+     * in another activity's task. */
+    public static final int TRANSIT_TASK_OPEN = 8;
+    /** A window in the top-most activity is being closed to reveal the
+     * previous activity in a different task. */
+    public static final int TRANSIT_TASK_CLOSE = 9;
+    /** A window in an existing task is being displayed on top of an existing one
+     * in another activity's task. */
+    public static final int TRANSIT_TASK_TO_FRONT = 10;
+    /** A window in an existing task is being put below all other tasks. */
+    public static final int TRANSIT_TASK_TO_BACK = 11;
+    /** A window in a new activity that doesn't have a wallpaper is being opened on top of one that
+     * does, effectively closing the wallpaper. */
+    public static final int TRANSIT_WALLPAPER_CLOSE = 12;
+    /** A window in a new activity that does have a wallpaper is being opened on one that didn't,
+     * effectively opening the wallpaper. */
+    public static final int TRANSIT_WALLPAPER_OPEN = 13;
+    /** A window in a new activity is being opened on top of an existing one, and both are on top
+     * of the wallpaper. */
+    public static final int TRANSIT_WALLPAPER_INTRA_OPEN = 14;
+    /** The window in the top-most activity is being closed to reveal the previous activity, and
+     * both are on top of the wallpaper. */
+    public static final int TRANSIT_WALLPAPER_INTRA_CLOSE = 15;
+    /** A window in a new task is being opened behind an existing one in another activity's task.
+     * The new window will show briefly and then be gone. */
+    public static final int TRANSIT_TASK_OPEN_BEHIND = 16;
+    /** A window in a task is being animated in-place. */
+    public static final int TRANSIT_TASK_IN_PLACE = 17;
+    /** An activity is being relaunched (e.g. due to configuration change). */
+    public static final int TRANSIT_ACTIVITY_RELAUNCH = 18;
+    /** A task is being docked from recents. */
+    public static final int TRANSIT_DOCK_TASK_FROM_RECENTS = 19;
+    /** Keyguard is going away */
+    public static final int TRANSIT_KEYGUARD_GOING_AWAY = 20;
+    /** Keyguard is going away with showing an activity behind that requests wallpaper */
+    public static final int TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER = 21;
+    /** Keyguard is being occluded */
+    public static final int TRANSIT_KEYGUARD_OCCLUDE = 22;
+    /** Keyguard is being unoccluded */
+    public static final int TRANSIT_KEYGUARD_UNOCCLUDE = 23;
+
+    /** Transition flag: Keyguard is going away, but keeping the notification shade open */
+    public static final int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE = 0x1;
+    /** Transition flag: Keyguard is going away, but doesn't want an animation for it */
+    public static final int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION = 0x2;
+    /** Transition flag: Keyguard is going away while it was showing the system wallpaper. */
+    public static final int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER = 0x4;
+
+    /** Fraction of animation at which the recents thumbnail stays completely transparent */
+    private static final float RECENTS_THUMBNAIL_FADEIN_FRACTION = 0.5f;
+    /** Fraction of animation at which the recents thumbnail becomes completely transparent */
+    private static final float RECENTS_THUMBNAIL_FADEOUT_FRACTION = 0.5f;
+
+    static final int DEFAULT_APP_TRANSITION_DURATION = 336;
+
+    /** Interpolator to be used for animations that respond directly to a touch */
+    static final Interpolator TOUCH_RESPONSE_INTERPOLATOR =
+            new PathInterpolator(0.3f, 0f, 0.1f, 1f);
+
+    private static final Interpolator THUMBNAIL_DOCK_INTERPOLATOR =
+            new PathInterpolator(0.85f, 0f, 1f, 1f);
+
+    /**
+     * Maximum duration for the clip reveal animation. This is used when there is a lot of movement
+     * involved, to make it more understandable.
+     */
+    private static final int MAX_CLIP_REVEAL_TRANSITION_DURATION = 420;
+    private static final int THUMBNAIL_APP_TRANSITION_DURATION = 336;
+    private static final long APP_TRANSITION_TIMEOUT_MS = 5000;
+
+    private final Context mContext;
+    private final WindowManagerService mService;
+
+    private int mNextAppTransition = TRANSIT_UNSET;
+    private int mNextAppTransitionFlags = 0;
+    private int mLastUsedAppTransition = TRANSIT_UNSET;
+    private String mLastOpeningApp;
+    private String mLastClosingApp;
+
+    private static final int NEXT_TRANSIT_TYPE_NONE = 0;
+    private static final int NEXT_TRANSIT_TYPE_CUSTOM = 1;
+    private static final int NEXT_TRANSIT_TYPE_SCALE_UP = 2;
+    private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP = 3;
+    private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN = 4;
+    private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP = 5;
+    private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN = 6;
+    private static final int NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE = 7;
+    private static final int NEXT_TRANSIT_TYPE_CLIP_REVEAL = 8;
+    private int mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
+
+    // These are the possible states for the enter/exit activities during a thumbnail transition
+    private static final int THUMBNAIL_TRANSITION_ENTER_SCALE_UP = 0;
+    private static final int THUMBNAIL_TRANSITION_EXIT_SCALE_UP = 1;
+    private static final int THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN = 2;
+    private static final int THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN = 3;
+
+    private String mNextAppTransitionPackage;
+    // Used for thumbnail transitions. True if we're scaling up, false if scaling down
+    private boolean mNextAppTransitionScaleUp;
+    private IRemoteCallback mNextAppTransitionCallback;
+    private IRemoteCallback mNextAppTransitionFutureCallback;
+    private IRemoteCallback mAnimationFinishedCallback;
+    private int mNextAppTransitionEnter;
+    private int mNextAppTransitionExit;
+    private int mNextAppTransitionInPlace;
+
+    // Keyed by task id.
+    private final SparseArray<AppTransitionAnimationSpec> mNextAppTransitionAnimationsSpecs
+            = new SparseArray<>();
+    private IAppTransitionAnimationSpecsFuture mNextAppTransitionAnimationsSpecsFuture;
+    private boolean mNextAppTransitionAnimationsSpecsPending;
+    private AppTransitionAnimationSpec mDefaultNextAppTransitionAnimationSpec;
+
+    private Rect mNextAppTransitionInsets = new Rect();
+
+    private Rect mTmpFromClipRect = new Rect();
+    private Rect mTmpToClipRect = new Rect();
+
+    private final Rect mTmpRect = new Rect();
+
+    private final static int APP_STATE_IDLE = 0;
+    private final static int APP_STATE_READY = 1;
+    private final static int APP_STATE_RUNNING = 2;
+    private final static int APP_STATE_TIMEOUT = 3;
+    private int mAppTransitionState = APP_STATE_IDLE;
+
+    private final int mConfigShortAnimTime;
+    private final Interpolator mDecelerateInterpolator;
+    private final Interpolator mThumbnailFadeInInterpolator;
+    private final Interpolator mThumbnailFadeOutInterpolator;
+    private final Interpolator mLinearOutSlowInInterpolator;
+    private final Interpolator mFastOutLinearInInterpolator;
+    private final Interpolator mFastOutSlowInInterpolator;
+    private final Interpolator mClipHorizontalInterpolator = new PathInterpolator(0, 0, 0.4f, 1f);
+
+    private final int mClipRevealTranslationY;
+
+    private int mCurrentUserId = 0;
+    private long mLastClipRevealTransitionDuration = DEFAULT_APP_TRANSITION_DURATION;
+
+    private final ArrayList<AppTransitionListener> mListeners = new ArrayList<>();
+    private final ExecutorService mDefaultExecutor = Executors.newSingleThreadExecutor();
+
+    private int mLastClipRevealMaxTranslation;
+    private boolean mLastHadClipReveal;
+    private boolean mProlongedAnimationsEnded;
+
+    private final boolean mGridLayoutRecentsEnabled;
+    private final boolean mLowRamRecentsEnabled;
+
+    AppTransition(Context context, WindowManagerService service) {
+        mContext = context;
+        mService = service;
+        mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+                com.android.internal.R.interpolator.linear_out_slow_in);
+        mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
+                com.android.internal.R.interpolator.fast_out_linear_in);
+        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+                com.android.internal.R.interpolator.fast_out_slow_in);
+        mConfigShortAnimTime = context.getResources().getInteger(
+                com.android.internal.R.integer.config_shortAnimTime);
+        mDecelerateInterpolator = AnimationUtils.loadInterpolator(context,
+                com.android.internal.R.interpolator.decelerate_cubic);
+        mThumbnailFadeInInterpolator = new Interpolator() {
+            @Override
+            public float getInterpolation(float input) {
+                // Linear response for first fraction, then complete after that.
+                if (input < RECENTS_THUMBNAIL_FADEIN_FRACTION) {
+                    return 0f;
+                }
+                float t = (input - RECENTS_THUMBNAIL_FADEIN_FRACTION) /
+                        (1f - RECENTS_THUMBNAIL_FADEIN_FRACTION);
+                return mFastOutLinearInInterpolator.getInterpolation(t);
+            }
+        };
+        mThumbnailFadeOutInterpolator = new Interpolator() {
+            @Override
+            public float getInterpolation(float input) {
+                // Linear response for first fraction, then complete after that.
+                if (input < RECENTS_THUMBNAIL_FADEOUT_FRACTION) {
+                    float t = input / RECENTS_THUMBNAIL_FADEOUT_FRACTION;
+                    return mLinearOutSlowInInterpolator.getInterpolation(t);
+                }
+                return 1f;
+            }
+        };
+        mClipRevealTranslationY = (int) (CLIP_REVEAL_TRANSLATION_Y_DP
+                * mContext.getResources().getDisplayMetrics().density);
+        mGridLayoutRecentsEnabled = SystemProperties.getBoolean("ro.recents.grid", false);
+        mLowRamRecentsEnabled = ActivityManager.isLowRamDeviceStatic();
+    }
+
+    boolean isTransitionSet() {
+        return mNextAppTransition != TRANSIT_UNSET;
+    }
+
+    boolean isTransitionEqual(int transit) {
+        return mNextAppTransition == transit;
+    }
+
+    int getAppTransition() {
+        return mNextAppTransition;
+     }
+
+    private void setAppTransition(int transit, int flags) {
+        mNextAppTransition = transit;
+        mNextAppTransitionFlags |= flags;
+        setLastAppTransition(TRANSIT_UNSET, null, null);
+        updateBooster();
+    }
+
+    void setLastAppTransition(int transit, AppWindowToken openingApp, AppWindowToken closingApp) {
+        mLastUsedAppTransition = transit;
+        mLastOpeningApp = "" + openingApp;
+        mLastClosingApp = "" + closingApp;
+    }
+
+    boolean isReady() {
+        return mAppTransitionState == APP_STATE_READY
+                || mAppTransitionState == APP_STATE_TIMEOUT;
+    }
+
+    void setReady() {
+        setAppTransitionState(APP_STATE_READY);
+        fetchAppTransitionSpecsFromFuture();
+    }
+
+    boolean isRunning() {
+        return mAppTransitionState == APP_STATE_RUNNING;
+    }
+
+    void setIdle() {
+        setAppTransitionState(APP_STATE_IDLE);
+    }
+
+    boolean isTimeout() {
+        return mAppTransitionState == APP_STATE_TIMEOUT;
+    }
+
+    void setTimeout() {
+        setAppTransitionState(APP_STATE_TIMEOUT);
+    }
+
+    GraphicBuffer getAppTransitionThumbnailHeader(int taskId) {
+        AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(taskId);
+        if (spec == null) {
+            spec = mDefaultNextAppTransitionAnimationSpec;
+        }
+        return spec != null ? spec.buffer : null;
+    }
+
+    /** Returns whether the next thumbnail transition is aspect scaled up. */
+    boolean isNextThumbnailTransitionAspectScaled() {
+        return mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP ||
+                mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
+    }
+
+    /** Returns whether the next thumbnail transition is scaling up. */
+    boolean isNextThumbnailTransitionScaleUp() {
+        return mNextAppTransitionScaleUp;
+    }
+
+    boolean isNextAppTransitionThumbnailUp() {
+        return mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP ||
+                mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP;
+    }
+
+    boolean isNextAppTransitionThumbnailDown() {
+        return mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN ||
+                mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
+    }
+
+    /**
+     * @return true if and only if we are currently fetching app transition specs from the future
+     *         passed into {@link #overridePendingAppTransitionMultiThumbFuture}
+     */
+    boolean isFetchingAppTransitionsSpecs() {
+        return mNextAppTransitionAnimationsSpecsPending;
+    }
+
+    private boolean prepare() {
+        if (!isRunning()) {
+            setAppTransitionState(APP_STATE_IDLE);
+            notifyAppTransitionPendingLocked();
+            mLastHadClipReveal = false;
+            mLastClipRevealMaxTranslation = 0;
+            mLastClipRevealTransitionDuration = DEFAULT_APP_TRANSITION_DURATION;
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * @return bit-map of WindowManagerPolicy#FINISH_LAYOUT_REDO_* to indicate whether another
+     *         layout pass needs to be done
+     */
+    int goodToGo(int transit, AppWindowAnimator topOpeningAppAnimator,
+            AppWindowAnimator topClosingAppAnimator, ArraySet<AppWindowToken> openingApps,
+            ArraySet<AppWindowToken> closingApps) {
+        mNextAppTransition = TRANSIT_UNSET;
+        mNextAppTransitionFlags = 0;
+        setAppTransitionState(APP_STATE_RUNNING);
+        int redoLayout = notifyAppTransitionStartingLocked(transit,
+                topOpeningAppAnimator != null ? topOpeningAppAnimator.mAppToken.token : null,
+                topClosingAppAnimator != null ? topClosingAppAnimator.mAppToken.token : null,
+                topOpeningAppAnimator != null ? topOpeningAppAnimator.animation : null,
+                topClosingAppAnimator != null ? topClosingAppAnimator.animation : null);
+        mService.getDefaultDisplayContentLocked().getDockedDividerController()
+                .notifyAppTransitionStarting(openingApps, transit);
+
+        // Prolong the start for the transition when docking a task from recents, unless recents
+        // ended it already then we don't need to wait.
+        if (transit == TRANSIT_DOCK_TASK_FROM_RECENTS && !mProlongedAnimationsEnded) {
+            for (int i = openingApps.size() - 1; i >= 0; i--) {
+                final AppWindowAnimator appAnimator = openingApps.valueAt(i).mAppAnimator;
+                appAnimator.startProlongAnimation(PROLONG_ANIMATION_AT_START);
+            }
+        }
+        return redoLayout;
+    }
+
+    /**
+     * Let the transitions manager know that the somebody wanted to end the prolonged animations.
+     */
+    void notifyProlongedAnimationsEnded() {
+        mProlongedAnimationsEnded = true;
+    }
+
+    void clear() {
+        mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
+        mNextAppTransitionPackage = null;
+        mNextAppTransitionAnimationsSpecs.clear();
+        mNextAppTransitionAnimationsSpecsFuture = null;
+        mDefaultNextAppTransitionAnimationSpec = null;
+        mAnimationFinishedCallback = null;
+        mProlongedAnimationsEnded = false;
+    }
+
+    void freeze() {
+        final int transit = mNextAppTransition;
+        setAppTransition(AppTransition.TRANSIT_UNSET, 0 /* flags */);
+        clear();
+        setReady();
+        notifyAppTransitionCancelledLocked(transit);
+    }
+
+    private void setAppTransitionState(int state) {
+        mAppTransitionState = state;
+        updateBooster();
+    }
+
+    /**
+     * Updates whether we currently boost wm locked sections and the animation thread. We want to
+     * boost the priorities to a more important value whenever an app transition is going to happen
+     * soon or an app transition is running.
+     */
+    private void updateBooster() {
+        WindowManagerService.sThreadPriorityBooster.setAppTransitionRunning(
+                mNextAppTransition != TRANSIT_UNSET || mAppTransitionState == APP_STATE_READY
+                        || mAppTransitionState == APP_STATE_RUNNING);
+    }
+
+    void registerListenerLocked(AppTransitionListener listener) {
+        mListeners.add(listener);
+    }
+
+    public void notifyAppTransitionFinishedLocked(IBinder token) {
+        for (int i = 0; i < mListeners.size(); i++) {
+            mListeners.get(i).onAppTransitionFinishedLocked(token);
+        }
+    }
+
+    private void notifyAppTransitionPendingLocked() {
+        for (int i = 0; i < mListeners.size(); i++) {
+            mListeners.get(i).onAppTransitionPendingLocked();
+        }
+    }
+
+    private void notifyAppTransitionCancelledLocked(int transit) {
+        for (int i = 0; i < mListeners.size(); i++) {
+            mListeners.get(i).onAppTransitionCancelledLocked(transit);
+        }
+    }
+
+    private int notifyAppTransitionStartingLocked(int transit, IBinder openToken,
+            IBinder closeToken, Animation openAnimation, Animation closeAnimation) {
+        int redoLayout = 0;
+        for (int i = 0; i < mListeners.size(); i++) {
+            redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(transit, openToken,
+                    closeToken, openAnimation, closeAnimation);
+        }
+        return redoLayout;
+    }
+
+    private AttributeCache.Entry getCachedAnimations(WindowManager.LayoutParams lp) {
+        if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: layout params pkg="
+                + (lp != null ? lp.packageName : null)
+                + " resId=0x" + (lp != null ? Integer.toHexString(lp.windowAnimations) : null));
+        if (lp != null && lp.windowAnimations != 0) {
+            // If this is a system resource, don't try to load it from the
+            // application resources.  It is nice to avoid loading application
+            // resources if we can.
+            String packageName = lp.packageName != null ? lp.packageName : "android";
+            int resId = lp.windowAnimations;
+            if ((resId&0xFF000000) == 0x01000000) {
+                packageName = "android";
+            }
+            if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: picked package="
+                    + packageName);
+            return AttributeCache.instance().get(packageName, resId,
+                    com.android.internal.R.styleable.WindowAnimation, mCurrentUserId);
+        }
+        return null;
+    }
+
+    private AttributeCache.Entry getCachedAnimations(String packageName, int resId) {
+        if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: package="
+                + packageName + " resId=0x" + Integer.toHexString(resId));
+        if (packageName != null) {
+            if ((resId&0xFF000000) == 0x01000000) {
+                packageName = "android";
+            }
+            if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: picked package="
+                    + packageName);
+            return AttributeCache.instance().get(packageName, resId,
+                    com.android.internal.R.styleable.WindowAnimation, mCurrentUserId);
+        }
+        return null;
+    }
+
+    Animation loadAnimationAttr(WindowManager.LayoutParams lp, int animAttr) {
+        int anim = 0;
+        Context context = mContext;
+        if (animAttr >= 0) {
+            AttributeCache.Entry ent = getCachedAnimations(lp);
+            if (ent != null) {
+                context = ent.context;
+                anim = ent.array.getResourceId(animAttr, 0);
+            }
+        }
+        if (anim != 0) {
+            return AnimationUtils.loadAnimation(context, anim);
+        }
+        return null;
+    }
+
+    Animation loadAnimationRes(WindowManager.LayoutParams lp, int resId) {
+        Context context = mContext;
+        if (resId >= 0) {
+            AttributeCache.Entry ent = getCachedAnimations(lp);
+            if (ent != null) {
+                context = ent.context;
+            }
+            return AnimationUtils.loadAnimation(context, resId);
+        }
+        return null;
+    }
+
+    private Animation loadAnimationRes(String packageName, int resId) {
+        int anim = 0;
+        Context context = mContext;
+        if (resId >= 0) {
+            AttributeCache.Entry ent = getCachedAnimations(packageName, resId);
+            if (ent != null) {
+                context = ent.context;
+                anim = resId;
+            }
+        }
+        if (anim != 0) {
+            return AnimationUtils.loadAnimation(context, anim);
+        }
+        return null;
+    }
+
+    /**
+     * Compute the pivot point for an animation that is scaling from a small
+     * rect on screen to a larger rect.  The pivot point varies depending on
+     * the distance between the inner and outer edges on both sides.  This
+     * function computes the pivot point for one dimension.
+     * @param startPos  Offset from left/top edge of outer rectangle to
+     * left/top edge of inner rectangle.
+     * @param finalScale The scaling factor between the size of the outer
+     * and inner rectangles.
+     */
+    private static float computePivot(int startPos, float finalScale) {
+
+        /*
+        Theorem of intercepting lines:
+
+          +      +   +-----------------------------------------------+
+          |      |   |                                               |
+          |      |   |                                               |
+          |      |   |                                               |
+          |      |   |                                               |
+        x |    y |   |                                               |
+          |      |   |                                               |
+          |      |   |                                               |
+          |      |   |                                               |
+          |      |   |                                               |
+          |      +   |             +--------------------+            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             +--------------------+            |
+          |          |                                               |
+          |          |                                               |
+          |          |                                               |
+          |          |                                               |
+          |          |                                               |
+          |          |                                               |
+          |          |                                               |
+          |          +-----------------------------------------------+
+          |
+          |
+          |
+          |
+          |
+          |
+          |
+          |
+          |
+          +                                 ++
+                                         p  ++
+
+        scale = (x - y) / x
+        <=> x = -y / (scale - 1)
+        */
+        final float denom = finalScale-1;
+        if (Math.abs(denom) < .0001f) {
+            return startPos;
+        }
+        return -startPos / denom;
+    }
+
+    private Animation createScaleUpAnimationLocked(int transit, boolean enter,
+            Rect containingFrame) {
+        Animation a;
+        getDefaultNextAppTransitionStartRect(mTmpRect);
+        final int appWidth = containingFrame.width();
+        final int appHeight = containingFrame.height();
+        if (enter) {
+            // Entering app zooms out from the center of the initial rect.
+            float scaleW = mTmpRect.width() / (float) appWidth;
+            float scaleH = mTmpRect.height() / (float) appHeight;
+            Animation scale = new ScaleAnimation(scaleW, 1, scaleH, 1,
+                    computePivot(mTmpRect.left, scaleW),
+                    computePivot(mTmpRect.top, scaleH));
+            scale.setInterpolator(mDecelerateInterpolator);
+
+            Animation alpha = new AlphaAnimation(0, 1);
+            alpha.setInterpolator(mThumbnailFadeOutInterpolator);
+
+            AnimationSet set = new AnimationSet(false);
+            set.addAnimation(scale);
+            set.addAnimation(alpha);
+            set.setDetachWallpaper(true);
+            a = set;
+        } else  if (transit == TRANSIT_WALLPAPER_INTRA_OPEN ||
+                    transit == TRANSIT_WALLPAPER_INTRA_CLOSE) {
+            // If we are on top of the wallpaper, we need an animation that
+            // correctly handles the wallpaper staying static behind all of
+            // the animated elements.  To do this, will just have the existing
+            // element fade out.
+            a = new AlphaAnimation(1, 0);
+            a.setDetachWallpaper(true);
+        } else {
+            // For normal animations, the exiting element just holds in place.
+            a = new AlphaAnimation(1, 1);
+        }
+
+        // Pick the desired duration.  If this is an inter-activity transition,
+        // it  is the standard duration for that.  Otherwise we use the longer
+        // task transition duration.
+        final long duration;
+        switch (transit) {
+            case TRANSIT_ACTIVITY_OPEN:
+            case TRANSIT_ACTIVITY_CLOSE:
+                duration = mConfigShortAnimTime;
+                break;
+            default:
+                duration = DEFAULT_APP_TRANSITION_DURATION;
+                break;
+        }
+        a.setDuration(duration);
+        a.setFillAfter(true);
+        a.setInterpolator(mDecelerateInterpolator);
+        a.initialize(appWidth, appHeight, appWidth, appHeight);
+        return a;
+    }
+
+    private void getDefaultNextAppTransitionStartRect(Rect rect) {
+        if (mDefaultNextAppTransitionAnimationSpec == null ||
+                mDefaultNextAppTransitionAnimationSpec.rect == null) {
+            Slog.e(TAG, "Starting rect for app requested, but none available", new Throwable());
+            rect.setEmpty();
+        } else {
+            rect.set(mDefaultNextAppTransitionAnimationSpec.rect);
+        }
+    }
+
+    void getNextAppTransitionStartRect(int taskId, Rect rect) {
+        AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(taskId);
+        if (spec == null) {
+            spec = mDefaultNextAppTransitionAnimationSpec;
+        }
+        if (spec == null || spec.rect == null) {
+            Slog.e(TAG, "Starting rect for task: " + taskId + " requested, but not available",
+                    new Throwable());
+            rect.setEmpty();
+        } else {
+            rect.set(spec.rect);
+        }
+    }
+
+    private void putDefaultNextAppTransitionCoordinates(int left, int top, int width, int height,
+            GraphicBuffer buffer) {
+        mDefaultNextAppTransitionAnimationSpec = new AppTransitionAnimationSpec(-1 /* taskId */,
+                buffer, new Rect(left, top, left + width, top + height));
+    }
+
+    /**
+     * @return the duration of the last clip reveal animation
+     */
+    long getLastClipRevealTransitionDuration() {
+        return mLastClipRevealTransitionDuration;
+    }
+
+    /**
+     * @return the maximum distance the app surface is traveling of the last clip reveal animation
+     */
+    int getLastClipRevealMaxTranslation() {
+        return mLastClipRevealMaxTranslation;
+    }
+
+    /**
+     * @return true if in the last app transition had a clip reveal animation, false otherwise
+     */
+    boolean hadClipRevealAnimation() {
+        return mLastHadClipReveal;
+    }
+
+    /**
+     * Calculates the duration for the clip reveal animation. If the clip is "cut off", meaning that
+     * the start rect is outside of the target rect, and there is a lot of movement going on.
+     *
+     * @param cutOff whether the start rect was not fully contained by the end rect
+     * @param translationX the total translation the surface moves in x direction
+     * @param translationY the total translation the surfaces moves in y direction
+     * @param displayFrame our display frame
+     *
+     * @return the duration of the clip reveal animation, in milliseconds
+     */
+    private long calculateClipRevealTransitionDuration(boolean cutOff, float translationX,
+            float translationY, Rect displayFrame) {
+        if (!cutOff) {
+            return DEFAULT_APP_TRANSITION_DURATION;
+        }
+        final float fraction = Math.max(Math.abs(translationX) / displayFrame.width(),
+                Math.abs(translationY) / displayFrame.height());
+        return (long) (DEFAULT_APP_TRANSITION_DURATION + fraction *
+                (MAX_CLIP_REVEAL_TRANSITION_DURATION - DEFAULT_APP_TRANSITION_DURATION));
+    }
+
+    private Animation createClipRevealAnimationLocked(int transit, boolean enter, Rect appFrame,
+            Rect displayFrame) {
+        final Animation anim;
+        if (enter) {
+            final int appWidth = appFrame.width();
+            final int appHeight = appFrame.height();
+
+            // mTmpRect will contain an area around the launcher icon that was pressed. We will
+            // clip reveal from that area in the final area of the app.
+            getDefaultNextAppTransitionStartRect(mTmpRect);
+
+            float t = 0f;
+            if (appHeight > 0) {
+                t = (float) mTmpRect.top / displayFrame.height();
+            }
+            int translationY = mClipRevealTranslationY + (int)(displayFrame.height() / 7f * t);
+            int translationX = 0;
+            int translationYCorrection = translationY;
+            int centerX = mTmpRect.centerX();
+            int centerY = mTmpRect.centerY();
+            int halfWidth = mTmpRect.width() / 2;
+            int halfHeight = mTmpRect.height() / 2;
+            int clipStartX = centerX - halfWidth - appFrame.left;
+            int clipStartY = centerY - halfHeight - appFrame.top;
+            boolean cutOff = false;
+
+            // If the starting rectangle is fully or partially outside of the target rectangle, we
+            // need to start the clipping at the edge and then achieve the rest with translation
+            // and extending the clip rect from that edge.
+            if (appFrame.top > centerY - halfHeight) {
+                translationY = (centerY - halfHeight) - appFrame.top;
+                translationYCorrection = 0;
+                clipStartY = 0;
+                cutOff = true;
+            }
+            if (appFrame.left > centerX - halfWidth) {
+                translationX = (centerX - halfWidth) - appFrame.left;
+                clipStartX = 0;
+                cutOff = true;
+            }
+            if (appFrame.right < centerX + halfWidth) {
+                translationX = (centerX + halfWidth) - appFrame.right;
+                clipStartX = appWidth - mTmpRect.width();
+                cutOff = true;
+            }
+            final long duration = calculateClipRevealTransitionDuration(cutOff, translationX,
+                    translationY, displayFrame);
+
+            // Clip third of the from size of launch icon, expand to full width/height
+            Animation clipAnimLR = new ClipRectLRAnimation(
+                    clipStartX, clipStartX + mTmpRect.width(), 0, appWidth);
+            clipAnimLR.setInterpolator(mClipHorizontalInterpolator);
+            clipAnimLR.setDuration((long) (duration / 2.5f));
+
+            TranslateAnimation translate = new TranslateAnimation(translationX, 0, translationY, 0);
+            translate.setInterpolator(cutOff ? TOUCH_RESPONSE_INTERPOLATOR
+                    : mLinearOutSlowInInterpolator);
+            translate.setDuration(duration);
+
+            Animation clipAnimTB = new ClipRectTBAnimation(
+                    clipStartY, clipStartY + mTmpRect.height(),
+                    0, appHeight,
+                    translationYCorrection, 0,
+                    mLinearOutSlowInInterpolator);
+            clipAnimTB.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
+            clipAnimTB.setDuration(duration);
+
+            // Quick fade-in from icon to app window
+            final long alphaDuration = duration / 4;
+            AlphaAnimation alpha = new AlphaAnimation(0.5f, 1);
+            alpha.setDuration(alphaDuration);
+            alpha.setInterpolator(mLinearOutSlowInInterpolator);
+
+            AnimationSet set = new AnimationSet(false);
+            set.addAnimation(clipAnimLR);
+            set.addAnimation(clipAnimTB);
+            set.addAnimation(translate);
+            set.addAnimation(alpha);
+            set.setZAdjustment(Animation.ZORDER_TOP);
+            set.initialize(appWidth, appHeight, appWidth, appHeight);
+            anim = set;
+            mLastHadClipReveal = true;
+            mLastClipRevealTransitionDuration = duration;
+
+            // If the start rect was full inside the target rect (cutOff == false), we don't need
+            // to store the translation, because it's only used if cutOff == true.
+            mLastClipRevealMaxTranslation = cutOff
+                    ? Math.max(Math.abs(translationY), Math.abs(translationX)) : 0;
+        } else {
+            final long duration;
+            switch (transit) {
+                case TRANSIT_ACTIVITY_OPEN:
+                case TRANSIT_ACTIVITY_CLOSE:
+                    duration = mConfigShortAnimTime;
+                    break;
+                default:
+                    duration = DEFAULT_APP_TRANSITION_DURATION;
+                    break;
+            }
+            if (transit == TRANSIT_WALLPAPER_INTRA_OPEN ||
+                    transit == TRANSIT_WALLPAPER_INTRA_CLOSE) {
+                // If we are on top of the wallpaper, we need an animation that
+                // correctly handles the wallpaper staying static behind all of
+                // the animated elements.  To do this, will just have the existing
+                // element fade out.
+                anim = new AlphaAnimation(1, 0);
+                anim.setDetachWallpaper(true);
+            } else {
+                // For normal animations, the exiting element just holds in place.
+                anim = new AlphaAnimation(1, 1);
+            }
+            anim.setInterpolator(mDecelerateInterpolator);
+            anim.setDuration(duration);
+            anim.setFillAfter(true);
+        }
+        return anim;
+    }
+
+    /**
+     * Prepares the specified animation with a standard duration, interpolator, etc.
+     */
+    Animation prepareThumbnailAnimationWithDuration(Animation a, int appWidth, int appHeight,
+            long duration, Interpolator interpolator) {
+        if (duration > 0) {
+            a.setDuration(duration);
+        }
+        a.setFillAfter(true);
+        if (interpolator != null) {
+            a.setInterpolator(interpolator);
+        }
+        a.initialize(appWidth, appHeight, appWidth, appHeight);
+        return a;
+    }
+
+    /**
+     * Prepares the specified animation with a standard duration, interpolator, etc.
+     */
+    Animation prepareThumbnailAnimation(Animation a, int appWidth, int appHeight, int transit) {
+        // Pick the desired duration.  If this is an inter-activity transition,
+        // it  is the standard duration for that.  Otherwise we use the longer
+        // task transition duration.
+        final int duration;
+        switch (transit) {
+            case TRANSIT_ACTIVITY_OPEN:
+            case TRANSIT_ACTIVITY_CLOSE:
+                duration = mConfigShortAnimTime;
+                break;
+            default:
+                duration = DEFAULT_APP_TRANSITION_DURATION;
+                break;
+        }
+        return prepareThumbnailAnimationWithDuration(a, appWidth, appHeight, duration,
+                mDecelerateInterpolator);
+    }
+
+    /**
+     * Return the current thumbnail transition state.
+     */
+    int getThumbnailTransitionState(boolean enter) {
+        if (enter) {
+            if (mNextAppTransitionScaleUp) {
+                return THUMBNAIL_TRANSITION_ENTER_SCALE_UP;
+            } else {
+                return THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN;
+            }
+        } else {
+            if (mNextAppTransitionScaleUp) {
+                return THUMBNAIL_TRANSITION_EXIT_SCALE_UP;
+            } else {
+                return THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN;
+            }
+        }
+    }
+
+    /**
+     * This animation runs for the thumbnail that gets cross faded with the enter/exit activity
+     * when a thumbnail is specified with the pending animation override.
+     */
+    Animation createThumbnailAspectScaleAnimationLocked(Rect appRect, @Nullable Rect contentInsets,
+            GraphicBuffer thumbnailHeader, final int taskId, int uiMode, int orientation) {
+        Animation a;
+        final int thumbWidthI = thumbnailHeader.getWidth();
+        final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
+        final int thumbHeightI = thumbnailHeader.getHeight();
+        final int appWidth = appRect.width();
+
+        float scaleW = appWidth / thumbWidth;
+        getNextAppTransitionStartRect(taskId, mTmpRect);
+        final float fromX;
+        float fromY;
+        final float toX;
+        float toY;
+        final float pivotX;
+        final float pivotY;
+        if (shouldScaleDownThumbnailTransition(uiMode, orientation)) {
+            fromX = mTmpRect.left;
+            fromY = mTmpRect.top;
+
+            // For the curved translate animation to work, the pivot points needs to be at the
+            // same absolute position as the one from the real surface.
+            toX = mTmpRect.width() / 2 * (scaleW - 1f) + appRect.left;
+            toY = appRect.height() / 2 * (1 - 1 / scaleW) + appRect.top;
+            pivotX = mTmpRect.width() / 2;
+            pivotY = appRect.height() / 2 / scaleW;
+            if (mGridLayoutRecentsEnabled) {
+                // In the grid layout, the header is displayed above the thumbnail instead of
+                // overlapping it.
+                fromY -= thumbHeightI;
+                toY -= thumbHeightI * scaleW;
+            }
+        } else {
+            pivotX = 0;
+            pivotY = 0;
+            fromX = mTmpRect.left;
+            fromY = mTmpRect.top;
+            toX = appRect.left;
+            toY = appRect.top;
+        }
+        final long duration = getAspectScaleDuration();
+        final Interpolator interpolator = getAspectScaleInterpolator();
+        if (mNextAppTransitionScaleUp) {
+            // Animation up from the thumbnail to the full screen
+            Animation scale = new ScaleAnimation(1f, scaleW, 1f, scaleW, pivotX, pivotY);
+            scale.setInterpolator(interpolator);
+            scale.setDuration(duration);
+            Animation alpha = new AlphaAnimation(1f, 0f);
+            alpha.setInterpolator(mNextAppTransition == TRANSIT_DOCK_TASK_FROM_RECENTS
+                    ? THUMBNAIL_DOCK_INTERPOLATOR : mThumbnailFadeOutInterpolator);
+            alpha.setDuration(mNextAppTransition == TRANSIT_DOCK_TASK_FROM_RECENTS
+                    ? duration / 2
+                    : duration);
+            Animation translate = createCurvedMotion(fromX, toX, fromY, toY);
+            translate.setInterpolator(interpolator);
+            translate.setDuration(duration);
+
+            mTmpFromClipRect.set(0, 0, thumbWidthI, thumbHeightI);
+            mTmpToClipRect.set(appRect);
+
+            // Containing frame is in screen space, but we need the clip rect in the
+            // app space.
+            mTmpToClipRect.offsetTo(0, 0);
+            mTmpToClipRect.right = (int) (mTmpToClipRect.right / scaleW);
+            mTmpToClipRect.bottom = (int) (mTmpToClipRect.bottom / scaleW);
+
+            if (contentInsets != null) {
+                mTmpToClipRect.inset((int) (-contentInsets.left * scaleW),
+                        (int) (-contentInsets.top * scaleW),
+                        (int) (-contentInsets.right * scaleW),
+                        (int) (-contentInsets.bottom * scaleW));
+            }
+
+            Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
+            clipAnim.setInterpolator(interpolator);
+            clipAnim.setDuration(duration);
+
+            // This AnimationSet uses the Interpolators assigned above.
+            AnimationSet set = new AnimationSet(false);
+            set.addAnimation(scale);
+            if (!mGridLayoutRecentsEnabled) {
+                // In the grid layout, the header should be shown for the whole animation.
+                set.addAnimation(alpha);
+            }
+            set.addAnimation(translate);
+            set.addAnimation(clipAnim);
+            a = set;
+        } else {
+            // Animation down from the full screen to the thumbnail
+            Animation scale = new ScaleAnimation(scaleW, 1f, scaleW, 1f, pivotX, pivotY);
+            scale.setInterpolator(interpolator);
+            scale.setDuration(duration);
+            Animation alpha = new AlphaAnimation(0f, 1f);
+            alpha.setInterpolator(mThumbnailFadeInInterpolator);
+            alpha.setDuration(duration);
+            Animation translate = createCurvedMotion(toX, fromX, toY, fromY);
+            translate.setInterpolator(interpolator);
+            translate.setDuration(duration);
+
+            // This AnimationSet uses the Interpolators assigned above.
+            AnimationSet set = new AnimationSet(false);
+            set.addAnimation(scale);
+            if (!mGridLayoutRecentsEnabled) {
+                // In the grid layout, the header should be shown for the whole animation.
+                set.addAnimation(alpha);
+            }
+            set.addAnimation(translate);
+            a = set;
+
+        }
+        return prepareThumbnailAnimationWithDuration(a, appWidth, appRect.height(), 0,
+                null);
+    }
+
+    private Animation createCurvedMotion(float fromX, float toX, float fromY, float toY) {
+
+        // Almost no x-change - use linear animation
+        if (Math.abs(toX - fromX) < 1f || mNextAppTransition != TRANSIT_DOCK_TASK_FROM_RECENTS) {
+            return new TranslateAnimation(fromX, toX, fromY, toY);
+        } else {
+            final Path path = createCurvedPath(fromX, toX, fromY, toY);
+            return new CurvedTranslateAnimation(path);
+        }
+    }
+
+    private Path createCurvedPath(float fromX, float toX, float fromY, float toY) {
+        final Path path = new Path();
+        path.moveTo(fromX, fromY);
+
+        if (fromY > toY) {
+            // If the object needs to go up, move it in horizontal direction first, then vertical.
+            path.cubicTo(fromX, fromY, toX, 0.9f * fromY + 0.1f * toY, toX, toY);
+        } else {
+            // If the object needs to go down, move it in vertical direction first, then horizontal.
+            path.cubicTo(fromX, fromY, fromX, 0.1f * fromY + 0.9f * toY, toX, toY);
+        }
+        return path;
+    }
+
+    private long getAspectScaleDuration() {
+        if (mNextAppTransition == TRANSIT_DOCK_TASK_FROM_RECENTS) {
+            return (long) (THUMBNAIL_APP_TRANSITION_DURATION * 1.35f);
+        } else {
+            return THUMBNAIL_APP_TRANSITION_DURATION;
+        }
+    }
+
+    private Interpolator getAspectScaleInterpolator() {
+        if (mNextAppTransition == TRANSIT_DOCK_TASK_FROM_RECENTS) {
+            return mFastOutSlowInInterpolator;
+        } else {
+            return TOUCH_RESPONSE_INTERPOLATOR;
+        }
+    }
+
+    /**
+     * This alternate animation is created when we are doing a thumbnail transition, for the
+     * activity that is leaving, and the activity that is entering.
+     */
+    Animation createAspectScaledThumbnailEnterExitAnimationLocked(int thumbTransitState,
+            int uiMode, int orientation, int transit, Rect containingFrame, Rect contentInsets,
+            @Nullable Rect surfaceInsets, @Nullable Rect stableInsets, boolean freeform,
+            int taskId) {
+        Animation a;
+        final int appWidth = containingFrame.width();
+        final int appHeight = containingFrame.height();
+        getDefaultNextAppTransitionStartRect(mTmpRect);
+        final int thumbWidthI = mTmpRect.width();
+        final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
+        final int thumbHeightI = mTmpRect.height();
+        final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
+        final int thumbStartX = mTmpRect.left - containingFrame.left - contentInsets.left;
+        final int thumbStartY = mTmpRect.top - containingFrame.top;
+
+        switch (thumbTransitState) {
+            case THUMBNAIL_TRANSITION_ENTER_SCALE_UP:
+            case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: {
+                final boolean scaleUp = thumbTransitState == THUMBNAIL_TRANSITION_ENTER_SCALE_UP;
+                if (freeform && scaleUp) {
+                    a = createAspectScaledThumbnailEnterFreeformAnimationLocked(
+                            containingFrame, surfaceInsets, taskId);
+                } else if (freeform) {
+                    a = createAspectScaledThumbnailExitFreeformAnimationLocked(
+                            containingFrame, surfaceInsets, taskId);
+                } else {
+                    AnimationSet set = new AnimationSet(true);
+
+                    // In portrait, we scale to fit the width
+                    mTmpFromClipRect.set(containingFrame);
+                    mTmpToClipRect.set(containingFrame);
+
+                    // Containing frame is in screen space, but we need the clip rect in the
+                    // app space.
+                    mTmpFromClipRect.offsetTo(0, 0);
+                    mTmpToClipRect.offsetTo(0, 0);
+
+                    // Exclude insets region from the source clip.
+                    mTmpFromClipRect.inset(contentInsets);
+                    mNextAppTransitionInsets.set(contentInsets);
+
+                    if (shouldScaleDownThumbnailTransition(uiMode, orientation)) {
+                        // We scale the width and clip to the top/left square
+                        float scale = thumbWidth /
+                                (appWidth - contentInsets.left - contentInsets.right);
+                        if (!mGridLayoutRecentsEnabled) {
+                            int unscaledThumbHeight = (int) (thumbHeight / scale);
+                            mTmpFromClipRect.bottom = mTmpFromClipRect.top + unscaledThumbHeight;
+                        }
+
+                        mNextAppTransitionInsets.set(contentInsets);
+
+                        Animation scaleAnim = new ScaleAnimation(
+                                scaleUp ? scale : 1, scaleUp ? 1 : scale,
+                                scaleUp ? scale : 1, scaleUp ? 1 : scale,
+                                containingFrame.width() / 2f,
+                                containingFrame.height() / 2f + contentInsets.top);
+                        final float targetX = (mTmpRect.left - containingFrame.left);
+                        final float x = containingFrame.width() / 2f
+                                - containingFrame.width() / 2f * scale;
+                        final float targetY = (mTmpRect.top - containingFrame.top);
+                        float y = containingFrame.height() / 2f
+                                - containingFrame.height() / 2f * scale;
+
+                        // During transition may require clipping offset from any top stable insets
+                        // such as the statusbar height when statusbar is hidden
+                        if (mLowRamRecentsEnabled && contentInsets.top == 0 && scaleUp) {
+                            mTmpFromClipRect.top += stableInsets.top;
+                            y += stableInsets.top;
+                        }
+                        final float startX = targetX - x;
+                        final float startY = targetY - y;
+                        Animation clipAnim = scaleUp
+                                ? new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect)
+                                : new ClipRectAnimation(mTmpToClipRect, mTmpFromClipRect);
+                        Animation translateAnim = scaleUp
+                                ? createCurvedMotion(startX, 0, startY - contentInsets.top, 0)
+                                : createCurvedMotion(0, startX, 0, startY - contentInsets.top);
+
+                        set.addAnimation(clipAnim);
+                        set.addAnimation(scaleAnim);
+                        set.addAnimation(translateAnim);
+
+                    } else {
+                        // In landscape, we don't scale at all and only crop
+                        mTmpFromClipRect.bottom = mTmpFromClipRect.top + thumbHeightI;
+                        mTmpFromClipRect.right = mTmpFromClipRect.left + thumbWidthI;
+
+                        Animation clipAnim = scaleUp
+                                ? new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect)
+                                : new ClipRectAnimation(mTmpToClipRect, mTmpFromClipRect);
+                        Animation translateAnim = scaleUp
+                                ? createCurvedMotion(thumbStartX, 0,
+                                thumbStartY - contentInsets.top, 0)
+                                : createCurvedMotion(0, thumbStartX, 0,
+                                        thumbStartY - contentInsets.top);
+
+                        set.addAnimation(clipAnim);
+                        set.addAnimation(translateAnim);
+                    }
+                    a = set;
+                    a.setZAdjustment(Animation.ZORDER_TOP);
+                }
+                break;
+            }
+            case THUMBNAIL_TRANSITION_EXIT_SCALE_UP: {
+                // Previous app window during the scale up
+                if (transit == TRANSIT_WALLPAPER_INTRA_OPEN) {
+                    // Fade out the source activity if we are animating to a wallpaper
+                    // activity.
+                    a = new AlphaAnimation(1, 0);
+                } else {
+                    a = new AlphaAnimation(1, 1);
+                }
+                break;
+            }
+            case THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN: {
+                // Target app window during the scale down
+                if (transit == TRANSIT_WALLPAPER_INTRA_OPEN) {
+                    // Fade in the destination activity if we are animating from a wallpaper
+                    // activity.
+                    a = new AlphaAnimation(0, 1);
+                } else {
+                    a = new AlphaAnimation(1, 1);
+                }
+                break;
+            }
+            default:
+                throw new RuntimeException("Invalid thumbnail transition state");
+        }
+
+        return prepareThumbnailAnimationWithDuration(a, appWidth, appHeight,
+                getAspectScaleDuration(), getAspectScaleInterpolator());
+    }
+
+    private Animation createAspectScaledThumbnailEnterFreeformAnimationLocked(Rect frame,
+            @Nullable Rect surfaceInsets, int taskId) {
+        getNextAppTransitionStartRect(taskId, mTmpRect);
+        return createAspectScaledThumbnailFreeformAnimationLocked(mTmpRect, frame, surfaceInsets,
+                true);
+    }
+
+    private Animation createAspectScaledThumbnailExitFreeformAnimationLocked(Rect frame,
+            @Nullable Rect surfaceInsets, int taskId) {
+        getNextAppTransitionStartRect(taskId, mTmpRect);
+        return createAspectScaledThumbnailFreeformAnimationLocked(frame, mTmpRect, surfaceInsets,
+                false);
+    }
+
+    private AnimationSet createAspectScaledThumbnailFreeformAnimationLocked(Rect sourceFrame,
+            Rect destFrame, @Nullable Rect surfaceInsets, boolean enter) {
+        final float sourceWidth = sourceFrame.width();
+        final float sourceHeight = sourceFrame.height();
+        final float destWidth = destFrame.width();
+        final float destHeight = destFrame.height();
+        final float scaleH = enter ? sourceWidth / destWidth : destWidth / sourceWidth;
+        final float scaleV = enter ? sourceHeight / destHeight : destHeight / sourceHeight;
+        AnimationSet set = new AnimationSet(true);
+        final int surfaceInsetsH = surfaceInsets == null
+                ? 0 : surfaceInsets.left + surfaceInsets.right;
+        final int surfaceInsetsV = surfaceInsets == null
+                ? 0 : surfaceInsets.top + surfaceInsets.bottom;
+        // We want the scaling to happen from the center of the surface. In order to achieve that,
+        // we need to account for surface insets that will be used to enlarge the surface.
+        final float scaleHCenter = ((enter ? destWidth : sourceWidth) + surfaceInsetsH) / 2;
+        final float scaleVCenter = ((enter ? destHeight : sourceHeight) + surfaceInsetsV) / 2;
+        final ScaleAnimation scale = enter ?
+                new ScaleAnimation(scaleH, 1, scaleV, 1, scaleHCenter, scaleVCenter)
+                : new ScaleAnimation(1, scaleH, 1, scaleV, scaleHCenter, scaleVCenter);
+        final int sourceHCenter = sourceFrame.left + sourceFrame.width() / 2;
+        final int sourceVCenter = sourceFrame.top + sourceFrame.height() / 2;
+        final int destHCenter = destFrame.left + destFrame.width() / 2;
+        final int destVCenter = destFrame.top + destFrame.height() / 2;
+        final int fromX = enter ? sourceHCenter - destHCenter : destHCenter - sourceHCenter;
+        final int fromY = enter ? sourceVCenter - destVCenter : destVCenter - sourceVCenter;
+        final TranslateAnimation translation = enter ? new TranslateAnimation(fromX, 0, fromY, 0)
+                : new TranslateAnimation(0, fromX, 0, fromY);
+        set.addAnimation(scale);
+        set.addAnimation(translation);
+
+        final IRemoteCallback callback = mAnimationFinishedCallback;
+        if (callback != null) {
+            set.setAnimationListener(new Animation.AnimationListener() {
+                @Override
+                public void onAnimationStart(Animation animation) { }
+
+                @Override
+                public void onAnimationEnd(Animation animation) {
+                    mService.mH.obtainMessage(H.DO_ANIMATION_CALLBACK, callback).sendToTarget();
+                }
+
+                @Override
+                public void onAnimationRepeat(Animation animation) { }
+            });
+        }
+        return set;
+    }
+
+    /**
+     * This animation runs for the thumbnail that gets cross faded with the enter/exit activity
+     * when a thumbnail is specified with the pending animation override.
+     */
+    Animation createThumbnailScaleAnimationLocked(int appWidth, int appHeight, int transit,
+            GraphicBuffer thumbnailHeader) {
+        Animation a;
+        getDefaultNextAppTransitionStartRect(mTmpRect);
+        final int thumbWidthI = thumbnailHeader.getWidth();
+        final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
+        final int thumbHeightI = thumbnailHeader.getHeight();
+        final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
+
+        if (mNextAppTransitionScaleUp) {
+            // Animation for the thumbnail zooming from its initial size to the full screen
+            float scaleW = appWidth / thumbWidth;
+            float scaleH = appHeight / thumbHeight;
+            Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH,
+                    computePivot(mTmpRect.left, 1 / scaleW),
+                    computePivot(mTmpRect.top, 1 / scaleH));
+            scale.setInterpolator(mDecelerateInterpolator);
+
+            Animation alpha = new AlphaAnimation(1, 0);
+            alpha.setInterpolator(mThumbnailFadeOutInterpolator);
+
+            // This AnimationSet uses the Interpolators assigned above.
+            AnimationSet set = new AnimationSet(false);
+            set.addAnimation(scale);
+            set.addAnimation(alpha);
+            a = set;
+        } else {
+            // Animation for the thumbnail zooming down from the full screen to its final size
+            float scaleW = appWidth / thumbWidth;
+            float scaleH = appHeight / thumbHeight;
+            a = new ScaleAnimation(scaleW, 1, scaleH, 1,
+                    computePivot(mTmpRect.left, 1 / scaleW),
+                    computePivot(mTmpRect.top, 1 / scaleH));
+        }
+
+        return prepareThumbnailAnimation(a, appWidth, appHeight, transit);
+    }
+
+    /**
+     * This animation is created when we are doing a thumbnail transition, for the activity that is
+     * leaving, and the activity that is entering.
+     */
+    Animation createThumbnailEnterExitAnimationLocked(int thumbTransitState, Rect containingFrame,
+            int transit, int taskId) {
+        final int appWidth = containingFrame.width();
+        final int appHeight = containingFrame.height();
+        final GraphicBuffer thumbnailHeader = getAppTransitionThumbnailHeader(taskId);
+        Animation a;
+        getDefaultNextAppTransitionStartRect(mTmpRect);
+        final int thumbWidthI = thumbnailHeader != null ? thumbnailHeader.getWidth() : appWidth;
+        final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
+        final int thumbHeightI = thumbnailHeader != null ? thumbnailHeader.getHeight() : appHeight;
+        final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
+
+        switch (thumbTransitState) {
+            case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: {
+                // Entering app scales up with the thumbnail
+                float scaleW = thumbWidth / appWidth;
+                float scaleH = thumbHeight / appHeight;
+                a = new ScaleAnimation(scaleW, 1, scaleH, 1,
+                        computePivot(mTmpRect.left, scaleW),
+                        computePivot(mTmpRect.top, scaleH));
+                break;
+            }
+            case THUMBNAIL_TRANSITION_EXIT_SCALE_UP: {
+                // Exiting app while the thumbnail is scaling up should fade or stay in place
+                if (transit == TRANSIT_WALLPAPER_INTRA_OPEN) {
+                    // Fade out while bringing up selected activity. This keeps the
+                    // current activity from showing through a launching wallpaper
+                    // activity.
+                    a = new AlphaAnimation(1, 0);
+                } else {
+                    // noop animation
+                    a = new AlphaAnimation(1, 1);
+                }
+                break;
+            }
+            case THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN: {
+                // Entering the other app, it should just be visible while we scale the thumbnail
+                // down above it
+                a = new AlphaAnimation(1, 1);
+                break;
+            }
+            case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: {
+                // Exiting the current app, the app should scale down with the thumbnail
+                float scaleW = thumbWidth / appWidth;
+                float scaleH = thumbHeight / appHeight;
+                Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH,
+                        computePivot(mTmpRect.left, scaleW),
+                        computePivot(mTmpRect.top, scaleH));
+
+                Animation alpha = new AlphaAnimation(1, 0);
+
+                AnimationSet set = new AnimationSet(true);
+                set.addAnimation(scale);
+                set.addAnimation(alpha);
+                set.setZAdjustment(Animation.ZORDER_TOP);
+                a = set;
+                break;
+            }
+            default:
+                throw new RuntimeException("Invalid thumbnail transition state");
+        }
+
+        return prepareThumbnailAnimation(a, appWidth, appHeight, transit);
+    }
+
+    private Animation createRelaunchAnimation(Rect containingFrame, Rect contentInsets) {
+        getDefaultNextAppTransitionStartRect(mTmpFromClipRect);
+        final int left = mTmpFromClipRect.left;
+        final int top = mTmpFromClipRect.top;
+        mTmpFromClipRect.offset(-left, -top);
+        // TODO: Isn't that strange that we ignore exact position of the containingFrame?
+        mTmpToClipRect.set(0, 0, containingFrame.width(), containingFrame.height());
+        AnimationSet set = new AnimationSet(true);
+        float fromWidth = mTmpFromClipRect.width();
+        float toWidth = mTmpToClipRect.width();
+        float fromHeight = mTmpFromClipRect.height();
+        // While the window might span the whole display, the actual content will be cropped to the
+        // system decoration frame, for example when the window is docked. We need to take into
+        // account the visible height when constructing the animation.
+        float toHeight = mTmpToClipRect.height() - contentInsets.top - contentInsets.bottom;
+        int translateAdjustment = 0;
+        if (fromWidth <= toWidth && fromHeight <= toHeight) {
+            // The final window is larger in both dimensions than current window (e.g. we are
+            // maximizing), so we can simply unclip the new window and there will be no disappearing
+            // frame.
+            set.addAnimation(new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect));
+        } else {
+            // The disappearing window has one larger dimension. We need to apply scaling, so the
+            // first frame of the entry animation matches the old window.
+            set.addAnimation(new ScaleAnimation(fromWidth / toWidth, 1, fromHeight / toHeight, 1));
+            // We might not be going exactly full screen, but instead be aligned under the status
+            // bar using cropping. We still need to account for the cropped part, which will also
+            // be scaled.
+            translateAdjustment = (int) (contentInsets.top * fromHeight / toHeight);
+        }
+
+        // We animate the translation from the old position of the removed window, to the new
+        // position of the added window. The latter might not be full screen, for example docked for
+        // docked windows.
+        TranslateAnimation translate = new TranslateAnimation(left - containingFrame.left,
+                0, top - containingFrame.top - translateAdjustment, 0);
+        set.addAnimation(translate);
+        set.setDuration(DEFAULT_APP_TRANSITION_DURATION);
+        set.setZAdjustment(Animation.ZORDER_TOP);
+        return set;
+    }
+
+    /**
+     * @return true if and only if the first frame of the transition can be skipped, i.e. the first
+     *         frame of the transition doesn't change the visuals on screen, so we can start
+     *         directly with the second one
+     */
+    boolean canSkipFirstFrame() {
+        return mNextAppTransitionType != NEXT_TRANSIT_TYPE_CUSTOM
+                && mNextAppTransitionType != NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE
+                && mNextAppTransitionType != NEXT_TRANSIT_TYPE_CLIP_REVEAL
+                && mNextAppTransition != TRANSIT_KEYGUARD_GOING_AWAY;
+    }
+
+    /**
+     *
+     * @param frame These are the bounds of the window when it finishes the animation. This is where
+     *              the animation must usually finish in entrance animation, as the next frame will
+     *              display the window at these coordinates. In case of exit animation, this is
+     *              where the animation must start, as the frame before the animation is displaying
+     *              the window at these bounds.
+     * @param insets Knowing where the window will be positioned is not enough. Some parts of the
+     *               window might be obscured, usually by the system windows (status bar and
+     *               navigation bar) and we use content insets to convey that information. This
+     *               usually affects the animation aspects vertically, as the system decoration is
+     *               at the top and the bottom. For example when we animate from full screen to
+     *               recents, we want to exclude the covered parts, because they won't match the
+     *               thumbnail after the last frame is executed.
+     * @param surfaceInsets In rare situation the surface is larger than the content and we need to
+     *                      know about this to make the animation frames match. We currently use
+     *                      this for freeform windows, which have larger surfaces to display
+     *                      shadows. When we animate them from recents, we want to match the content
+     *                      to the recents thumbnail and hence need to account for the surface being
+     *                      bigger.
+     */
+    Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter, int uiMode,
+            int orientation, Rect frame, Rect displayFrame, Rect insets,
+            @Nullable Rect surfaceInsets, @Nullable Rect stableInsets, boolean isVoiceInteraction,
+            boolean freeform, int taskId) {
+        Animation a;
+        if (isKeyguardGoingAwayTransit(transit) && enter) {
+            a = loadKeyguardExitAnimation(transit);
+        } else if (transit == TRANSIT_KEYGUARD_OCCLUDE) {
+            a = null;
+        } else if (transit == TRANSIT_KEYGUARD_UNOCCLUDE && !enter) {
+            a = loadAnimationRes(lp, com.android.internal.R.anim.wallpaper_open_exit);
+        } else if (isVoiceInteraction && (transit == TRANSIT_ACTIVITY_OPEN
+                || transit == TRANSIT_TASK_OPEN
+                || transit == TRANSIT_TASK_TO_FRONT)) {
+            a = loadAnimationRes(lp, enter
+                    ? com.android.internal.R.anim.voice_activity_open_enter
+                    : com.android.internal.R.anim.voice_activity_open_exit);
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
+                    "applyAnimation voice:"
+                    + " anim=" + a + " transit=" + appTransitionToString(transit)
+                    + " isEntrance=" + enter + " Callers=" + Debug.getCallers(3));
+        } else if (isVoiceInteraction && (transit == TRANSIT_ACTIVITY_CLOSE
+                || transit == TRANSIT_TASK_CLOSE
+                || transit == TRANSIT_TASK_TO_BACK)) {
+            a = loadAnimationRes(lp, enter
+                    ? com.android.internal.R.anim.voice_activity_close_enter
+                    : com.android.internal.R.anim.voice_activity_close_exit);
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
+                    "applyAnimation voice:"
+                    + " anim=" + a + " transit=" + appTransitionToString(transit)
+                    + " isEntrance=" + enter + " Callers=" + Debug.getCallers(3));
+        } else if (transit == TRANSIT_ACTIVITY_RELAUNCH) {
+            a = createRelaunchAnimation(frame, insets);
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
+                    "applyAnimation:"
+                    + " anim=" + a + " nextAppTransition=" + mNextAppTransition
+                    + " transit=" + appTransitionToString(transit)
+                    + " Callers=" + Debug.getCallers(3));
+        } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) {
+            a = loadAnimationRes(mNextAppTransitionPackage, enter ?
+                    mNextAppTransitionEnter : mNextAppTransitionExit);
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
+                    "applyAnimation:"
+                    + " anim=" + a + " nextAppTransition=ANIM_CUSTOM"
+                    + " transit=" + appTransitionToString(transit) + " isEntrance=" + enter
+                    + " Callers=" + Debug.getCallers(3));
+        } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE) {
+            a = loadAnimationRes(mNextAppTransitionPackage, mNextAppTransitionInPlace);
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
+                    "applyAnimation:"
+                    + " anim=" + a + " nextAppTransition=ANIM_CUSTOM_IN_PLACE"
+                    + " transit=" + appTransitionToString(transit)
+                    + " Callers=" + Debug.getCallers(3));
+        } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL) {
+            a = createClipRevealAnimationLocked(transit, enter, frame, displayFrame);
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
+                    "applyAnimation:"
+                            + " anim=" + a + " nextAppTransition=ANIM_CLIP_REVEAL"
+                            + " transit=" + appTransitionToString(transit)
+                            + " Callers=" + Debug.getCallers(3));
+        } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_SCALE_UP) {
+            a = createScaleUpAnimationLocked(transit, enter, frame);
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
+                    "applyAnimation:"
+                    + " anim=" + a + " nextAppTransition=ANIM_SCALE_UP"
+                    + " transit=" + appTransitionToString(transit) + " isEntrance=" + enter
+                    + " Callers=" + Debug.getCallers(3));
+        } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP ||
+                mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN) {
+            mNextAppTransitionScaleUp =
+                    (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP);
+            a = createThumbnailEnterExitAnimationLocked(getThumbnailTransitionState(enter),
+                    frame, transit, taskId);
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
+                String animName = mNextAppTransitionScaleUp ?
+                        "ANIM_THUMBNAIL_SCALE_UP" : "ANIM_THUMBNAIL_SCALE_DOWN";
+                Slog.v(TAG, "applyAnimation:"
+                        + " anim=" + a + " nextAppTransition=" + animName
+                        + " transit=" + appTransitionToString(transit) + " isEntrance=" + enter
+                        + " Callers=" + Debug.getCallers(3));
+            }
+        } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP ||
+                mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN) {
+            mNextAppTransitionScaleUp =
+                    (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP);
+            a = createAspectScaledThumbnailEnterExitAnimationLocked(
+                    getThumbnailTransitionState(enter), uiMode, orientation, transit, frame,
+                    insets, surfaceInsets, stableInsets, freeform, taskId);
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
+                String animName = mNextAppTransitionScaleUp ?
+                        "ANIM_THUMBNAIL_ASPECT_SCALE_UP" : "ANIM_THUMBNAIL_ASPECT_SCALE_DOWN";
+                Slog.v(TAG, "applyAnimation:"
+                        + " anim=" + a + " nextAppTransition=" + animName
+                        + " transit=" + appTransitionToString(transit) + " isEntrance=" + enter
+                        + " Callers=" + Debug.getCallers(3));
+            }
+        } else {
+            int animAttr = 0;
+            switch (transit) {
+                case TRANSIT_ACTIVITY_OPEN:
+                    animAttr = enter
+                            ? WindowAnimation_activityOpenEnterAnimation
+                            : WindowAnimation_activityOpenExitAnimation;
+                    break;
+                case TRANSIT_ACTIVITY_CLOSE:
+                    animAttr = enter
+                            ? WindowAnimation_activityCloseEnterAnimation
+                            : WindowAnimation_activityCloseExitAnimation;
+                    break;
+                case TRANSIT_DOCK_TASK_FROM_RECENTS:
+                case TRANSIT_TASK_OPEN:
+                    animAttr = enter
+                            ? WindowAnimation_taskOpenEnterAnimation
+                            : WindowAnimation_taskOpenExitAnimation;
+                    break;
+                case TRANSIT_TASK_CLOSE:
+                    animAttr = enter
+                            ? WindowAnimation_taskCloseEnterAnimation
+                            : WindowAnimation_taskCloseExitAnimation;
+                    break;
+                case TRANSIT_TASK_TO_FRONT:
+                    animAttr = enter
+                            ? WindowAnimation_taskToFrontEnterAnimation
+                            : WindowAnimation_taskToFrontExitAnimation;
+                    break;
+                case TRANSIT_TASK_TO_BACK:
+                    animAttr = enter
+                            ? WindowAnimation_taskToBackEnterAnimation
+                            : WindowAnimation_taskToBackExitAnimation;
+                    break;
+                case TRANSIT_WALLPAPER_OPEN:
+                    animAttr = enter
+                            ? WindowAnimation_wallpaperOpenEnterAnimation
+                            : WindowAnimation_wallpaperOpenExitAnimation;
+                    break;
+                case TRANSIT_WALLPAPER_CLOSE:
+                    animAttr = enter
+                            ? WindowAnimation_wallpaperCloseEnterAnimation
+                            : WindowAnimation_wallpaperCloseExitAnimation;
+                    break;
+                case TRANSIT_WALLPAPER_INTRA_OPEN:
+                    animAttr = enter
+                            ? WindowAnimation_wallpaperIntraOpenEnterAnimation
+                            : WindowAnimation_wallpaperIntraOpenExitAnimation;
+                    break;
+                case TRANSIT_WALLPAPER_INTRA_CLOSE:
+                    animAttr = enter
+                            ? WindowAnimation_wallpaperIntraCloseEnterAnimation
+                            : WindowAnimation_wallpaperIntraCloseExitAnimation;
+                    break;
+                case TRANSIT_TASK_OPEN_BEHIND:
+                    animAttr = enter
+                            ? WindowAnimation_launchTaskBehindSourceAnimation
+                            : WindowAnimation_launchTaskBehindTargetAnimation;
+            }
+            a = animAttr != 0 ? loadAnimationAttr(lp, animAttr) : null;
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
+                    "applyAnimation:"
+                    + " anim=" + a
+                    + " animAttr=0x" + Integer.toHexString(animAttr)
+                    + " transit=" + appTransitionToString(transit) + " isEntrance=" + enter
+                    + " Callers=" + Debug.getCallers(3));
+        }
+        return a;
+    }
+
+    private Animation loadKeyguardExitAnimation(int transit) {
+        if ((mNextAppTransitionFlags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) != 0) {
+            return null;
+        }
+        final boolean toShade =
+                (mNextAppTransitionFlags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0;
+        return mService.mPolicy.createHiddenByKeyguardExit(
+                transit == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER, toShade);
+    }
+
+    int getAppStackClipMode() {
+        // When dismiss keyguard animation occurs, clip before the animation to prevent docked
+        // app from showing beyond the divider
+        if (mNextAppTransition == TRANSIT_KEYGUARD_GOING_AWAY
+                || mNextAppTransition == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER) {
+            return STACK_CLIP_BEFORE_ANIM;
+        }
+        return mNextAppTransition == TRANSIT_ACTIVITY_RELAUNCH
+                || mNextAppTransition == TRANSIT_DOCK_TASK_FROM_RECENTS
+                || mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL
+                ? STACK_CLIP_NONE
+                : STACK_CLIP_AFTER_ANIM;
+    }
+
+    public int getTransitFlags() {
+        return mNextAppTransitionFlags;
+    }
+
+    void postAnimationCallback() {
+        if (mNextAppTransitionCallback != null) {
+            mService.mH.sendMessage(mService.mH.obtainMessage(H.DO_ANIMATION_CALLBACK,
+                    mNextAppTransitionCallback));
+            mNextAppTransitionCallback = null;
+        }
+    }
+
+    void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim,
+            IRemoteCallback startedCallback) {
+        if (isTransitionSet()) {
+            clear();
+            mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM;
+            mNextAppTransitionPackage = packageName;
+            mNextAppTransitionEnter = enterAnim;
+            mNextAppTransitionExit = exitAnim;
+            postAnimationCallback();
+            mNextAppTransitionCallback = startedCallback;
+        } else {
+            postAnimationCallback();
+        }
+    }
+
+    void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth,
+            int startHeight) {
+        if (isTransitionSet()) {
+            clear();
+            mNextAppTransitionType = NEXT_TRANSIT_TYPE_SCALE_UP;
+            putDefaultNextAppTransitionCoordinates(startX, startY, startWidth, startHeight, null);
+            postAnimationCallback();
+        }
+    }
+
+    void overridePendingAppTransitionClipReveal(int startX, int startY,
+                                                int startWidth, int startHeight) {
+        if (isTransitionSet()) {
+            clear();
+            mNextAppTransitionType = NEXT_TRANSIT_TYPE_CLIP_REVEAL;
+            putDefaultNextAppTransitionCoordinates(startX, startY, startWidth, startHeight, null);
+            postAnimationCallback();
+        }
+    }
+
+    void overridePendingAppTransitionThumb(GraphicBuffer srcThumb, int startX, int startY,
+                                           IRemoteCallback startedCallback, boolean scaleUp) {
+        if (isTransitionSet()) {
+            clear();
+            mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP
+                    : NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN;
+            mNextAppTransitionScaleUp = scaleUp;
+            putDefaultNextAppTransitionCoordinates(startX, startY, 0, 0, srcThumb);
+            postAnimationCallback();
+            mNextAppTransitionCallback = startedCallback;
+        } else {
+            postAnimationCallback();
+        }
+    }
+
+    void overridePendingAppTransitionAspectScaledThumb(GraphicBuffer srcThumb, int startX, int startY,
+            int targetWidth, int targetHeight, IRemoteCallback startedCallback, boolean scaleUp) {
+        if (isTransitionSet()) {
+            clear();
+            mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP
+                    : NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
+            mNextAppTransitionScaleUp = scaleUp;
+            putDefaultNextAppTransitionCoordinates(startX, startY, targetWidth, targetHeight,
+                    srcThumb);
+            postAnimationCallback();
+            mNextAppTransitionCallback = startedCallback;
+        } else {
+            postAnimationCallback();
+        }
+    }
+
+    public void overridePendingAppTransitionMultiThumb(AppTransitionAnimationSpec[] specs,
+            IRemoteCallback onAnimationStartedCallback, IRemoteCallback onAnimationFinishedCallback,
+            boolean scaleUp) {
+        if (isTransitionSet()) {
+            clear();
+            mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP
+                    : NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
+            mNextAppTransitionScaleUp = scaleUp;
+            if (specs != null) {
+                for (int i = 0; i < specs.length; i++) {
+                    AppTransitionAnimationSpec spec = specs[i];
+                    if (spec != null) {
+                        mNextAppTransitionAnimationsSpecs.put(spec.taskId, spec);
+                        if (i == 0) {
+                            // In full screen mode, the transition code depends on the default spec
+                            // to be set.
+                            Rect rect = spec.rect;
+                            putDefaultNextAppTransitionCoordinates(rect.left, rect.top,
+                                    rect.width(), rect.height(), spec.buffer);
+                        }
+                    }
+                }
+            }
+            postAnimationCallback();
+            mNextAppTransitionCallback = onAnimationStartedCallback;
+            mAnimationFinishedCallback = onAnimationFinishedCallback;
+        } else {
+            postAnimationCallback();
+        }
+    }
+
+    void overridePendingAppTransitionMultiThumbFuture(
+            IAppTransitionAnimationSpecsFuture specsFuture, IRemoteCallback callback,
+            boolean scaleUp) {
+        if (isTransitionSet()) {
+            clear();
+            mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP
+                    : NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
+            mNextAppTransitionAnimationsSpecsFuture = specsFuture;
+            mNextAppTransitionScaleUp = scaleUp;
+            mNextAppTransitionFutureCallback = callback;
+        }
+    }
+
+    void overrideInPlaceAppTransition(String packageName, int anim) {
+        if (isTransitionSet()) {
+            clear();
+            mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE;
+            mNextAppTransitionPackage = packageName;
+            mNextAppTransitionInPlace = anim;
+        } else {
+            postAnimationCallback();
+        }
+    }
+
+    /**
+     * If a future is set for the app transition specs, fetch it in another thread.
+     */
+    private void fetchAppTransitionSpecsFromFuture() {
+        if (mNextAppTransitionAnimationsSpecsFuture != null) {
+            mNextAppTransitionAnimationsSpecsPending = true;
+            final IAppTransitionAnimationSpecsFuture future
+                    = mNextAppTransitionAnimationsSpecsFuture;
+            mNextAppTransitionAnimationsSpecsFuture = null;
+            mDefaultExecutor.execute(() -> {
+                AppTransitionAnimationSpec[] specs = null;
+                try {
+                    Binder.allowBlocking(future.asBinder());
+                    specs = future.get();
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to fetch app transition specs: " + e);
+                }
+                synchronized (mService.mWindowMap) {
+                    mNextAppTransitionAnimationsSpecsPending = false;
+                    overridePendingAppTransitionMultiThumb(specs,
+                            mNextAppTransitionFutureCallback, null /* finishedCallback */,
+                            mNextAppTransitionScaleUp);
+                    mNextAppTransitionFutureCallback = null;
+                    if (specs != null) {
+                        mService.prolongAnimationsFromSpecs(specs, mNextAppTransitionScaleUp);
+                    }
+                }
+                mService.requestTraversal();
+            });
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "mNextAppTransition=" + appTransitionToString(mNextAppTransition);
+    }
+
+    /**
+     * Returns the human readable name of a window transition.
+     *
+     * @param transition The window transition.
+     * @return The transition symbolic name.
+     */
+    public static String appTransitionToString(int transition) {
+        switch (transition) {
+            case TRANSIT_UNSET: {
+                return "TRANSIT_UNSET";
+            }
+            case TRANSIT_NONE: {
+                return "TRANSIT_NONE";
+            }
+            case TRANSIT_ACTIVITY_OPEN: {
+                return "TRANSIT_ACTIVITY_OPEN";
+            }
+            case TRANSIT_ACTIVITY_CLOSE: {
+                return "TRANSIT_ACTIVITY_CLOSE";
+            }
+            case TRANSIT_TASK_OPEN: {
+                return "TRANSIT_TASK_OPEN";
+            }
+            case TRANSIT_TASK_CLOSE: {
+                return "TRANSIT_TASK_CLOSE";
+            }
+            case TRANSIT_TASK_TO_FRONT: {
+                return "TRANSIT_TASK_TO_FRONT";
+            }
+            case TRANSIT_TASK_TO_BACK: {
+                return "TRANSIT_TASK_TO_BACK";
+            }
+            case TRANSIT_WALLPAPER_CLOSE: {
+                return "TRANSIT_WALLPAPER_CLOSE";
+            }
+            case TRANSIT_WALLPAPER_OPEN: {
+                return "TRANSIT_WALLPAPER_OPEN";
+            }
+            case TRANSIT_WALLPAPER_INTRA_OPEN: {
+                return "TRANSIT_WALLPAPER_INTRA_OPEN";
+            }
+            case TRANSIT_WALLPAPER_INTRA_CLOSE: {
+                return "TRANSIT_WALLPAPER_INTRA_CLOSE";
+            }
+            case TRANSIT_TASK_OPEN_BEHIND: {
+                return "TRANSIT_TASK_OPEN_BEHIND";
+            }
+            case TRANSIT_ACTIVITY_RELAUNCH: {
+                return "TRANSIT_ACTIVITY_RELAUNCH";
+            }
+            case TRANSIT_DOCK_TASK_FROM_RECENTS: {
+                return "TRANSIT_DOCK_TASK_FROM_RECENTS";
+            }
+            case TRANSIT_KEYGUARD_GOING_AWAY: {
+                return "TRANSIT_KEYGUARD_GOING_AWAY";
+            }
+            case TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER: {
+                return "TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER";
+            }
+            case TRANSIT_KEYGUARD_OCCLUDE: {
+                return "TRANSIT_KEYGUARD_OCCLUDE";
+            }
+            case TRANSIT_KEYGUARD_UNOCCLUDE: {
+                return "TRANSIT_KEYGUARD_UNOCCLUDE";
+            }
+            default: {
+                return "<UNKNOWN>";
+            }
+        }
+    }
+
+    private String appStateToString() {
+        switch (mAppTransitionState) {
+            case APP_STATE_IDLE:
+                return "APP_STATE_IDLE";
+            case APP_STATE_READY:
+                return "APP_STATE_READY";
+            case APP_STATE_RUNNING:
+                return "APP_STATE_RUNNING";
+            case APP_STATE_TIMEOUT:
+                return "APP_STATE_TIMEOUT";
+            default:
+                return "unknown state=" + mAppTransitionState;
+        }
+    }
+
+    private String transitTypeToString() {
+        switch (mNextAppTransitionType) {
+            case NEXT_TRANSIT_TYPE_NONE:
+                return "NEXT_TRANSIT_TYPE_NONE";
+            case NEXT_TRANSIT_TYPE_CUSTOM:
+                return "NEXT_TRANSIT_TYPE_CUSTOM";
+            case NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE:
+                return "NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE";
+            case NEXT_TRANSIT_TYPE_SCALE_UP:
+                return "NEXT_TRANSIT_TYPE_SCALE_UP";
+            case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP:
+                return "NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP";
+            case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN:
+                return "NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN";
+            case NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP:
+                return "NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP";
+            case NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN:
+                return "NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN";
+            default:
+                return "unknown type=" + mNextAppTransitionType;
+        }
+    }
+
+    void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(APP_TRANSITION_STATE, mAppTransitionState);
+        proto.write(LAST_USED_APP_TRANSITION, mLastUsedAppTransition);
+        proto.end(token);
+    }
+
+    @Override
+    public void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix); pw.println(this);
+        pw.print(prefix); pw.print("mAppTransitionState="); pw.println(appStateToString());
+        if (mNextAppTransitionType != NEXT_TRANSIT_TYPE_NONE) {
+            pw.print(prefix); pw.print("mNextAppTransitionType=");
+                    pw.println(transitTypeToString());
+        }
+        switch (mNextAppTransitionType) {
+            case NEXT_TRANSIT_TYPE_CUSTOM:
+                pw.print(prefix); pw.print("mNextAppTransitionPackage=");
+                        pw.println(mNextAppTransitionPackage);
+                pw.print(prefix); pw.print("mNextAppTransitionEnter=0x");
+                        pw.print(Integer.toHexString(mNextAppTransitionEnter));
+                        pw.print(" mNextAppTransitionExit=0x");
+                        pw.println(Integer.toHexString(mNextAppTransitionExit));
+                break;
+            case NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE:
+                pw.print(prefix); pw.print("mNextAppTransitionPackage=");
+                        pw.println(mNextAppTransitionPackage);
+                pw.print(prefix); pw.print("mNextAppTransitionInPlace=0x");
+                        pw.print(Integer.toHexString(mNextAppTransitionInPlace));
+                break;
+            case NEXT_TRANSIT_TYPE_SCALE_UP: {
+                getDefaultNextAppTransitionStartRect(mTmpRect);
+                pw.print(prefix); pw.print("mNextAppTransitionStartX=");
+                        pw.print(mTmpRect.left);
+                        pw.print(" mNextAppTransitionStartY=");
+                        pw.println(mTmpRect.top);
+                pw.print(prefix); pw.print("mNextAppTransitionStartWidth=");
+                        pw.print(mTmpRect.width());
+                        pw.print(" mNextAppTransitionStartHeight=");
+                        pw.println(mTmpRect.height());
+                break;
+            }
+            case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP:
+            case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN:
+            case NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP:
+            case NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN: {
+                pw.print(prefix); pw.print("mDefaultNextAppTransitionAnimationSpec=");
+                        pw.println(mDefaultNextAppTransitionAnimationSpec);
+                pw.print(prefix); pw.print("mNextAppTransitionAnimationsSpecs=");
+                        pw.println(mNextAppTransitionAnimationsSpecs);
+                pw.print(prefix); pw.print("mNextAppTransitionScaleUp=");
+                        pw.println(mNextAppTransitionScaleUp);
+                break;
+            }
+        }
+        if (mNextAppTransitionCallback != null) {
+            pw.print(prefix); pw.print("mNextAppTransitionCallback=");
+                    pw.println(mNextAppTransitionCallback);
+        }
+        if (mLastUsedAppTransition != TRANSIT_NONE) {
+            pw.print(prefix); pw.print("mLastUsedAppTransition=");
+                    pw.println(appTransitionToString(mLastUsedAppTransition));
+            pw.print(prefix); pw.print("mLastOpeningApp=");
+                    pw.println(mLastOpeningApp);
+            pw.print(prefix); pw.print("mLastClosingApp=");
+                    pw.println(mLastClosingApp);
+        }
+    }
+
+    public void setCurrentUser(int newUserId) {
+        mCurrentUserId = newUserId;
+    }
+
+    /**
+     * @return true if transition is not running and should not be skipped, false if transition is
+     *         already running
+     */
+    boolean prepareAppTransitionLocked(int transit, boolean alwaysKeepCurrent, int flags,
+            boolean forceOverride) {
+        if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Prepare app transition:"
+                + " transit=" + appTransitionToString(transit)
+                + " " + this
+                + " alwaysKeepCurrent=" + alwaysKeepCurrent
+                + " Callers=" + Debug.getCallers(3));
+        if (forceOverride || isKeyguardTransit(transit) || !isTransitionSet()
+                || mNextAppTransition == TRANSIT_NONE) {
+            setAppTransition(transit, flags);
+        }
+        // We never want to change from a Keyguard transit to a non-Keyguard transit, as our logic
+        // relies on the fact that we always execute a Keyguard transition after preparing one.
+        else if (!alwaysKeepCurrent && !isKeyguardTransit(transit)) {
+            if (transit == TRANSIT_TASK_OPEN && isTransitionEqual(TRANSIT_TASK_CLOSE)) {
+                // Opening a new task always supersedes a close for the anim.
+                setAppTransition(transit, flags);
+            } else if (transit == TRANSIT_ACTIVITY_OPEN
+                    && isTransitionEqual(TRANSIT_ACTIVITY_CLOSE)) {
+                // Opening a new activity always supersedes a close for the anim.
+                setAppTransition(transit, flags);
+            }
+        }
+        boolean prepared = prepare();
+        if (isTransitionSet()) {
+            mService.mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
+            mService.mH.sendEmptyMessageDelayed(H.APP_TRANSITION_TIMEOUT, APP_TRANSITION_TIMEOUT_MS);
+        }
+        return prepared;
+    }
+
+    /**
+     * @return true if {@param transit} is representing a transition in which Keyguard is going
+     *         away, false otherwise
+     */
+    public static boolean isKeyguardGoingAwayTransit(int transit) {
+        return transit == TRANSIT_KEYGUARD_GOING_AWAY
+                || transit == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
+    }
+
+    private static boolean isKeyguardTransit(int transit) {
+        return isKeyguardGoingAwayTransit(transit) || transit == TRANSIT_KEYGUARD_OCCLUDE
+                || transit == TRANSIT_KEYGUARD_UNOCCLUDE;
+    }
+
+    /**
+     * @return whether the transition should show the thumbnail being scaled down.
+     */
+    private boolean shouldScaleDownThumbnailTransition(int uiMode, int orientation) {
+        return mGridLayoutRecentsEnabled
+                || orientation == Configuration.ORIENTATION_PORTRAIT;
+    }
+}
diff --git a/com/android/server/wm/AppWindowAnimator.java b/com/android/server/wm/AppWindowAnimator.java
new file mode 100644
index 0000000..c76b905
--- /dev/null
+++ b/com/android/server/wm/AppWindowAnimator.java
@@ -0,0 +1,507 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
+import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.TYPE_LAYER_OFFSET;
+import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
+
+import android.graphics.Matrix;
+import android.util.Slog;
+import android.util.TimeUtils;
+import android.view.Choreographer;
+import android.view.Display;
+import android.view.SurfaceControl;
+import android.view.animation.Animation;
+import android.view.animation.Transformation;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+public class AppWindowAnimator {
+    static final String TAG = TAG_WITH_CLASS_NAME ? "AppWindowAnimator" : TAG_WM;
+
+    private static final int PROLONG_ANIMATION_DISABLED = 0;
+    static final int PROLONG_ANIMATION_AT_END = 1;
+    static final int PROLONG_ANIMATION_AT_START = 2;
+
+    final AppWindowToken mAppToken;
+    final WindowManagerService mService;
+    final WindowAnimator mAnimator;
+
+    boolean animating;
+    boolean wasAnimating;
+    Animation animation;
+    boolean hasTransformation;
+    final Transformation transformation = new Transformation();
+
+    // Have we been asked to have this token keep the screen frozen?
+    // Protect with mAnimator.
+    boolean freezingScreen;
+
+    /**
+     * How long we last kept the screen frozen.
+     */
+    int lastFreezeDuration;
+
+    // Offset to the window of all layers in the token, for use by
+    // AppWindowToken animations.
+    int animLayerAdjustment;
+
+    // Propagated from AppWindowToken.allDrawn, to determine when
+    // the state changes.
+    boolean allDrawn;
+
+    // Special surface for thumbnail animation.  If deferThumbnailDestruction is enabled, then we
+    // will make sure that the thumbnail is destroyed after the other surface is completed.  This
+    // requires that the duration of the two animations are the same.
+    SurfaceControl thumbnail;
+    int thumbnailTransactionSeq;
+    // TODO(b/62029108): combine both members into a private one. Create a member function to set
+    // the thumbnail layer to +1 to the highest layer position and replace all setter instances
+    // with this function. Remove all unnecessary calls to both variables in other classes.
+    int thumbnailLayer;
+    int thumbnailForceAboveLayer;
+    Animation thumbnailAnimation;
+    final Transformation thumbnailTransformation = new Transformation();
+    // This flag indicates that the destruction of the thumbnail surface is synchronized with
+    // another animation, so defer the destruction of this thumbnail surface for a single frame
+    // after the secondary animation completes.
+    boolean deferThumbnailDestruction;
+    // This flag is set if the animator has deferThumbnailDestruction set and has reached the final
+    // frame of animation.  It will extend the animation by one frame and then clean up afterwards.
+    boolean deferFinalFrameCleanup;
+    // If true when the animation hits the last frame, it will keep running on that last frame.
+    // This is used to synchronize animation with Recents and we wait for Recents to tell us to
+    // finish or for a new animation be set as fail-safe mechanism.
+    private int mProlongAnimation;
+    // Whether the prolong animation can be removed when animation is set. The purpose of this is
+    // that if recents doesn't tell us to remove the prolonged animation, we will get rid of it
+    // when new animation is set.
+    private boolean mClearProlongedAnimation;
+    private int mTransit;
+    private int mTransitFlags;
+
+    /** WindowStateAnimator from mAppAnimator.allAppWindows as of last performLayout */
+    ArrayList<WindowStateAnimator> mAllAppWinAnimators = new ArrayList<>();
+
+    /** True if the current animation was transferred from another AppWindowAnimator.
+     *  See {@link #transferCurrentAnimation}*/
+    boolean usingTransferredAnimation = false;
+
+    private boolean mSkipFirstFrame = false;
+    private int mStackClip = STACK_CLIP_BEFORE_ANIM;
+
+    static final Animation sDummyAnimation = new DummyAnimation();
+
+    public AppWindowAnimator(final AppWindowToken atoken, WindowManagerService service) {
+        mAppToken = atoken;
+        mService = service;
+        mAnimator = mService.mAnimator;
+    }
+
+    public void setAnimation(Animation anim, int width, int height, int parentWidth,
+            int parentHeight, boolean skipFirstFrame, int stackClip, int transit,
+            int transitFlags) {
+        if (WindowManagerService.localLOGV) Slog.v(TAG, "Setting animation in " + mAppToken
+                + ": " + anim + " wxh=" + width + "x" + height
+                + " hasContentToDisplay=" + mAppToken.hasContentToDisplay());
+        animation = anim;
+        animating = false;
+        if (!anim.isInitialized()) {
+            anim.initialize(width, height, parentWidth, parentHeight);
+        }
+        anim.restrictDuration(WindowManagerService.MAX_ANIMATION_DURATION);
+        anim.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked());
+        int zorder = anim.getZAdjustment();
+        int adj = 0;
+        if (zorder == Animation.ZORDER_TOP) {
+            adj = TYPE_LAYER_OFFSET;
+        } else if (zorder == Animation.ZORDER_BOTTOM) {
+            adj = -TYPE_LAYER_OFFSET;
+        }
+
+        if (animLayerAdjustment != adj) {
+            animLayerAdjustment = adj;
+            updateLayers();
+        }
+        // Start out animation gone if window is gone, or visible if window is visible.
+        transformation.clear();
+        transformation.setAlpha(mAppToken.isVisible() ? 1 : 0);
+        hasTransformation = true;
+        mStackClip = stackClip;
+
+        mSkipFirstFrame = skipFirstFrame;
+        mTransit = transit;
+        mTransitFlags = transitFlags;
+
+        if (!mAppToken.fillsParent()) {
+            anim.setBackgroundColor(0);
+        }
+        if (mClearProlongedAnimation) {
+            mProlongAnimation = PROLONG_ANIMATION_DISABLED;
+        } else {
+            mClearProlongedAnimation = true;
+        }
+    }
+
+    public void setDummyAnimation() {
+        if (WindowManagerService.localLOGV) Slog.v(TAG, "Setting dummy animation in " + mAppToken
+                + " hasContentToDisplay=" + mAppToken.hasContentToDisplay());
+        animation = sDummyAnimation;
+        hasTransformation = true;
+        transformation.clear();
+        transformation.setAlpha(mAppToken.isVisible() ? 1 : 0);
+    }
+
+    void setNullAnimation() {
+        animation = null;
+        usingTransferredAnimation = false;
+    }
+
+    public void clearAnimation() {
+        if (animation != null) {
+            animating = true;
+        }
+        clearThumbnail();
+        setNullAnimation();
+        if (mAppToken.deferClearAllDrawn) {
+            mAppToken.clearAllDrawn();
+        }
+        mStackClip = STACK_CLIP_BEFORE_ANIM;
+        mTransit = TRANSIT_UNSET;
+        mTransitFlags = 0;
+    }
+
+    public boolean isAnimating() {
+        return animation != null || mAppToken.inPendingTransaction;
+    }
+
+    /**
+     * @return whether an animation is about to start, i.e. the animation is set already but we
+     *         haven't processed the first frame yet.
+     */
+    boolean isAnimationStarting() {
+        return animation != null && !animating;
+    }
+
+    public int getTransit() {
+        return mTransit;
+    }
+
+    int getTransitFlags() {
+        return mTransitFlags;
+    }
+
+    public void clearThumbnail() {
+        if (thumbnail != null) {
+            thumbnail.hide();
+            mService.mWindowPlacerLocked.destroyAfterTransaction(thumbnail);
+            thumbnail = null;
+        }
+        deferThumbnailDestruction = false;
+    }
+
+    int getStackClip() {
+        return mStackClip;
+    }
+
+    void transferCurrentAnimation(
+            AppWindowAnimator toAppAnimator, WindowStateAnimator transferWinAnimator) {
+
+        if (animation != null) {
+            toAppAnimator.animation = animation;
+            toAppAnimator.animating = animating;
+            toAppAnimator.animLayerAdjustment = animLayerAdjustment;
+            setNullAnimation();
+            animLayerAdjustment = 0;
+            toAppAnimator.updateLayers();
+            updateLayers();
+            toAppAnimator.usingTransferredAnimation = true;
+            toAppAnimator.mTransit = mTransit;
+        }
+        if (transferWinAnimator != null) {
+            mAllAppWinAnimators.remove(transferWinAnimator);
+            toAppAnimator.mAllAppWinAnimators.add(transferWinAnimator);
+            toAppAnimator.hasTransformation = transferWinAnimator.mAppAnimator.hasTransformation;
+            if (toAppAnimator.hasTransformation) {
+                toAppAnimator.transformation.set(transferWinAnimator.mAppAnimator.transformation);
+            } else {
+                toAppAnimator.transformation.clear();
+            }
+            transferWinAnimator.mAppAnimator = toAppAnimator;
+        }
+    }
+
+    private void updateLayers() {
+        mAppToken.getDisplayContent().assignWindowLayers(false /* relayoutNeeded */);
+        thumbnailLayer = mAppToken.getHighestAnimLayer();
+    }
+
+    private void stepThumbnailAnimation(long currentTime) {
+        thumbnailTransformation.clear();
+        final long animationFrameTime = getAnimationFrameTime(thumbnailAnimation, currentTime);
+        thumbnailAnimation.getTransformation(animationFrameTime, thumbnailTransformation);
+
+        ScreenRotationAnimation screenRotationAnimation =
+                mAnimator.getScreenRotationAnimationLocked(Display.DEFAULT_DISPLAY);
+        final boolean screenAnimation = screenRotationAnimation != null
+                && screenRotationAnimation.isAnimating();
+        if (screenAnimation) {
+            thumbnailTransformation.postCompose(screenRotationAnimation.getEnterTransformation());
+        }
+        // cache often used attributes locally
+        final float tmpFloats[] = mService.mTmpFloats;
+        thumbnailTransformation.getMatrix().getValues(tmpFloats);
+        if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(thumbnail,
+                "thumbnail", "POS " + tmpFloats[Matrix.MTRANS_X]
+                + ", " + tmpFloats[Matrix.MTRANS_Y]);
+        thumbnail.setPosition(tmpFloats[Matrix.MTRANS_X], tmpFloats[Matrix.MTRANS_Y]);
+        if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(thumbnail,
+                "thumbnail", "alpha=" + thumbnailTransformation.getAlpha()
+                + " layer=" + thumbnailLayer
+                + " matrix=[" + tmpFloats[Matrix.MSCALE_X]
+                + "," + tmpFloats[Matrix.MSKEW_Y]
+                + "][" + tmpFloats[Matrix.MSKEW_X]
+                + "," + tmpFloats[Matrix.MSCALE_Y] + "]");
+        thumbnail.setAlpha(thumbnailTransformation.getAlpha());
+        if (thumbnailForceAboveLayer > 0) {
+            thumbnail.setLayer(thumbnailForceAboveLayer + 1);
+        } else {
+            // The thumbnail is layered below the window immediately above this
+            // token's anim layer.
+            thumbnail.setLayer(thumbnailLayer + WindowManagerService.WINDOW_LAYER_MULTIPLIER
+                    - WindowManagerService.LAYER_OFFSET_THUMBNAIL);
+        }
+        thumbnail.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y],
+                tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
+        thumbnail.setWindowCrop(thumbnailTransformation.getClipRect());
+    }
+
+    /**
+     * Sometimes we need to synchronize the first frame of animation with some external event, e.g.
+     * Recents hiding some of its content. To achieve this, we prolong the start of the animaiton
+     * and keep producing the first frame of the animation.
+     */
+    private long getAnimationFrameTime(Animation animation, long currentTime) {
+        if (mProlongAnimation == PROLONG_ANIMATION_AT_START) {
+            animation.setStartTime(currentTime);
+            return currentTime + 1;
+        }
+        return currentTime;
+    }
+
+    private boolean stepAnimation(long currentTime) {
+        if (animation == null) {
+            return false;
+        }
+        transformation.clear();
+        final long animationFrameTime = getAnimationFrameTime(animation, currentTime);
+        boolean hasMoreFrames = animation.getTransformation(animationFrameTime, transformation);
+        if (!hasMoreFrames) {
+            if (deferThumbnailDestruction && !deferFinalFrameCleanup) {
+                // We are deferring the thumbnail destruction, so extend the animation for one more
+                // (dummy) frame before we clean up
+                deferFinalFrameCleanup = true;
+                hasMoreFrames = true;
+            } else {
+                if (false && DEBUG_ANIM) Slog.v(TAG,
+                        "Stepped animation in " + mAppToken + ": more=" + hasMoreFrames +
+                        ", xform=" + transformation + ", mProlongAnimation=" + mProlongAnimation);
+                deferFinalFrameCleanup = false;
+                if (mProlongAnimation == PROLONG_ANIMATION_AT_END) {
+                    hasMoreFrames = true;
+                } else {
+                    setNullAnimation();
+                    clearThumbnail();
+                    if (DEBUG_ANIM) Slog.v(TAG, "Finished animation in " + mAppToken + " @ "
+                            + currentTime);
+                }
+            }
+        }
+        hasTransformation = hasMoreFrames;
+        return hasMoreFrames;
+    }
+
+    private long getStartTimeCorrection() {
+        if (mSkipFirstFrame) {
+
+            // If the transition is an animation in which the first frame doesn't change the screen
+            // contents at all, we can just skip it and start at the second frame. So we shift the
+            // start time of the animation forward by minus the frame duration.
+            return -Choreographer.getInstance().getFrameIntervalNanos() / TimeUtils.NANOS_PER_MS;
+        } else {
+            return 0;
+        }
+    }
+
+    // This must be called while inside a transaction.
+    boolean stepAnimationLocked(long currentTime) {
+        if (mAppToken.okToAnimate()) {
+            // We will run animations as long as the display isn't frozen.
+
+            if (animation == sDummyAnimation) {
+                // This guy is going to animate, but not yet.  For now count
+                // it as not animating for purposes of scheduling transactions;
+                // when it is really time to animate, this will be set to
+                // a real animation and the next call will execute normally.
+                return false;
+            }
+
+            if ((mAppToken.allDrawn || animating || mAppToken.startingDisplayed)
+                    && animation != null) {
+                if (!animating) {
+                    if (DEBUG_ANIM) Slog.v(TAG,
+                        "Starting animation in " + mAppToken +
+                        " @ " + currentTime + " scale="
+                        + mService.getTransitionAnimationScaleLocked()
+                        + " allDrawn=" + mAppToken.allDrawn + " animating=" + animating);
+                    long correction = getStartTimeCorrection();
+                    animation.setStartTime(currentTime + correction);
+                    animating = true;
+                    if (thumbnail != null) {
+                        thumbnail.show();
+                        thumbnailAnimation.setStartTime(currentTime + correction);
+                    }
+                    mSkipFirstFrame = false;
+                }
+                if (stepAnimation(currentTime)) {
+                    // animation isn't over, step any thumbnail and that's
+                    // it for now.
+                    if (thumbnail != null) {
+                        stepThumbnailAnimation(currentTime);
+                    }
+                    return true;
+                }
+            }
+        } else if (animation != null) {
+            // If the display is frozen, and there is a pending animation,
+            // clear it and make sure we run the cleanup code.
+            animating = true;
+            animation = null;
+        }
+
+        hasTransformation = false;
+
+        if (!animating && animation == null) {
+            return false;
+        }
+
+        mAppToken.setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM, "AppWindowToken");
+
+        clearAnimation();
+        animating = false;
+        if (animLayerAdjustment != 0) {
+            animLayerAdjustment = 0;
+            updateLayers();
+        }
+        if (mService.mInputMethodTarget != null
+                && mService.mInputMethodTarget.mAppToken == mAppToken) {
+            mAppToken.getDisplayContent().computeImeTarget(true /* updateImeTarget */);
+        }
+
+        if (DEBUG_ANIM) Slog.v(TAG, "Animation done in " + mAppToken
+                + ": reportedVisible=" + mAppToken.reportedVisible
+                + " okToDisplay=" + mAppToken.okToDisplay()
+                + " okToAnimate=" + mAppToken.okToAnimate()
+                + " startingDisplayed=" + mAppToken.startingDisplayed);
+
+        transformation.clear();
+
+        final int numAllAppWinAnimators = mAllAppWinAnimators.size();
+        for (int i = 0; i < numAllAppWinAnimators; i++) {
+            mAllAppWinAnimators.get(i).mWin.onExitAnimationDone();
+        }
+        mService.mAppTransition.notifyAppTransitionFinishedLocked(mAppToken.token);
+        return false;
+    }
+
+    // This must be called while inside a transaction.
+    boolean showAllWindowsLocked() {
+        boolean isAnimating = false;
+        final int NW = mAllAppWinAnimators.size();
+        for (int i=0; i<NW; i++) {
+            WindowStateAnimator winAnimator = mAllAppWinAnimators.get(i);
+            if (DEBUG_VISIBILITY) Slog.v(TAG, "performing show on: " + winAnimator);
+            winAnimator.mWin.performShowLocked();
+            isAnimating |= winAnimator.isAnimationSet();
+        }
+        return isAnimating;
+    }
+
+    void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix); pw.print("mAppToken="); pw.println(mAppToken);
+        pw.print(prefix); pw.print("mAnimator="); pw.println(mAnimator);
+        pw.print(prefix); pw.print("freezingScreen="); pw.print(freezingScreen);
+                pw.print(" allDrawn="); pw.print(allDrawn);
+                pw.print(" animLayerAdjustment="); pw.println(animLayerAdjustment);
+        if (lastFreezeDuration != 0) {
+            pw.print(prefix); pw.print("lastFreezeDuration=");
+                    TimeUtils.formatDuration(lastFreezeDuration, pw); pw.println();
+        }
+        if (animating || animation != null) {
+            pw.print(prefix); pw.print("animating="); pw.println(animating);
+            pw.print(prefix); pw.print("animation="); pw.println(animation);
+            pw.print(prefix); pw.print("mTransit="); pw.println(mTransit);
+            pw.print(prefix); pw.print("mTransitFlags="); pw.println(mTransitFlags);
+        }
+        if (hasTransformation) {
+            pw.print(prefix); pw.print("XForm: ");
+                    transformation.printShortString(pw);
+                    pw.println();
+        }
+        if (thumbnail != null) {
+            pw.print(prefix); pw.print("thumbnail="); pw.print(thumbnail);
+                    pw.print(" layer="); pw.println(thumbnailLayer);
+            pw.print(prefix); pw.print("thumbnailAnimation="); pw.println(thumbnailAnimation);
+            pw.print(prefix); pw.print("thumbnailTransformation=");
+                    pw.println(thumbnailTransformation.toShortString());
+        }
+        for (int i=0; i<mAllAppWinAnimators.size(); i++) {
+            WindowStateAnimator wanim = mAllAppWinAnimators.get(i);
+            pw.print(prefix); pw.print("App Win Anim #"); pw.print(i);
+                    pw.print(": "); pw.println(wanim);
+        }
+    }
+
+    void startProlongAnimation(int prolongType) {
+        mProlongAnimation = prolongType;
+        mClearProlongedAnimation = false;
+    }
+
+    void endProlongedAnimation() {
+        mProlongAnimation = PROLONG_ANIMATION_DISABLED;
+    }
+
+    // This is an animation that does nothing: it just immediately finishes
+    // itself every time it is called.  It is used as a stub animation in cases
+    // where we want to synchronize multiple things that may be animating.
+    static final class DummyAnimation extends Animation {
+        @Override
+        public boolean getTransformation(long currentTime, Transformation outTransformation) {
+            return false;
+        }
+    }
+
+}
diff --git a/com/android/server/wm/AppWindowContainerController.java b/com/android/server/wm/AppWindowContainerController.java
new file mode 100644
index 0000000..5841840
--- /dev/null
+++ b/com/android/server/wm/AppWindowContainerController.java
@@ -0,0 +1,758 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+
+import static com.android.server.wm.AppTransition.TRANSIT_DOCK_TASK_FROM_RECENTS;
+import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TOKEN_MOVEMENT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.app.ActivityManager.TaskSnapshot;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Trace;
+import android.util.Slog;
+import android.view.DisplayInfo;
+import android.view.IApplicationToken;
+import android.view.WindowManagerPolicy.StartingSurface;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.AttributeCache;
+/**
+ * Controller for the app window token container. This is created by activity manager to link
+ * activity records to the app window token container they use in window manager.
+ *
+ * Test class: {@link AppWindowContainerControllerTests}
+ */
+public class AppWindowContainerController
+        extends WindowContainerController<AppWindowToken, AppWindowContainerListener> {
+
+    private static final int STARTING_WINDOW_TYPE_NONE = 0;
+    private static final int STARTING_WINDOW_TYPE_SNAPSHOT = 1;
+    private static final int STARTING_WINDOW_TYPE_SPLASH_SCREEN = 2;
+
+    private final IApplicationToken mToken;
+    private final Handler mHandler;
+
+    private final class H extends Handler {
+        public static final int NOTIFY_WINDOWS_DRAWN = 1;
+        public static final int NOTIFY_STARTING_WINDOW_DRAWN = 2;
+
+        public H(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case NOTIFY_WINDOWS_DRAWN:
+                    if (mListener == null) {
+                        return;
+                    }
+                    if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting drawn in "
+                            + AppWindowContainerController.this.mToken);
+                    mListener.onWindowsDrawn(msg.getWhen());
+                    break;
+                case NOTIFY_STARTING_WINDOW_DRAWN:
+                    if (mListener == null) {
+                        return;
+                    }
+                    if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting drawn in "
+                            + AppWindowContainerController.this.mToken);
+                    mListener.onStartingWindowDrawn(msg.getWhen());
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    private final Runnable mOnWindowsVisible = () -> {
+        if (mListener == null) {
+            return;
+        }
+        if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting visible in "
+                + AppWindowContainerController.this.mToken);
+        mListener.onWindowsVisible();
+    };
+
+    private final Runnable mOnWindowsGone = () -> {
+        if (mListener == null) {
+            return;
+        }
+        if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting gone in "
+                + AppWindowContainerController.this.mToken);
+        mListener.onWindowsGone();
+    };
+
+    private final Runnable mAddStartingWindow = () -> {
+        final StartingData startingData;
+        final AppWindowToken container;
+
+        synchronized (mWindowMap) {
+            if (mContainer == null) {
+                if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "mContainer was null while trying to"
+                        + " add starting window");
+                return;
+            }
+            startingData = mContainer.startingData;
+            container = mContainer;
+        }
+
+        if (startingData == null) {
+            // Animation has been canceled... do nothing.
+            if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "startingData was nulled out before handling"
+                    + " mAddStartingWindow: " + mContainer);
+            return;
+        }
+
+        if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Add starting "
+                + this + ": startingData=" + container.startingData);
+
+        StartingSurface surface = null;
+        try {
+            surface = startingData.createStartingSurface(container);
+        } catch (Exception e) {
+            Slog.w(TAG_WM, "Exception when adding starting window", e);
+        }
+        if (surface != null) {
+            boolean abort = false;
+            synchronized(mWindowMap) {
+                // If the window was successfully added, then
+                // we need to remove it.
+                if (container.removed || container.startingData == null) {
+                    if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM,
+                            "Aborted starting " + container
+                                    + ": removed=" + container.removed
+                                    + " startingData=" + container.startingData);
+                    container.startingWindow = null;
+                    container.startingData = null;
+                    abort = true;
+                } else {
+                    container.startingSurface = surface;
+                }
+                if (DEBUG_STARTING_WINDOW && !abort) Slog.v(TAG_WM,
+                        "Added starting " + mContainer
+                                + ": startingWindow="
+                                + container.startingWindow + " startingView="
+                                + container.startingSurface);
+            }
+            if (abort) {
+                surface.remove();
+            }
+        } else if (DEBUG_STARTING_WINDOW) {
+            Slog.v(TAG_WM, "Surface returned was null: " + mContainer);
+        }
+    };
+
+    public AppWindowContainerController(TaskWindowContainerController taskController,
+            IApplicationToken token, AppWindowContainerListener listener, int index,
+            int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int configChanges,
+            boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable,
+            int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
+            Rect bounds) {
+        this(taskController, token, listener, index, requestedOrientation, fullscreen,
+                showForAllUsers,
+                configChanges, voiceInteraction, launchTaskBehind, alwaysFocusable,
+                targetSdkVersion, rotationAnimationHint, inputDispatchingTimeoutNanos,
+                WindowManagerService.getInstance(), bounds);
+    }
+
+    public AppWindowContainerController(TaskWindowContainerController taskController,
+            IApplicationToken token, AppWindowContainerListener listener, int index,
+            int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int configChanges,
+            boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable,
+            int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
+            WindowManagerService service, Rect bounds) {
+        super(listener, service);
+        mHandler = new H(service.mH.getLooper());
+        mToken = token;
+        synchronized(mWindowMap) {
+            AppWindowToken atoken = mRoot.getAppWindowToken(mToken.asBinder());
+            if (atoken != null) {
+                // TODO: Should this throw an exception instead?
+                Slog.w(TAG_WM, "Attempted to add existing app token: " + mToken);
+                return;
+            }
+
+            final Task task = taskController.mContainer;
+            if (task == null) {
+                throw new IllegalArgumentException("AppWindowContainerController: invalid "
+                        + " controller=" + taskController);
+            }
+
+            atoken = createAppWindow(mService, token, voiceInteraction, task.getDisplayContent(),
+                    inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdkVersion,
+                    requestedOrientation, rotationAnimationHint, configChanges, launchTaskBehind,
+                    alwaysFocusable, this, bounds);
+            if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addAppToken: " + atoken
+                    + " controller=" + taskController + " at " + index);
+            task.addChild(atoken, index);
+        }
+    }
+
+    @VisibleForTesting
+    AppWindowToken createAppWindow(WindowManagerService service, IApplicationToken token,
+            boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos,
+            boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation,
+            int rotationAnimationHint, int configChanges, boolean launchTaskBehind,
+            boolean alwaysFocusable, AppWindowContainerController controller, Rect bounds) {
+        return new AppWindowToken(service, token, voiceInteraction, dc,
+                inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdk, orientation,
+                rotationAnimationHint, configChanges, launchTaskBehind, alwaysFocusable,
+                controller, bounds);
+    }
+
+    public void removeContainer(int displayId) {
+        synchronized(mWindowMap) {
+            final DisplayContent dc = mRoot.getDisplayContent(displayId);
+            if (dc == null) {
+                Slog.w(TAG_WM, "removeAppToken: Attempted to remove binder token: "
+                        + mToken + " from non-existing displayId=" + displayId);
+                return;
+            }
+            dc.removeAppToken(mToken.asBinder());
+            super.removeContainer();
+        }
+    }
+
+    @Override
+    public void removeContainer() {
+        throw new UnsupportedOperationException("Use removeContainer(displayId) instead.");
+    }
+
+    public void reparent(TaskWindowContainerController taskController, int position) {
+        synchronized (mWindowMap) {
+            if (DEBUG_ADD_REMOVE) Slog.i(TAG_WM, "reparent: moving app token=" + mToken
+                    + " to task=" + taskController + " at " + position);
+            if (mContainer == null) {
+                if (DEBUG_ADD_REMOVE) Slog.i(TAG_WM,
+                        "reparent: could not find app token=" + mToken);
+                return;
+            }
+            final Task task = taskController.mContainer;
+            if (task == null) {
+                throw new IllegalArgumentException("reparent: could not find task="
+                        + taskController);
+            }
+            mContainer.reparent(task, position);
+            mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
+        }
+    }
+
+    public Configuration setOrientation(int requestedOrientation, int displayId,
+            Configuration displayConfig, boolean freezeScreenIfNeeded) {
+        synchronized(mWindowMap) {
+            if (mContainer == null) {
+                Slog.w(TAG_WM,
+                        "Attempted to set orientation of non-existing app token: " + mToken);
+                return null;
+            }
+
+            mContainer.setOrientation(requestedOrientation);
+
+            final IBinder binder = freezeScreenIfNeeded ? mToken.asBinder() : null;
+            return mService.updateOrientationFromAppTokens(displayConfig, binder, displayId);
+
+        }
+    }
+
+    public int getOrientation() {
+        synchronized(mWindowMap) {
+            if (mContainer == null) {
+                return SCREEN_ORIENTATION_UNSPECIFIED;
+            }
+
+            return mContainer.getOrientationIgnoreVisibility();
+        }
+    }
+
+    // TODO(b/36505427): Maybe move to WindowContainerController so other sub-classes can use it as
+    // a generic way to set override config. Need to untangle current ways the override config is
+    // currently set for tasks and displays before we are doing that though.
+    public void onOverrideConfigurationChanged(Configuration overrideConfiguration, Rect bounds) {
+        synchronized(mWindowMap) {
+            if (mContainer != null) {
+                mContainer.onOverrideConfigurationChanged(overrideConfiguration, bounds);
+            }
+        }
+    }
+
+    public void setDisablePreviewScreenshots(boolean disable) {
+        synchronized (mWindowMap) {
+            if (mContainer == null) {
+                Slog.w(TAG_WM, "Attempted to set disable screenshots of non-existing app"
+                        + " token: " + mToken);
+                return;
+            }
+            mContainer.setDisablePreviewScreenshots(disable);
+        }
+    }
+
+    public void setVisibility(boolean visible, boolean deferHidingClient) {
+        synchronized(mWindowMap) {
+            if (mContainer == null) {
+                Slog.w(TAG_WM, "Attempted to set visibility of non-existing app token: "
+                        + mToken);
+                return;
+            }
+
+            final AppWindowToken wtoken = mContainer;
+
+            // Don't set visibility to false if we were already not visible. This prevents WM from
+            // adding the app to the closing app list which doesn't make sense for something that is
+            // already not visible. However, set visibility to true even if we are already visible.
+            // This makes sure the app is added to the opening apps list so that the right
+            // transition can be selected.
+            // TODO: Probably a good idea to separate the concept of opening/closing apps from the
+            // concept of setting visibility...
+            if (!visible && wtoken.hiddenRequested) {
+
+                if (!deferHidingClient && wtoken.mDeferHidingClient) {
+                    // We previously deferred telling the client to hide itself when visibility was
+                    // initially set to false. Now we would like it to hide, so go ahead and set it.
+                    wtoken.mDeferHidingClient = deferHidingClient;
+                    wtoken.setClientHidden(true);
+                }
+                return;
+            }
+
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ORIENTATION) Slog.v(TAG_WM, "setAppVisibility("
+                    + mToken + ", visible=" + visible + "): " + mService.mAppTransition
+                    + " hidden=" + wtoken.hidden + " hiddenRequested="
+                    + wtoken.hiddenRequested + " Callers=" + Debug.getCallers(6));
+
+            mService.mOpeningApps.remove(wtoken);
+            mService.mClosingApps.remove(wtoken);
+            wtoken.waitingToShow = false;
+            wtoken.hiddenRequested = !visible;
+            wtoken.mDeferHidingClient = deferHidingClient;
+
+            if (!visible) {
+                // If the 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.removeDeadWindows();
+            } else {
+                if (!mService.mAppTransition.isTransitionSet()
+                        && mService.mAppTransition.isReady()) {
+                    // Add the app mOpeningApps if transition is unset but ready. This means
+                    // we're doing a screen freeze, and the unfreeze will wait for all opening
+                    // apps to be ready.
+                    mService.mOpeningApps.add(wtoken);
+                }
+                wtoken.startingMoved = false;
+                // If the token is currently hidden (should be the common case), or has been
+                // stopped, then we need to set up to wait for its windows to be ready.
+                if (wtoken.hidden || wtoken.mAppStopped) {
+                    wtoken.clearAllDrawn();
+
+                    // If the app was already visible, don't reset the waitingToShow state.
+                    if (wtoken.hidden) {
+                        wtoken.waitingToShow = true;
+                    }
+
+                    if (wtoken.isClientHidden()) {
+                        // In the case where we are making an app visible but holding off for a
+                        // transition, we still need to tell the client to make its windows visible
+                        // so they get drawn. Otherwise, we will wait on performing the transition
+                        // until all windows have been drawn, they never will be, and we are sad.
+                        wtoken.setClientHidden(false);
+                    }
+                }
+                wtoken.requestUpdateWallpaperIfNeeded();
+
+                if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "No longer Stopped: " + wtoken);
+                wtoken.mAppStopped = false;
+            }
+
+            // If we are preparing an app transition, then delay changing
+            // the visibility of this token until we execute that transition.
+            if (wtoken.okToAnimate() && mService.mAppTransition.isTransitionSet()) {
+                // A dummy animation is a placeholder animation which informs others that an
+                // animation is going on (in this case an application transition). If the animation
+                // was transferred from another application/animator, no dummy animator should be
+                // created since an animation is already in progress.
+                if (wtoken.mAppAnimator.usingTransferredAnimation
+                        && wtoken.mAppAnimator.animation == null) {
+                    Slog.wtf(TAG_WM, "Will NOT set dummy animation on: " + wtoken
+                            + ", using null transferred animation!");
+                }
+                if (!wtoken.mAppAnimator.usingTransferredAnimation &&
+                        (!wtoken.startingDisplayed || mService.mSkipAppTransitionAnimation)) {
+                    if (DEBUG_APP_TRANSITIONS) Slog.v(
+                            TAG_WM, "Setting dummy animation on: " + wtoken);
+                    wtoken.mAppAnimator.setDummyAnimation();
+                }
+                wtoken.inPendingTransaction = true;
+                if (visible) {
+                    mService.mOpeningApps.add(wtoken);
+                    wtoken.mEnteringAnimation = true;
+                } else {
+                    mService.mClosingApps.add(wtoken);
+                    wtoken.mEnteringAnimation = false;
+                }
+                if (mService.mAppTransition.getAppTransition()
+                        == AppTransition.TRANSIT_TASK_OPEN_BEHIND) {
+                    // We're launchingBehind, add the launching activity to mOpeningApps.
+                    final WindowState win =
+                            mService.getDefaultDisplayContentLocked().findFocusedWindow();
+                    if (win != null) {
+                        final AppWindowToken focusedToken = win.mAppToken;
+                        if (focusedToken != null) {
+                            if (DEBUG_APP_TRANSITIONS) Slog.d(TAG_WM, "TRANSIT_TASK_OPEN_BEHIND, "
+                                    + " adding " + focusedToken + " to mOpeningApps");
+                            // Force animation to be loaded.
+                            focusedToken.hidden = true;
+                            mService.mOpeningApps.add(focusedToken);
+                        }
+                    }
+                }
+                return;
+            }
+
+            wtoken.setVisibility(null, visible, TRANSIT_UNSET, true, wtoken.mVoiceInteraction);
+            wtoken.updateReportedVisibilityLocked();
+        }
+    }
+
+    /**
+     * Notifies that we launched an app that might be visible or not visible depending on what kind
+     * of Keyguard flags it's going to set on its windows.
+     */
+    public void notifyUnknownVisibilityLaunched() {
+        synchronized(mWindowMap) {
+            if (mContainer != null) {
+                mService.mUnknownAppVisibilityController.notifyLaunched(mContainer);
+            }
+        }
+    }
+
+    public boolean addStartingWindow(String pkg, int theme, CompatibilityInfo compatInfo,
+            CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags,
+            IBinder transferFrom, boolean newTask, boolean taskSwitch, boolean processRunning,
+            boolean allowTaskSnapshot, boolean activityCreated, boolean fromRecents) {
+        synchronized(mWindowMap) {
+            if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "setAppStartingWindow: token=" + mToken
+                    + " pkg=" + pkg + " transferFrom=" + transferFrom + " newTask=" + newTask
+                    + " taskSwitch=" + taskSwitch + " processRunning=" + processRunning
+                    + " allowTaskSnapshot=" + allowTaskSnapshot);
+
+            if (mContainer == null) {
+                Slog.w(TAG_WM, "Attempted to set icon of non-existing app token: " + mToken);
+                return false;
+            }
+
+            // If the display is frozen, we won't do anything until the actual window is
+            // displayed so there is no reason to put in the starting window.
+            if (!mContainer.okToDisplay()) {
+                return false;
+            }
+
+            if (mContainer.startingData != null) {
+                return false;
+            }
+
+            final WindowState mainWin = mContainer.findMainWindow();
+            if (mainWin != null && mainWin.mWinAnimator.getShown()) {
+                // App already has a visible window...why would you want a starting window?
+                return false;
+            }
+
+            final TaskSnapshot snapshot = mService.mTaskSnapshotController.getSnapshot(
+                    mContainer.getTask().mTaskId, mContainer.getTask().mUserId,
+                    false /* restoreFromDisk */, false /* reducedResolution */);
+            final int type = getStartingWindowType(newTask, taskSwitch, processRunning,
+                    allowTaskSnapshot, activityCreated, fromRecents, snapshot);
+
+            if (type == STARTING_WINDOW_TYPE_SNAPSHOT) {
+                return createSnapshot(snapshot);
+            }
+
+            // If this is a translucent window, then don't show a starting window -- the current
+            // effect (a full-screen opaque starting window that fades away to the real contents
+            // when it is ready) does not work for this.
+            if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Checking theme of starting window: 0x"
+                    + Integer.toHexString(theme));
+            if (theme != 0) {
+                AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
+                        com.android.internal.R.styleable.Window, mService.mCurrentUserId);
+                if (ent == null) {
+                    // Whoops!  App doesn't exist. Um. Okay. We'll just pretend like we didn't
+                    // see that.
+                    return false;
+                }
+                final boolean windowIsTranslucent = ent.array.getBoolean(
+                        com.android.internal.R.styleable.Window_windowIsTranslucent, false);
+                final boolean windowIsFloating = ent.array.getBoolean(
+                        com.android.internal.R.styleable.Window_windowIsFloating, false);
+                final boolean windowShowWallpaper = ent.array.getBoolean(
+                        com.android.internal.R.styleable.Window_windowShowWallpaper, false);
+                final boolean windowDisableStarting = ent.array.getBoolean(
+                        com.android.internal.R.styleable.Window_windowDisablePreview, false);
+                if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Translucent=" + windowIsTranslucent
+                        + " Floating=" + windowIsFloating
+                        + " ShowWallpaper=" + windowShowWallpaper);
+                if (windowIsTranslucent) {
+                    return false;
+                }
+                if (windowIsFloating || windowDisableStarting) {
+                    return false;
+                }
+                if (windowShowWallpaper) {
+                    if (mContainer.getDisplayContent().mWallpaperController.getWallpaperTarget()
+                            == null) {
+                        // If this theme is requesting a wallpaper, and the wallpaper
+                        // is not currently visible, then this effectively serves as
+                        // an opaque window and our starting window transition animation
+                        // can still work.  We just need to make sure the starting window
+                        // is also showing the wallpaper.
+                        windowFlags |= FLAG_SHOW_WALLPAPER;
+                    } else {
+                        return false;
+                    }
+                }
+            }
+
+            if (mContainer.transferStartingWindow(transferFrom)) {
+                return true;
+            }
+
+            // There is no existing starting window, and we don't want to create a splash screen, so
+            // that's it!
+            if (type != STARTING_WINDOW_TYPE_SPLASH_SCREEN) {
+                return false;
+            }
+
+            if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Creating SplashScreenStartingData");
+            mContainer.startingData = new SplashScreenStartingData(mService, pkg, theme,
+                    compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
+                    mContainer.getMergedOverrideConfiguration());
+            scheduleAddStartingWindow();
+        }
+        return true;
+    }
+
+    private int getStartingWindowType(boolean newTask, boolean taskSwitch, boolean processRunning,
+            boolean allowTaskSnapshot, boolean activityCreated, boolean fromRecents,
+            TaskSnapshot snapshot) {
+        if (mService.mAppTransition.getAppTransition() == TRANSIT_DOCK_TASK_FROM_RECENTS) {
+            // TODO(b/34099271): Remove this statement to add back the starting window and figure
+            // out why it causes flickering, the starting window appears over the thumbnail while
+            // the docked from recents transition occurs
+            return STARTING_WINDOW_TYPE_NONE;
+        } else if (newTask || !processRunning || (taskSwitch && !activityCreated)) {
+            return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+        } else if (taskSwitch && allowTaskSnapshot) {
+            return snapshot == null ? STARTING_WINDOW_TYPE_NONE
+                    : snapshotOrientationSameAsTask(snapshot) || fromRecents
+                            ? STARTING_WINDOW_TYPE_SNAPSHOT : STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+        } else {
+            return STARTING_WINDOW_TYPE_NONE;
+        }
+    }
+
+    void scheduleAddStartingWindow() {
+        // Note: we really want to do sendMessageAtFrontOfQueue() because we
+        // want to process the message ASAP, before any other queued
+        // messages.
+        if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Enqueueing ADD_STARTING");
+        mService.mAnimationHandler.postAtFrontOfQueue(mAddStartingWindow);
+    }
+
+    private boolean createSnapshot(TaskSnapshot snapshot) {
+        if (snapshot == null) {
+            return false;
+        }
+
+        if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Creating SnapshotStartingData");
+        mContainer.startingData = new SnapshotStartingData(mService, snapshot);
+        scheduleAddStartingWindow();
+        return true;
+    }
+
+    private boolean snapshotOrientationSameAsTask(TaskSnapshot snapshot) {
+        if (snapshot == null) {
+            return false;
+        }
+        return mContainer.getTask().getConfiguration().orientation == snapshot.getOrientation();
+    }
+
+    public void removeStartingWindow() {
+        synchronized (mWindowMap) {
+            if (mContainer.startingWindow == null) {
+                if (mContainer.startingData != null) {
+                    // Starting window has not been added yet, but it is scheduled to be added.
+                    // Go ahead and cancel the request.
+                    if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM,
+                            "Clearing startingData for token=" + mContainer);
+                    mContainer.startingData = null;
+                }
+                return;
+            }
+
+            final StartingSurface surface;
+            if (mContainer.startingData != null) {
+                surface = mContainer.startingSurface;
+                mContainer.startingData = null;
+                mContainer.startingSurface = null;
+                mContainer.startingWindow = null;
+                mContainer.startingDisplayed = false;
+                if (surface == null) {
+                    if (DEBUG_STARTING_WINDOW) {
+                        Slog.v(TAG_WM, "startingWindow was set but startingSurface==null, couldn't "
+                                + "remove");
+                    }
+                    return;
+                }
+            } else {
+                if (DEBUG_STARTING_WINDOW) {
+                    Slog.v(TAG_WM, "Tried to remove starting window but startingWindow was null:"
+                            + mContainer);
+                }
+                return;
+            }
+
+            if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Schedule remove starting " + mContainer
+                    + " startingWindow=" + mContainer.startingWindow
+                    + " startingView=" + mContainer.startingSurface);
+
+            // Use the same thread to remove the window as we used to add it, as otherwise we end up
+            // with things in the view hierarchy being called from different threads.
+            mService.mAnimationHandler.post(() -> {
+                if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Removing startingView=" + surface);
+                try {
+                    surface.remove();
+                } catch (Exception e) {
+                    Slog.w(TAG_WM, "Exception when removing starting window", e);
+                }
+            });
+        }
+    }
+
+    public void pauseKeyDispatching() {
+        synchronized (mWindowMap) {
+            if (mContainer != null) {
+                mService.mInputMonitor.pauseDispatchingLw(mContainer);
+            }
+        }
+    }
+
+    public void resumeKeyDispatching() {
+        synchronized (mWindowMap) {
+            if (mContainer != null) {
+                mService.mInputMonitor.resumeDispatchingLw(mContainer);
+            }
+        }
+    }
+
+    public void notifyAppResumed(boolean wasStopped) {
+        synchronized(mWindowMap) {
+            if (mContainer == null) {
+                Slog.w(TAG_WM, "Attempted to notify resumed of non-existing app token: " + mToken);
+                return;
+            }
+            mContainer.notifyAppResumed(wasStopped);
+        }
+    }
+
+    public void notifyAppStopped() {
+        synchronized(mWindowMap) {
+            if (mContainer == null) {
+                Slog.w(TAG_WM, "Attempted to notify stopped of non-existing app token: "
+                        + mToken);
+                return;
+            }
+            mContainer.notifyAppStopped();
+        }
+    }
+
+    public void startFreezingScreen(int configChanges) {
+        synchronized(mWindowMap) {
+            if (mContainer == null) {
+                Slog.w(TAG_WM,
+                        "Attempted to freeze screen with non-existing app token: " + mContainer);
+                return;
+            }
+
+            if (configChanges == 0 && mContainer.okToDisplay()) {
+                if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Skipping set freeze of " + mToken);
+                return;
+            }
+
+            mContainer.startFreezingScreen();
+        }
+    }
+
+    public void stopFreezingScreen(boolean force) {
+        synchronized(mWindowMap) {
+            if (mContainer == null) {
+                return;
+            }
+            if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Clear freezing of " + mToken + ": hidden="
+                    + mContainer.hidden + " freezing=" + mContainer.mAppAnimator.freezingScreen);
+            mContainer.stopFreezingScreen(true, force);
+        }
+    }
+
+    void reportStartingWindowDrawn() {
+        mHandler.sendMessage(mHandler.obtainMessage(H.NOTIFY_STARTING_WINDOW_DRAWN));
+    }
+
+    void reportWindowsDrawn() {
+        mHandler.sendMessage(mHandler.obtainMessage(H.NOTIFY_WINDOWS_DRAWN));
+    }
+
+    void reportWindowsVisible() {
+        mHandler.post(mOnWindowsVisible);
+    }
+
+    void reportWindowsGone() {
+        mHandler.post(mOnWindowsGone);
+    }
+
+    /** Calls directly into activity manager so window manager lock shouldn't held. */
+    boolean keyDispatchingTimedOut(String reason, int windowPid) {
+        return mListener != null && mListener.keyDispatchingTimedOut(reason, windowPid);
+    }
+
+    @Override
+    public String toString() {
+        return "AppWindowContainerController{"
+                + " token=" + mToken
+                + " mContainer=" + mContainer
+                + " mListener=" + mListener
+                + "}";
+    }
+}
diff --git a/com/android/server/wm/AppWindowContainerListener.java b/com/android/server/wm/AppWindowContainerListener.java
new file mode 100644
index 0000000..8a39a74
--- /dev/null
+++ b/com/android/server/wm/AppWindowContainerListener.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+/** Interface used by the creator of the controller to listen to changes with the container. */
+public interface AppWindowContainerListener extends WindowContainerListener {
+    /** Called when the windows associated app window container are drawn. */
+    void onWindowsDrawn(long timestamp);
+    /** Called when the windows associated app window container are visible. */
+    void onWindowsVisible();
+    /** Called when the windows associated app window container are no longer visible. */
+    void onWindowsGone();
+
+    /**
+     * Called when the starting window for this container is drawn.
+     */
+    void onStartingWindowDrawn(long timestamp);
+
+    /**
+     * Called when the key dispatching to a window associated with the app window container
+     * timed-out.
+     *
+     * @param reason The reason for the key dispatching time out.
+     * @param windowPid The pid of the window key dispatching timed out on.
+     * @return True if input dispatching should be aborted.
+     */
+    boolean keyDispatchingTimedOut(String reason, int windowPid);
+}
diff --git a/com/android/server/wm/AppWindowToken.java b/com/android/server/wm/AppWindowToken.java
new file mode 100644
index 0000000..ea2f305
--- /dev/null
+++ b/com/android/server/wm/AppWindowToken.java
@@ -0,0 +1,1654 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.ActivityManager.StackId;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
+import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.os.Build.VERSION_CODES.O_MR1;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
+import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
+import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TOKEN_MOVEMENT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEMENT;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.H.NOTIFY_ACTIVITY_DRAWN;
+import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
+import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
+import static com.android.server.wm.WindowManagerService.logWithStack;
+import static com.android.server.wm.proto.AppWindowTokenProto.NAME;
+import static com.android.server.wm.proto.AppWindowTokenProto.WINDOW_TOKEN;
+
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.Debug;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+import android.view.IApplicationToken;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.view.WindowManagerPolicy.StartingSurface;
+
+import com.android.internal.util.ToBooleanFunction;
+import com.android.server.input.InputApplicationHandle;
+import com.android.server.wm.WindowManagerService.H;
+
+import java.io.PrintWriter;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+
+class AppTokenList extends ArrayList<AppWindowToken> {
+}
+
+/**
+ * Version of WindowToken that is specifically for a particular application (or
+ * really activity) that is displaying windows.
+ */
+class AppWindowToken extends WindowToken implements WindowManagerService.AppFreezeListener {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "AppWindowToken" : TAG_WM;
+
+    // Non-null only for application tokens.
+    final IApplicationToken appToken;
+
+    @NonNull final AppWindowAnimator mAppAnimator;
+
+    final boolean mVoiceInteraction;
+
+    /** @see WindowContainer#fillsParent() */
+    private boolean mFillsParent;
+    boolean layoutConfigChanges;
+    boolean mShowForAllUsers;
+    int mTargetSdk;
+
+    // Flag set while reparenting to prevent actions normally triggered by an individual parent
+    // change.
+    private boolean mReparenting;
+
+    // True if we are current in the process of removing this app token from the display
+    private boolean mRemovingFromDisplay = false;
+
+    // The input dispatching timeout for this application token in nanoseconds.
+    long mInputDispatchingTimeoutNanos;
+
+    // These are used for determining when all windows associated with
+    // an activity have been drawn, so they can be made visible together
+    // at the same time.
+    // initialize so that it doesn't match mTransactionSequence which is an int.
+    private long mLastTransactionSequence = Long.MIN_VALUE;
+    private int mNumInterestingWindows;
+    private int mNumDrawnWindows;
+    boolean inPendingTransaction;
+    boolean allDrawn;
+    // Set to true when this app creates a surface while in the middle of an animation. In that
+    // case do not clear allDrawn until the animation completes.
+    boolean deferClearAllDrawn;
+
+    // Is this window's surface needed?  This is almost like hidden, except
+    // it will sometimes be true a little earlier: when the token has
+    // been shown, but is still waiting for its app transition to execute
+    // before making its windows shown.
+    boolean hiddenRequested;
+
+    // Have we told the window clients to hide themselves?
+    private boolean mClientHidden;
+
+    // If true we will defer setting mClientHidden to true and reporting to the client that it is
+    // hidden.
+    boolean mDeferHidingClient;
+
+    // Last visibility state we reported to the app token.
+    boolean reportedVisible;
+
+    // Last drawn state we reported to the app token.
+    private boolean reportedDrawn;
+
+    // Set to true when the token has been removed from the window mgr.
+    boolean removed;
+
+    // Information about an application starting window if displayed.
+    StartingData startingData;
+    WindowState startingWindow;
+    StartingSurface startingSurface;
+    boolean startingDisplayed;
+    boolean startingMoved;
+    // True if the hidden state of this token was forced to false due to a transferred starting
+    // window.
+    private boolean mHiddenSetFromTransferredStartingWindow;
+    boolean firstWindowDrawn;
+    private final WindowState.UpdateReportedVisibilityResults mReportedVisibilityResults =
+            new WindowState.UpdateReportedVisibilityResults();
+
+    // Input application handle used by the input dispatcher.
+    final InputApplicationHandle mInputApplicationHandle;
+
+    // TODO: Have a WindowContainer state for tracking exiting/deferred removal.
+    boolean mIsExiting;
+
+    boolean mLaunchTaskBehind;
+    boolean mEnteringAnimation;
+
+    private boolean mAlwaysFocusable;
+
+    boolean mAppStopped;
+    int mRotationAnimationHint;
+    private int mPendingRelaunchCount;
+
+    private boolean mLastContainsShowWhenLockedWindow;
+    private boolean mLastContainsDismissKeyguardWindow;
+
+    // The bounds of this activity. Mainly used for aspect-ratio compatibility.
+    // TODO(b/36505427): Every level on WindowContainer now has bounds information, which directly
+    // affects the configuration. We should probably move this into that class.
+    private final Rect mBounds = new Rect();
+
+    ArrayDeque<Rect> mFrozenBounds = new ArrayDeque<>();
+    ArrayDeque<Configuration> mFrozenMergedConfig = new ArrayDeque<>();
+
+    private boolean mDisablePreviewScreenshots;
+
+    private Task mLastParent;
+
+    /**
+     * See {@link #canTurnScreenOn()}
+     */
+    private boolean mCanTurnScreenOn = true;
+
+    AppWindowToken(WindowManagerService service, IApplicationToken token, boolean voiceInteraction,
+            DisplayContent dc, long inputDispatchingTimeoutNanos, boolean fullscreen,
+            boolean showForAllUsers, int targetSdk, int orientation, int rotationAnimationHint,
+            int configChanges, boolean launchTaskBehind, boolean alwaysFocusable,
+            AppWindowContainerController controller, Rect bounds) {
+        this(service, token, voiceInteraction, dc, fullscreen, bounds);
+        setController(controller);
+        mInputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos;
+        mShowForAllUsers = showForAllUsers;
+        mTargetSdk = targetSdk;
+        mOrientation = orientation;
+        layoutConfigChanges = (configChanges & (CONFIG_SCREEN_SIZE | CONFIG_ORIENTATION)) != 0;
+        mLaunchTaskBehind = launchTaskBehind;
+        mAlwaysFocusable = alwaysFocusable;
+        mRotationAnimationHint = rotationAnimationHint;
+
+        // Application tokens start out hidden.
+        hidden = true;
+        hiddenRequested = true;
+    }
+
+    AppWindowToken(WindowManagerService service, IApplicationToken token, boolean voiceInteraction,
+            DisplayContent dc, boolean fillsParent, Rect bounds) {
+        super(service, token != null ? token.asBinder() : null, TYPE_APPLICATION, true, dc,
+                false /* ownerCanManageAppTokens */);
+        appToken = token;
+        mVoiceInteraction = voiceInteraction;
+        mFillsParent = fillsParent;
+        mInputApplicationHandle = new InputApplicationHandle(this);
+        mAppAnimator = new AppWindowAnimator(this, service);
+        if (bounds != null) {
+            mBounds.set(bounds);
+        }
+    }
+
+    void onOverrideConfigurationChanged(Configuration overrideConfiguration, Rect bounds) {
+        onOverrideConfigurationChanged(overrideConfiguration);
+        if (mBounds.equals(bounds)) {
+            return;
+        }
+        // TODO(b/36505427): If bounds is in WC, then we can automatically call onResize() when set.
+        mBounds.set(bounds);
+        onResize();
+    }
+
+    void getBounds(Rect outBounds) {
+        outBounds.set(mBounds);
+    }
+
+    boolean hasBounds() {
+        return !mBounds.isEmpty();
+    }
+
+    void onFirstWindowDrawn(WindowState win, WindowStateAnimator winAnimator) {
+        firstWindowDrawn = true;
+
+        // We now have a good window to show, remove dead placeholders
+        removeDeadWindows();
+
+        if (startingWindow != null) {
+            if (DEBUG_STARTING_WINDOW || DEBUG_ANIM) Slog.v(TAG, "Finish starting "
+                    + win.mToken + ": first real window is shown, no animation");
+            // If this initial window is animating, stop it -- we will do an animation to reveal
+            // it from behind the starting window, so there is no need for it to also be doing its
+            // own stuff.
+            winAnimator.clearAnimation();
+            if (getController() != null) {
+                getController().removeStartingWindow();
+            }
+        }
+        updateReportedVisibilityLocked();
+    }
+
+    void updateReportedVisibilityLocked() {
+        if (appToken == null) {
+            return;
+        }
+
+        if (DEBUG_VISIBILITY) Slog.v(TAG, "Update reported visibility: " + this);
+        final int count = mChildren.size();
+
+        mReportedVisibilityResults.reset();
+
+        for (int i = 0; i < count; i++) {
+            final WindowState win = mChildren.get(i);
+            win.updateReportedVisibility(mReportedVisibilityResults);
+        }
+
+        int numInteresting = mReportedVisibilityResults.numInteresting;
+        int numVisible = mReportedVisibilityResults.numVisible;
+        int numDrawn = mReportedVisibilityResults.numDrawn;
+        boolean nowGone = mReportedVisibilityResults.nowGone;
+
+        boolean nowDrawn = numInteresting > 0 && numDrawn >= numInteresting;
+        boolean nowVisible = numInteresting > 0 && numVisible >= numInteresting && !hidden;
+        if (!nowGone) {
+            // If the app is not yet gone, then it can only become visible/drawn.
+            if (!nowDrawn) {
+                nowDrawn = reportedDrawn;
+            }
+            if (!nowVisible) {
+                nowVisible = reportedVisible;
+            }
+        }
+        if (DEBUG_VISIBILITY) Slog.v(TAG, "VIS " + this + ": interesting="
+                + numInteresting + " visible=" + numVisible);
+        final AppWindowContainerController controller = getController();
+        if (nowDrawn != reportedDrawn) {
+            if (nowDrawn) {
+                if (controller != null) {
+                    controller.reportWindowsDrawn();
+                }
+            }
+            reportedDrawn = nowDrawn;
+        }
+        if (nowVisible != reportedVisible) {
+            if (DEBUG_VISIBILITY) Slog.v(TAG,
+                    "Visibility changed in " + this + ": vis=" + nowVisible);
+            reportedVisible = nowVisible;
+            if (controller != null) {
+                if (nowVisible) {
+                    controller.reportWindowsVisible();
+                } else {
+                    controller.reportWindowsGone();
+                }
+            }
+        }
+    }
+
+    boolean isClientHidden() {
+        return mClientHidden;
+    }
+
+    void setClientHidden(boolean hideClient) {
+        if (mClientHidden == hideClient || (hideClient && mDeferHidingClient)) {
+            return;
+        }
+        mClientHidden = hideClient;
+        sendAppVisibilityToClients();
+    }
+
+    boolean setVisibility(WindowManager.LayoutParams lp,
+            boolean visible, int transit, boolean performLayout, boolean isVoiceInteraction) {
+
+        boolean delayed = false;
+        inPendingTransaction = false;
+        // Reset the state of mHiddenSetFromTransferredStartingWindow since visibility is actually
+        // been set by the app now.
+        mHiddenSetFromTransferredStartingWindow = false;
+        setClientHidden(!visible);
+
+        // Allow for state changes and animation to be applied if:
+        // * token is transitioning visibility state
+        // * or the token was marked as hidden and is exiting before we had a chance to play the
+        // transition animation
+        // * or this is an opening app and windows are being replaced.
+        boolean visibilityChanged = false;
+        if (hidden == visible || (hidden && mIsExiting) || (visible && waitingForReplacement())) {
+            final AccessibilityController accessibilityController = mService.mAccessibilityController;
+            boolean changed = false;
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
+                    "Changing app " + this + " hidden=" + hidden + " performLayout=" + performLayout);
+
+            boolean runningAppAnimation = false;
+
+            if (mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) {
+                mAppAnimator.setNullAnimation();
+            }
+            if (transit != AppTransition.TRANSIT_UNSET) {
+                if (mService.applyAnimationLocked(this, lp, transit, visible, isVoiceInteraction)) {
+                    delayed = runningAppAnimation = true;
+                }
+                final WindowState window = findMainWindow();
+                //TODO (multidisplay): Magnification is supported only for the default display.
+                if (window != null && accessibilityController != null
+                        && getDisplayContent().getDisplayId() == DEFAULT_DISPLAY) {
+                    accessibilityController.onAppWindowTransitionLocked(window, transit);
+                }
+                changed = true;
+            }
+
+            final int windowsCount = mChildren.size();
+            for (int i = 0; i < windowsCount; i++) {
+                final WindowState win = mChildren.get(i);
+                changed |= win.onAppVisibilityChanged(visible, runningAppAnimation);
+            }
+
+            hidden = hiddenRequested = !visible;
+            visibilityChanged = true;
+            if (!visible) {
+                stopFreezingScreen(true, true);
+            } else {
+                // If we are being set visible, and the starting window is not yet displayed,
+                // then make sure it doesn't get displayed.
+                if (startingWindow != null && !startingWindow.isDrawnLw()) {
+                    startingWindow.mPolicyVisibility = false;
+                    startingWindow.mPolicyVisibilityAfterAnim = false;
+                }
+
+                // We are becoming visible, so better freeze the screen with the windows that are
+                // getting visible so we also wait for them.
+                forAllWindows(mService::makeWindowFreezingScreenIfNeededLocked, true);
+            }
+
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "setVisibility: " + this
+                    + ": hidden=" + hidden + " hiddenRequested=" + hiddenRequested);
+
+            if (changed) {
+                mService.mInputMonitor.setUpdateInputWindowsNeededLw();
+                if (performLayout) {
+                    mService.updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
+                            false /*updateInputWindows*/);
+                    mService.mWindowPlacerLocked.performSurfacePlacement();
+                }
+                mService.mInputMonitor.updateInputWindowsLw(false /*force*/);
+            }
+        }
+
+        if (mAppAnimator.animation != null) {
+            delayed = true;
+        }
+
+        for (int i = mChildren.size() - 1; i >= 0 && !delayed; i--) {
+            if ((mChildren.get(i)).isWindowAnimationSet()) {
+                delayed = true;
+            }
+        }
+
+        if (visibilityChanged) {
+            if (visible && !delayed) {
+                // The token was made immediately visible, there will be no entrance animation.
+                // We need to inform the client the enter animation was finished.
+                mEnteringAnimation = true;
+                mService.mActivityManagerAppTransitionNotifier.onAppTransitionFinishedLocked(token);
+            }
+
+            // If we are hidden but there is no delay needed we immediately
+            // apply the Surface transaction so that the ActivityManager
+            // can have some guarantee on the Surface state following
+            // setting the visibility. This captures cases like dismissing
+            // the docked or pinned stack where there is no app transition.
+            //
+            // In the case of a "Null" animation, there will be
+            // no animation but there will still be a transition set.
+            // We still need to delay hiding the surface such that it
+            // can be synchronized with showing the next surface in the transition.
+            if (hidden && !delayed && !mService.mAppTransition.isTransitionSet()) {
+                SurfaceControl.openTransaction();
+                for (int i = mChildren.size() - 1; i >= 0; i--) {
+                    mChildren.get(i).mWinAnimator.hide("immediately hidden");
+                }
+                SurfaceControl.closeTransaction();
+            }
+
+            if (!mService.mClosingApps.contains(this) && !mService.mOpeningApps.contains(this)) {
+                // The token is not closing nor opening, so even if there is an animation set, that
+                // doesn't mean that it goes through the normal app transition cycle so we have
+                // to inform the docked controller about visibility change.
+                // TODO(multi-display): notify docked divider on all displays where visibility was
+                // affected.
+                mService.getDefaultDisplayContentLocked().getDockedDividerController()
+                        .notifyAppVisibilityChanged();
+                mService.mTaskSnapshotController.notifyAppVisibilityChanged(this, visible);
+            }
+        }
+
+        return delayed;
+    }
+
+    /**
+     * @return The to top most child window for which {@link LayoutParams#isFullscreen()} returns
+     *         true.
+     */
+    WindowState getTopFullscreenWindow() {
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final WindowState win = mChildren.get(i);
+            if (win != null && win.mAttrs.isFullscreen()) {
+                return win;
+            }
+        }
+        return null;
+    }
+
+    WindowState findMainWindow() {
+        WindowState candidate = null;
+        int j = mChildren.size();
+        while (j > 0) {
+            j--;
+            final WindowState win = mChildren.get(j);
+            final int type = win.mAttrs.type;
+            // No need to loop through child window as base application and starting types can't be
+            // child windows.
+            if (type == TYPE_BASE_APPLICATION || type == TYPE_APPLICATION_STARTING) {
+                // In cases where there are multiple windows, we prefer the non-exiting window. This
+                // happens for example when replacing windows during an activity relaunch. When
+                // constructing the animation, we want the new window, not the exiting one.
+                if (win.mAnimatingExit) {
+                    candidate = win;
+                } else {
+                    return win;
+                }
+            }
+        }
+        return candidate;
+    }
+
+    boolean windowsAreFocusable() {
+        return getWindowConfiguration().canReceiveKeys() || mAlwaysFocusable;
+    }
+
+    AppWindowContainerController getController() {
+        final WindowContainerController controller = super.getController();
+        return controller != null ? (AppWindowContainerController) controller : null;
+    }
+
+    @Override
+    boolean isVisible() {
+        // If the app token isn't hidden then it is considered visible and there is no need to check
+        // its children windows to see if they are visible.
+        return !hidden;
+    }
+
+    @Override
+    void removeImmediately() {
+        onRemovedFromDisplay();
+        super.removeImmediately();
+    }
+
+    @Override
+    void removeIfPossible() {
+        mIsExiting = false;
+        removeAllWindowsIfPossible();
+        removeImmediately();
+    }
+
+    @Override
+    boolean checkCompleteDeferredRemoval() {
+        if (mIsExiting) {
+            removeIfPossible();
+        }
+        return super.checkCompleteDeferredRemoval();
+    }
+
+    void onRemovedFromDisplay() {
+        if (mRemovingFromDisplay) {
+            return;
+        }
+        mRemovingFromDisplay = true;
+
+        if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "Removing app token: " + this);
+
+        boolean delayed = setVisibility(null, false, TRANSIT_UNSET, true, mVoiceInteraction);
+
+        mService.mOpeningApps.remove(this);
+        mService.mUnknownAppVisibilityController.appRemovedOrHidden(this);
+        mService.mTaskSnapshotController.onAppRemoved(this);
+        waitingToShow = false;
+        if (mService.mClosingApps.contains(this)) {
+            delayed = true;
+        } else if (mService.mAppTransition.isTransitionSet()) {
+            mService.mClosingApps.add(this);
+            delayed = true;
+        }
+
+        if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "Removing app " + this + " delayed=" + delayed
+                + " animation=" + mAppAnimator.animation + " animating=" + mAppAnimator.animating);
+
+        if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG_WM, "removeAppToken: "
+                + this + " delayed=" + delayed + " Callers=" + Debug.getCallers(4));
+
+        if (startingData != null && getController() != null) {
+            getController().removeStartingWindow();
+        }
+
+        // If this window was animating, then we need to ensure that the app transition notifies
+        // that animations have completed in WMS.handleAnimatingStoppedAndTransitionLocked(), so
+        // add to that list now
+        if (mAppAnimator.animating) {
+            mService.mNoAnimationNotifyOnTransitionFinished.add(token);
+        }
+
+        final TaskStack stack = getStack();
+        if (delayed && !isEmpty()) {
+            // set the token aside because it has an active animation to be finished
+            if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG_WM,
+                    "removeAppToken make exiting: " + this);
+            if (stack != null) {
+                stack.mExitingAppTokens.add(this);
+            }
+            mIsExiting = true;
+        } else {
+            // Make sure there is no animation running on this token, so any windows associated
+            // with it will be removed as soon as their animations are complete
+            mAppAnimator.clearAnimation();
+            mAppAnimator.animating = false;
+            if (stack != null) {
+                stack.mExitingAppTokens.remove(this);
+            }
+            removeIfPossible();
+        }
+
+        removed = true;
+        stopFreezingScreen(true, true);
+
+        if (mService.mFocusedApp == this) {
+            if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "Removing focused app token:" + this);
+            mService.mFocusedApp = null;
+            mService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
+            mService.mInputMonitor.setFocusedAppLw(null);
+        }
+
+        if (!delayed) {
+            updateReportedVisibilityLocked();
+        }
+
+        mRemovingFromDisplay = false;
+    }
+
+    void clearAnimatingFlags() {
+        boolean wallpaperMightChange = false;
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final WindowState win = mChildren.get(i);
+            wallpaperMightChange |= win.clearAnimatingFlags();
+        }
+        if (wallpaperMightChange) {
+            requestUpdateWallpaperIfNeeded();
+        }
+    }
+
+    void destroySurfaces() {
+        destroySurfaces(false /*cleanupOnResume*/);
+    }
+
+    /**
+     * Destroy surfaces which have been marked as eligible by the animator, taking care to ensure
+     * the client has finished with them.
+     *
+     * @param cleanupOnResume whether this is done when app is resumed without fully stopped. If
+     * set to true, destroy only surfaces of removed windows, and clear relevant flags of the
+     * others so that they are ready to be reused. If set to false (common case), destroy all
+     * surfaces that's eligible, if the app is already stopped.
+     */
+    private void destroySurfaces(boolean cleanupOnResume) {
+        boolean destroyedSomething = false;
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final WindowState win = mChildren.get(i);
+            destroyedSomething |= win.destroySurface(cleanupOnResume, mAppStopped);
+        }
+        if (destroyedSomething) {
+            final DisplayContent dc = getDisplayContent();
+            dc.assignWindowLayers(true /*setLayoutNeeded*/);
+        }
+    }
+
+    /**
+     * Notify that the app is now resumed, and it was not stopped before, perform a clean
+     * up of the surfaces
+     */
+    void notifyAppResumed(boolean wasStopped) {
+        if (DEBUG_ADD_REMOVE) Slog.v(TAG, "notifyAppResumed: wasStopped=" + wasStopped
+                + " " + this);
+        mAppStopped = false;
+        // Allow the window to turn the screen on once the app is resumed again.
+        setCanTurnScreenOn(true);
+        if (!wasStopped) {
+            destroySurfaces(true /*cleanupOnResume*/);
+        }
+    }
+
+    /**
+     * Notify that the app has stopped, and it is okay to destroy any surfaces which were
+     * keeping alive in case they were still being used.
+     */
+    void notifyAppStopped() {
+        if (DEBUG_ADD_REMOVE) Slog.v(TAG, "notifyAppStopped: " + this);
+        mAppStopped = true;
+        destroySurfaces();
+        // Remove any starting window that was added for this app if they are still around.
+        if (getController() != null) {
+            getController().removeStartingWindow();
+        }
+    }
+
+    void clearAllDrawn() {
+        allDrawn = false;
+        deferClearAllDrawn = false;
+    }
+
+    Task getTask() {
+        return (Task) getParent();
+    }
+
+    TaskStack getStack() {
+        final Task task = getTask();
+        if (task != null) {
+            return task.mStack;
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    void onParentSet() {
+        super.onParentSet();
+
+        final Task task = getTask();
+
+        // When the associated task is {@code null}, the {@link AppWindowToken} can no longer
+        // access visual elements like the {@link DisplayContent}. We must remove any associations
+        // such as animations.
+        if (!mReparenting) {
+            if (task == null) {
+                // It is possible we have been marked as a closing app earlier. We must remove ourselves
+                // from this list so we do not participate in any future animations.
+                mService.mClosingApps.remove(this);
+            } else if (mLastParent != null && mLastParent.mStack != null) {
+                task.mStack.mExitingAppTokens.remove(this);
+            }
+        }
+        mLastParent = task;
+    }
+
+    void postWindowRemoveStartingWindowCleanup(WindowState win) {
+        // TODO: Something smells about the code below...Is there a better way?
+        if (startingWindow == win) {
+            if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Notify removed startingWindow " + win);
+            if (getController() != null) {
+                getController().removeStartingWindow();
+            }
+        } else if (mChildren.size() == 0) {
+            // If this is the last window and we had requested a starting transition window,
+            // well there is no point now.
+            if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Nulling last startingData");
+            startingData = null;
+            if (mHiddenSetFromTransferredStartingWindow) {
+                // We set the hidden state to false for the token from a transferred starting window.
+                // We now reset it back to true since the starting window was the last window in the
+                // token.
+                hidden = true;
+            }
+        } else if (mChildren.size() == 1 && startingSurface != null && !isRelaunching()) {
+            // If this is the last window except for a starting transition window,
+            // we need to get rid of the starting transition.
+            if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Last window, removing starting window "
+                    + win);
+            if (getController() != null) {
+                getController().removeStartingWindow();
+            }
+        }
+    }
+
+    void removeDeadWindows() {
+        for (int winNdx = mChildren.size() - 1; winNdx >= 0; --winNdx) {
+            WindowState win = mChildren.get(winNdx);
+            if (win.mAppDied) {
+                if (DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.w(TAG,
+                        "removeDeadWindows: " + win);
+                // Set mDestroying, we don't want any animation or delayed removal here.
+                win.mDestroying = true;
+                // Also removes child windows.
+                win.removeIfPossible();
+            }
+        }
+    }
+
+    boolean hasWindowsAlive() {
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            // No need to loop through child windows as the answer should be the same as that of the
+            // parent window.
+            if (!(mChildren.get(i)).mAppDied) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    void setWillReplaceWindows(boolean animate) {
+        if (DEBUG_ADD_REMOVE) Slog.d(TAG_WM,
+                "Marking app token " + this + " with replacing windows.");
+
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final WindowState w = mChildren.get(i);
+            w.setWillReplaceWindow(animate);
+        }
+        if (animate) {
+            // Set-up dummy animation so we can start treating windows associated with this
+            // token like they are in transition before the new app window is ready for us to
+            // run the real transition animation.
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
+                    "setWillReplaceWindow() Setting dummy animation on: " + this);
+            mAppAnimator.setDummyAnimation();
+        }
+    }
+
+    void setWillReplaceChildWindows() {
+        if (DEBUG_ADD_REMOVE) Slog.d(TAG_WM, "Marking app token " + this
+                + " with replacing child windows.");
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final WindowState w = mChildren.get(i);
+            w.setWillReplaceChildWindows();
+        }
+    }
+
+    void clearWillReplaceWindows() {
+        if (DEBUG_ADD_REMOVE) Slog.d(TAG_WM,
+                "Resetting app token " + this + " of replacing window marks.");
+
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final WindowState w = mChildren.get(i);
+            w.clearWillReplaceWindow();
+        }
+    }
+
+    void requestUpdateWallpaperIfNeeded() {
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final WindowState w = mChildren.get(i);
+            w.requestUpdateWallpaperIfNeeded();
+        }
+    }
+
+    boolean isRelaunching() {
+        return mPendingRelaunchCount > 0;
+    }
+
+    boolean shouldFreezeBounds() {
+        final Task task = getTask();
+
+        // For freeform windows, we can't freeze the bounds at the moment because this would make
+        // the resizing unresponsive.
+        if (task == null || task.inFreeformWorkspace()) {
+            return false;
+        }
+
+        // We freeze the bounds while drag resizing to deal with the time between
+        // the divider/drag handle being released, and the handling it's new
+        // configuration. If we are relaunched outside of the drag resizing state,
+        // we need to be careful not to do this.
+        return getTask().isDragResizing();
+    }
+
+    void startRelaunching() {
+        if (shouldFreezeBounds()) {
+            freezeBounds();
+        }
+
+        // In the process of tearing down before relaunching, the app will
+        // try and clean up it's child surfaces. We need to prevent this from
+        // happening, so we sever the children, transfering their ownership
+        // from the client it-self to the parent surface (owned by us).
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final WindowState w = mChildren.get(i);
+            w.mWinAnimator.detachChildren();
+        }
+
+        mPendingRelaunchCount++;
+    }
+
+    void finishRelaunching() {
+        unfreezeBounds();
+
+        if (mPendingRelaunchCount > 0) {
+            mPendingRelaunchCount--;
+        } else {
+            // Update keyguard flags upon finishing relaunch.
+            checkKeyguardFlagsChanged();
+        }
+    }
+
+    void clearRelaunching() {
+        if (mPendingRelaunchCount == 0) {
+            return;
+        }
+        unfreezeBounds();
+        mPendingRelaunchCount = 0;
+    }
+
+    /**
+     * Returns true if the new child window we are adding to this token is considered greater than
+     * the existing child window in this token in terms of z-order.
+     */
+    @Override
+    protected boolean isFirstChildWindowGreaterThanSecond(WindowState newWindow,
+            WindowState existingWindow) {
+        final int type1 = newWindow.mAttrs.type;
+        final int type2 = existingWindow.mAttrs.type;
+
+        // Base application windows should be z-ordered BELOW all other windows in the app token.
+        if (type1 == TYPE_BASE_APPLICATION && type2 != TYPE_BASE_APPLICATION) {
+            return false;
+        } else if (type1 != TYPE_BASE_APPLICATION && type2 == TYPE_BASE_APPLICATION) {
+            return true;
+        }
+
+        // Starting windows should be z-ordered ABOVE all other windows in the app token.
+        if (type1 == TYPE_APPLICATION_STARTING && type2 != TYPE_APPLICATION_STARTING) {
+            return true;
+        } else if (type1 != TYPE_APPLICATION_STARTING && type2 == TYPE_APPLICATION_STARTING) {
+            return false;
+        }
+
+        // Otherwise the new window is greater than the existing window.
+        return true;
+    }
+
+    @Override
+    void addWindow(WindowState w) {
+        super.addWindow(w);
+
+        boolean gotReplacementWindow = false;
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final WindowState candidate = mChildren.get(i);
+            gotReplacementWindow |= candidate.setReplacementWindowIfNeeded(w);
+        }
+
+        // if we got a replacement window, reset the timeout to give drawing more time
+        if (gotReplacementWindow) {
+            mService.scheduleWindowReplacementTimeouts(this);
+        }
+        checkKeyguardFlagsChanged();
+    }
+
+    @Override
+    void removeChild(WindowState child) {
+        super.removeChild(child);
+        checkKeyguardFlagsChanged();
+    }
+
+    private boolean waitingForReplacement() {
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final WindowState candidate = mChildren.get(i);
+            if (candidate.waitingForReplacement()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    void onWindowReplacementTimeout() {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            (mChildren.get(i)).onWindowReplacementTimeout();
+        }
+    }
+
+    void reparent(Task task, int position) {
+        final Task currentTask = getTask();
+        if (task == currentTask) {
+            throw new IllegalArgumentException(
+                    "window token=" + this + " already child of task=" + currentTask);
+        }
+
+        if (currentTask.mStack != task.mStack) {
+            throw new IllegalArgumentException(
+                    "window token=" + this + " current task=" + currentTask
+                        + " belongs to a different stack than " + task);
+        }
+
+        if (DEBUG_ADD_REMOVE) Slog.i(TAG, "reParentWindowToken: removing window token=" + this
+                + " from task=" + currentTask);
+        final DisplayContent prevDisplayContent = getDisplayContent();
+
+        mReparenting = true;
+
+        getParent().removeChild(this);
+        task.addChild(this, position);
+
+        mReparenting = false;
+
+        // Relayout display(s).
+        final DisplayContent displayContent = task.getDisplayContent();
+        displayContent.setLayoutNeeded();
+        if (prevDisplayContent != displayContent) {
+            onDisplayChanged(displayContent);
+            prevDisplayContent.setLayoutNeeded();
+        }
+    }
+
+    /**
+     * Freezes the task bounds. The size of this task reported the app will be fixed to the bounds
+     * freezed by {@link Task#prepareFreezingBounds} until {@link #unfreezeBounds} gets called, even
+     * if they change in the meantime. If the bounds are already frozen, the bounds will be frozen
+     * with a queue.
+     */
+    private void freezeBounds() {
+        final Task task = getTask();
+        mFrozenBounds.offer(new Rect(task.mPreparedFrozenBounds));
+
+        if (task.mPreparedFrozenMergedConfig.equals(Configuration.EMPTY)) {
+            // We didn't call prepareFreezingBounds on the task, so use the current value.
+            mFrozenMergedConfig.offer(new Configuration(task.getConfiguration()));
+        } else {
+            mFrozenMergedConfig.offer(new Configuration(task.mPreparedFrozenMergedConfig));
+        }
+        // Calling unset() to make it equal to Configuration.EMPTY.
+        task.mPreparedFrozenMergedConfig.unset();
+    }
+
+    /**
+     * Unfreezes the previously frozen bounds. See {@link #freezeBounds}.
+     */
+    private void unfreezeBounds() {
+        if (mFrozenBounds.isEmpty()) {
+            return;
+        }
+        mFrozenBounds.remove();
+        if (!mFrozenMergedConfig.isEmpty()) {
+            mFrozenMergedConfig.remove();
+        }
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final WindowState win = mChildren.get(i);
+            win.onUnfreezeBounds();
+        }
+        mService.mWindowPlacerLocked.performSurfacePlacement();
+    }
+
+    void setAppLayoutChanges(int changes, String reason) {
+        if (!mChildren.isEmpty()) {
+            final DisplayContent dc = getDisplayContent();
+            dc.pendingLayoutChanges |= changes;
+            if (DEBUG_LAYOUT_REPEATS) {
+                mService.mWindowPlacerLocked.debugLayoutRepeats(reason, dc.pendingLayoutChanges);
+            }
+        }
+    }
+
+    void removeReplacedWindowIfNeeded(WindowState replacement) {
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final WindowState win = mChildren.get(i);
+            if (win.removeReplacedWindowIfNeeded(replacement)) {
+                return;
+            }
+        }
+    }
+
+    void startFreezingScreen() {
+        if (DEBUG_ORIENTATION) logWithStack(TAG, "Set freezing of " + appToken + ": hidden="
+                + hidden + " freezing=" + mAppAnimator.freezingScreen + " hiddenRequested="
+                + hiddenRequested);
+        if (!hiddenRequested) {
+            if (!mAppAnimator.freezingScreen) {
+                mAppAnimator.freezingScreen = true;
+                mService.registerAppFreezeListener(this);
+                mAppAnimator.lastFreezeDuration = 0;
+                mService.mAppsFreezingScreen++;
+                if (mService.mAppsFreezingScreen == 1) {
+                    mService.startFreezingDisplayLocked(false, 0, 0, getDisplayContent());
+                    mService.mH.removeMessages(H.APP_FREEZE_TIMEOUT);
+                    mService.mH.sendEmptyMessageDelayed(H.APP_FREEZE_TIMEOUT, 2000);
+                }
+            }
+            final int count = mChildren.size();
+            for (int i = 0; i < count; i++) {
+                final WindowState w = mChildren.get(i);
+                w.onStartFreezingScreen();
+            }
+        }
+    }
+
+    void stopFreezingScreen(boolean unfreezeSurfaceNow, boolean force) {
+        if (!mAppAnimator.freezingScreen) {
+            return;
+        }
+        if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Clear freezing of " + this + " force=" + force);
+        final int count = mChildren.size();
+        boolean unfrozeWindows = false;
+        for (int i = 0; i < count; i++) {
+            final WindowState w = mChildren.get(i);
+            unfrozeWindows |= w.onStopFreezingScreen();
+        }
+        if (force || unfrozeWindows) {
+            if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "No longer freezing: " + this);
+            mAppAnimator.freezingScreen = false;
+            mService.unregisterAppFreezeListener(this);
+            mAppAnimator.lastFreezeDuration =
+                    (int)(SystemClock.elapsedRealtime() - mService.mDisplayFreezeTime);
+            mService.mAppsFreezingScreen--;
+            mService.mLastFinishedFreezeSource = this;
+        }
+        if (unfreezeSurfaceNow) {
+            if (unfrozeWindows) {
+                mService.mWindowPlacerLocked.performSurfacePlacement();
+            }
+            mService.stopFreezingDisplayLocked();
+        }
+    }
+
+    @Override
+    public void onAppFreezeTimeout() {
+        Slog.w(TAG_WM, "Force clearing freeze: " + this);
+        stopFreezingScreen(true, true);
+    }
+
+    boolean transferStartingWindow(IBinder transferFrom) {
+        final AppWindowToken fromToken = getDisplayContent().getAppWindowToken(transferFrom);
+        if (fromToken == null) {
+            return false;
+        }
+
+        final WindowState tStartingWindow = fromToken.startingWindow;
+        if (tStartingWindow != null && fromToken.startingSurface != null) {
+            // In this case, the starting icon has already been displayed, so start
+            // letting windows get shown immediately without any more transitions.
+            mService.mSkipAppTransitionAnimation = true;
+
+            if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Moving existing starting " + tStartingWindow
+                    + " from " + fromToken + " to " + this);
+
+            final long origId = Binder.clearCallingIdentity();
+
+            // Transfer the starting window over to the new token.
+            startingData = fromToken.startingData;
+            startingSurface = fromToken.startingSurface;
+            startingDisplayed = fromToken.startingDisplayed;
+            fromToken.startingDisplayed = false;
+            startingWindow = tStartingWindow;
+            reportedVisible = fromToken.reportedVisible;
+            fromToken.startingData = null;
+            fromToken.startingSurface = null;
+            fromToken.startingWindow = null;
+            fromToken.startingMoved = true;
+            tStartingWindow.mToken = this;
+            tStartingWindow.mAppToken = this;
+
+            if (DEBUG_ADD_REMOVE || DEBUG_STARTING_WINDOW) Slog.v(TAG_WM,
+                    "Removing starting " + tStartingWindow + " from " + fromToken);
+            fromToken.removeChild(tStartingWindow);
+            fromToken.postWindowRemoveStartingWindowCleanup(tStartingWindow);
+            fromToken.mHiddenSetFromTransferredStartingWindow = false;
+            addWindow(tStartingWindow);
+
+            // Propagate other interesting state between the tokens. If the old token is displayed,
+            // we should immediately force the new one to be displayed. If it is animating, we need
+            // to move that animation to the new one.
+            if (fromToken.allDrawn) {
+                allDrawn = true;
+                deferClearAllDrawn = fromToken.deferClearAllDrawn;
+            }
+            if (fromToken.firstWindowDrawn) {
+                firstWindowDrawn = true;
+            }
+            if (!fromToken.hidden) {
+                hidden = false;
+                hiddenRequested = false;
+                mHiddenSetFromTransferredStartingWindow = true;
+            }
+            setClientHidden(fromToken.mClientHidden);
+            fromToken.mAppAnimator.transferCurrentAnimation(
+                    mAppAnimator, tStartingWindow.mWinAnimator);
+
+            mService.updateFocusedWindowLocked(
+                    UPDATE_FOCUS_WILL_PLACE_SURFACES, true /*updateInputWindows*/);
+            getDisplayContent().setLayoutNeeded();
+            mService.mWindowPlacerLocked.performSurfacePlacement();
+            Binder.restoreCallingIdentity(origId);
+            return true;
+        } else if (fromToken.startingData != null) {
+            // The previous app was getting ready to show a
+            // starting window, but hasn't yet done so.  Steal it!
+            if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM,
+                    "Moving pending starting from " + fromToken + " to " + this);
+            startingData = fromToken.startingData;
+            fromToken.startingData = null;
+            fromToken.startingMoved = true;
+            if (getController() != null) {
+                getController().scheduleAddStartingWindow();
+            }
+            return true;
+        }
+
+        final AppWindowAnimator tAppAnimator = fromToken.mAppAnimator;
+        final AppWindowAnimator wAppAnimator = mAppAnimator;
+        if (tAppAnimator.thumbnail != null) {
+            // The old token is animating with a thumbnail, transfer that to the new token.
+            if (wAppAnimator.thumbnail != null) {
+                wAppAnimator.thumbnail.destroy();
+            }
+            wAppAnimator.thumbnail = tAppAnimator.thumbnail;
+            wAppAnimator.thumbnailLayer = tAppAnimator.thumbnailLayer;
+            wAppAnimator.thumbnailAnimation = tAppAnimator.thumbnailAnimation;
+            tAppAnimator.thumbnail = null;
+        }
+        return false;
+    }
+
+    boolean isLastWindow(WindowState win) {
+        return mChildren.size() == 1 && mChildren.get(0) == win;
+    }
+
+    void setAllAppWinAnimators() {
+        final ArrayList<WindowStateAnimator> allAppWinAnimators = mAppAnimator.mAllAppWinAnimators;
+        allAppWinAnimators.clear();
+
+        final int windowsCount = mChildren.size();
+        for (int j = 0; j < windowsCount; j++) {
+            (mChildren.get(j)).addWinAnimatorToList(allAppWinAnimators);
+        }
+    }
+
+    @Override
+    void onAppTransitionDone() {
+        sendingToBottom = false;
+    }
+
+    /**
+     * We override because this class doesn't want its children affecting its reported orientation
+     * in anyway.
+     */
+    @Override
+    int getOrientation(int candidate) {
+        // We do not allow non-fullscreen apps to influence orientation starting in O-MR1. While we
+        // do throw an exception in {@link Activity#onCreate} and
+        // {@link Activity#setRequestedOrientation}, we also ignore the orientation here so that
+        // other calculations aren't affected.
+        if (!fillsParent() && mTargetSdk >= O_MR1) {
+            // Can't specify orientation if app doesn't fill parent.
+            return SCREEN_ORIENTATION_UNSET;
+        }
+
+        if (candidate == SCREEN_ORIENTATION_BEHIND) {
+            // Allow app to specify orientation regardless of its visibility state if the current
+            // candidate want us to use orientation behind. I.e. the visible app on-top of this one
+            // wants us to use the orientation of the app behind it.
+            return mOrientation;
+        }
+
+        // The {@link AppWindowToken} should only specify an orientation when it is not closing or
+        // going to the bottom. Allowing closing {@link AppWindowToken} to participate can lead to
+        // an Activity in another task being started in the wrong orientation during the transition.
+        if (!(sendingToBottom || mService.mClosingApps.contains(this))
+                && (isVisible() || mService.mOpeningApps.contains(this) || isOnTop())) {
+            return mOrientation;
+        }
+
+        return SCREEN_ORIENTATION_UNSET;
+    }
+
+    /** Returns the app's preferred orientation regardless of its currently visibility state. */
+    int getOrientationIgnoreVisibility() {
+        return mOrientation;
+    }
+
+    @Override
+    void checkAppWindowsReadyToShow() {
+        if (allDrawn == mAppAnimator.allDrawn) {
+            return;
+        }
+
+        mAppAnimator.allDrawn = allDrawn;
+        if (!allDrawn) {
+            return;
+        }
+
+        // The token has now changed state to having all windows shown...  what to do, what to do?
+        if (mAppAnimator.freezingScreen) {
+            mAppAnimator.showAllWindowsLocked();
+            stopFreezingScreen(false, true);
+            if (DEBUG_ORIENTATION) Slog.i(TAG,
+                    "Setting mOrientationChangeComplete=true because wtoken " + this
+                    + " numInteresting=" + mNumInterestingWindows + " numDrawn=" + mNumDrawnWindows);
+            // This will set mOrientationChangeComplete and cause a pass through layout.
+            setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER,
+                    "checkAppWindowsReadyToShow: freezingScreen");
+        } else {
+            setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM, "checkAppWindowsReadyToShow");
+
+            // We can now show all of the drawn windows!
+            if (!mService.mOpeningApps.contains(this)) {
+                mService.mAnimator.orAnimating(mAppAnimator.showAllWindowsLocked());
+            }
+        }
+    }
+
+    /**
+     * Returns whether the drawn window states of this {@link AppWindowToken} has considered every
+     * child {@link WindowState}. A child is considered if it has been passed into
+     * {@link #updateDrawnWindowStates(WindowState)} after being added. This is used to determine
+     * whether states, such as {@code allDrawn}, can be set, which relies on state variables such as
+     * {@code mNumInterestingWindows}, which depend on all {@link WindowState}s being considered.
+     *
+     * @return {@code true} If all children have been considered, {@code false}.
+     */
+    private boolean allDrawnStatesConsidered() {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowState child = mChildren.get(i);
+            if (child.mightAffectAllDrawn() && !child.getDrawnStateEvaluated()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     *  Determines if the token has finished drawing. This should only be called from
+     *  {@link DisplayContent#applySurfaceChangesTransaction}
+     */
+    void updateAllDrawn() {
+        if (!allDrawn) {
+            // Number of drawn windows can be less when a window is being relaunched, wait for
+            // all windows to be launched and drawn for this token be considered all drawn.
+            final int numInteresting = mNumInterestingWindows;
+
+            // We must make sure that all present children have been considered (determined by
+            // {@link #allDrawnStatesConsidered}) before evaluating whether everything has been
+            // drawn.
+            if (numInteresting > 0 && allDrawnStatesConsidered()
+                    && mNumDrawnWindows >= numInteresting && !isRelaunching()) {
+                if (DEBUG_VISIBILITY) Slog.v(TAG, "allDrawn: " + this
+                        + " interesting=" + numInteresting + " drawn=" + mNumDrawnWindows);
+                allDrawn = true;
+                // Force an additional layout pass where
+                // WindowStateAnimator#commitFinishDrawingLocked() will call performShowLocked().
+                if (mDisplayContent != null) {
+                    mDisplayContent.setLayoutNeeded();
+                }
+                mService.mH.obtainMessage(NOTIFY_ACTIVITY_DRAWN, token).sendToTarget();
+
+                // Notify the pinned stack upon all windows drawn. If there was an animation in
+                // progress then this signal will resume that animation.
+                final TaskStack pinnedStack = mDisplayContent.getStackById(PINNED_STACK_ID);
+                if (pinnedStack != null) {
+                    pinnedStack.onAllWindowsDrawn();
+                }
+            }
+        }
+    }
+
+    /**
+     * Updated this app token tracking states for interesting and drawn windows based on the window.
+     *
+     * @return Returns true if the input window is considered interesting and drawn while all the
+     *         windows in this app token where not considered drawn as of the last pass.
+     */
+    boolean updateDrawnWindowStates(WindowState w) {
+        w.setDrawnStateEvaluated(true /*evaluated*/);
+
+        if (DEBUG_STARTING_WINDOW_VERBOSE && w == startingWindow) {
+            Slog.d(TAG, "updateWindows: starting " + w + " isOnScreen=" + w.isOnScreen()
+                    + " allDrawn=" + allDrawn + " freezingScreen=" + mAppAnimator.freezingScreen);
+        }
+
+        if (allDrawn && !mAppAnimator.freezingScreen) {
+            return false;
+        }
+
+        if (mLastTransactionSequence != mService.mTransactionSequence) {
+            mLastTransactionSequence = mService.mTransactionSequence;
+            mNumInterestingWindows = mNumDrawnWindows = 0;
+            startingDisplayed = false;
+        }
+
+        final WindowStateAnimator winAnimator = w.mWinAnimator;
+
+        boolean isInterestingAndDrawn = false;
+
+        if (!allDrawn && w.mightAffectAllDrawn()) {
+            if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) {
+                Slog.v(TAG, "Eval win " + w + ": isDrawn=" + w.isDrawnLw()
+                        + ", isAnimationSet=" + winAnimator.isAnimationSet());
+                if (!w.isDrawnLw()) {
+                    Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceController
+                            + " pv=" + w.mPolicyVisibility
+                            + " mDrawState=" + winAnimator.drawStateToString()
+                            + " ph=" + w.isParentWindowHidden() + " th=" + hiddenRequested
+                            + " a=" + winAnimator.mAnimating);
+                }
+            }
+
+            if (w != startingWindow) {
+                if (w.isInteresting()) {
+                    mNumInterestingWindows++;
+                    if (w.isDrawnLw()) {
+                        mNumDrawnWindows++;
+
+                        if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) Slog.v(TAG, "tokenMayBeDrawn: "
+                                + this + " w=" + w + " numInteresting=" + mNumInterestingWindows
+                                + " freezingScreen=" + mAppAnimator.freezingScreen
+                                + " mAppFreezing=" + w.mAppFreezing);
+
+                        isInterestingAndDrawn = true;
+                    }
+                }
+            } else if (w.isDrawnLw()) {
+                if (getController() != null) {
+                    getController().reportStartingWindowDrawn();
+                }
+                startingDisplayed = true;
+            }
+        }
+
+        return isInterestingAndDrawn;
+    }
+
+    @Override
+    void stepAppWindowsAnimation(long currentTime) {
+        mAppAnimator.wasAnimating = mAppAnimator.animating;
+        if (mAppAnimator.stepAnimationLocked(currentTime)) {
+            mAppAnimator.animating = true;
+            mService.mAnimator.setAnimating(true);
+            mService.mAnimator.mAppWindowAnimating = true;
+        } else if (mAppAnimator.wasAnimating) {
+            // stopped animating, do one more pass through the layout
+            setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER,
+                    DEBUG_LAYOUT_REPEATS ? "appToken " + this + " done" : null);
+            if (DEBUG_ANIM) Slog.v(TAG, "updateWindowsApps...: done animating " + this);
+        }
+    }
+
+    @Override
+    boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
+        // For legacy reasons we process the TaskStack.mExitingAppTokens first in DisplayContent
+        // before the non-exiting app tokens. So, we skip the exiting app tokens here.
+        // TODO: Investigate if we need to continue to do this or if we can just process them
+        // in-order.
+        if (mIsExiting && !waitingForReplacement()) {
+            return false;
+        }
+        return forAllWindowsUnchecked(callback, traverseTopToBottom);
+    }
+
+    boolean forAllWindowsUnchecked(ToBooleanFunction<WindowState> callback,
+            boolean traverseTopToBottom) {
+        return super.forAllWindows(callback, traverseTopToBottom);
+    }
+
+    @Override
+    AppWindowToken asAppWindowToken() {
+        // I am an app window token!
+        return this;
+    }
+
+    @Override
+    boolean fillsParent() {
+        return mFillsParent;
+    }
+
+    void setFillsParent(boolean fillsParent) {
+        mFillsParent = fillsParent;
+    }
+
+    boolean containsDismissKeyguardWindow() {
+        // Window state is transient during relaunch. We are not guaranteed to be frozen during the
+        // entirety of the relaunch.
+        if (isRelaunching()) {
+            return mLastContainsDismissKeyguardWindow;
+        }
+
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            if ((mChildren.get(i).mAttrs.flags & FLAG_DISMISS_KEYGUARD) != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    boolean containsShowWhenLockedWindow() {
+        // When we are relaunching, it is possible for us to be unfrozen before our previous
+        // windows have been added back. Using the cached value ensures that our previous
+        // showWhenLocked preference is honored until relaunching is complete.
+        if (isRelaunching()) {
+            return mLastContainsShowWhenLockedWindow;
+        }
+
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            if ((mChildren.get(i).mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    void checkKeyguardFlagsChanged() {
+        final boolean containsDismissKeyguard = containsDismissKeyguardWindow();
+        final boolean containsShowWhenLocked = containsShowWhenLockedWindow();
+        if (containsDismissKeyguard != mLastContainsDismissKeyguardWindow
+                || containsShowWhenLocked != mLastContainsShowWhenLockedWindow) {
+            mService.notifyKeyguardFlagsChanged(null /* callback */);
+        }
+        mLastContainsDismissKeyguardWindow = containsDismissKeyguard;
+        mLastContainsShowWhenLockedWindow = containsShowWhenLocked;
+    }
+
+    WindowState getImeTargetBelowWindow(WindowState w) {
+        final int index = mChildren.indexOf(w);
+        if (index > 0) {
+            final WindowState target = mChildren.get(index - 1);
+            if (target.canBeImeTarget()) {
+                return target;
+            }
+        }
+        return null;
+    }
+
+    int getLowestAnimLayer() {
+        for (int i = 0; i < mChildren.size(); i++) {
+            final WindowState w = mChildren.get(i);
+            if (w.mRemoved) {
+                continue;
+            }
+            return w.mWinAnimator.mAnimLayer;
+        }
+        return Integer.MAX_VALUE;
+    }
+
+    WindowState getHighestAnimLayerWindow(WindowState currentTarget) {
+        WindowState candidate = null;
+        for (int i = mChildren.indexOf(currentTarget); i >= 0; i--) {
+            final WindowState w = mChildren.get(i);
+            if (w.mRemoved) {
+                continue;
+            }
+            if (candidate == null || w.mWinAnimator.mAnimLayer >
+                    candidate.mWinAnimator.mAnimLayer) {
+                candidate = w;
+            }
+        }
+        return candidate;
+    }
+
+    /**
+     * See {@link Activity#setDisablePreviewScreenshots}.
+     */
+    void setDisablePreviewScreenshots(boolean disable) {
+        mDisablePreviewScreenshots = disable;
+    }
+
+    /**
+     * Sets whether the current launch can turn the screen on. See {@link #canTurnScreenOn()}
+     */
+    void setCanTurnScreenOn(boolean canTurnScreenOn) {
+        mCanTurnScreenOn = canTurnScreenOn;
+    }
+
+    /**
+     * Indicates whether the current launch can turn the screen on. This is to prevent multiple
+     * relayouts from turning the screen back on. The screen should only turn on at most
+     * once per activity resume.
+     *
+     * @return true if the screen can be turned on.
+     */
+    boolean canTurnScreenOn() {
+        return mCanTurnScreenOn;
+    }
+
+    /**
+     * Retrieves whether we'd like to generate a snapshot that's based solely on the theme. This is
+     * the case when preview screenshots are disabled {@link #setDisablePreviewScreenshots} or when
+     * we can't take a snapshot for other reasons, for example, if we have a secure window.
+     *
+     * @return True if we need to generate an app theme snapshot, false if we'd like to take a real
+     *         screenshot.
+     */
+    boolean shouldUseAppThemeSnapshot() {
+        return mDisablePreviewScreenshots || forAllWindows(w -> (w.mAttrs.flags & FLAG_SECURE) != 0,
+                true /* topToBottom */);
+    }
+
+    @Override
+    int getAnimLayerAdjustment() {
+        return mAppAnimator.animLayerAdjustment;
+    }
+
+    @Override
+    void dump(PrintWriter pw, String prefix) {
+        super.dump(pw, prefix);
+        if (appToken != null) {
+            pw.println(prefix + "app=true mVoiceInteraction=" + mVoiceInteraction);
+        }
+        pw.print(prefix); pw.print("task="); pw.println(getTask());
+        pw.print(prefix); pw.print(" mFillsParent="); pw.print(mFillsParent);
+                pw.print(" mOrientation="); pw.println(mOrientation);
+        pw.println(prefix + "hiddenRequested=" + hiddenRequested + " mClientHidden=" + mClientHidden
+            + ((mDeferHidingClient) ? " mDeferHidingClient=" + mDeferHidingClient : "")
+            + " reportedDrawn=" + reportedDrawn + " reportedVisible=" + reportedVisible);
+        if (paused) {
+            pw.print(prefix); pw.print("paused="); pw.println(paused);
+        }
+        if (mAppStopped) {
+            pw.print(prefix); pw.print("mAppStopped="); pw.println(mAppStopped);
+        }
+        if (mNumInterestingWindows != 0 || mNumDrawnWindows != 0
+                || allDrawn || mAppAnimator.allDrawn) {
+            pw.print(prefix); pw.print("mNumInterestingWindows=");
+                    pw.print(mNumInterestingWindows);
+                    pw.print(" mNumDrawnWindows="); pw.print(mNumDrawnWindows);
+                    pw.print(" inPendingTransaction="); pw.print(inPendingTransaction);
+                    pw.print(" allDrawn="); pw.print(allDrawn);
+                    pw.print(" (animator="); pw.print(mAppAnimator.allDrawn);
+                    pw.println(")");
+        }
+        if (inPendingTransaction) {
+            pw.print(prefix); pw.print("inPendingTransaction=");
+                    pw.println(inPendingTransaction);
+        }
+        if (startingData != null || removed || firstWindowDrawn || mIsExiting) {
+            pw.print(prefix); pw.print("startingData="); pw.print(startingData);
+                    pw.print(" removed="); pw.print(removed);
+                    pw.print(" firstWindowDrawn="); pw.print(firstWindowDrawn);
+                    pw.print(" mIsExiting="); pw.println(mIsExiting);
+        }
+        if (startingWindow != null || startingSurface != null
+                || startingDisplayed || startingMoved || mHiddenSetFromTransferredStartingWindow) {
+            pw.print(prefix); pw.print("startingWindow="); pw.print(startingWindow);
+                    pw.print(" startingSurface="); pw.print(startingSurface);
+                    pw.print(" startingDisplayed="); pw.print(startingDisplayed);
+                    pw.print(" startingMoved="); pw.print(startingMoved);
+                    pw.println(" mHiddenSetFromTransferredStartingWindow="
+                            + mHiddenSetFromTransferredStartingWindow);
+        }
+        if (!mFrozenBounds.isEmpty()) {
+            pw.print(prefix); pw.print("mFrozenBounds="); pw.println(mFrozenBounds);
+            pw.print(prefix); pw.print("mFrozenMergedConfig="); pw.println(mFrozenMergedConfig);
+        }
+        if (mPendingRelaunchCount != 0) {
+            pw.print(prefix); pw.print("mPendingRelaunchCount="); pw.println(mPendingRelaunchCount);
+        }
+        if (getController() != null) {
+            pw.print(prefix); pw.print("controller="); pw.println(getController());
+        }
+        if (mRemovingFromDisplay) {
+            pw.println(prefix + "mRemovingFromDisplay=" + mRemovingFromDisplay);
+        }
+        if (mAppAnimator.isAnimating()) {
+            mAppAnimator.dump(pw, prefix + "  ");
+        }
+    }
+
+    @Override
+    void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        writeNameToProto(proto, NAME);
+        super.writeToProto(proto, WINDOW_TOKEN);
+        proto.end(token);
+    }
+
+    void writeNameToProto(ProtoOutputStream proto, long fieldId) {
+        if (appToken == null) {
+            return;
+        }
+        try {
+            proto.write(fieldId, appToken.getName());
+        } catch (RemoteException e) {
+            // This shouldn't happen, but in this case fall back to outputting nothing
+            Slog.e(TAG, e.toString());
+        }
+    }
+
+    @Override
+    public String toString() {
+        if (stringName == null) {
+            StringBuilder sb = new StringBuilder();
+            sb.append("AppWindowToken{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(" token="); sb.append(token); sb.append('}');
+            stringName = sb.toString();
+        }
+        return stringName + ((mIsExiting) ? " mIsExiting=" : "");
+    }
+}
diff --git a/com/android/server/wm/BlackFrame.java b/com/android/server/wm/BlackFrame.java
new file mode 100644
index 0000000..5c29a0a
--- /dev/null
+++ b/com/android/server/wm/BlackFrame.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.graphics.PixelFormat.OPAQUE;
+import static android.view.SurfaceControl.FX_SURFACE_DIM;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import java.io.PrintWriter;
+
+import android.graphics.Matrix;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.util.Slog;
+import android.view.Surface.OutOfResourcesException;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+
+/**
+ * Four black surfaces put together to make a black frame.
+ */
+public class BlackFrame {
+    class BlackSurface {
+        final int left;
+        final int top;
+        final int layer;
+        final SurfaceControl surface;
+
+        BlackSurface(SurfaceSession session, int layer, int l, int t, int r, int b, int layerStack)
+                throws OutOfResourcesException {
+            left = l;
+            top = t;
+            this.layer = layer;
+            int w = r-l;
+            int h = b-t;
+
+            if (DEBUG_SURFACE_TRACE) {
+                surface = new WindowSurfaceController.SurfaceTrace(session, "BlackSurface("
+                        + l + ", " + t + ")",
+                        w, h, OPAQUE, FX_SURFACE_DIM | SurfaceControl.HIDDEN);
+            } else {
+                surface = new SurfaceControl(session, "BlackSurface",
+                        w, h, OPAQUE, FX_SURFACE_DIM | SurfaceControl.HIDDEN);
+            }
+
+            surface.setAlpha(1);
+            surface.setLayerStack(layerStack);
+            surface.setLayer(layer);
+            surface.show();
+            if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG_WM,
+                            "  BLACK " + surface + ": CREATE layer=" + layer);
+        }
+
+        void setAlpha(float alpha) {
+            surface.setAlpha(alpha);
+        }
+
+        void setMatrix(Matrix matrix) {
+            mTmpMatrix.setTranslate(left, top);
+            mTmpMatrix.postConcat(matrix);
+            mTmpMatrix.getValues(mTmpFloats);
+            surface.setPosition(mTmpFloats[Matrix.MTRANS_X],
+                    mTmpFloats[Matrix.MTRANS_Y]);
+            surface.setMatrix(
+                    mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
+                    mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
+            if (false) {
+                Slog.i(TAG_WM, "Black Surface @ (" + left + "," + top + "): ("
+                        + mTmpFloats[Matrix.MTRANS_X] + ","
+                        + mTmpFloats[Matrix.MTRANS_Y] + ") matrix=["
+                        + mTmpFloats[Matrix.MSCALE_X] + ","
+                        + mTmpFloats[Matrix.MSCALE_Y] + "]["
+                        + mTmpFloats[Matrix.MSKEW_X] + ","
+                        + mTmpFloats[Matrix.MSKEW_Y] + "]");
+            }
+        }
+
+        void clearMatrix() {
+            surface.setMatrix(1, 0, 0, 1);
+        }
+    }
+
+    final Rect mOuterRect;
+    final Rect mInnerRect;
+    final Matrix mTmpMatrix = new Matrix();
+    final float[] mTmpFloats = new float[9];
+    final BlackSurface[] mBlackSurfaces = new BlackSurface[4];
+
+    final boolean mForceDefaultOrientation;
+
+    public void printTo(String prefix, PrintWriter pw) {
+        pw.print(prefix); pw.print("Outer: "); mOuterRect.printShortString(pw);
+                pw.print(" / Inner: "); mInnerRect.printShortString(pw);
+                pw.println();
+        for (int i=0; i<mBlackSurfaces.length; i++) {
+            BlackSurface bs = mBlackSurfaces[i];
+            pw.print(prefix); pw.print("#"); pw.print(i);
+                    pw.print(": "); pw.print(bs.surface);
+                    pw.print(" left="); pw.print(bs.left);
+                    pw.print(" top="); pw.println(bs.top);
+        }
+    }
+
+    public BlackFrame(SurfaceSession session, Rect outer, Rect inner, int layer, int layerStack,
+            boolean forceDefaultOrientation) throws OutOfResourcesException {
+        boolean success = false;
+
+        mForceDefaultOrientation = forceDefaultOrientation;
+
+        mOuterRect = new Rect(outer);
+        mInnerRect = new Rect(inner);
+        try {
+            if (outer.top < inner.top) {
+                mBlackSurfaces[0] = new BlackSurface(session, layer,
+                        outer.left, outer.top, inner.right, inner.top, layerStack);
+            }
+            if (outer.left < inner.left) {
+                mBlackSurfaces[1] = new BlackSurface(session, layer,
+                        outer.left, inner.top, inner.left, outer.bottom, layerStack);
+            }
+            if (outer.bottom > inner.bottom) {
+                mBlackSurfaces[2] = new BlackSurface(session, layer,
+                        inner.left, inner.bottom, outer.right, outer.bottom, layerStack);
+            }
+            if (outer.right > inner.right) {
+                mBlackSurfaces[3] = new BlackSurface(session, layer,
+                        inner.right, outer.top, outer.right, inner.bottom, layerStack);
+            }
+            success = true;
+        } finally {
+            if (!success) {
+                kill();
+            }
+        }
+    }
+
+    public void kill() {
+        if (mBlackSurfaces != null) {
+            for (int i=0; i<mBlackSurfaces.length; i++) {
+                if (mBlackSurfaces[i] != null) {
+                    if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG_WM,
+                            "  BLACK " + mBlackSurfaces[i].surface + ": DESTROY");
+                    mBlackSurfaces[i].surface.destroy();
+                    mBlackSurfaces[i] = null;
+                }
+            }
+        }
+    }
+
+    public void hide() {
+        if (mBlackSurfaces != null) {
+            for (int i=0; i<mBlackSurfaces.length; i++) {
+                if (mBlackSurfaces[i] != null) {
+                    mBlackSurfaces[i].surface.hide();
+                }
+            }
+        }
+    }
+
+    public void setAlpha(float alpha) {
+        for (int i=0; i<mBlackSurfaces.length; i++) {
+            if (mBlackSurfaces[i] != null) {
+                mBlackSurfaces[i].setAlpha(alpha);
+            }
+        }
+    }
+
+    public void setMatrix(Matrix matrix) {
+        for (int i=0; i<mBlackSurfaces.length; i++) {
+            if (mBlackSurfaces[i] != null) {
+                mBlackSurfaces[i].setMatrix(matrix);
+            }
+        }
+    }
+
+    public void clearMatrix() {
+        for (int i=0; i<mBlackSurfaces.length; i++) {
+            if (mBlackSurfaces[i] != null) {
+                mBlackSurfaces[i].clearMatrix();
+            }
+        }
+    }
+}
diff --git a/com/android/server/wm/BoundsAnimationController.java b/com/android/server/wm/BoundsAnimationController.java
new file mode 100644
index 0000000..7953ee4
--- /dev/null
+++ b/com/android/server/wm/BoundsAnimationController.java
@@ -0,0 +1,470 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.animation.AnimationHandler;
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.annotation.IntDef;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Debug;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.view.WindowManagerInternal;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Enables animating bounds of objects.
+ *
+ * In multi-window world bounds of both stack and tasks can change. When we need these bounds to
+ * change smoothly and not require the app to relaunch (e.g. because it handles resizes and
+ * relaunching it would cause poorer experience), these class provides a way to directly animate
+ * the bounds of the resized object.
+ *
+ * The object that is resized needs to implement {@link BoundsAnimationTarget} interface.
+ *
+ * NOTE: All calls to methods in this class should be done on the Animation thread
+ */
+public class BoundsAnimationController {
+    private static final boolean DEBUG_LOCAL = false;
+    private static final boolean DEBUG = DEBUG_LOCAL || DEBUG_ANIM;
+    private static final String TAG = TAG_WITH_CLASS_NAME || DEBUG_LOCAL
+            ? "BoundsAnimationController" : TAG_WM;
+    private static final int DEBUG_ANIMATION_SLOW_DOWN_FACTOR = 1;
+
+    private static final int DEFAULT_TRANSITION_DURATION = 425;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({NO_PIP_MODE_CHANGED_CALLBACKS, SCHEDULE_PIP_MODE_CHANGED_ON_START,
+        SCHEDULE_PIP_MODE_CHANGED_ON_END})
+    public @interface SchedulePipModeChangedState {}
+    /** Do not schedule any PiP mode changed callbacks as a part of this animation. */
+    public static final int NO_PIP_MODE_CHANGED_CALLBACKS = 0;
+    /** Schedule a PiP mode changed callback when this animation starts. */
+    public static final int SCHEDULE_PIP_MODE_CHANGED_ON_START = 1;
+    /** Schedule a PiP mode changed callback when this animation ends. */
+    public static final int SCHEDULE_PIP_MODE_CHANGED_ON_END = 2;
+
+    // Only accessed on UI thread.
+    private ArrayMap<BoundsAnimationTarget, BoundsAnimator> mRunningAnimations = new ArrayMap<>();
+
+    private final class AppTransitionNotifier
+            extends WindowManagerInternal.AppTransitionListener implements Runnable {
+
+        public void onAppTransitionCancelledLocked() {
+            if (DEBUG) Slog.d(TAG, "onAppTransitionCancelledLocked:"
+                    + " mFinishAnimationAfterTransition=" + mFinishAnimationAfterTransition);
+            animationFinished();
+        }
+        public void onAppTransitionFinishedLocked(IBinder token) {
+            if (DEBUG) Slog.d(TAG, "onAppTransitionFinishedLocked:"
+                    + " mFinishAnimationAfterTransition=" + mFinishAnimationAfterTransition);
+            animationFinished();
+        }
+        private void animationFinished() {
+            if (mFinishAnimationAfterTransition) {
+                mHandler.removeCallbacks(this);
+                // This might end up calling into activity manager which will be bad since we have
+                // the window manager lock held at this point. Post a message to take care of the
+                // processing so we don't deadlock.
+                mHandler.post(this);
+            }
+        }
+
+        @Override
+        public void run() {
+            for (int i = 0; i < mRunningAnimations.size(); i++) {
+                final BoundsAnimator b = mRunningAnimations.valueAt(i);
+                b.onAnimationEnd(null);
+            }
+        }
+    }
+
+    private final Handler mHandler;
+    private final AppTransition mAppTransition;
+    private final AppTransitionNotifier mAppTransitionNotifier = new AppTransitionNotifier();
+    private final Interpolator mFastOutSlowInInterpolator;
+    private boolean mFinishAnimationAfterTransition = false;
+    private final AnimationHandler mAnimationHandler;
+
+    private static final int WAIT_FOR_DRAW_TIMEOUT_MS = 3000;
+
+    BoundsAnimationController(Context context, AppTransition transition, Handler handler,
+            AnimationHandler animationHandler) {
+        mHandler = handler;
+        mAppTransition = transition;
+        mAppTransition.registerListenerLocked(mAppTransitionNotifier);
+        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+                com.android.internal.R.interpolator.fast_out_slow_in);
+        mAnimationHandler = animationHandler;
+    }
+
+    @VisibleForTesting
+    final class BoundsAnimator extends ValueAnimator
+            implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener {
+
+        private final BoundsAnimationTarget mTarget;
+        private final Rect mFrom = new Rect();
+        private final Rect mTo = new Rect();
+        private final Rect mTmpRect = new Rect();
+        private final Rect mTmpTaskBounds = new Rect();
+
+        // True if this this animation was canceled and will be replaced the another animation from
+        // the same {@link #BoundsAnimationTarget} target.
+        private boolean mSkipFinalResize;
+        // True if this animation was canceled by the user, not as a part of a replacing animation
+        private boolean mSkipAnimationEnd;
+
+        // True if the animation target is animating from the fullscreen. Only one of
+        // {@link mMoveToFullscreen} or {@link mMoveFromFullscreen} can be true at any time in the
+        // animation.
+        private boolean mMoveFromFullscreen;
+        // True if the animation target should be moved to the fullscreen stack at the end of this
+        // animation. Only one of {@link mMoveToFullscreen} or {@link mMoveFromFullscreen} can be
+        // true at any time in the animation.
+        private boolean mMoveToFullscreen;
+
+        // Whether to schedule PiP mode changes on animation start/end
+        private @SchedulePipModeChangedState int mSchedulePipModeChangedState;
+        private @SchedulePipModeChangedState int mPrevSchedulePipModeChangedState;
+
+        // Depending on whether we are animating from
+        // a smaller to a larger size
+        private final int mFrozenTaskWidth;
+        private final int mFrozenTaskHeight;
+
+        // Timeout callback to ensure we continue the animation if waiting for resuming or app
+        // windows drawn fails
+        private final Runnable mResumeRunnable = () -> resume();
+
+        BoundsAnimator(BoundsAnimationTarget target, Rect from, Rect to,
+                @SchedulePipModeChangedState int schedulePipModeChangedState,
+                @SchedulePipModeChangedState int prevShedulePipModeChangedState,
+                boolean moveFromFullscreen, boolean moveToFullscreen) {
+            super();
+            mTarget = target;
+            mFrom.set(from);
+            mTo.set(to);
+            mSchedulePipModeChangedState = schedulePipModeChangedState;
+            mPrevSchedulePipModeChangedState = prevShedulePipModeChangedState;
+            mMoveFromFullscreen = moveFromFullscreen;
+            mMoveToFullscreen = moveToFullscreen;
+            addUpdateListener(this);
+            addListener(this);
+
+            // If we are animating from smaller to larger, we want to change the task bounds
+            // to their final size immediately so we can use scaling to make the window
+            // larger. Likewise if we are going from bigger to smaller, we want to wait until
+            // the end so we don't have to upscale from the smaller finished size.
+            if (animatingToLargerSize()) {
+                mFrozenTaskWidth = mTo.width();
+                mFrozenTaskHeight = mTo.height();
+            } else {
+                mFrozenTaskWidth = mFrom.width();
+                mFrozenTaskHeight = mFrom.height();
+            }
+        }
+
+        @Override
+        public void onAnimationStart(Animator animation) {
+            if (DEBUG) Slog.d(TAG, "onAnimationStart: mTarget=" + mTarget
+                    + " mPrevSchedulePipModeChangedState=" + mPrevSchedulePipModeChangedState
+                    + " mSchedulePipModeChangedState=" + mSchedulePipModeChangedState);
+            mFinishAnimationAfterTransition = false;
+            mTmpRect.set(mFrom.left, mFrom.top, mFrom.left + mFrozenTaskWidth,
+                    mFrom.top + mFrozenTaskHeight);
+
+            // Boost the thread priority of the animation thread while the bounds animation is
+            // running
+            updateBooster();
+
+            // Ensure that we have prepared the target for animation before we trigger any size
+            // changes, so it can swap surfaces in to appropriate modes, or do as it wishes
+            // otherwise.
+            if (mPrevSchedulePipModeChangedState == NO_PIP_MODE_CHANGED_CALLBACKS) {
+                mTarget.onAnimationStart(mSchedulePipModeChangedState ==
+                        SCHEDULE_PIP_MODE_CHANGED_ON_START, false /* forceUpdate */);
+
+                // When starting an animation from fullscreen, pause here and wait for the
+                // windows-drawn signal before we start the rest of the transition down into PiP.
+                if (mMoveFromFullscreen) {
+                    pause();
+                }
+            } else if (mPrevSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_END &&
+                    mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
+                // We are replacing a running animation into PiP, but since it hasn't completed, the
+                // client will not currently receive any picture-in-picture mode change callbacks.
+                // However, we still need to report to them that they are leaving PiP, so this will
+                // force an update via a mode changed callback.
+                mTarget.onAnimationStart(true /* schedulePipModeChangedCallback */,
+                        true /* forceUpdate */);
+            }
+
+            // Immediately update the task bounds if they have to become larger, but preserve
+            // the starting position so we don't jump at the beginning of the animation.
+            if (animatingToLargerSize()) {
+                mTarget.setPinnedStackSize(mFrom, mTmpRect);
+
+                // We pause the animation until the app has drawn at the new size.
+                // The target will notify us via BoundsAnimationController#resume.
+                // We do this here and pause the animation, rather than just defer starting it
+                // so we can enter the animating state and have WindowStateAnimator apply the
+                // correct logic to make this resize seamless.
+                if (mMoveToFullscreen) {
+                    pause();
+                }
+            }
+        }
+
+        @Override
+        public void pause() {
+            if (DEBUG) Slog.d(TAG, "pause: waiting for windows drawn");
+            super.pause();
+            mHandler.postDelayed(mResumeRunnable, WAIT_FOR_DRAW_TIMEOUT_MS);
+        }
+
+        @Override
+        public void resume() {
+            if (DEBUG) Slog.d(TAG, "resume:");
+            mHandler.removeCallbacks(mResumeRunnable);
+            super.resume();
+        }
+
+        @Override
+        public void onAnimationUpdate(ValueAnimator animation) {
+            final float value = (Float) animation.getAnimatedValue();
+            final float remains = 1 - value;
+            mTmpRect.left = (int) (mFrom.left * remains + mTo.left * value + 0.5f);
+            mTmpRect.top = (int) (mFrom.top * remains + mTo.top * value + 0.5f);
+            mTmpRect.right = (int) (mFrom.right * remains + mTo.right * value + 0.5f);
+            mTmpRect.bottom = (int) (mFrom.bottom * remains + mTo.bottom * value + 0.5f);
+            if (DEBUG) Slog.d(TAG, "animateUpdate: mTarget=" + mTarget + " mBounds="
+                    + mTmpRect + " from=" + mFrom + " mTo=" + mTo + " value=" + value
+                    + " remains=" + remains);
+
+            mTmpTaskBounds.set(mTmpRect.left, mTmpRect.top,
+                    mTmpRect.left + mFrozenTaskWidth, mTmpRect.top + mFrozenTaskHeight);
+
+            if (!mTarget.setPinnedStackSize(mTmpRect, mTmpTaskBounds)) {
+                // Whoops, the target doesn't feel like animating anymore. Let's immediately finish
+                // any further animation.
+                if (DEBUG) Slog.d(TAG, "animateUpdate: cancelled");
+
+                // If we have already scheduled a PiP mode changed at the start of the animation,
+                // then we need to clean up and schedule one at the end, since we have canceled the
+                // animation to the final state.
+                if (mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
+                    mSchedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END;
+                }
+
+                // Since we are cancelling immediately without a replacement animation, send the
+                // animation end to maintain callback parity, but also skip any further resizes
+                cancelAndCallAnimationEnd();
+            }
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            if (DEBUG) Slog.d(TAG, "onAnimationEnd: mTarget=" + mTarget
+                    + " mSkipFinalResize=" + mSkipFinalResize
+                    + " mFinishAnimationAfterTransition=" + mFinishAnimationAfterTransition
+                    + " mAppTransitionIsRunning=" + mAppTransition.isRunning()
+                    + " callers=" + Debug.getCallers(2));
+
+            // There could be another animation running. For example in the
+            // move to fullscreen case, recents will also be closing while the
+            // previous task will be taking its place in the fullscreen stack.
+            // we have to ensure this is completed before we finish the animation
+            // and take our place in the fullscreen stack.
+            if (mAppTransition.isRunning() && !mFinishAnimationAfterTransition) {
+                mFinishAnimationAfterTransition = true;
+                return;
+            }
+
+            if (!mSkipAnimationEnd) {
+                // If this animation has already scheduled the picture-in-picture mode on start, and
+                // we are not skipping the final resize due to being canceled, then move the PiP to
+                // fullscreen once the animation ends
+                if (DEBUG) Slog.d(TAG, "onAnimationEnd: mTarget=" + mTarget
+                        + " moveToFullscreen=" + mMoveToFullscreen);
+                mTarget.onAnimationEnd(mSchedulePipModeChangedState ==
+                        SCHEDULE_PIP_MODE_CHANGED_ON_END, !mSkipFinalResize ? mTo : null,
+                                mMoveToFullscreen);
+            }
+
+            // Clean up this animation
+            removeListener(this);
+            removeUpdateListener(this);
+            mRunningAnimations.remove(mTarget);
+
+            // Reset the thread priority of the animation thread after the bounds animation is done
+            updateBooster();
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            // Always skip the final resize when the animation is canceled
+            mSkipFinalResize = true;
+            mMoveToFullscreen = false;
+        }
+
+        private void cancelAndCallAnimationEnd() {
+            if (DEBUG) Slog.d(TAG, "cancelAndCallAnimationEnd: mTarget=" + mTarget);
+            mSkipAnimationEnd = false;
+            super.cancel();
+        }
+
+        @Override
+        public void cancel() {
+            if (DEBUG) Slog.d(TAG, "cancel: mTarget=" + mTarget);
+            mSkipAnimationEnd = true;
+            super.cancel();
+        }
+
+        /**
+         * @return true if the animation target is the same as the input bounds.
+         */
+        boolean isAnimatingTo(Rect bounds) {
+            return mTo.equals(bounds);
+        }
+
+        /**
+         * @return true if we are animating to a larger surface size
+         */
+        @VisibleForTesting
+        boolean animatingToLargerSize() {
+            // TODO: Fix this check for aspect ratio changes
+            return (mFrom.width() * mFrom.height() <= mTo.width() * mTo.height());
+        }
+
+        @Override
+        public void onAnimationRepeat(Animator animation) {
+            // Do nothing
+        }
+
+        @Override
+        public AnimationHandler getAnimationHandler() {
+            if (mAnimationHandler != null) {
+                return mAnimationHandler;
+            }
+            return super.getAnimationHandler();
+        }
+    }
+
+    public void animateBounds(final BoundsAnimationTarget target, Rect from, Rect to,
+            int animationDuration, @SchedulePipModeChangedState int schedulePipModeChangedState,
+            boolean moveFromFullscreen, boolean moveToFullscreen) {
+        animateBoundsImpl(target, from, to, animationDuration, schedulePipModeChangedState,
+                moveFromFullscreen, moveToFullscreen);
+    }
+
+    @VisibleForTesting
+    BoundsAnimator animateBoundsImpl(final BoundsAnimationTarget target, Rect from, Rect to,
+            int animationDuration, @SchedulePipModeChangedState int schedulePipModeChangedState,
+            boolean moveFromFullscreen, boolean moveToFullscreen) {
+        final BoundsAnimator existing = mRunningAnimations.get(target);
+        final boolean replacing = existing != null;
+        @SchedulePipModeChangedState int prevSchedulePipModeChangedState =
+                NO_PIP_MODE_CHANGED_CALLBACKS;
+
+        if (DEBUG) Slog.d(TAG, "animateBounds: target=" + target + " from=" + from + " to=" + to
+                + " schedulePipModeChangedState=" + schedulePipModeChangedState
+                + " replacing=" + replacing);
+
+        if (replacing) {
+            if (existing.isAnimatingTo(to)) {
+                // Just let the current animation complete if it has the same destination as the
+                // one we are trying to start.
+                if (DEBUG) Slog.d(TAG, "animateBounds: same destination as existing=" + existing
+                        + " ignoring...");
+
+                return existing;
+            }
+
+            // Save the previous state
+            prevSchedulePipModeChangedState = existing.mSchedulePipModeChangedState;
+
+            // Update the PiP callback states if we are replacing the animation
+            if (existing.mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
+                if (schedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
+                    if (DEBUG) Slog.d(TAG, "animateBounds: still animating to fullscreen, keep"
+                            + " existing deferred state");
+                } else {
+                    if (DEBUG) Slog.d(TAG, "animateBounds: fullscreen animation canceled, callback"
+                            + " on start already processed, schedule deferred update on end");
+                    schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END;
+                }
+            } else if (existing.mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_END) {
+                if (schedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) {
+                    if (DEBUG) Slog.d(TAG, "animateBounds: non-fullscreen animation canceled,"
+                            + " callback on start will be processed");
+                } else {
+                    if (DEBUG) Slog.d(TAG, "animateBounds: still animating from fullscreen, keep"
+                            + " existing deferred state");
+                    schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END;
+                }
+            }
+
+            // Since we are replacing, we skip both animation start and end callbacks
+            existing.cancel();
+        }
+        final BoundsAnimator animator = new BoundsAnimator(target, from, to,
+                schedulePipModeChangedState, prevSchedulePipModeChangedState,
+                moveFromFullscreen, moveToFullscreen);
+        mRunningAnimations.put(target, animator);
+        animator.setFloatValues(0f, 1f);
+        animator.setDuration((animationDuration != -1 ? animationDuration
+                : DEFAULT_TRANSITION_DURATION) * DEBUG_ANIMATION_SLOW_DOWN_FACTOR);
+        animator.setInterpolator(mFastOutSlowInInterpolator);
+        animator.start();
+        return animator;
+    }
+
+    public Handler getHandler() {
+        return mHandler;
+    }
+
+    public void onAllWindowsDrawn() {
+        if (DEBUG) Slog.d(TAG, "onAllWindowsDrawn:");
+        mHandler.post(this::resume);
+    }
+
+    private void resume() {
+        for (int i = 0; i < mRunningAnimations.size(); i++) {
+            final BoundsAnimator b = mRunningAnimations.valueAt(i);
+            b.resume();
+        }
+    }
+
+    private void updateBooster() {
+        WindowManagerService.sThreadPriorityBooster.setBoundsAnimationRunning(
+                !mRunningAnimations.isEmpty());
+    }
+}
diff --git a/com/android/server/wm/BoundsAnimationTarget.java b/com/android/server/wm/BoundsAnimationTarget.java
new file mode 100644
index 0000000..647a2d6
--- /dev/null
+++ b/com/android/server/wm/BoundsAnimationTarget.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.graphics.Rect;
+
+/**
+ * The target for a BoundsAnimation.
+ * @see BoundsAnimationController
+ */
+interface BoundsAnimationTarget {
+
+    /**
+     * Callback for the target to inform it that the animation has started, so it can do some
+     * necessary preparation.
+     *
+     * @param schedulePipModeChangedCallback whether or not to schedule the PiP mode changed
+     * callbacks
+     */
+    void onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate);
+
+    /**
+     * Sets the size of the target (without any intermediate steps, like scheduling animation)
+     * but freezes the bounds of any tasks in the target at taskBounds, to allow for more
+     * flexibility during resizing. Only works for the pinned stack at the moment.  This will
+     * only be called between onAnimationStart() and onAnimationEnd().
+     *
+     * @return Whether the target should continue to be animated and this call was successful.
+     * If false, the animation will be cancelled because the user has determined that the
+     * animation is now invalid and not required. In such a case, the cancel will trigger the
+     * animation end callback as well, but will not send any further size changes.
+     */
+    boolean setPinnedStackSize(Rect stackBounds, Rect taskBounds);
+
+    /**
+     * Callback for the target to inform it that the animation has ended, so it can do some
+     * necessary cleanup.
+     *
+     * @param schedulePipModeChangedCallback whether or not to schedule the PiP mode changed
+     * callbacks
+     * @param finalStackSize the final stack bounds to set on the target (can be to indicate that
+     * the animation was cancelled and the target does not need to update to the final stack bounds)
+     * @param moveToFullscreen whether or the target should reparent itself to the fullscreen stack
+     * when the animation completes
+     */
+    void onAnimationEnd(boolean schedulePipModeChangedCallback, Rect finalStackSize,
+            boolean moveToFullscreen);
+}
diff --git a/com/android/server/wm/CircularDisplayMask.java b/com/android/server/wm/CircularDisplayMask.java
new file mode 100644
index 0000000..ae41541
--- /dev/null
+++ b/com/android/server/wm/CircularDisplayMask.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.util.Slog;
+import android.view.Display;
+import android.view.Surface;
+import android.view.Surface.OutOfResourcesException;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+
+class CircularDisplayMask {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "CircularDisplayMask" : TAG_WM;
+
+    // size of the chin
+    private int mScreenOffset = 0;
+    // Display dimensions
+    private Point mScreenSize;
+
+    private final SurfaceControl mSurfaceControl;
+    private final Surface mSurface = new Surface();
+    private int mLastDW;
+    private int mLastDH;
+    private boolean mDrawNeeded;
+    private Paint mPaint;
+    private int mRotation;
+    private boolean mVisible;
+    private boolean mDimensionsUnequal = false;
+    private int mMaskThickness;
+
+    public CircularDisplayMask(Display display, SurfaceSession session, int zOrder,
+            int screenOffset, int maskThickness) {
+        mScreenSize = new Point();
+        display.getSize(mScreenSize);
+        if (mScreenSize.x != mScreenSize.y + screenOffset) {
+            Slog.w(TAG, "Screen dimensions of displayId = " + display.getDisplayId() +
+                    "are not equal, circularMask will not be drawn.");
+            mDimensionsUnequal = true;
+        }
+
+        SurfaceControl ctrl = null;
+        try {
+            if (DEBUG_SURFACE_TRACE) {
+                ctrl = new WindowSurfaceController.SurfaceTrace(session, "CircularDisplayMask",
+                        mScreenSize.x, mScreenSize.y, PixelFormat.TRANSLUCENT,
+                        SurfaceControl.HIDDEN);
+            } else {
+                ctrl = new SurfaceControl(session, "CircularDisplayMask", mScreenSize.x,
+                        mScreenSize.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+            }
+            ctrl.setLayerStack(display.getLayerStack());
+            ctrl.setLayer(zOrder);
+            ctrl.setPosition(0, 0);
+            ctrl.show();
+            mSurface.copyFrom(ctrl);
+        } catch (OutOfResourcesException e) {
+        }
+        mSurfaceControl = ctrl;
+        mDrawNeeded = true;
+        mPaint = new Paint();
+        mPaint.setAntiAlias(true);
+        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+        mScreenOffset = screenOffset;
+        mMaskThickness = maskThickness;
+    }
+
+    private void drawIfNeeded() {
+        if (!mDrawNeeded || !mVisible || mDimensionsUnequal) {
+            return;
+        }
+        mDrawNeeded = false;
+
+        Rect dirty = new Rect(0, 0, mScreenSize.x, mScreenSize.y);
+        Canvas c = null;
+        try {
+            c = mSurface.lockCanvas(dirty);
+        } catch (IllegalArgumentException e) {
+        } catch (Surface.OutOfResourcesException e) {
+        }
+        if (c == null) {
+            return;
+        }
+        switch (mRotation) {
+        case Surface.ROTATION_0:
+        case Surface.ROTATION_90:
+            // chin bottom or right
+            mSurfaceControl.setPosition(0, 0);
+            break;
+        case Surface.ROTATION_180:
+            // chin top
+            mSurfaceControl.setPosition(0, -mScreenOffset);
+            break;
+        case Surface.ROTATION_270:
+            // chin left
+            mSurfaceControl.setPosition(-mScreenOffset, 0);
+            break;
+        }
+
+        int circleRadius = mScreenSize.x / 2;
+        c.drawColor(Color.BLACK);
+
+        // The radius is reduced by mMaskThickness to provide an anti aliasing effect on the display edges.
+        c.drawCircle(circleRadius, circleRadius, circleRadius - mMaskThickness, mPaint);
+        mSurface.unlockCanvasAndPost(c);
+    }
+
+    // Note: caller responsible for being inside
+    // Surface.openTransaction() / closeTransaction()
+    public void setVisibility(boolean on) {
+        if (mSurfaceControl == null) {
+            return;
+        }
+        mVisible = on;
+        drawIfNeeded();
+        if (on) {
+            mSurfaceControl.show();
+        } else {
+            mSurfaceControl.hide();
+        }
+    }
+
+    void positionSurface(int dw, int dh, int rotation) {
+        if (mLastDW == dw && mLastDH == dh && mRotation == rotation) {
+            return;
+        }
+        mLastDW = dw;
+        mLastDH = dh;
+        mDrawNeeded = true;
+        mRotation = rotation;
+        drawIfNeeded();
+    }
+
+}
diff --git a/com/android/server/wm/ConfigurationContainer.java b/com/android/server/wm/ConfigurationContainer.java
new file mode 100644
index 0000000..28ba9b3
--- /dev/null
+++ b/com/android/server/wm/ConfigurationContainer.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.activityTypeToString;
+
+import android.app.WindowConfiguration;
+import android.content.res.Configuration;
+
+import java.util.ArrayList;
+
+/**
+ * Contains common logic for classes that have override configurations and are organized in a
+ * hierarchy.
+ */
+public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
+
+    /** Contains override configuration settings applied to this configuration container. */
+    private Configuration mOverrideConfiguration = new Configuration();
+
+    /**
+     * Contains full configuration applied to this configuration container. Corresponds to full
+     * parent's config with applied {@link #mOverrideConfiguration}.
+     */
+    private Configuration mFullConfiguration = new Configuration();
+
+    /**
+     * Contains merged override configuration settings from the top of the hierarchy down to this
+     * particular instance. It is different from {@link #mFullConfiguration} because it starts from
+     * topmost container's override config instead of global config.
+     */
+    private Configuration mMergedOverrideConfiguration = new Configuration();
+
+    private ArrayList<ConfigurationContainerListener> mChangeListeners = new ArrayList<>();
+
+    // TODO: Can't have ag/2592611 soon enough!
+    private final Configuration mTmpConfig = new Configuration();
+
+    /**
+     * Returns full configuration applied to this configuration container.
+     * This method should be used for getting settings applied in each particular level of the
+     * hierarchy.
+     */
+    public Configuration getConfiguration() {
+        return mFullConfiguration;
+    }
+
+    /**
+     * Notify that parent config changed and we need to update full configuration.
+     * @see #mFullConfiguration
+     */
+    public void onConfigurationChanged(Configuration newParentConfig) {
+        mFullConfiguration.setTo(newParentConfig);
+        mFullConfiguration.updateFrom(mOverrideConfiguration);
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            final ConfigurationContainer child = getChildAt(i);
+            child.onConfigurationChanged(mFullConfiguration);
+        }
+    }
+
+    /** Returns override configuration applied to this configuration container. */
+    public Configuration getOverrideConfiguration() {
+        return mOverrideConfiguration;
+    }
+
+    /**
+     * Update override configuration and recalculate full config.
+     * @see #mOverrideConfiguration
+     * @see #mFullConfiguration
+     */
+    public void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
+        mOverrideConfiguration.setTo(overrideConfiguration);
+        // Update full configuration of this container and all its children.
+        final ConfigurationContainer parent = getParent();
+        onConfigurationChanged(parent != null ? parent.getConfiguration() : Configuration.EMPTY);
+        // Update merged override config of this container and all its children.
+        onMergedOverrideConfigurationChanged();
+
+        // Inform listeners of the change.
+        for (int i = mChangeListeners.size() - 1; i >=0; --i) {
+            mChangeListeners.get(i).onOverrideConfigurationChanged(overrideConfiguration);
+        }
+    }
+
+    /**
+     * Get merged override configuration from the top of the hierarchy down to this particular
+     * instance. This should be reported to client as override config.
+     */
+    public Configuration getMergedOverrideConfiguration() {
+        return mMergedOverrideConfiguration;
+    }
+
+    /**
+     * Update merged override configuration based on corresponding parent's config and notify all
+     * its children. If there is no parent, merged override configuration will set equal to current
+     * override config.
+     * @see #mMergedOverrideConfiguration
+     */
+    void onMergedOverrideConfigurationChanged() {
+        final ConfigurationContainer parent = getParent();
+        if (parent != null) {
+            mMergedOverrideConfiguration.setTo(parent.getMergedOverrideConfiguration());
+            mMergedOverrideConfiguration.updateFrom(mOverrideConfiguration);
+        } else {
+            mMergedOverrideConfiguration.setTo(mOverrideConfiguration);
+        }
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            final ConfigurationContainer child = getChildAt(i);
+            child.onMergedOverrideConfigurationChanged();
+        }
+    }
+
+    public WindowConfiguration getWindowConfiguration() {
+        return mFullConfiguration.windowConfiguration;
+    }
+
+    /** Returns the windowing mode the configuration container is currently in. */
+    public int getWindowingMode() {
+        return mFullConfiguration.windowConfiguration.getWindowingMode();
+    }
+
+    /** Sets the windowing mode for the configuration container. */
+    public void setWindowingMode(/*@WindowConfiguration.WindowingMode*/ int windowingMode) {
+        mTmpConfig.setTo(getOverrideConfiguration());
+        mTmpConfig.windowConfiguration.setWindowingMode(windowingMode);
+        onOverrideConfigurationChanged(mTmpConfig);
+    }
+
+    /** Returns true if this container is currently in split-screen windowing mode. */
+    public boolean inSplitScreenWindowingMode() {
+        /*@WindowConfiguration.WindowingMode*/ int windowingMode =
+                mFullConfiguration.windowConfiguration.getWindowingMode();
+
+        return windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+    }
+
+    /** Returns true if this container is currently in split-screen secondary windowing mode. */
+    public boolean inSplitScreenSecondaryWindowingMode() {
+        /*@WindowConfiguration.WindowingMode*/ int windowingMode =
+                mFullConfiguration.windowConfiguration.getWindowingMode();
+
+        return windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+    }
+
+    /**
+     * Returns true if this container can be put in either
+     * {@link WindowConfiguration#WINDOWING_MODE_SPLIT_SCREEN_PRIMARY} or
+     * {@link WindowConfiguration##WINDOWING_MODE_SPLIT_SCREEN_SECONDARY} windowing modes based on
+     * its current state.
+     */
+    public boolean supportSplitScreenWindowingMode() {
+        return mFullConfiguration.windowConfiguration.supportSplitScreenWindowingMode();
+    }
+
+    /** Returns the activity type associated with the the configuration container. */
+    /*@WindowConfiguration.ActivityType*/
+    public int getActivityType() {
+        return mFullConfiguration.windowConfiguration.getActivityType();
+    }
+
+    /** Sets the activity type to associate with the configuration container. */
+    public void setActivityType(/*@WindowConfiguration.ActivityType*/ int activityType) {
+        int currentActivityType = getActivityType();
+        if (currentActivityType == activityType) {
+            return;
+        }
+        if (currentActivityType != ACTIVITY_TYPE_UNDEFINED) {
+            throw new IllegalStateException("Can't change activity type once set: " + this
+                    + " activityType=" + activityTypeToString(activityType));
+        }
+        mTmpConfig.setTo(getOverrideConfiguration());
+        mTmpConfig.windowConfiguration.setActivityType(activityType);
+        onOverrideConfigurationChanged(mTmpConfig);
+    }
+
+    public boolean isActivityTypeHome() {
+        return getActivityType() == ACTIVITY_TYPE_HOME;
+    }
+
+    public boolean isActivityTypeRecents() {
+        return getActivityType() == ACTIVITY_TYPE_RECENTS;
+    }
+
+    public boolean isActivityTypeAssistant() {
+        return getActivityType() == ACTIVITY_TYPE_ASSISTANT;
+    }
+
+    public boolean isActivityTypeStandard() {
+        return getActivityType() == ACTIVITY_TYPE_STANDARD;
+    }
+
+    public boolean isActivityTypeStandardOrUndefined() {
+        /*@WindowConfiguration.ActivityType*/ final int activityType = getActivityType();
+        return activityType == ACTIVITY_TYPE_STANDARD || activityType == ACTIVITY_TYPE_UNDEFINED;
+    }
+
+    public boolean hasCompatibleActivityType(ConfigurationContainer other) {
+        /*@WindowConfiguration.ActivityType*/ int thisType = getActivityType();
+        /*@WindowConfiguration.ActivityType*/ int otherType = other.getActivityType();
+
+        return thisType == otherType
+                || thisType == ACTIVITY_TYPE_UNDEFINED
+                || otherType == ACTIVITY_TYPE_UNDEFINED;
+    }
+
+    public void registerConfigurationChangeListener(ConfigurationContainerListener listener) {
+        if (mChangeListeners.contains(listener)) {
+            return;
+        }
+        mChangeListeners.add(listener);
+        listener.onOverrideConfigurationChanged(mOverrideConfiguration);
+    }
+
+    public void unregisterConfigurationChangeListener(ConfigurationContainerListener listener) {
+        mChangeListeners.remove(listener);
+    }
+
+    /**
+     * Must be called when new parent for the container was set.
+     */
+    protected void onParentChanged() {
+        final ConfigurationContainer parent = getParent();
+        // Removing parent usually means that we've detached this entity to destroy it or to attach
+        // to another parent. In both cases we don't need to update the configuration now.
+        if (parent != null) {
+            // Update full configuration of this container and all its children.
+            onConfigurationChanged(parent.mFullConfiguration);
+            // Update merged override configuration of this container and all its children.
+            onMergedOverrideConfigurationChanged();
+        }
+    }
+
+    abstract protected int getChildCount();
+
+    abstract protected E getChildAt(int index);
+
+    abstract protected ConfigurationContainer getParent();
+}
diff --git a/com/android/server/wm/ConfigurationContainerListener.java b/com/android/server/wm/ConfigurationContainerListener.java
new file mode 100644
index 0000000..ff14d97
--- /dev/null
+++ b/com/android/server/wm/ConfigurationContainerListener.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import android.content.res.Configuration;
+
+/**
+ * Interface for listening to changes in a {@link ConfigurationContainer}.
+ */
+public interface ConfigurationContainerListener {
+
+    void onOverrideConfigurationChanged(Configuration overrideConfiguration);
+}
diff --git a/com/android/server/wm/DimLayer.java b/com/android/server/wm/DimLayer.java
new file mode 100644
index 0000000..708973d
--- /dev/null
+++ b/com/android/server/wm/DimLayer.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DIM_LAYER;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.view.DisplayInfo;
+import android.view.SurfaceControl;
+
+import java.io.PrintWriter;
+
+public class DimLayer {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "DimLayer" : TAG_WM;
+    private final WindowManagerService mService;
+
+    /** Actual surface that dims */
+    private SurfaceControl mDimSurface;
+
+    /** Last value passed to mDimSurface.setAlpha() */
+    private float mAlpha = 0;
+
+    /** Last value passed to mDimSurface.setLayer() */
+    private int mLayer = -1;
+
+    /** Next values to pass to mDimSurface.setPosition() and mDimSurface.setSize() */
+    private final Rect mBounds = new Rect();
+
+    /** Last values passed to mDimSurface.setPosition() and mDimSurface.setSize() */
+    private final Rect mLastBounds = new Rect();
+
+    /** True after mDimSurface.show() has been called, false after mDimSurface.hide(). */
+    private boolean mShowing = false;
+
+    /** Value of mAlpha when beginning transition to mTargetAlpha */
+    private float mStartAlpha = 0;
+
+    /** Final value of mAlpha following transition */
+    private float mTargetAlpha = 0;
+
+    /** Time in units of SystemClock.uptimeMillis() at which the current transition started */
+    private long mStartTime;
+
+    /** Time in milliseconds to take to transition from mStartAlpha to mTargetAlpha */
+    private long mDuration;
+
+    private boolean mDestroyed = false;
+
+    private final int mDisplayId;
+
+
+    /** Interface implemented by users of the dim layer */
+    interface DimLayerUser {
+        /** Returns true if the  dim should be fullscreen. */
+        boolean dimFullscreen();
+        /** Returns the display info. of the dim layer user. */
+        DisplayInfo getDisplayInfo();
+        /** Returns true if the dim layer user is currently attached to a display */
+        boolean isAttachedToDisplay();
+        /** Gets the bounds of the dim layer user. */
+        void getDimBounds(Rect outBounds);
+        /** Returns the layer to place a dim layer. */
+        default int getLayerForDim(WindowStateAnimator animator, int layerOffset,
+                int defaultLayer) {
+            return defaultLayer;
+        }
+
+        String toShortString();
+    }
+    /** The user of this dim layer. */
+    private final DimLayerUser mUser;
+
+    private final String mName;
+
+    DimLayer(WindowManagerService service, DimLayerUser user, int displayId, String name) {
+        mUser = user;
+        mDisplayId = displayId;
+        mService = service;
+        mName = name;
+        if (DEBUG_DIM_LAYER) Slog.v(TAG, "Ctor: displayId=" + displayId);
+    }
+
+    private void constructSurface(WindowManagerService service) {
+        service.openSurfaceTransaction();
+        try {
+            if (DEBUG_SURFACE_TRACE) {
+                mDimSurface = new WindowSurfaceController.SurfaceTrace(service.mFxSession,
+                    "DimSurface",
+                    16, 16, PixelFormat.OPAQUE,
+                    SurfaceControl.FX_SURFACE_DIM | SurfaceControl.HIDDEN);
+            } else {
+                mDimSurface = new SurfaceControl(service.mFxSession, mName,
+                    16, 16, PixelFormat.OPAQUE,
+                    SurfaceControl.FX_SURFACE_DIM | SurfaceControl.HIDDEN);
+            }
+            if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG,
+                    "  DIM " + mDimSurface + ": CREATE");
+            mDimSurface.setLayerStack(mDisplayId);
+            adjustBounds();
+            adjustAlpha(mAlpha);
+            adjustLayer(mLayer);
+        } catch (Exception e) {
+            Slog.e(TAG_WM, "Exception creating Dim surface", e);
+        } finally {
+            service.closeSurfaceTransaction();
+        }
+    }
+
+    /** Return true if dim layer is showing */
+    boolean isDimming() {
+        return mTargetAlpha != 0;
+    }
+
+    /** Return true if in a transition period */
+    boolean isAnimating() {
+        return mTargetAlpha != mAlpha;
+    }
+
+    float getTargetAlpha() {
+        return mTargetAlpha;
+    }
+
+    void setLayer(int layer) {
+        if (mLayer == layer) {
+            return;
+        }
+        mLayer = layer;
+        adjustLayer(layer);
+    }
+
+    private void adjustLayer(int layer) {
+        if (mDimSurface != null) {
+            mDimSurface.setLayer(layer);
+        }
+    }
+
+    int getLayer() {
+        return mLayer;
+    }
+
+    private void setAlpha(float alpha) {
+        if (mAlpha == alpha) {
+            return;
+        }
+        mAlpha = alpha;
+        adjustAlpha(alpha);
+    }
+
+    private void adjustAlpha(float alpha) {
+        if (DEBUG_DIM_LAYER) Slog.v(TAG, "setAlpha alpha=" + alpha);
+        try {
+            if (mDimSurface != null) {
+                mDimSurface.setAlpha(alpha);
+            }
+            if (alpha == 0 && mShowing) {
+                if (DEBUG_DIM_LAYER) Slog.v(TAG, "setAlpha hiding");
+                if (mDimSurface != null) {
+                    mDimSurface.hide();
+                    mShowing = false;
+                }
+            } else if (alpha > 0 && !mShowing) {
+                if (DEBUG_DIM_LAYER) Slog.v(TAG, "setAlpha showing");
+                if (mDimSurface != null) {
+                    mDimSurface.show();
+                    mShowing = true;
+                }
+            }
+        } catch (RuntimeException e) {
+            Slog.w(TAG, "Failure setting alpha immediately", e);
+        }
+    }
+
+    /**
+     * NOTE: Must be called with Surface transaction open.
+     */
+    private void adjustBounds() {
+        if (mUser.dimFullscreen()) {
+            getBoundsForFullscreen(mBounds);
+        }
+
+        if (mDimSurface != null) {
+            mDimSurface.setPosition(mBounds.left, mBounds.top);
+            mDimSurface.setSize(mBounds.width(), mBounds.height());
+            if (DEBUG_DIM_LAYER) Slog.v(TAG,
+                    "adjustBounds user=" + mUser.toShortString() + " mBounds=" + mBounds);
+        }
+
+        mLastBounds.set(mBounds);
+    }
+
+    private void getBoundsForFullscreen(Rect outBounds) {
+        final int dw, dh;
+        final float xPos, yPos;
+        // Set surface size to screen size.
+        final DisplayInfo info = mUser.getDisplayInfo();
+        // Multiply by 1.5 so that rotating a frozen surface that includes this does not expose
+        // a corner.
+        dw = (int) (info.logicalWidth * 1.5);
+        dh = (int) (info.logicalHeight * 1.5);
+        // back off position so 1/4 of Surface is before and 1/4 is after.
+        xPos = -1 * dw / 6;
+        yPos = -1 * dh / 6;
+        outBounds.set((int) xPos, (int) yPos, (int) xPos + dw, (int) yPos + dh);
+    }
+
+    void setBoundsForFullscreen() {
+        getBoundsForFullscreen(mBounds);
+        setBounds(mBounds);
+    }
+
+    /** @param bounds The new bounds to set */
+    void setBounds(Rect bounds) {
+        mBounds.set(bounds);
+        if (isDimming() && !mLastBounds.equals(bounds)) {
+            try {
+                mService.openSurfaceTransaction();
+                adjustBounds();
+            } catch (RuntimeException e) {
+                Slog.w(TAG, "Failure setting size", e);
+            } finally {
+                mService.closeSurfaceTransaction();
+            }
+        }
+    }
+
+    /**
+     * @param duration The time to test.
+     * @return True if the duration would lead to an earlier end to the current animation.
+     */
+    private boolean durationEndsEarlier(long duration) {
+        return SystemClock.uptimeMillis() + duration < mStartTime + mDuration;
+    }
+
+    /** Jump to the end of the animation.
+     * NOTE: Must be called with Surface transaction open. */
+    void show() {
+        if (isAnimating()) {
+            if (DEBUG_DIM_LAYER) Slog.v(TAG, "show: immediate");
+            show(mLayer, mTargetAlpha, 0);
+        }
+    }
+
+    /**
+     * Begin an animation to a new dim value.
+     * NOTE: Must be called with Surface transaction open.
+     *
+     * @param layer The layer to set the surface to.
+     * @param alpha The dim value to end at.
+     * @param duration How long to take to get there in milliseconds.
+     */
+    void show(int layer, float alpha, long duration) {
+        if (DEBUG_DIM_LAYER) Slog.v(TAG, "show: layer=" + layer + " alpha=" + alpha
+                + " duration=" + duration + ", mDestroyed=" + mDestroyed);
+        if (mDestroyed) {
+            Slog.e(TAG, "show: no Surface");
+            // Make sure isAnimating() returns false.
+            mTargetAlpha = mAlpha = 0;
+            return;
+        }
+
+        if (mDimSurface == null) {
+            constructSurface(mService);
+        }
+
+        if (!mLastBounds.equals(mBounds)) {
+            adjustBounds();
+        }
+        setLayer(layer);
+
+        long curTime = SystemClock.uptimeMillis();
+        final boolean animating = isAnimating();
+        if ((animating && (mTargetAlpha != alpha || durationEndsEarlier(duration)))
+                || (!animating && mAlpha != alpha)) {
+            if (duration <= 0) {
+                // No animation required, just set values.
+                setAlpha(alpha);
+            } else {
+                // Start or continue animation with new parameters.
+                mStartAlpha = mAlpha;
+                mStartTime = curTime;
+                mDuration = duration;
+            }
+        }
+        mTargetAlpha = alpha;
+        if (DEBUG_DIM_LAYER) Slog.v(TAG, "show: mStartAlpha=" + mStartAlpha + " mStartTime="
+                + mStartTime + " mTargetAlpha=" + mTargetAlpha);
+    }
+
+    /** Immediate hide.
+     * NOTE: Must be called with Surface transaction open. */
+    void hide() {
+        if (mShowing) {
+            if (DEBUG_DIM_LAYER) Slog.v(TAG, "hide: immediate");
+            hide(0);
+        }
+    }
+
+    /**
+     * Gradually fade to transparent.
+     * NOTE: Must be called with Surface transaction open.
+     *
+     * @param duration Time to fade in milliseconds.
+     */
+    void hide(long duration) {
+        if (mShowing && (mTargetAlpha != 0 || durationEndsEarlier(duration))) {
+            if (DEBUG_DIM_LAYER) Slog.v(TAG, "hide: duration=" + duration);
+            show(mLayer, 0, duration);
+        }
+    }
+
+    /**
+     * Advance the dimming per the last #show(int, float, long) call.
+     * NOTE: Must be called with Surface transaction open.
+     *
+     * @return True if animation is still required after this step.
+     */
+    boolean stepAnimation() {
+        if (mDestroyed) {
+            Slog.e(TAG, "stepAnimation: surface destroyed");
+            // Ensure that isAnimating() returns false;
+            mTargetAlpha = mAlpha = 0;
+            return false;
+        }
+        if (isAnimating()) {
+            final long curTime = SystemClock.uptimeMillis();
+            final float alphaDelta = mTargetAlpha - mStartAlpha;
+            float alpha = mStartAlpha + alphaDelta * (curTime - mStartTime) / mDuration;
+            if (alphaDelta > 0 && alpha > mTargetAlpha ||
+                    alphaDelta < 0 && alpha < mTargetAlpha) {
+                // Don't exceed limits.
+                alpha = mTargetAlpha;
+            }
+            if (DEBUG_DIM_LAYER) Slog.v(TAG, "stepAnimation: curTime=" + curTime + " alpha=" + alpha);
+            setAlpha(alpha);
+        }
+
+        return isAnimating();
+    }
+
+    /** Cleanup */
+    void destroySurface() {
+        if (DEBUG_DIM_LAYER) Slog.v(TAG, "destroySurface.");
+        if (mDimSurface != null) {
+            mDimSurface.destroy();
+            mDimSurface = null;
+        }
+        mDestroyed = true;
+    }
+
+    public void printTo(String prefix, PrintWriter pw) {
+        pw.print(prefix); pw.print("mDimSurface="); pw.print(mDimSurface);
+                pw.print(" mLayer="); pw.print(mLayer);
+                pw.print(" mAlpha="); pw.println(mAlpha);
+        pw.print(prefix); pw.print("mLastBounds="); pw.print(mLastBounds.toShortString());
+                pw.print(" mBounds="); pw.println(mBounds.toShortString());
+        pw.print(prefix); pw.print("Last animation: ");
+                pw.print(" mDuration="); pw.print(mDuration);
+                pw.print(" mStartTime="); pw.print(mStartTime);
+                pw.print(" curTime="); pw.println(SystemClock.uptimeMillis());
+        pw.print(prefix); pw.print(" mStartAlpha="); pw.print(mStartAlpha);
+                pw.print(" mTargetAlpha="); pw.println(mTargetAlpha);
+    }
+}
diff --git a/com/android/server/wm/DimLayerController.java b/com/android/server/wm/DimLayerController.java
new file mode 100644
index 0000000..6f9e45a
--- /dev/null
+++ b/com/android/server/wm/DimLayerController.java
@@ -0,0 +1,403 @@
+package com.android.server.wm;
+
+import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DIM_LAYER;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;
+
+import android.graphics.Rect;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.TypedValue;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wm.DimLayer.DimLayerUser;
+
+import java.io.PrintWriter;
+
+/**
+ * Centralizes the control of dim layers used for
+ * {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND}
+ * as well as other use cases (such as dimming above a dead window).
+ */
+class DimLayerController {
+    private static final String TAG_LOCAL = "DimLayerController";
+    private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM;
+
+    /** 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;
+
+    /**
+     * 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, DimLayerState> mState = new ArrayMap<>();
+
+    private DisplayContent mDisplayContent;
+
+    private Rect mTmpBounds = new Rect();
+
+    DimLayerController(DisplayContent displayContent) {
+        mDisplayContent = displayContent;
+    }
+
+    /** Updates the dim layer bounds, recreating it if needed. */
+    void updateDimLayer(DimLayer.DimLayerUser dimLayerUser) {
+        final DimLayerState state = getOrCreateDimLayerState(dimLayerUser);
+        final boolean previousFullscreen = state.dimLayer != null
+                && state.dimLayer == mSharedFullScreenDimLayer;
+        DimLayer newDimLayer;
+        final int displayId = mDisplayContent.getDisplayId();
+        if (dimLayerUser.dimFullscreen()) {
+            if (previousFullscreen && mSharedFullScreenDimLayer != null) {
+                // Update the bounds for fullscreen in case of rotation.
+                mSharedFullScreenDimLayer.setBoundsForFullscreen();
+                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,
+                            getDimLayerTag(dimLayerUser));
+                }
+                dimLayerUser.getDimBounds(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,
+                            getDimLayerTag(dimLayerUser))
+                    : state.dimLayer;
+            dimLayerUser.getDimBounds(mTmpBounds);
+            newDimLayer.setBounds(mTmpBounds);
+        }
+        state.dimLayer = newDimLayer;
+    }
+
+    private static String getDimLayerTag(DimLayerUser dimLayerUser) {
+        return TAG_LOCAL + "/" + dimLayerUser.toShortString();
+    }
+
+    private DimLayerState getOrCreateDimLayerState(DimLayer.DimLayerUser dimLayerUser) {
+        if (DEBUG_DIM_LAYER) Slog.v(TAG, "getOrCreateDimLayerState, dimLayerUser="
+                + dimLayerUser.toShortString());
+        DimLayerState state = mState.get(dimLayerUser);
+        if (state == null) {
+            state = new DimLayerState();
+            mState.put(dimLayerUser, state);
+        }
+        return state;
+    }
+
+    private void setContinueDimming(DimLayer.DimLayerUser dimLayerUser) {
+        DimLayerState 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--) {
+            DimLayerState 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) {
+        DimLayerState state = mState.get(dimLayerUser);
+        return state != null && state.continueDimming;
+    }
+
+    void startDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser,
+            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.
+        DimLayerState state = getOrCreateDimLayerState(dimLayerUser);
+        state.dimAbove = aboveApp;
+        if (DEBUG_DIM_LAYER) Slog.v(TAG, "startDimmingIfNeeded,"
+                + " dimLayerUser=" + dimLayerUser.toShortString()
+                + " newWinAnimator=" + newWinAnimator
+                + " state.animator=" + state.animator);
+        if (newWinAnimator.getShown() && (state.animator == null
+                || !state.animator.getShown()
+                || state.animator.mAnimLayer <= newWinAnimator.mAnimLayer)) {
+            state.animator = newWinAnimator;
+            if (state.animator.mWin.mAppToken == null && !dimLayerUser.dimFullscreen()) {
+                // Dim should cover the entire screen for system windows.
+                mDisplayContent.getLogicalDisplayRect(mTmpBounds);
+            } else {
+                dimLayerUser.getDimBounds(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.
+        DimLayerState 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.animator != null && state.animator.mWin.mWillReplaceWindow) {
+            return;
+        }
+
+        if (!state.continueDimming && state.dimLayer.isDimming()) {
+            state.animator = null;
+            dimLayerUser.getDimBounds(mTmpBounds);
+            state.dimLayer.setBounds(mTmpBounds);
+        }
+    }
+
+    boolean animateDimLayers() {
+        int fullScreen = -1;
+        int fullScreenAndDimming = -1;
+        int topFullScreenUserLayer = 0;
+        boolean result = false;
+
+        for (int i = mState.size() - 1; i >= 0; i--) {
+            final DimLayer.DimLayerUser user = mState.keyAt(i);
+            final DimLayerState state = mState.valueAt(i);
+
+            if (!user.isAttachedToDisplay()) {
+                // Leaked dim user that is no longer attached to the display. Go ahead and clean it
+                // clean-up and log what happened.
+                // TODO: This is a work around for b/34395537 as the dim user should have cleaned-up
+                // it self when it was detached from the display. Need to investigate how the dim
+                // user is leaking...
+                //Slog.wtfStack(TAG_WM, "Leaked dim user=" + user.toShortString()
+                //        + " state=" + state);
+                Slog.w(TAG_WM, "Leaked dim user=" + user.toShortString() + " state=" + state);
+                removeDimLayerUser(user);
+                continue;
+            }
+
+            // We have to check that we are actually the shared fullscreen layer
+            // for this path. If we began as non fullscreen and became fullscreen
+            // (e.g. Docked stack closing), then we may not be the shared layer
+            // and we have to make sure we always animate the layer.
+            if (user.dimFullscreen() && state.dimLayer == mSharedFullScreenDimLayer) {
+                fullScreen = i;
+                if (!state.continueDimming) {
+                    continue;
+                }
+
+                // When choosing which user to assign the shared fullscreen layer to
+                // we need to look at Z-order.
+                if (topFullScreenUserLayer == 0 ||
+                        (state.animator != null && state.animator.mAnimLayer > topFullScreenUserLayer)) {
+                    fullScreenAndDimming = i;
+                    if (state.animator != null) {
+                        topFullScreenUserLayer = state.animator.mAnimLayer;
+                    }
+                }
+            } else {
+                // We always want to animate the non fullscreen windows, they don't share their
+                // dim layers.
+                result |= animateDimLayers(user);
+            }
+        }
+        // For the shared, full screen dim layer, we prefer the animation that is causing it to
+        // appear.
+        if (fullScreenAndDimming != -1) {
+            result |= animateDimLayers(mState.keyAt(fullScreenAndDimming));
+        } else if (fullScreen != -1) {
+            // If there is no animation for the full screen dim layer to appear, we can use any of
+            // the animators that will cause it to disappear.
+            result |= animateDimLayers(mState.keyAt(fullScreen));
+        }
+        return result;
+    }
+
+    private boolean animateDimLayers(DimLayer.DimLayerUser dimLayerUser) {
+        DimLayerState 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 {
+            if (state.dimAbove) {
+                dimLayer = state.animator.mAnimLayer + LAYER_OFFSET_DIM;
+                dimAmount = DEFAULT_DIM_AMOUNT_DEAD_WINDOW;
+            } else {
+                dimLayer = dimLayerUser.getLayerForDim(state.animator, LAYER_OFFSET_DIM,
+                        state.animator.mAnimLayer - 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 = getDimLayerFadeDuration(duration);
+                }
+                state.dimLayer.show(dimLayer, dimAmount, duration);
+
+                // If we showed a dim layer, make sure to redo the layout because some things depend
+                // on whether a dim layer is showing or not.
+                if (targetAlpha == 0) {
+                    mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_LAYOUT;
+                    mDisplayContent.setLayoutNeeded();
+                }
+            }
+        } else if (state.dimLayer.getLayer() != dimLayer) {
+            state.dimLayer.setLayer(dimLayer);
+        }
+        if (state.dimLayer.isAnimating()) {
+            if (!mDisplayContent.okToAnimate()) {
+                // Jump to the end of the animation.
+                state.dimLayer.show();
+            } else {
+                return state.dimLayer.stepAnimation();
+            }
+        }
+        return false;
+    }
+
+    boolean isDimming(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator winAnimator) {
+        DimLayerState state = mState.get(dimLayerUser);
+        return state != null && state.animator == winAnimator && state.dimLayer.isDimming();
+    }
+
+    private long getDimLayerFadeDuration(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--) {
+            DimLayerState state = mState.valueAt(i);
+            state.dimLayer.destroySurface();
+        }
+        mState.clear();
+        mSharedFullScreenDimLayer = null;
+    }
+
+    void removeDimLayerUser(DimLayer.DimLayerUser dimLayerUser) {
+        DimLayerState state = mState.get(dimLayerUser);
+        if (state != null) {
+            // Destroy the surface, unless it's the shared fullscreen dim.
+            if (state.dimLayer != mSharedFullScreenDimLayer) {
+                state.dimLayer.destroySurface();
+            }
+            mState.remove(dimLayerUser);
+        }
+        if (mState.isEmpty()) {
+            mSharedFullScreenDimLayer = null;
+        }
+    }
+
+    @VisibleForTesting
+    boolean hasDimLayerUser(DimLayer.DimLayerUser dimLayerUser) {
+        return mState.containsKey(dimLayerUser);
+    }
+
+    @VisibleForTesting
+    boolean hasSharedFullScreenDimLayer() {
+        return mSharedFullScreenDimLayer != null;
+    }
+
+    void applyDimBehind(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
+        applyDim(dimLayerUser, animator, false /* aboveApp */);
+    }
+
+    void applyDimAbove(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
+        applyDim(dimLayerUser, animator, true /* aboveApp */);
+    }
+
+    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.");
+            return;
+        }
+        if (!getContinueDimming(dimLayerUser)) {
+            setContinueDimming(dimLayerUser);
+            if (!isDimming(dimLayerUser, animator)) {
+                if (DEBUG_DIM_LAYER) Slog.v(TAG, "Win " + this + " start dimming.");
+                startDimmingIfNeeded(dimLayerUser, animator, aboveApp);
+            }
+        }
+    }
+
+    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 + "DimLayerController");
+        final String doubleSpace = "  ";
+        final String prefixPlusDoubleSpace = prefix + doubleSpace;
+
+        for (int i = 0, n = mState.size(); i < n; i++) {
+            pw.println(prefixPlusDoubleSpace + mState.keyAt(i).toShortString());
+            DimLayerState state = mState.valueAt(i);
+            pw.println(prefixPlusDoubleSpace + doubleSpace + "dimLayer="
+                    + (state.dimLayer == mSharedFullScreenDimLayer ? "shared" : state.dimLayer)
+                    + ", animator=" + state.animator + ", continueDimming=" + state.continueDimming);
+            if (state.dimLayer != null) {
+                state.dimLayer.printTo(prefixPlusDoubleSpace + doubleSpace, pw);
+            }
+        }
+    }
+}
diff --git a/com/android/server/wm/DisplayContent.java b/com/android/server/wm/DisplayContent.java
new file mode 100644
index 0000000..f0b9f17
--- /dev/null
+++ b/com/android/server/wm/DisplayContent.java
@@ -0,0 +1,3632 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.FLAG_PRIVATE;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+import static android.view.View.GONE;
+import static android.view.WindowManager.DOCKED_BOTTOM;
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_TOP;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
+import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+import static android.view.WindowManager.LayoutParams.NEEDS_MENU_SET_TRUE;
+import static android.view.WindowManager.LayoutParams.NEEDS_MENU_UNSET;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS;
+import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
+import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
+import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
+import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
+import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_UNOCCLUDE;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREEN_ON;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TOKEN_MOVEMENT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_STACK_CRAWLS;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.CUSTOM_SCREEN_ROTATION;
+import static com.android.server.wm.WindowManagerService.H.SEND_NEW_CONFIGURATION;
+import static com.android.server.wm.WindowManagerService.H.UPDATE_DOCKED_STACK_DIVIDER;
+import static com.android.server.wm.WindowManagerService.H.WINDOW_HIDE_TIMEOUT;
+import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD;
+import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
+import static com.android.server.wm.WindowManagerService.SEAMLESS_ROTATION_TIMEOUT_DURATION;
+import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER;
+import static com.android.server.wm.WindowManagerService.TYPE_LAYER_OFFSET;
+import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
+import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_ACTIVE;
+import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT;
+import static com.android.server.wm.WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION;
+import static com.android.server.wm.WindowManagerService.dipToPixel;
+import static com.android.server.wm.WindowManagerService.logSurface;
+import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP;
+import static com.android.server.wm.WindowStateAnimator.DRAW_PENDING;
+import static com.android.server.wm.WindowStateAnimator.READY_TO_SHOW;
+import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_MAY_CHANGE;
+import static com.android.server.wm.proto.DisplayProto.ABOVE_APP_WINDOWS;
+import static com.android.server.wm.proto.DisplayProto.BELOW_APP_WINDOWS;
+import static com.android.server.wm.proto.DisplayProto.DISPLAY_INFO;
+import static com.android.server.wm.proto.DisplayProto.DOCKED_STACK_DIVIDER_CONTROLLER;
+import static com.android.server.wm.proto.DisplayProto.DPI;
+import static com.android.server.wm.proto.DisplayProto.ID;
+import static com.android.server.wm.proto.DisplayProto.IME_WINDOWS;
+import static com.android.server.wm.proto.DisplayProto.PINNED_STACK_CONTROLLER;
+import static com.android.server.wm.proto.DisplayProto.ROTATION;
+import static com.android.server.wm.proto.DisplayProto.SCREEN_ROTATION_ANIMATION;
+import static com.android.server.wm.proto.DisplayProto.STACKS;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager.StackId;
+import android.app.WindowConfiguration;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.GraphicBuffer;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.graphics.Region.Op;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.DisplayMetrics;
+import android.util.MutableBoolean;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.InputDevice;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.WindowManagerPolicy;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ToBooleanFunction;
+import com.android.internal.view.IInputMethodClient;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+/**
+ * Utility class for keeping track of the WindowStates and other pertinent contents of a
+ * particular Display.
+ *
+ * IMPORTANT: No method from this class should ever be used without holding
+ * WindowManagerService.mWindowMap.
+ */
+class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowContainer> {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayContent" : TAG_WM;
+
+    /** Unique identifier of this stack. */
+    private final int mDisplayId;
+
+    /** The containers below are the only child containers the display can have. */
+    // Contains all window containers that are related to apps (Activities)
+    private final TaskStackContainers mTaskStackContainers = new TaskStackContainers();
+    // Contains all non-app window containers that should be displayed above the app containers
+    // (e.g. Status bar)
+    private final NonAppWindowContainers mAboveAppWindowsContainers =
+            new NonAppWindowContainers("mAboveAppWindowsContainers");
+    // Contains all non-app window containers that should be displayed below the app containers
+    // (e.g. Wallpaper).
+    private final NonAppWindowContainers mBelowAppWindowsContainers =
+            new NonAppWindowContainers("mBelowAppWindowsContainers");
+    // Contains all IME window containers. Note that the z-ordering of the IME windows will depend
+    // on the IME target. We mainly have this container grouping so we can keep track of all the IME
+    // window containers together and move them in-sync if/when needed.
+    private final NonAppWindowContainers mImeWindowsContainers =
+            new NonAppWindowContainers("mImeWindowsContainers");
+
+    private WindowState mTmpWindow;
+    private WindowState mTmpWindow2;
+    private WindowAnimator mTmpWindowAnimator;
+    private boolean mTmpRecoveringMemory;
+    private boolean mUpdateImeTarget;
+    private boolean mTmpInitial;
+    private int mMaxUiWidth;
+
+    // Mapping from a token IBinder to a WindowToken object on this display.
+    private final HashMap<IBinder, WindowToken> mTokenMap = new HashMap();
+
+    // Initial display metrics.
+    int mInitialDisplayWidth = 0;
+    int mInitialDisplayHeight = 0;
+    int mInitialDisplayDensity = 0;
+
+    /**
+     * Overridden display size. Initialized with {@link #mInitialDisplayWidth}
+     * and {@link #mInitialDisplayHeight}, but can be set via shell command "adb shell wm size".
+     * @see WindowManagerService#setForcedDisplaySize(int, int, int)
+     */
+    int mBaseDisplayWidth = 0;
+    int mBaseDisplayHeight = 0;
+    /**
+     * Overridden display density for current user. Initialized with {@link #mInitialDisplayDensity}
+     * but can be set from Settings or via shell command "adb shell wm density".
+     * @see WindowManagerService#setForcedDisplayDensityForUser(int, int, int)
+     */
+    int mBaseDisplayDensity = 0;
+    boolean mDisplayScalingDisabled;
+    private final DisplayInfo mDisplayInfo = new DisplayInfo();
+    private final Display mDisplay;
+    private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+    /**
+     * For default display it contains real metrics, empty for others.
+     * @see WindowManagerService#createWatermarkInTransaction()
+     */
+    final DisplayMetrics mRealDisplayMetrics = new DisplayMetrics();
+    /** @see #computeCompatSmallestWidth(boolean, int, int, int, int) */
+    private final DisplayMetrics mTmpDisplayMetrics = new DisplayMetrics();
+
+    /**
+     * Compat metrics computed based on {@link #mDisplayMetrics}.
+     * @see #updateDisplayAndOrientation(int)
+     */
+    private final DisplayMetrics mCompatDisplayMetrics = new DisplayMetrics();
+
+    /** The desired scaling factor for compatible apps. */
+    float mCompatibleScreenScale;
+
+    /**
+     * Current rotation of the display.
+     * Constants as per {@link android.view.Surface.Rotation}.
+     *
+     * @see #updateRotationUnchecked(boolean)
+     */
+    private int mRotation = 0;
+
+    /**
+     * Last applied orientation of the display.
+     * Constants as per {@link android.content.pm.ActivityInfo.ScreenOrientation}.
+     *
+     * @see WindowManagerService#updateOrientationFromAppTokensLocked(boolean, int)
+     */
+    private int mLastOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+
+    /**
+     * Flag indicating that the application is receiving an orientation that has different metrics
+     * than it expected. E.g. Portrait instead of Landscape.
+     *
+     * @see #updateRotationUnchecked(boolean)
+     */
+    private boolean mAltOrientation = false;
+
+    /**
+     * Orientation forced by some window. If there is no visible window that specifies orientation
+     * it is set to {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED}.
+     *
+     * @see NonAppWindowContainers#getOrientation()
+     */
+    private int mLastWindowForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+
+    /**
+     * Last orientation forced by the keyguard. It is applied when keyguard is shown and is not
+     * occluded.
+     *
+     * @see NonAppWindowContainers#getOrientation()
+     */
+    private int mLastKeyguardForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+
+    /**
+     * Keep track of wallpaper visibility to notify changes.
+     */
+    private boolean mLastWallpaperVisible = false;
+
+    private Rect mBaseDisplayRect = new Rect();
+    private Rect mContentRect = new Rect();
+
+    // Accessed directly by all users.
+    private boolean mLayoutNeeded;
+    int pendingLayoutChanges;
+    // TODO(multi-display): remove some of the usages.
+    boolean isDefaultDisplay;
+
+    /** Window tokens that are in the process of exiting, but still on screen for animations. */
+    final ArrayList<WindowToken> mExitingTokens = new ArrayList<>();
+
+    /** A special TaskStack with id==HOME_STACK_ID that moves to the bottom whenever any TaskStack
+     * (except a future lockscreen TaskStack) moves to the top. */
+    private TaskStack mHomeStack = null;
+
+    /** Detect user tapping outside of current focused task bounds .*/
+    TaskTapPointerEventListener mTapDetector;
+
+    /** Detect user tapping outside of current focused stack bounds .*/
+    private Region mTouchExcludeRegion = new Region();
+
+    /** Save allocating when calculating rects */
+    private final Rect mTmpRect = new Rect();
+    private final Rect mTmpRect2 = new Rect();
+    private final RectF mTmpRectF = new RectF();
+    private final Matrix mTmpMatrix = new Matrix();
+    private final Region mTmpRegion = new Region();
+
+    WindowManagerService mService;
+
+    /** Remove this display when animation on it has completed. */
+    private boolean mDeferredRemoval;
+
+    final DockedStackDividerController mDividerControllerLocked;
+    final PinnedStackController mPinnedStackControllerLocked;
+
+    DimLayerController mDimLayerController;
+
+    final ArrayList<WindowState> mTapExcludedWindows = new ArrayList<>();
+
+    private boolean mHaveBootMsg = false;
+    private boolean mHaveApp = false;
+    private boolean mHaveWallpaper = false;
+    private boolean mHaveKeyguard = true;
+
+    private final LinkedList<AppWindowToken> mTmpUpdateAllDrawn = new LinkedList();
+
+    private final TaskForResizePointSearchResult mTmpTaskForResizePointSearchResult =
+            new TaskForResizePointSearchResult();
+    private final ApplySurfaceChangesTransactionState mTmpApplySurfaceChangesTransactionState =
+            new ApplySurfaceChangesTransactionState();
+    private final ScreenshotApplicationState mScreenshotApplicationState =
+            new ScreenshotApplicationState();
+
+    // True if this display is in the process of being removed. Used to determine if the removal of
+    // the display's direct children should be allowed.
+    private boolean mRemovingDisplay = false;
+
+    // {@code false} if this display is in the processing of being created.
+    private boolean mDisplayReady = false;
+
+    private final WindowLayersController mLayersController;
+    WallpaperController mWallpaperController;
+    int mInputMethodAnimLayerAdjustment;
+
+    private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> {
+        WindowStateAnimator winAnimator = w.mWinAnimator;
+        if (winAnimator.hasSurface()) {
+            final boolean wasAnimating = winAnimator.mWasAnimating;
+            final boolean nowAnimating = winAnimator.stepAnimationLocked(
+                    mTmpWindowAnimator.mCurrentTime);
+            winAnimator.mWasAnimating = nowAnimating;
+            mTmpWindowAnimator.orAnimating(nowAnimating);
+
+            if (DEBUG_WALLPAPER) Slog.v(TAG,
+                    w + ": wasAnimating=" + wasAnimating + ", nowAnimating=" + nowAnimating);
+
+            if (wasAnimating && !winAnimator.mAnimating
+                    && mWallpaperController.isWallpaperTarget(w)) {
+                mTmpWindowAnimator.mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE;
+                pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+                if (DEBUG_LAYOUT_REPEATS) {
+                    mService.mWindowPlacerLocked.debugLayoutRepeats(
+                            "updateWindowsAndWallpaperLocked 2", pendingLayoutChanges);
+                }
+            }
+        }
+
+        final AppWindowToken atoken = w.mAppToken;
+        if (winAnimator.mDrawState == READY_TO_SHOW) {
+            if (atoken == null || atoken.allDrawn) {
+                if (w.performShowLocked()) {
+                    pendingLayoutChanges |= FINISH_LAYOUT_REDO_ANIM;
+                    if (DEBUG_LAYOUT_REPEATS) {
+                        mService.mWindowPlacerLocked.debugLayoutRepeats(
+                                "updateWindowsAndWallpaperLocked 5", pendingLayoutChanges);
+                    }
+                }
+            }
+        }
+        final AppWindowAnimator appAnimator = winAnimator.mAppAnimator;
+        if (appAnimator != null && appAnimator.thumbnail != null) {
+            if (appAnimator.thumbnailTransactionSeq
+                    != mTmpWindowAnimator.mAnimTransactionSequence) {
+                appAnimator.thumbnailTransactionSeq =
+                        mTmpWindowAnimator.mAnimTransactionSequence;
+                appAnimator.thumbnailLayer = 0;
+            }
+            if (appAnimator.thumbnailLayer < winAnimator.mAnimLayer) {
+                appAnimator.thumbnailLayer = winAnimator.mAnimLayer;
+            }
+        }
+    };
+
+    private final Consumer<WindowState> mUpdateWallpaperForAnimator = w -> {
+        final WindowStateAnimator winAnimator = w.mWinAnimator;
+        if (winAnimator.mSurfaceController == null || !winAnimator.hasSurface()) {
+            return;
+        }
+
+        final int flags = w.mAttrs.flags;
+
+        // If this window is animating, make a note that we have an animating window and take
+        // care of a request to run a detached wallpaper animation.
+        if (winAnimator.mAnimating) {
+            if (winAnimator.mAnimation != null) {
+                if ((flags & FLAG_SHOW_WALLPAPER) != 0
+                        && winAnimator.mAnimation.getDetachWallpaper()) {
+                    mTmpWindow = w;
+                }
+                final int color = winAnimator.mAnimation.getBackgroundColor();
+                if (color != 0) {
+                    final TaskStack stack = w.getStack();
+                    if (stack != null) {
+                        stack.setAnimationBackground(winAnimator, color);
+                    }
+                }
+            }
+            mTmpWindowAnimator.setAnimating(true);
+        }
+
+        // If this window's app token is running a detached wallpaper animation, make a note so
+        // we can ensure the wallpaper is displayed behind it.
+        final AppWindowAnimator appAnimator = winAnimator.mAppAnimator;
+        if (appAnimator != null && appAnimator.animation != null
+                && appAnimator.animating) {
+            if ((flags & FLAG_SHOW_WALLPAPER) != 0
+                    && appAnimator.animation.getDetachWallpaper()) {
+                mTmpWindow = w;
+            }
+
+            final int color = appAnimator.animation.getBackgroundColor();
+            if (color != 0) {
+                final TaskStack stack = w.getStack();
+                if (stack != null) {
+                    stack.setAnimationBackground(winAnimator, color);
+                }
+            }
+        }
+    };
+
+    private final Consumer<WindowState> mScheduleToastTimeout = w -> {
+        final int lostFocusUid = mTmpWindow.mOwnerUid;
+        final Handler handler = mService.mH;
+        if (w.mAttrs.type == TYPE_TOAST && w.mOwnerUid == lostFocusUid) {
+            if (!handler.hasMessages(WINDOW_HIDE_TIMEOUT, w)) {
+                handler.sendMessageDelayed(handler.obtainMessage(WINDOW_HIDE_TIMEOUT, w),
+                        w.mAttrs.hideTimeoutMilliseconds);
+            }
+        }
+    };
+
+    private final ToBooleanFunction<WindowState> mFindFocusedWindow = w -> {
+        final AppWindowToken focusedApp = mService.mFocusedApp;
+        if (DEBUG_FOCUS) Slog.v(TAG_WM, "Looking for focus: " + w
+                + ", flags=" + w.mAttrs.flags + ", canReceive=" + w.canReceiveKeys());
+
+        if (!w.canReceiveKeys()) {
+            return false;
+        }
+
+        final AppWindowToken wtoken = w.mAppToken;
+
+        // If this window's application has been removed, just skip it.
+        if (wtoken != null && (wtoken.removed || wtoken.sendingToBottom)) {
+            if (DEBUG_FOCUS) Slog.v(TAG_WM, "Skipping " + wtoken + " because "
+                    + (wtoken.removed ? "removed" : "sendingToBottom"));
+            return false;
+        }
+
+        if (focusedApp == null) {
+            if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: focusedApp=null"
+                    + " using new focus @ " + w);
+            mTmpWindow = w;
+            return true;
+        }
+
+        if (!focusedApp.windowsAreFocusable()) {
+            // Current focused app windows aren't focusable...
+            if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: focusedApp windows not"
+                    + " focusable using new focus @ " + w);
+            mTmpWindow = w;
+            return true;
+        }
+
+        // Descend through all of the app tokens and find the first that either matches
+        // win.mAppToken (return win) or mFocusedApp (return null).
+        if (wtoken != null && w.mAttrs.type != TYPE_APPLICATION_STARTING) {
+            if (focusedApp.compareTo(wtoken) > 0) {
+                // App stack below focused app stack. No focus for you!!!
+                if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM,
+                        "findFocusedWindow: Reached focused app=" + focusedApp);
+                mTmpWindow = null;
+                return true;
+            }
+        }
+
+        if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: Found new focus @ " + w);
+        mTmpWindow = w;
+        return true;
+    };
+
+    private final Consumer<WindowState> mPrepareWindowSurfaces =
+            w -> w.mWinAnimator.prepareSurfaceLocked(true);
+
+    private final Consumer<WindowState> mPerformLayout = w -> {
+        // Don't do layout of a window if it is not visible, or soon won't be visible, to avoid
+        // wasting time and funky changes while a window is animating away.
+        final boolean gone = (mTmpWindow != null && mService.mPolicy.canBeHiddenByKeyguardLw(w))
+                || w.isGoneForLayoutLw();
+
+        if (DEBUG_LAYOUT && !w.mLayoutAttached) {
+            Slog.v(TAG, "1ST PASS " + w + ": gone=" + gone + " mHaveFrame=" + w.mHaveFrame
+                    + " mLayoutAttached=" + w.mLayoutAttached
+                    + " screen changed=" + w.isConfigChanged());
+            final AppWindowToken atoken = w.mAppToken;
+            if (gone) Slog.v(TAG, "  GONE: mViewVisibility=" + w.mViewVisibility
+                    + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.hidden
+                    + " hiddenRequested=" + (atoken != null && atoken.hiddenRequested)
+                    + " parentHidden=" + w.isParentWindowHidden());
+            else Slog.v(TAG, "  VIS: mViewVisibility=" + w.mViewVisibility
+                    + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.hidden
+                    + " hiddenRequested=" + (atoken != null && atoken.hiddenRequested)
+                    + " parentHidden=" + w.isParentWindowHidden());
+        }
+
+        // If this view is GONE, then skip it -- keep the current frame, and let the caller know
+        // so they can ignore it if they want.  (We do the normal layout for INVISIBLE windows,
+        // since that means "perform layout as normal, just don't display").
+        if (!gone || !w.mHaveFrame || w.mLayoutNeeded
+                || ((w.isConfigChanged() || w.setReportResizeHints())
+                && !w.isGoneForLayoutLw() &&
+                ((w.mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0 ||
+                        (w.mHasSurface && w.mAppToken != null &&
+                                w.mAppToken.layoutConfigChanges)))) {
+            if (!w.mLayoutAttached) {
+                if (mTmpInitial) {
+                    //Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
+                    w.mContentChanged = false;
+                }
+                if (w.mAttrs.type == TYPE_DREAM) {
+                    // Don't layout windows behind a dream, so that if it does stuff like hide
+                    // the status bar we won't get a bad transition when it goes away.
+                    mTmpWindow = w;
+                }
+                w.mLayoutNeeded = false;
+                w.prelayout();
+                final boolean firstLayout = !w.isLaidOut();
+                mService.mPolicy.layoutWindowLw(w, null);
+                w.mLayoutSeq = mService.mLayoutSeq;
+
+                // If this is the first layout, we need to initialize the last inset values as
+                // otherwise we'd immediately cause an unnecessary resize.
+                if (firstLayout) {
+                    w.updateLastInsetValues();
+                }
+
+                // Window frames may have changed. Update dim layer with the new bounds.
+                final Task task = w.getTask();
+                if (task != null) {
+                    mDimLayerController.updateDimLayer(task);
+                }
+
+                if (DEBUG_LAYOUT) Slog.v(TAG, "  LAYOUT: mFrame=" + w.mFrame
+                        + " mContainingFrame=" + w.mContainingFrame
+                        + " mDisplayFrame=" + w.mDisplayFrame);
+            }
+        }
+    };
+
+    private final Consumer<WindowState> mPerformLayoutAttached = w -> {
+        if (w.mLayoutAttached) {
+            if (DEBUG_LAYOUT) Slog.v(TAG, "2ND PASS " + w + " mHaveFrame=" + w.mHaveFrame
+                    + " mViewVisibility=" + w.mViewVisibility
+                    + " mRelayoutCalled=" + w.mRelayoutCalled);
+            // If this view is GONE, then skip it -- keep the current frame, and let the caller
+            // know so they can ignore it if they want.  (We do the normal layout for INVISIBLE
+            // windows, since that means "perform layout as normal, just don't display").
+            if (mTmpWindow != null && mService.mPolicy.canBeHiddenByKeyguardLw(w)) {
+                return;
+            }
+            if ((w.mViewVisibility != GONE && w.mRelayoutCalled) || !w.mHaveFrame
+                    || w.mLayoutNeeded) {
+                if (mTmpInitial) {
+                    //Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
+                    w.mContentChanged = false;
+                }
+                w.mLayoutNeeded = false;
+                w.prelayout();
+                mService.mPolicy.layoutWindowLw(w, w.getParentWindow());
+                w.mLayoutSeq = mService.mLayoutSeq;
+                if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.mFrame
+                        + " mContainingFrame=" + w.mContainingFrame
+                        + " mDisplayFrame=" + w.mDisplayFrame);
+            }
+        } else if (w.mAttrs.type == TYPE_DREAM) {
+            // Don't layout windows behind a dream, so that if it does stuff like hide the
+            // status bar we won't get a bad transition when it goes away.
+            mTmpWindow = mTmpWindow2;
+        }
+    };
+
+    private final Predicate<WindowState> mComputeImeTargetPredicate = w -> {
+        if (DEBUG_INPUT_METHOD && mUpdateImeTarget) Slog.i(TAG_WM, "Checking window @" + w
+                + " fl=0x" + Integer.toHexString(w.mAttrs.flags));
+        return w.canBeImeTarget();
+    };
+
+    private final Consumer<WindowState> mApplyPostLayoutPolicy =
+            w -> mService.mPolicy.applyPostLayoutPolicyLw(w, w.mAttrs, w.getParentWindow(),
+                    mService.mInputMethodTarget);
+
+    private final Consumer<WindowState> mApplySurfaceChangesTransaction = w -> {
+        final WindowSurfacePlacer surfacePlacer = mService.mWindowPlacerLocked;
+        final boolean obscuredChanged = w.mObscured !=
+                mTmpApplySurfaceChangesTransactionState.obscured;
+        final RootWindowContainer root = mService.mRoot;
+        // Only used if default window
+        final boolean someoneLosingFocus = !mService.mLosingFocus.isEmpty();
+
+        // Update effect.
+        w.mObscured = mTmpApplySurfaceChangesTransactionState.obscured;
+        if (!mTmpApplySurfaceChangesTransactionState.obscured) {
+            final boolean isDisplayed = w.isDisplayedLw();
+
+            if (isDisplayed && w.isObscuringDisplay()) {
+                // This window completely covers everything behind it, so we want to leave all
+                // of them as undimmed (for performance reasons).
+                root.mObscuringWindow = w;
+                mTmpApplySurfaceChangesTransactionState.obscured = true;
+            }
+
+            mTmpApplySurfaceChangesTransactionState.displayHasContent |=
+                    root.handleNotObscuredLocked(w,
+                            mTmpApplySurfaceChangesTransactionState.obscured,
+                            mTmpApplySurfaceChangesTransactionState.syswin);
+
+            if (w.mHasSurface && isDisplayed) {
+                final int type = w.mAttrs.type;
+                if (type == TYPE_SYSTEM_DIALOG || type == TYPE_SYSTEM_ERROR
+                        || (w.mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
+                    mTmpApplySurfaceChangesTransactionState.syswin = true;
+                }
+                if (mTmpApplySurfaceChangesTransactionState.preferredRefreshRate == 0
+                        && w.mAttrs.preferredRefreshRate != 0) {
+                    mTmpApplySurfaceChangesTransactionState.preferredRefreshRate
+                            = w.mAttrs.preferredRefreshRate;
+                }
+                if (mTmpApplySurfaceChangesTransactionState.preferredModeId == 0
+                        && w.mAttrs.preferredDisplayModeId != 0) {
+                    mTmpApplySurfaceChangesTransactionState.preferredModeId
+                            = w.mAttrs.preferredDisplayModeId;
+                }
+            }
+        }
+
+        w.applyDimLayerIfNeeded();
+
+        if (isDefaultDisplay && obscuredChanged && w.isVisibleLw()
+                && mWallpaperController.isWallpaperTarget(w)) {
+            // This is the wallpaper target and its obscured state changed... make sure the
+            // current wallpaper's visibility has been updated accordingly.
+            mWallpaperController.updateWallpaperVisibility();
+        }
+
+        w.handleWindowMovedIfNeeded();
+
+        final WindowStateAnimator winAnimator = w.mWinAnimator;
+
+        //Slog.i(TAG, "Window " + this + " clearing mContentChanged - done placing");
+        w.mContentChanged = false;
+
+        // Moved from updateWindowsAndWallpaperLocked().
+        if (w.mHasSurface) {
+            // Take care of the window being ready to display.
+            final boolean committed = winAnimator.commitFinishDrawingLocked();
+            if (isDefaultDisplay && committed) {
+                if (w.mAttrs.type == TYPE_DREAM) {
+                    // HACK: When a dream is shown, it may at that point hide the lock screen.
+                    // So we need to redo the layout to let the phone window manager make this
+                    // happen.
+                    pendingLayoutChanges |= FINISH_LAYOUT_REDO_LAYOUT;
+                    if (DEBUG_LAYOUT_REPEATS) {
+                        surfacePlacer.debugLayoutRepeats(
+                                "dream and commitFinishDrawingLocked true",
+                                pendingLayoutChanges);
+                    }
+                }
+                if ((w.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
+                    if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
+                            "First draw done in potential wallpaper target " + w);
+                    root.mWallpaperMayChange = true;
+                    pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+                    if (DEBUG_LAYOUT_REPEATS) {
+                        surfacePlacer.debugLayoutRepeats(
+                                "wallpaper and commitFinishDrawingLocked true",
+                                pendingLayoutChanges);
+                    }
+                }
+            }
+            final TaskStack stack = w.getStack();
+            if ((!winAnimator.isAnimationStarting() && !winAnimator.isWaitingForOpening())
+                    || (stack != null && stack.isAnimatingBounds())) {
+                // Updates the shown frame before we set up the surface. This is needed
+                // because the resizing could change the top-left position (in addition to
+                // size) of the window. setSurfaceBoundariesLocked uses mShownPosition to
+                // position the surface.
+                //
+                // If an animation is being started, we can't call this method because the
+                // animation hasn't processed its initial transformation yet, but in general
+                // we do want to update the position if the window is animating. We make an exception
+                // for the bounds animating state, where an application may have been waiting
+                // for an exit animation to start, but instead enters PiP. We need to ensure
+                // we always recompute the top-left in this case.
+                winAnimator.computeShownFrameLocked();
+            }
+            winAnimator.setSurfaceBoundariesLocked(mTmpRecoveringMemory /* recoveringMemory */);
+        }
+
+        final AppWindowToken atoken = w.mAppToken;
+        if (atoken != null) {
+            final boolean updateAllDrawn = atoken.updateDrawnWindowStates(w);
+            if (updateAllDrawn && !mTmpUpdateAllDrawn.contains(atoken)) {
+                mTmpUpdateAllDrawn.add(atoken);
+            }
+        }
+
+        if (isDefaultDisplay && someoneLosingFocus && w == mService.mCurrentFocus
+                && w.isDisplayedLw()) {
+            mTmpApplySurfaceChangesTransactionState.focusDisplayed = true;
+        }
+
+        w.updateResizingWindowIfNeeded();
+    };
+
+    /**
+     * Create new {@link DisplayContent} instance, add itself to the root window container and
+     * initialize direct children.
+     * @param display May not be null.
+     * @param service You know.
+     * @param layersController window layer controller used to assign layer to the windows on this
+     *                         display.
+     * @param wallpaperController wallpaper windows controller used to adjust the positioning of the
+     *                            wallpaper windows in the window list.
+     */
+    DisplayContent(Display display, WindowManagerService service,
+            WindowLayersController layersController, WallpaperController wallpaperController) {
+        if (service.mRoot.getDisplayContent(display.getDisplayId()) != null) {
+            throw new IllegalArgumentException("Display with ID=" + display.getDisplayId()
+                    + " already exists=" + service.mRoot.getDisplayContent(display.getDisplayId())
+                    + " new=" + display);
+        }
+
+        mDisplay = display;
+        mDisplayId = display.getDisplayId();
+        mLayersController = layersController;
+        mWallpaperController = wallpaperController;
+        display.getDisplayInfo(mDisplayInfo);
+        display.getMetrics(mDisplayMetrics);
+        isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
+        mService = service;
+        initializeDisplayBaseInfo();
+        mDividerControllerLocked = new DockedStackDividerController(service, this);
+        mPinnedStackControllerLocked = new PinnedStackController(service, this);
+        mDimLayerController = new DimLayerController(this);
+
+        // These are the only direct children we should ever have and they are permanent.
+        super.addChild(mBelowAppWindowsContainers, null);
+        super.addChild(mTaskStackContainers, null);
+        super.addChild(mAboveAppWindowsContainers, null);
+        super.addChild(mImeWindowsContainers, null);
+
+        // Add itself as a child to the root container.
+        mService.mRoot.addChild(this, null);
+
+        // TODO(b/62541591): evaluate whether this is the best spot to declare the
+        // {@link DisplayContent} ready for use.
+        mDisplayReady = true;
+    }
+
+    boolean isReady() {
+        // The display is ready when the system and the individual display are both ready.
+        return mService.mDisplayReady && mDisplayReady;
+    }
+
+    int getDisplayId() {
+        return mDisplayId;
+    }
+
+    WindowToken getWindowToken(IBinder binder) {
+        return mTokenMap.get(binder);
+    }
+
+    AppWindowToken getAppWindowToken(IBinder binder) {
+        final WindowToken token = getWindowToken(binder);
+        if (token == null) {
+            return null;
+        }
+        return token.asAppWindowToken();
+    }
+
+    private void addWindowToken(IBinder binder, WindowToken token) {
+        final DisplayContent dc = mService.mRoot.getWindowTokenDisplay(token);
+        if (dc != null) {
+            // We currently don't support adding a window token to the display if the display
+            // already has the binder mapped to another token. If there is a use case for supporting
+            // this moving forward we will either need to merge the WindowTokens some how or have
+            // the binder map to a list of window tokens.
+            throw new IllegalArgumentException("Can't map token=" + token + " to display="
+                    + getName() + " already mapped to display=" + dc + " tokens=" + dc.mTokenMap);
+        }
+        if (binder == null) {
+            throw new IllegalArgumentException("Can't map token=" + token + " to display="
+                    + getName() + " binder is null");
+        }
+        if (token == null) {
+            throw new IllegalArgumentException("Can't map null token to display="
+                    + getName() + " binder=" + binder);
+        }
+
+        mTokenMap.put(binder, token);
+
+        if (token.asAppWindowToken() == null) {
+            // Add non-app token to container hierarchy on the display. App tokens are added through
+            // the parent container managing them (e.g. Tasks).
+            switch (token.windowType) {
+                case TYPE_WALLPAPER:
+                    mBelowAppWindowsContainers.addChild(token);
+                    break;
+                case TYPE_INPUT_METHOD:
+                case TYPE_INPUT_METHOD_DIALOG:
+                    mImeWindowsContainers.addChild(token);
+                    break;
+                default:
+                    mAboveAppWindowsContainers.addChild(token);
+                    break;
+            }
+        }
+    }
+
+    WindowToken removeWindowToken(IBinder binder) {
+        final WindowToken token = mTokenMap.remove(binder);
+        if (token != null && token.asAppWindowToken() == null) {
+            token.setExiting();
+        }
+        return token;
+    }
+
+    /** Changes the display the input window token is housed on to this one. */
+    void reParentWindowToken(WindowToken token) {
+        final DisplayContent prevDc = token.getDisplayContent();
+        if (prevDc == this) {
+            return;
+        }
+        if (prevDc != null && prevDc.mTokenMap.remove(token.token) != null
+                && token.asAppWindowToken() == null) {
+            // Removed the token from the map, but made sure it's not an app token before removing
+            // from parent.
+            token.getParent().removeChild(token);
+        }
+
+        addWindowToken(token.token, token);
+    }
+
+    void removeAppToken(IBinder binder) {
+        final WindowToken token = removeWindowToken(binder);
+        if (token == null) {
+            Slog.w(TAG_WM, "removeAppToken: Attempted to remove non-existing token: " + binder);
+            return;
+        }
+
+        final AppWindowToken appToken = token.asAppWindowToken();
+
+        if (appToken == null) {
+            Slog.w(TAG_WM, "Attempted to remove non-App token: " + binder + " token=" + token);
+            return;
+        }
+
+        appToken.onRemovedFromDisplay();
+    }
+
+    Display getDisplay() {
+        return mDisplay;
+    }
+
+    DisplayInfo getDisplayInfo() {
+        return mDisplayInfo;
+    }
+
+    DisplayMetrics getDisplayMetrics() {
+        return mDisplayMetrics;
+    }
+
+    int getRotation() {
+        return mRotation;
+    }
+
+    void setRotation(int newRotation) {
+        mRotation = newRotation;
+    }
+
+    int getLastOrientation() {
+        return mLastOrientation;
+    }
+
+    void setLastOrientation(int orientation) {
+        mLastOrientation = orientation;
+    }
+
+    boolean getAltOrientation() {
+        return mAltOrientation;
+    }
+
+    void setAltOrientation(boolean altOrientation) {
+        mAltOrientation = altOrientation;
+    }
+
+    int getLastWindowForcedOrientation() {
+        return mLastWindowForcedOrientation;
+    }
+
+    /**
+     * Update rotation of the display.
+     *
+     * Returns true if the rotation has been changed.  In this case YOU MUST CALL
+     * {@link WindowManagerService#sendNewConfiguration(int)} TO UNFREEZE THE SCREEN.
+     */
+    boolean updateRotationUnchecked(boolean inTransaction) {
+        if (mService.mDeferredRotationPauseCount > 0) {
+            // Rotation updates have been paused temporarily.  Defer the update until
+            // updates have been resumed.
+            if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, rotation is paused.");
+            return false;
+        }
+
+        ScreenRotationAnimation screenRotationAnimation =
+                mService.mAnimator.getScreenRotationAnimationLocked(mDisplayId);
+        if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
+            // Rotation updates cannot be performed while the previous rotation change
+            // animation is still in progress.  Skip this update.  We will try updating
+            // again after the animation is finished and the display is unfrozen.
+            if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, animation in progress.");
+            return false;
+        }
+        if (mService.mDisplayFrozen) {
+            // Even if the screen rotation animation has finished (e.g. isAnimating
+            // returns false), there is still some time where we haven't yet unfrozen
+            // the display. We also need to abort rotation here.
+            if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
+                    "Deferring rotation, still finishing previous rotation");
+            return false;
+        }
+
+        if (!mService.mDisplayEnabled) {
+            // No point choosing a rotation if the display is not enabled.
+            if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, display is not enabled.");
+            return false;
+        }
+
+        final int oldRotation = mRotation;
+        final int lastOrientation = mLastOrientation;
+        final boolean oldAltOrientation = mAltOrientation;
+        int rotation = mService.mPolicy.rotationForOrientationLw(lastOrientation, oldRotation);
+        final boolean rotateSeamlessly = mService.mPolicy.shouldRotateSeamlessly(oldRotation,
+                rotation);
+
+        if (rotateSeamlessly) {
+            final WindowState seamlessRotated = getWindow((w) -> w.mSeamlesslyRotated);
+            if (seamlessRotated != null) {
+                // We can't rotate (seamlessly or not) while waiting for the last seamless rotation
+                // to complete (that is, waiting for windows to redraw). It's tempting to check
+                // w.mSeamlessRotationCount but that could be incorrect in the case of
+                // window-removal.
+                return false;
+            }
+        }
+
+        // TODO: Implement forced rotation changes.
+        //       Set mAltOrientation to indicate that the application is receiving
+        //       an orientation that has different metrics than it expected.
+        //       eg. Portrait instead of Landscape.
+
+        final boolean altOrientation = !mService.mPolicy.rotationHasCompatibleMetricsLw(
+                lastOrientation, rotation);
+
+        if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Selected orientation " + lastOrientation
+                + ", got rotation " + rotation + " which has "
+                + (altOrientation ? "incompatible" : "compatible") + " metrics");
+
+        if (oldRotation == rotation && oldAltOrientation == altOrientation) {
+            // No change.
+            return false;
+        }
+
+        if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Rotation changed to " + rotation
+                + (altOrientation ? " (alt)" : "") + " from " + oldRotation
+                + (oldAltOrientation ? " (alt)" : "") + ", lastOrientation=" + lastOrientation);
+
+        if (DisplayContent.deltaRotation(rotation, oldRotation) != 2) {
+            mService.mWaitingForConfig = true;
+        }
+
+        mRotation = rotation;
+        mAltOrientation = altOrientation;
+        if (isDefaultDisplay) {
+            mService.mPolicy.setRotationLw(rotation);
+        }
+
+        mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
+        mService.mH.removeMessages(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT);
+        mService.mH.sendEmptyMessageDelayed(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT,
+                WINDOW_FREEZE_TIMEOUT_DURATION);
+
+        setLayoutNeeded();
+        final int[] anim = new int[2];
+        if (isDimming()) {
+            anim[0] = anim[1] = 0;
+        } else {
+            mService.mPolicy.selectRotationAnimationLw(anim);
+        }
+
+        if (!rotateSeamlessly) {
+            mService.startFreezingDisplayLocked(inTransaction, anim[0], anim[1], this);
+            // startFreezingDisplayLocked can reset the ScreenRotationAnimation.
+            screenRotationAnimation = mService.mAnimator.getScreenRotationAnimationLocked(
+                    mDisplayId);
+        } else {
+            // The screen rotation animation uses a screenshot to freeze the screen
+            // while windows resize underneath.
+            // When we are rotating seamlessly, we allow the elements to transition
+            // to their rotated state independently and without a freeze required.
+            screenRotationAnimation = null;
+
+            // We have to reset this in case a window was removed before it
+            // finished seamless rotation.
+            mService.mSeamlessRotationCount = 0;
+        }
+
+        // We need to update our screen size information to match the new rotation. If the rotation
+        // has actually changed then this method will return true and, according to the comment at
+        // the top of the method, the caller is obligated to call computeNewConfigurationLocked().
+        // By updating the Display info here it will be available to
+        // #computeScreenConfiguration() later.
+        updateDisplayAndOrientation(getConfiguration().uiMode);
+
+        if (!inTransaction) {
+            if (SHOW_TRANSACTIONS) {
+                Slog.i(TAG_WM, ">>> OPEN TRANSACTION setRotationUnchecked");
+            }
+            mService.openSurfaceTransaction();
+        }
+        try {
+            // NOTE: We disable the rotation in the emulator because
+            //       it doesn't support hardware OpenGL emulation yet.
+            if (CUSTOM_SCREEN_ROTATION && screenRotationAnimation != null
+                    && screenRotationAnimation.hasScreenshot()) {
+                if (screenRotationAnimation.setRotationInTransaction(
+                        rotation, mService.mFxSession,
+                        MAX_ANIMATION_DURATION, mService.getTransitionAnimationScaleLocked(),
+                        mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight)) {
+                    mService.scheduleAnimationLocked();
+                }
+            }
+
+            if (rotateSeamlessly) {
+                forAllWindows(w -> {
+                    w.mWinAnimator.seamlesslyRotateWindow(oldRotation, rotation);
+                }, true /* traverseTopToBottom */);
+            }
+
+            mService.mDisplayManagerInternal.performTraversalInTransactionFromWindowManager();
+        } finally {
+            if (!inTransaction) {
+                mService.closeSurfaceTransaction();
+                if (SHOW_LIGHT_TRANSACTIONS) {
+                    Slog.i(TAG_WM, "<<< CLOSE TRANSACTION setRotationUnchecked");
+                }
+            }
+        }
+
+        forAllWindows(w -> {
+            if (w.mHasSurface && !rotateSeamlessly) {
+                if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Set mOrientationChanging of " + w);
+                w.setOrientationChanging(true);
+                mService.mRoot.mOrientationChangeComplete = false;
+                w.mLastFreezeDuration = 0;
+            }
+            w.mReportOrientationChanged = true;
+        }, true /* traverseTopToBottom */);
+
+        if (rotateSeamlessly) {
+            mService.mH.removeMessages(WindowManagerService.H.SEAMLESS_ROTATION_TIMEOUT);
+            mService.mH.sendEmptyMessageDelayed(WindowManagerService.H.SEAMLESS_ROTATION_TIMEOUT,
+                    SEAMLESS_ROTATION_TIMEOUT_DURATION);
+        }
+
+        for (int i = mService.mRotationWatchers.size() - 1; i >= 0; i--) {
+            final WindowManagerService.RotationWatcher rotationWatcher
+                    = mService.mRotationWatchers.get(i);
+            if (rotationWatcher.mDisplayId == mDisplayId) {
+                try {
+                    rotationWatcher.mWatcher.onRotationChanged(rotation);
+                } catch (RemoteException e) {
+                    // Ignore
+                }
+            }
+        }
+
+        // TODO (multi-display): Magnification is supported only for the default display.
+        // Announce rotation only if we will not animate as we already have the
+        // windows in final state. Otherwise, we make this call at the rotation end.
+        if (screenRotationAnimation == null && mService.mAccessibilityController != null
+                && isDefaultDisplay) {
+            mService.mAccessibilityController.onRotationChangedLocked(this);
+        }
+
+        return true;
+    }
+
+    /**
+     * Update {@link #mDisplayInfo} and other internal variables when display is rotated or config
+     * changed.
+     * Do not call if {@link WindowManagerService#mDisplayReady} == false.
+     */
+    private DisplayInfo updateDisplayAndOrientation(int uiMode) {
+        // Use the effective "visual" dimensions based on current rotation
+        final boolean rotated = (mRotation == ROTATION_90 || mRotation == ROTATION_270);
+        final int realdw = rotated ? mBaseDisplayHeight : mBaseDisplayWidth;
+        final int realdh = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;
+        int dw = realdw;
+        int dh = realdh;
+
+        if (mAltOrientation) {
+            if (realdw > realdh) {
+                // Turn landscape into portrait.
+                int maxw = (int)(realdh/1.3f);
+                if (maxw < realdw) {
+                    dw = maxw;
+                }
+            } else {
+                // Turn portrait into landscape.
+                int maxh = (int)(realdw/1.3f);
+                if (maxh < realdh) {
+                    dh = maxh;
+                }
+            }
+        }
+
+        // Update application display metrics.
+        final int appWidth = mService.mPolicy.getNonDecorDisplayWidth(dw, dh, mRotation, uiMode,
+                mDisplayId);
+        final int appHeight = mService.mPolicy.getNonDecorDisplayHeight(dw, dh, mRotation, uiMode,
+                mDisplayId);
+        mDisplayInfo.rotation = mRotation;
+        mDisplayInfo.logicalWidth = dw;
+        mDisplayInfo.logicalHeight = dh;
+        mDisplayInfo.logicalDensityDpi = mBaseDisplayDensity;
+        mDisplayInfo.appWidth = appWidth;
+        mDisplayInfo.appHeight = appHeight;
+        if (isDefaultDisplay) {
+            mDisplayInfo.getLogicalMetrics(mRealDisplayMetrics,
+                    CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+        }
+        mDisplayInfo.getAppMetrics(mDisplayMetrics);
+        if (mDisplayScalingDisabled) {
+            mDisplayInfo.flags |= Display.FLAG_SCALING_DISABLED;
+        } else {
+            mDisplayInfo.flags &= ~Display.FLAG_SCALING_DISABLED;
+        }
+
+        mService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(mDisplayId,
+                mDisplayInfo);
+
+        mBaseDisplayRect.set(0, 0, dw, dh);
+
+        if (isDefaultDisplay) {
+            mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(mDisplayMetrics,
+                    mCompatDisplayMetrics);
+        }
+        return mDisplayInfo;
+    }
+
+    /**
+     * Compute display configuration based on display properties and policy settings.
+     * Do not call if mDisplayReady == false.
+     */
+    void computeScreenConfiguration(Configuration config) {
+        final DisplayInfo displayInfo = updateDisplayAndOrientation(config.uiMode);
+
+        final int dw = displayInfo.logicalWidth;
+        final int dh = displayInfo.logicalHeight;
+        config.orientation = (dw <= dh) ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
+        // TODO: Probably best to set this based on some setting in the display content object,
+        // so the display can be configured for things like fullscreen.
+        config.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+        config.screenWidthDp =
+                (int)(mService.mPolicy.getConfigDisplayWidth(dw, dh, displayInfo.rotation,
+                        config.uiMode, mDisplayId) / mDisplayMetrics.density);
+        config.screenHeightDp =
+                (int)(mService.mPolicy.getConfigDisplayHeight(dw, dh, displayInfo.rotation,
+                        config.uiMode, mDisplayId) / mDisplayMetrics.density);
+
+        mService.mPolicy.getNonDecorInsetsLw(displayInfo.rotation, dw, dh, mTmpRect);
+        final int leftInset = mTmpRect.left;
+        final int topInset = mTmpRect.top;
+        // appBounds at the root level should mirror the app screen size.
+        config.windowConfiguration.setAppBounds(leftInset /* left */, topInset /* top */,
+                leftInset + displayInfo.appWidth /* right */,
+                topInset + displayInfo.appHeight /* bottom */);
+        final boolean rotated = (displayInfo.rotation == Surface.ROTATION_90
+                || displayInfo.rotation == Surface.ROTATION_270);
+
+        computeSizeRangesAndScreenLayout(displayInfo, mDisplayId, rotated, config.uiMode, dw, dh,
+                mDisplayMetrics.density, config);
+
+        config.screenLayout = (config.screenLayout & ~Configuration.SCREENLAYOUT_ROUND_MASK)
+                | ((displayInfo.flags & Display.FLAG_ROUND) != 0
+                ? Configuration.SCREENLAYOUT_ROUND_YES
+                : Configuration.SCREENLAYOUT_ROUND_NO);
+
+        config.compatScreenWidthDp = (int)(config.screenWidthDp / mCompatibleScreenScale);
+        config.compatScreenHeightDp = (int)(config.screenHeightDp / mCompatibleScreenScale);
+        config.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, config.uiMode, dw,
+                dh, mDisplayId);
+        config.densityDpi = displayInfo.logicalDensityDpi;
+
+        config.colorMode =
+                (displayInfo.isHdr()
+                        ? Configuration.COLOR_MODE_HDR_YES
+                        : Configuration.COLOR_MODE_HDR_NO)
+                        | (displayInfo.isWideColorGamut() && mService.hasWideColorGamutSupport()
+                        ? Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_YES
+                        : Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_NO);
+
+        // Update the configuration based on available input devices, lid switch,
+        // and platform configuration.
+        config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
+        config.keyboard = Configuration.KEYBOARD_NOKEYS;
+        config.navigation = Configuration.NAVIGATION_NONAV;
+
+        int keyboardPresence = 0;
+        int navigationPresence = 0;
+        final InputDevice[] devices = mService.mInputManager.getInputDevices();
+        final int len = devices != null ? devices.length : 0;
+        for (int i = 0; i < len; i++) {
+            InputDevice device = devices[i];
+            if (!device.isVirtual()) {
+                final int sources = device.getSources();
+                final int presenceFlag = device.isExternal() ?
+                        WindowManagerPolicy.PRESENCE_EXTERNAL :
+                        WindowManagerPolicy.PRESENCE_INTERNAL;
+
+                // TODO(multi-display): Configure on per-display basis.
+                if (mService.mIsTouchDevice) {
+                    if ((sources & InputDevice.SOURCE_TOUCHSCREEN) ==
+                            InputDevice.SOURCE_TOUCHSCREEN) {
+                        config.touchscreen = Configuration.TOUCHSCREEN_FINGER;
+                    }
+                } else {
+                    config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
+                }
+
+                if ((sources & InputDevice.SOURCE_TRACKBALL) == InputDevice.SOURCE_TRACKBALL) {
+                    config.navigation = Configuration.NAVIGATION_TRACKBALL;
+                    navigationPresence |= presenceFlag;
+                } else if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD
+                        && config.navigation == Configuration.NAVIGATION_NONAV) {
+                    config.navigation = Configuration.NAVIGATION_DPAD;
+                    navigationPresence |= presenceFlag;
+                }
+
+                if (device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
+                    config.keyboard = Configuration.KEYBOARD_QWERTY;
+                    keyboardPresence |= presenceFlag;
+                }
+            }
+        }
+
+        if (config.navigation == Configuration.NAVIGATION_NONAV && mService.mHasPermanentDpad) {
+            config.navigation = Configuration.NAVIGATION_DPAD;
+            navigationPresence |= WindowManagerPolicy.PRESENCE_INTERNAL;
+        }
+
+        // Determine whether a hard keyboard is available and enabled.
+        // TODO(multi-display): Should the hardware keyboard be tied to a display or to a device?
+        boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS;
+        if (hardKeyboardAvailable != mService.mHardKeyboardAvailable) {
+            mService.mHardKeyboardAvailable = hardKeyboardAvailable;
+            mService.mH.removeMessages(WindowManagerService.H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
+            mService.mH.sendEmptyMessage(WindowManagerService.H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
+        }
+
+        // Let the policy update hidden states.
+        config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO;
+        config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO;
+        config.navigationHidden = Configuration.NAVIGATIONHIDDEN_NO;
+        mService.mPolicy.adjustConfigurationLw(config, keyboardPresence, navigationPresence);
+    }
+
+    private int computeCompatSmallestWidth(boolean rotated, int uiMode, int dw, int dh,
+            int displayId) {
+        mTmpDisplayMetrics.setTo(mDisplayMetrics);
+        final DisplayMetrics tmpDm = mTmpDisplayMetrics;
+        final int unrotDw, unrotDh;
+        if (rotated) {
+            unrotDw = dh;
+            unrotDh = dw;
+        } else {
+            unrotDw = dw;
+            unrotDh = dh;
+        }
+        int sw = reduceCompatConfigWidthSize(0, Surface.ROTATION_0, uiMode, tmpDm, unrotDw, unrotDh,
+                displayId);
+        sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_90, uiMode, tmpDm, unrotDh, unrotDw,
+                displayId);
+        sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_180, uiMode, tmpDm, unrotDw, unrotDh,
+                displayId);
+        sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_270, uiMode, tmpDm, unrotDh, unrotDw,
+                displayId);
+        return sw;
+    }
+
+    private int reduceCompatConfigWidthSize(int curSize, int rotation, int uiMode,
+            DisplayMetrics dm, int dw, int dh, int displayId) {
+        dm.noncompatWidthPixels = mService.mPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode,
+                displayId);
+        dm.noncompatHeightPixels = mService.mPolicy.getNonDecorDisplayHeight(dw, dh, rotation,
+                uiMode, displayId);
+        float scale = CompatibilityInfo.computeCompatibleScaling(dm, null);
+        int size = (int)(((dm.noncompatWidthPixels / scale) / dm.density) + .5f);
+        if (curSize == 0 || size < curSize) {
+            curSize = size;
+        }
+        return curSize;
+    }
+
+    private void computeSizeRangesAndScreenLayout(DisplayInfo displayInfo, int displayId,
+            boolean rotated, int uiMode, int dw, int dh, float density, Configuration outConfig) {
+
+        // We need to determine the smallest width that will occur under normal
+        // operation.  To this, start with the base screen size and compute the
+        // width under the different possible rotations.  We need to un-rotate
+        // the current screen dimensions before doing this.
+        int unrotDw, unrotDh;
+        if (rotated) {
+            unrotDw = dh;
+            unrotDh = dw;
+        } else {
+            unrotDw = dw;
+            unrotDh = dh;
+        }
+        displayInfo.smallestNominalAppWidth = 1<<30;
+        displayInfo.smallestNominalAppHeight = 1<<30;
+        displayInfo.largestNominalAppWidth = 0;
+        displayInfo.largestNominalAppHeight = 0;
+        adjustDisplaySizeRanges(displayInfo, displayId, Surface.ROTATION_0, uiMode, unrotDw,
+                unrotDh);
+        adjustDisplaySizeRanges(displayInfo, displayId, Surface.ROTATION_90, uiMode, unrotDh,
+                unrotDw);
+        adjustDisplaySizeRanges(displayInfo, displayId, Surface.ROTATION_180, uiMode, unrotDw,
+                unrotDh);
+        adjustDisplaySizeRanges(displayInfo, displayId, Surface.ROTATION_270, uiMode, unrotDh,
+                unrotDw);
+        int sl = Configuration.resetScreenLayout(outConfig.screenLayout);
+        sl = reduceConfigLayout(sl, Surface.ROTATION_0, density, unrotDw, unrotDh, uiMode,
+                displayId);
+        sl = reduceConfigLayout(sl, Surface.ROTATION_90, density, unrotDh, unrotDw, uiMode,
+                displayId);
+        sl = reduceConfigLayout(sl, Surface.ROTATION_180, density, unrotDw, unrotDh, uiMode,
+                displayId);
+        sl = reduceConfigLayout(sl, Surface.ROTATION_270, density, unrotDh, unrotDw, uiMode,
+                displayId);
+        outConfig.smallestScreenWidthDp = (int)(displayInfo.smallestNominalAppWidth / density);
+        outConfig.screenLayout = sl;
+    }
+
+    private int reduceConfigLayout(int curLayout, int rotation, float density, int dw, int dh,
+            int uiMode, int displayId) {
+        // Get the app screen size at this rotation.
+        int w = mService.mPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, displayId);
+        int h = mService.mPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode, displayId);
+
+        // Compute the screen layout size class for this rotation.
+        int longSize = w;
+        int shortSize = h;
+        if (longSize < shortSize) {
+            int tmp = longSize;
+            longSize = shortSize;
+            shortSize = tmp;
+        }
+        longSize = (int)(longSize/density);
+        shortSize = (int)(shortSize/density);
+        return Configuration.reduceScreenLayout(curLayout, longSize, shortSize);
+    }
+
+    private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int displayId, int rotation,
+            int uiMode, int dw, int dh) {
+        final int width = mService.mPolicy.getConfigDisplayWidth(dw, dh, rotation, uiMode,
+                displayId);
+        if (width < displayInfo.smallestNominalAppWidth) {
+            displayInfo.smallestNominalAppWidth = width;
+        }
+        if (width > displayInfo.largestNominalAppWidth) {
+            displayInfo.largestNominalAppWidth = width;
+        }
+        final int height = mService.mPolicy.getConfigDisplayHeight(dw, dh, rotation, uiMode,
+                displayId);
+        if (height < displayInfo.smallestNominalAppHeight) {
+            displayInfo.smallestNominalAppHeight = height;
+        }
+        if (height > displayInfo.largestNominalAppHeight) {
+            displayInfo.largestNominalAppHeight = height;
+        }
+    }
+
+    DockedStackDividerController getDockedDividerController() {
+        return mDividerControllerLocked;
+    }
+
+    PinnedStackController getPinnedStackController() {
+        return mPinnedStackControllerLocked;
+    }
+
+    /**
+     * Returns true if the specified UID has access to this display.
+     */
+    boolean hasAccess(int uid) {
+        return mDisplay.hasAccess(uid);
+    }
+
+    boolean isPrivate() {
+        return (mDisplay.getFlags() & FLAG_PRIVATE) != 0;
+    }
+
+    TaskStack getHomeStack() {
+        if (mHomeStack == null && mDisplayId == DEFAULT_DISPLAY) {
+            Slog.e(TAG_WM, "getHomeStack: Returning null from this=" + this);
+        }
+        return mHomeStack;
+    }
+
+    TaskStack getStackById(int stackId) {
+        for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
+            final TaskStack stack = mTaskStackContainers.get(i);
+            if (stack.mStackId == stackId) {
+                return stack;
+            }
+        }
+        return null;
+    }
+
+    @VisibleForTesting
+    int getStackCount() {
+        return mTaskStackContainers.size();
+    }
+
+    @VisibleForTesting
+    int getStackPosById(int stackId) {
+        for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
+            final TaskStack stack = mTaskStackContainers.get(i);
+            if (stack.mStackId == stackId) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newParentConfig) {
+        super.onConfigurationChanged(newParentConfig);
+
+        // The display size information is heavily dependent on the resources in the current
+        // configuration, so we need to reconfigure it every time the configuration changes.
+        // See {@link PhoneWindowManager#setInitialDisplaySize}...sigh...
+        mService.reconfigureDisplayLocked(this);
+
+        getDockedDividerController().onConfigurationChanged();
+        getPinnedStackController().onConfigurationChanged();
+    }
+
+    /**
+     * Callback used to trigger bounds update after configuration change and get ids of stacks whose
+     * bounds were updated.
+     */
+    void updateStackBoundsAfterConfigChange(@NonNull List<Integer> changedStackList) {
+        for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
+            final TaskStack stack = mTaskStackContainers.get(i);
+            if (stack.updateBoundsAfterConfigChange()) {
+                changedStackList.add(stack.mStackId);
+            }
+        }
+
+        // If there was no pinned stack, we still need to notify the controller of the display info
+        // update as a result of the config change.  We do this here to consolidate the flow between
+        // changes when there is and is not a stack.
+        if (getStackById(PINNED_STACK_ID) == null) {
+            mPinnedStackControllerLocked.onDisplayInfoChanged();
+        }
+    }
+
+    @Override
+    boolean fillsParent() {
+        return true;
+    }
+
+    @Override
+    boolean isVisible() {
+        return true;
+    }
+
+    @Override
+    void onAppTransitionDone() {
+        super.onAppTransitionDone();
+        mService.mWindowsChanged = true;
+    }
+
+    @Override
+    boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
+        // Special handling so we can process IME windows with #forAllImeWindows above their IME
+        // target, or here in order if there isn't an IME target.
+        if (traverseTopToBottom) {
+            for (int i = mChildren.size() - 1; i >= 0; --i) {
+                final DisplayChildWindowContainer child = mChildren.get(i);
+                if (child == mImeWindowsContainers && mService.mInputMethodTarget != null) {
+                    // In this case the Ime windows will be processed above their target so we skip
+                    // here.
+                    continue;
+                }
+                if (child.forAllWindows(callback, traverseTopToBottom)) {
+                    return true;
+                }
+            }
+        } else {
+            final int count = mChildren.size();
+            for (int i = 0; i < count; i++) {
+                final DisplayChildWindowContainer child = mChildren.get(i);
+                if (child == mImeWindowsContainers && mService.mInputMethodTarget != null) {
+                    // In this case the Ime windows will be processed above their target so we skip
+                    // here.
+                    continue;
+                }
+                if (child.forAllWindows(callback, traverseTopToBottom)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    boolean forAllImeWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
+        return mImeWindowsContainers.forAllWindows(callback, traverseTopToBottom);
+    }
+
+    @Override
+    int getOrientation() {
+        final WindowManagerPolicy policy = mService.mPolicy;
+
+        if (mService.mDisplayFrozen) {
+            if (mLastWindowForcedOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
+                if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
+                        "Display is frozen, return " + mLastWindowForcedOrientation);
+                // If the display is frozen, some activities may be in the middle of restarting, and
+                // thus have removed their old window. If the window has the flag to hide the lock
+                // screen, then the lock screen can re-appear and inflict its own orientation on us.
+                // Keep the orientation stable until this all settles down.
+                return mLastWindowForcedOrientation;
+            } else if (policy.isKeyguardLocked()) {
+                // Use the last orientation the while the display is frozen with the keyguard
+                // locked. This could be the keyguard forced orientation or from a SHOW_WHEN_LOCKED
+                // window. We don't want to check the show when locked window directly though as
+                // things aren't stable while the display is frozen, for example the window could be
+                // momentarily unavailable due to activity relaunch.
+                if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Display is frozen while keyguard locked, "
+                        + "return " + mLastOrientation);
+                return mLastOrientation;
+            }
+        } else {
+            final int orientation = mAboveAppWindowsContainers.getOrientation();
+            if (orientation != SCREEN_ORIENTATION_UNSET) {
+                return orientation;
+            }
+        }
+
+        // Top system windows are not requesting an orientation. Start searching from apps.
+        return mTaskStackContainers.getOrientation();
+    }
+
+    void updateDisplayInfo() {
+        // Check if display metrics changed and update base values if needed.
+        updateBaseDisplayMetricsIfNeeded();
+
+        mDisplay.getDisplayInfo(mDisplayInfo);
+        mDisplay.getMetrics(mDisplayMetrics);
+
+        for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
+            mTaskStackContainers.get(i).updateDisplayInfo(null);
+        }
+    }
+
+    void initializeDisplayBaseInfo() {
+        final DisplayManagerInternal displayManagerInternal = mService.mDisplayManagerInternal;
+        if (displayManagerInternal != null) {
+            // Bootstrap the default logical display from the display manager.
+            final DisplayInfo newDisplayInfo = displayManagerInternal.getDisplayInfo(mDisplayId);
+            if (newDisplayInfo != null) {
+                mDisplayInfo.copyFrom(newDisplayInfo);
+            }
+        }
+
+        updateBaseDisplayMetrics(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight,
+                mDisplayInfo.logicalDensityDpi);
+        mInitialDisplayWidth = mDisplayInfo.logicalWidth;
+        mInitialDisplayHeight = mDisplayInfo.logicalHeight;
+        mInitialDisplayDensity = mDisplayInfo.logicalDensityDpi;
+    }
+
+    void getLogicalDisplayRect(Rect out) {
+        // Uses same calculation as in LogicalDisplay#configureDisplayInTransactionLocked.
+        final int orientation = mDisplayInfo.rotation;
+        boolean rotated = (orientation == ROTATION_90 || orientation == ROTATION_270);
+        final int physWidth = rotated ? mBaseDisplayHeight : mBaseDisplayWidth;
+        final int physHeight = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;
+        int width = mDisplayInfo.logicalWidth;
+        int left = (physWidth - width) / 2;
+        int height = mDisplayInfo.logicalHeight;
+        int top = (physHeight - height) / 2;
+        out.set(left, top, left + width, top + height);
+    }
+
+    private void getLogicalDisplayRect(Rect out, int orientation) {
+        getLogicalDisplayRect(out);
+
+        // Rotate the Rect if needed.
+        final int currentRotation = mDisplayInfo.rotation;
+        final int rotationDelta = deltaRotation(currentRotation, orientation);
+        if (rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270) {
+            createRotationMatrix(rotationDelta, mBaseDisplayWidth, mBaseDisplayHeight, mTmpMatrix);
+            mTmpRectF.set(out);
+            mTmpMatrix.mapRect(mTmpRectF);
+            mTmpRectF.round(out);
+        }
+    }
+
+    /**
+     * If display metrics changed, overrides are not set and it's not just a rotation - update base
+     * values.
+     */
+    private void updateBaseDisplayMetricsIfNeeded() {
+        // Get real display metrics without overrides from WM.
+        mService.mDisplayManagerInternal.getNonOverrideDisplayInfo(mDisplayId, mDisplayInfo);
+        final int orientation = mDisplayInfo.rotation;
+        final boolean rotated = (orientation == ROTATION_90 || orientation == ROTATION_270);
+        final int newWidth = rotated ? mDisplayInfo.logicalHeight : mDisplayInfo.logicalWidth;
+        final int newHeight = rotated ? mDisplayInfo.logicalWidth : mDisplayInfo.logicalHeight;
+        final int newDensity = mDisplayInfo.logicalDensityDpi;
+
+        final boolean displayMetricsChanged = mInitialDisplayWidth != newWidth
+                || mInitialDisplayHeight != newHeight
+                || mInitialDisplayDensity != mDisplayInfo.logicalDensityDpi;
+
+        if (displayMetricsChanged) {
+            // Check if display size or density is forced.
+            final boolean isDisplaySizeForced = mBaseDisplayWidth != mInitialDisplayWidth
+                    || mBaseDisplayHeight != mInitialDisplayHeight;
+            final boolean isDisplayDensityForced = mBaseDisplayDensity != mInitialDisplayDensity;
+
+            // If there is an override set for base values - use it, otherwise use new values.
+            updateBaseDisplayMetrics(isDisplaySizeForced ? mBaseDisplayWidth : newWidth,
+                    isDisplaySizeForced ? mBaseDisplayHeight : newHeight,
+                    isDisplayDensityForced ? mBaseDisplayDensity : newDensity);
+
+            // Real display metrics changed, so we should also update initial values.
+            mInitialDisplayWidth = newWidth;
+            mInitialDisplayHeight = newHeight;
+            mInitialDisplayDensity = newDensity;
+            mService.reconfigureDisplayLocked(this);
+        }
+    }
+
+    /** Sets the maximum width the screen resolution can be */
+    void setMaxUiWidth(int width) {
+        if (DEBUG_DISPLAY) {
+            Slog.v(TAG_WM, "Setting max ui width:" + width + " on display:" + getDisplayId());
+        }
+
+        mMaxUiWidth = width;
+
+        // Update existing metrics.
+        updateBaseDisplayMetrics(mBaseDisplayWidth, mBaseDisplayHeight, mBaseDisplayDensity);
+    }
+
+    /** Update base (override) display metrics. */
+    void updateBaseDisplayMetrics(int baseWidth, int baseHeight, int baseDensity) {
+        mBaseDisplayWidth = baseWidth;
+        mBaseDisplayHeight = baseHeight;
+        mBaseDisplayDensity = baseDensity;
+
+        if (mMaxUiWidth > 0 && mBaseDisplayWidth > mMaxUiWidth) {
+            mBaseDisplayHeight = (mMaxUiWidth * mBaseDisplayHeight) / mBaseDisplayWidth;
+            mBaseDisplayDensity = (mMaxUiWidth * mBaseDisplayDensity) / mBaseDisplayWidth;
+            mBaseDisplayWidth = mMaxUiWidth;
+
+            if (DEBUG_DISPLAY) {
+                Slog.v(TAG_WM, "Applying config restraints:" + mBaseDisplayWidth + "x"
+                        + mBaseDisplayHeight + " at density:" + mBaseDisplayDensity
+                        + " on display:" + getDisplayId());
+            }
+        }
+
+        mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight);
+    }
+
+    void getContentRect(Rect out) {
+        out.set(mContentRect);
+    }
+
+    TaskStack addStackToDisplay(int stackId, boolean onTop) {
+        if (DEBUG_STACK) Slog.d(TAG_WM, "Create new stackId=" + stackId + " on displayId="
+                + mDisplayId);
+
+        TaskStack stack = getStackById(stackId);
+        if (stack != null) {
+            // It's already attached to the display...clear mDeferRemoval and move stack to
+            // appropriate z-order on display as needed.
+            stack.mDeferRemoval = false;
+            // We're not moving the display to front when we're adding stacks, only when
+            // requested to change the position of stack explicitly.
+            mTaskStackContainers.positionChildAt(onTop ? POSITION_TOP : POSITION_BOTTOM, stack,
+                    false /* includingParents */);
+        } else {
+            stack = new TaskStack(mService, stackId);
+            mTaskStackContainers.addStackToDisplay(stack, onTop);
+        }
+
+        if (stackId == DOCKED_STACK_ID) {
+            mDividerControllerLocked.notifyDockedStackExistsChanged(true);
+        }
+        return stack;
+    }
+
+    void moveStackToDisplay(TaskStack stack, boolean onTop) {
+        final DisplayContent prevDc = stack.getDisplayContent();
+        if (prevDc == null) {
+            throw new IllegalStateException("Trying to move stackId=" + stack.mStackId
+                    + " which is not currently attached to any display");
+        }
+        if (prevDc.getDisplayId() == mDisplayId) {
+            throw new IllegalArgumentException("Trying to move stackId=" + stack.mStackId
+                    + " to its current displayId=" + mDisplayId);
+        }
+
+        prevDc.mTaskStackContainers.removeStackFromDisplay(stack);
+        mTaskStackContainers.addStackToDisplay(stack, onTop);
+    }
+
+    @Override
+    protected void addChild(DisplayChildWindowContainer child,
+            Comparator<DisplayChildWindowContainer> comparator) {
+        throw new UnsupportedOperationException("See DisplayChildWindowContainer");
+    }
+
+    @Override
+    protected void addChild(DisplayChildWindowContainer child, int index) {
+        throw new UnsupportedOperationException("See DisplayChildWindowContainer");
+    }
+
+    @Override
+    protected void removeChild(DisplayChildWindowContainer child) {
+        // Only allow removal of direct children from this display if the display is in the process
+        // of been removed.
+        if (mRemovingDisplay) {
+            super.removeChild(child);
+            return;
+        }
+        throw new UnsupportedOperationException("See DisplayChildWindowContainer");
+    }
+
+    @Override
+    void positionChildAt(int position, DisplayChildWindowContainer child, boolean includingParents) {
+        // Children of the display are statically ordered, so the real intention here is to perform
+        // the operation on the display and not the static direct children.
+        getParent().positionChildAt(position, this, includingParents);
+    }
+
+    int taskIdFromPoint(int x, int y) {
+        for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
+            final TaskStack stack = mTaskStackContainers.get(stackNdx);
+            final int taskId = stack.taskIdFromPoint(x, y);
+            if (taskId != -1) {
+                return taskId;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Find the task whose outside touch area (for resizing) (x, y) falls within.
+     * Returns null if the touch doesn't fall into a resizing area.
+     */
+    Task findTaskForResizePoint(int x, int y) {
+        final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
+        mTmpTaskForResizePointSearchResult.reset();
+        for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
+            final TaskStack stack = mTaskStackContainers.get(stackNdx);
+            if (!stack.getWindowConfiguration().canResizeTask()) {
+                return null;
+            }
+
+            stack.findTaskForResizePoint(x, y, delta, mTmpTaskForResizePointSearchResult);
+            if (mTmpTaskForResizePointSearchResult.searchDone) {
+                return mTmpTaskForResizePointSearchResult.taskForResize;
+            }
+        }
+        return null;
+    }
+
+    void setTouchExcludeRegion(Task focusedTask) {
+        // The provided task is the task on this display with focus, so if WindowManagerService's
+        // focused app is not on this display, focusedTask will be null.
+        if (focusedTask == null) {
+            mTouchExcludeRegion.setEmpty();
+        } else {
+            mTouchExcludeRegion.set(mBaseDisplayRect);
+            final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
+            mTmpRect2.setEmpty();
+            for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
+                final TaskStack stack = mTaskStackContainers.get(stackNdx);
+                stack.setTouchExcludeRegion(
+                        focusedTask, delta, mTouchExcludeRegion, mContentRect, mTmpRect2);
+            }
+            // If we removed the focused task above, add it back and only leave its
+            // outside touch area in the exclusion. TapDectector is not interested in
+            // any touch inside the focused task itself.
+            if (!mTmpRect2.isEmpty()) {
+                mTouchExcludeRegion.op(mTmpRect2, Region.Op.UNION);
+            }
+        }
+        final WindowState inputMethod = mService.mInputMethodWindow;
+        if (inputMethod != null && inputMethod.isVisibleLw()) {
+            // If the input method is visible and the user is typing, we don't want these touch
+            // events to be intercepted and used to change focus. This would likely cause a
+            // disappearance of the input method.
+            inputMethod.getTouchableRegion(mTmpRegion);
+            if (inputMethod.getDisplayId() == mDisplayId) {
+                mTouchExcludeRegion.op(mTmpRegion, Op.UNION);
+            } else {
+                // IME is on a different display, so we need to update its tap detector.
+                // TODO(multidisplay): Remove when IME will always appear on same display.
+                inputMethod.getDisplayContent().setTouchExcludeRegion(null /* focusedTask */);
+            }
+        }
+        for (int i = mTapExcludedWindows.size() - 1; i >= 0; i--) {
+            WindowState win = mTapExcludedWindows.get(i);
+            win.getTouchableRegion(mTmpRegion);
+            mTouchExcludeRegion.op(mTmpRegion, Region.Op.UNION);
+        }
+        // TODO(multi-display): Support docked stacks on secondary displays.
+        if (mDisplayId == DEFAULT_DISPLAY && getDockedStackLocked() != null) {
+            mDividerControllerLocked.getTouchRegion(mTmpRect);
+            mTmpRegion.set(mTmpRect);
+            mTouchExcludeRegion.op(mTmpRegion, Op.UNION);
+        }
+        if (mTapDetector != null) {
+            mTapDetector.setTouchExcludeRegion(mTouchExcludeRegion);
+        }
+    }
+
+    @Override
+    void switchUser() {
+        super.switchUser();
+        mService.mWindowsChanged = true;
+    }
+
+    private void resetAnimationBackgroundAnimator() {
+        for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
+            mTaskStackContainers.get(stackNdx).resetAnimationBackgroundAnimator();
+        }
+    }
+
+    boolean animateDimLayers() {
+        return mDimLayerController.animateDimLayers();
+    }
+
+    private void resetDimming() {
+        mDimLayerController.resetDimming();
+    }
+
+    boolean isDimming() {
+        return mDimLayerController.isDimming();
+    }
+
+    private void stopDimmingIfNeeded() {
+        mDimLayerController.stopDimmingIfNeeded();
+    }
+
+    @Override
+    void removeIfPossible() {
+        if (isAnimating()) {
+            mDeferredRemoval = true;
+            return;
+        }
+        removeImmediately();
+    }
+
+    @Override
+    void removeImmediately() {
+        mRemovingDisplay = true;
+        try {
+            super.removeImmediately();
+            if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
+            mDimLayerController.close();
+            if (mService.canDispatchPointerEvents()) {
+                if (mTapDetector != null) {
+                    mService.unregisterPointerEventListener(mTapDetector);
+                }
+                if (mDisplayId == DEFAULT_DISPLAY && mService.mMousePositionTracker != null) {
+                    mService.unregisterPointerEventListener(mService.mMousePositionTracker);
+                }
+            }
+        } finally {
+            mRemovingDisplay = false;
+        }
+    }
+
+    /** Returns true if a removal action is still being deferred. */
+    @Override
+    boolean checkCompleteDeferredRemoval() {
+        final boolean stillDeferringRemoval = super.checkCompleteDeferredRemoval();
+
+        if (!stillDeferringRemoval && mDeferredRemoval) {
+            removeImmediately();
+            mService.onDisplayRemoved(mDisplayId);
+            return false;
+        }
+        return true;
+    }
+
+    /** @return 'true' if removal of this display content is deferred due to active animation. */
+    boolean isRemovalDeferred() {
+        return mDeferredRemoval;
+    }
+
+    boolean animateForIme(float interpolatedValue, float animationTarget,
+            float dividerAnimationTarget) {
+        boolean updated = false;
+
+        for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
+            final TaskStack stack = mTaskStackContainers.get(i);
+            if (stack == null || !stack.isAdjustedForIme()) {
+                continue;
+            }
+
+            if (interpolatedValue >= 1f && animationTarget == 0f && dividerAnimationTarget == 0f) {
+                stack.resetAdjustedForIme(true /* adjustBoundsNow */);
+                updated = true;
+            } else {
+                mDividerControllerLocked.mLastAnimationProgress =
+                        mDividerControllerLocked.getInterpolatedAnimationValue(interpolatedValue);
+                mDividerControllerLocked.mLastDividerProgress =
+                        mDividerControllerLocked.getInterpolatedDividerValue(interpolatedValue);
+                updated |= stack.updateAdjustForIme(
+                        mDividerControllerLocked.mLastAnimationProgress,
+                        mDividerControllerLocked.mLastDividerProgress,
+                        false /* force */);
+            }
+            if (interpolatedValue >= 1f) {
+                stack.endImeAdjustAnimation();
+            }
+        }
+
+        return updated;
+    }
+
+    boolean clearImeAdjustAnimation() {
+        boolean changed = false;
+        for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
+            final TaskStack stack = mTaskStackContainers.get(i);
+            if (stack != null && stack.isAdjustedForIme()) {
+                stack.resetAdjustedForIme(true /* adjustBoundsNow */);
+                changed  = true;
+            }
+        }
+        return changed;
+    }
+
+    void beginImeAdjustAnimation() {
+        for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
+            final TaskStack stack = mTaskStackContainers.get(i);
+            if (stack.isVisible() && stack.isAdjustedForIme()) {
+                stack.beginImeAdjustAnimation();
+            }
+        }
+    }
+
+    void adjustForImeIfNeeded() {
+        final WindowState imeWin = mService.mInputMethodWindow;
+        final boolean imeVisible = imeWin != null && imeWin.isVisibleLw() && imeWin.isDisplayedLw()
+                && !mDividerControllerLocked.isImeHideRequested();
+        final boolean dockVisible = isStackVisible(DOCKED_STACK_ID);
+        final TaskStack imeTargetStack = mService.getImeFocusStackLocked();
+        final int imeDockSide = (dockVisible && imeTargetStack != null) ?
+                imeTargetStack.getDockSide() : DOCKED_INVALID;
+        final boolean imeOnTop = (imeDockSide == DOCKED_TOP);
+        final boolean imeOnBottom = (imeDockSide == DOCKED_BOTTOM);
+        final boolean dockMinimized = mDividerControllerLocked.isMinimizedDock();
+        final int imeHeight = mService.mPolicy.getInputMethodWindowVisibleHeightLw();
+        final boolean imeHeightChanged = imeVisible &&
+                imeHeight != mDividerControllerLocked.getImeHeightAdjustedFor();
+
+        // The divider could be adjusted for IME position, or be thinner than usual,
+        // or both. There are three possible cases:
+        // - If IME is visible, and focus is on top, divider is not moved for IME but thinner.
+        // - If IME is visible, and focus is on bottom, divider is moved for IME and thinner.
+        // - If IME is not visible, divider is not moved and is normal width.
+
+        if (imeVisible && dockVisible && (imeOnTop || imeOnBottom) && !dockMinimized) {
+            for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
+                final TaskStack stack = mTaskStackContainers.get(i);
+                final boolean isDockedOnBottom = stack.getDockSide() == DOCKED_BOTTOM;
+                if (stack.isVisible() && (imeOnBottom || isDockedOnBottom) &&
+                        StackId.isStackAffectedByDragResizing(stack.mStackId)) {
+                    stack.setAdjustedForIme(imeWin, imeOnBottom && imeHeightChanged);
+                } else {
+                    stack.resetAdjustedForIme(false);
+                }
+            }
+            mDividerControllerLocked.setAdjustedForIme(
+                    imeOnBottom /*ime*/, true /*divider*/, true /*animate*/, imeWin, imeHeight);
+        } else {
+            for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
+                final TaskStack stack = mTaskStackContainers.get(i);
+                stack.resetAdjustedForIme(!dockVisible);
+            }
+            mDividerControllerLocked.setAdjustedForIme(
+                    false /*ime*/, false /*divider*/, dockVisible /*animate*/, imeWin, imeHeight);
+        }
+        mPinnedStackControllerLocked.setAdjustedForIme(imeVisible, imeHeight);
+    }
+
+    void setInputMethodAnimLayerAdjustment(int adj) {
+        if (DEBUG_LAYERS) Slog.v(TAG_WM, "Setting im layer adj to " + adj);
+        mInputMethodAnimLayerAdjustment = adj;
+        assignWindowLayers(false /* relayoutNeeded */);
+    }
+
+    /**
+     * If a window that has an animation specifying a colored background and the current wallpaper
+     * is visible, then the color goes *below* the wallpaper so we don't cause the wallpaper to
+     * suddenly disappear.
+     */
+    int getLayerForAnimationBackground(WindowStateAnimator winAnimator) {
+        final WindowState visibleWallpaper = mBelowAppWindowsContainers.getWindow(
+                w -> w.mIsWallpaper && w.isVisibleNow());
+
+        if (visibleWallpaper != null) {
+            return visibleWallpaper.mWinAnimator.mAnimLayer;
+        }
+        return winAnimator.mAnimLayer;
+    }
+
+    void prepareFreezingTaskBounds() {
+        for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
+            final TaskStack stack = mTaskStackContainers.get(stackNdx);
+            stack.prepareFreezingTaskBounds();
+        }
+    }
+
+    void rotateBounds(int oldRotation, int newRotation, Rect bounds) {
+        getLogicalDisplayRect(mTmpRect, newRotation);
+
+        // Compute a transform matrix to undo the coordinate space transformation,
+        // and present the window at the same physical position it previously occupied.
+        final int deltaRotation = deltaRotation(newRotation, oldRotation);
+        createRotationMatrix(deltaRotation, mTmpRect.width(), mTmpRect.height(), mTmpMatrix);
+
+        mTmpRectF.set(bounds);
+        mTmpMatrix.mapRect(mTmpRectF);
+        mTmpRectF.round(bounds);
+    }
+
+    static int deltaRotation(int oldRotation, int newRotation) {
+        int delta = newRotation - oldRotation;
+        if (delta < 0) delta += 4;
+        return delta;
+    }
+
+    private static void createRotationMatrix(int rotation, float displayWidth, float displayHeight,
+            Matrix outMatrix) {
+        // For rotations without Z-ordering we don't need the target rectangle's position.
+        createRotationMatrix(rotation, 0 /* rectLeft */, 0 /* rectTop */, displayWidth,
+                displayHeight, outMatrix);
+    }
+
+    static void createRotationMatrix(int rotation, float rectLeft, float rectTop,
+            float displayWidth, float displayHeight, Matrix outMatrix) {
+        switch (rotation) {
+            case ROTATION_0:
+                outMatrix.reset();
+                break;
+            case ROTATION_270:
+                outMatrix.setRotate(270, 0, 0);
+                outMatrix.postTranslate(0, displayHeight);
+                outMatrix.postTranslate(rectTop, 0);
+                break;
+            case ROTATION_180:
+                outMatrix.reset();
+                break;
+            case ROTATION_90:
+                outMatrix.setRotate(90, 0, 0);
+                outMatrix.postTranslate(displayWidth, 0);
+                outMatrix.postTranslate(-rectTop, rectLeft);
+                break;
+        }
+    }
+
+    void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(ID, mDisplayId);
+        for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
+            final TaskStack stack = mTaskStackContainers.get(stackNdx);
+            stack.writeToProto(proto, STACKS);
+        }
+        mDividerControllerLocked.writeToProto(proto, DOCKED_STACK_DIVIDER_CONTROLLER);
+        mPinnedStackControllerLocked.writeToProto(proto, PINNED_STACK_CONTROLLER);
+        for (int i = mAboveAppWindowsContainers.size() - 1; i >= 0; --i) {
+            final WindowToken windowToken = mAboveAppWindowsContainers.get(i);
+            windowToken.writeToProto(proto, ABOVE_APP_WINDOWS);
+        }
+        for (int i = mBelowAppWindowsContainers.size() - 1; i >= 0; --i) {
+            final WindowToken windowToken = mBelowAppWindowsContainers.get(i);
+            windowToken.writeToProto(proto, BELOW_APP_WINDOWS);
+        }
+        for (int i = mImeWindowsContainers.size() - 1; i >= 0; --i) {
+            final WindowToken windowToken = mImeWindowsContainers.get(i);
+            windowToken.writeToProto(proto, IME_WINDOWS);
+        }
+        proto.write(DPI, mBaseDisplayDensity);
+        mDisplayInfo.writeToProto(proto, DISPLAY_INFO);
+        proto.write(ROTATION, mRotation);
+        final ScreenRotationAnimation screenRotationAnimation =
+                mService.mAnimator.getScreenRotationAnimationLocked(mDisplayId);
+        if (screenRotationAnimation != null) {
+            screenRotationAnimation.writeToProto(proto, SCREEN_ROTATION_ANIMATION);
+        }
+        proto.end(token);
+    }
+
+    public void dump(String prefix, PrintWriter pw) {
+        pw.print(prefix); pw.print("Display: mDisplayId="); pw.println(mDisplayId);
+        final String subPrefix = "  " + prefix;
+        pw.print(subPrefix); pw.print("init="); pw.print(mInitialDisplayWidth); pw.print("x");
+            pw.print(mInitialDisplayHeight); pw.print(" "); pw.print(mInitialDisplayDensity);
+            pw.print("dpi");
+            if (mInitialDisplayWidth != mBaseDisplayWidth
+                    || mInitialDisplayHeight != mBaseDisplayHeight
+                    || mInitialDisplayDensity != mBaseDisplayDensity) {
+                pw.print(" base=");
+                pw.print(mBaseDisplayWidth); pw.print("x"); pw.print(mBaseDisplayHeight);
+                pw.print(" "); pw.print(mBaseDisplayDensity); pw.print("dpi");
+            }
+            if (mDisplayScalingDisabled) {
+                pw.println(" noscale");
+            }
+            pw.print(" cur=");
+            pw.print(mDisplayInfo.logicalWidth);
+            pw.print("x"); pw.print(mDisplayInfo.logicalHeight);
+            pw.print(" app=");
+            pw.print(mDisplayInfo.appWidth);
+            pw.print("x"); pw.print(mDisplayInfo.appHeight);
+            pw.print(" rng="); pw.print(mDisplayInfo.smallestNominalAppWidth);
+            pw.print("x"); pw.print(mDisplayInfo.smallestNominalAppHeight);
+            pw.print("-"); pw.print(mDisplayInfo.largestNominalAppWidth);
+            pw.print("x"); pw.println(mDisplayInfo.largestNominalAppHeight);
+            pw.print(subPrefix + "deferred=" + mDeferredRemoval
+                    + " mLayoutNeeded=" + mLayoutNeeded);
+            pw.println(" mTouchExcludeRegion=" + mTouchExcludeRegion);
+
+        pw.println();
+        pw.println(prefix + "Application tokens in top down Z order:");
+        for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
+            final TaskStack stack = mTaskStackContainers.get(stackNdx);
+            stack.dump(prefix + "  ", pw);
+        }
+
+        pw.println();
+        if (!mExitingTokens.isEmpty()) {
+            pw.println();
+            pw.println("  Exiting tokens:");
+            for (int i = mExitingTokens.size() - 1; i >= 0; i--) {
+                final WindowToken token = mExitingTokens.get(i);
+                pw.print("  Exiting #"); pw.print(i);
+                pw.print(' '); pw.print(token);
+                pw.println(':');
+                token.dump(pw, "    ");
+            }
+        }
+        pw.println();
+        mDimLayerController.dump(prefix, pw);
+        pw.println();
+        mDividerControllerLocked.dump(prefix, pw);
+        pw.println();
+        mPinnedStackControllerLocked.dump(prefix, pw);
+
+        if (mInputMethodAnimLayerAdjustment != 0) {
+            pw.println(subPrefix
+                    + "mInputMethodAnimLayerAdjustment=" + mInputMethodAnimLayerAdjustment);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "Display " + mDisplayId + " info=" + mDisplayInfo + " stacks=" + mChildren;
+    }
+
+    String getName() {
+        return "Display " + mDisplayId + " name=\"" + mDisplayInfo.name + "\"";
+    }
+
+    /** Checks if stack with provided id is visible on this display. */
+    boolean isStackVisible(int stackId) {
+        final TaskStack stack = getStackById(stackId);
+        return (stack != null && stack.isVisible());
+    }
+
+    /**
+     * @return The docked stack, but only if it is visible, and {@code null} otherwise.
+     */
+    TaskStack getDockedStackLocked() {
+        final TaskStack stack = getStackById(DOCKED_STACK_ID);
+        return (stack != null && stack.isVisible()) ? stack : null;
+    }
+
+    /**
+     * Like {@link #getDockedStackLocked}, but also returns the docked stack if it's currently not
+     * visible.
+     */
+    TaskStack getDockedStackIgnoringVisibility() {
+        return getStackById(DOCKED_STACK_ID);
+    }
+
+    /** Find the visible, touch-deliverable window under the given point */
+    WindowState getTouchableWinAtPointLocked(float xf, float yf) {
+        final int x = (int) xf;
+        final int y = (int) yf;
+        final WindowState touchedWin = getWindow(w -> {
+            final int flags = w.mAttrs.flags;
+            if (!w.isVisibleLw()) {
+                return false;
+            }
+            if ((flags & FLAG_NOT_TOUCHABLE) != 0) {
+                return false;
+            }
+
+            w.getVisibleBounds(mTmpRect);
+            if (!mTmpRect.contains(x, y)) {
+                return false;
+            }
+
+            w.getTouchableRegion(mTmpRegion);
+
+            final int touchFlags = flags & (FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL);
+            return mTmpRegion.contains(x, y) || touchFlags == 0;
+        });
+
+        return touchedWin;
+    }
+
+    boolean canAddToastWindowForUid(int uid) {
+        // We allow one toast window per UID being shown at a time.
+        // Also if the app is focused adding more than one toast at
+        // a time for better backwards compatibility.
+        final WindowState focusedWindowForUid = getWindow(w ->
+                w.mOwnerUid == uid && w.isFocused());
+        if (focusedWindowForUid != null) {
+            return true;
+        }
+        final WindowState win = getWindow(w ->
+                w.mAttrs.type == TYPE_TOAST && w.mOwnerUid == uid && !w.mPermanentlyHidden
+                && !w.mWindowRemovalAllowed);
+        return win == null;
+    }
+
+    void scheduleToastWindowsTimeoutIfNeededLocked(WindowState oldFocus, WindowState newFocus) {
+        if (oldFocus == null || (newFocus != null && newFocus.mOwnerUid == oldFocus.mOwnerUid)) {
+            return;
+        }
+
+        // Used to communicate the old focus to the callback method.
+        mTmpWindow = oldFocus;
+
+        forAllWindows(mScheduleToastTimeout, false /* traverseTopToBottom */);
+    }
+
+    WindowState findFocusedWindow() {
+        mTmpWindow = null;
+
+        forAllWindows(mFindFocusedWindow, true /* traverseTopToBottom */);
+
+        if (mTmpWindow == null) {
+            if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: No focusable windows.");
+            return null;
+        }
+        return mTmpWindow;
+    }
+
+    /** Updates the layer assignment of windows on this display. */
+    void assignWindowLayers(boolean setLayoutNeeded) {
+        mLayersController.assignWindowLayers(this);
+        if (setLayoutNeeded) {
+            setLayoutNeeded();
+        }
+    }
+
+    // TODO: This should probably be called any time a visual change is made to the hierarchy like
+    // moving containers or resizing them. Need to investigate the best way to have it automatically
+    // happen so we don't run into issues with programmers forgetting to do it.
+    void layoutAndAssignWindowLayersIfNeeded() {
+        mService.mWindowsChanged = true;
+        setLayoutNeeded();
+
+        if (!mService.updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
+                false /*updateInputWindows*/)) {
+            assignWindowLayers(false /* setLayoutNeeded */);
+        }
+
+        mService.mInputMonitor.setUpdateInputWindowsNeededLw();
+        mService.mWindowPlacerLocked.performSurfacePlacement();
+        mService.mInputMonitor.updateInputWindowsLw(false /*force*/);
+    }
+
+    /** Returns true if a leaked surface was destroyed */
+    boolean destroyLeakedSurfaces() {
+        // Used to indicate that a surface was leaked.
+        mTmpWindow = null;
+        forAllWindows(w -> {
+            final WindowStateAnimator wsa = w.mWinAnimator;
+            if (wsa.mSurfaceController == null) {
+                return;
+            }
+            if (!mService.mSessions.contains(wsa.mSession)) {
+                Slog.w(TAG_WM, "LEAKED SURFACE (session doesn't exist): "
+                        + w + " surface=" + wsa.mSurfaceController
+                        + " token=" + w.mToken
+                        + " pid=" + w.mSession.mPid
+                        + " uid=" + w.mSession.mUid);
+                wsa.destroySurface();
+                mService.mForceRemoves.add(w);
+                mTmpWindow = w;
+            } else if (w.mAppToken != null && w.mAppToken.isClientHidden()) {
+                Slog.w(TAG_WM, "LEAKED SURFACE (app token hidden): "
+                        + w + " surface=" + wsa.mSurfaceController
+                        + " token=" + w.mAppToken);
+                if (SHOW_TRANSACTIONS) logSurface(w, "LEAK DESTROY", false);
+                wsa.destroySurface();
+                mTmpWindow = w;
+            }
+        }, false /* traverseTopToBottom */);
+
+        return mTmpWindow != null;
+    }
+
+    /**
+     * Determine and return the window that should be the IME target.
+     * @param updateImeTarget If true the system IME target will be updated to match what we found.
+     * @return The window that should be used as the IME target or null if there isn't any.
+     */
+    WindowState computeImeTarget(boolean updateImeTarget) {
+        if (mService.mInputMethodWindow == null) {
+            // There isn't an IME so there shouldn't be a target...That was easy!
+            if (updateImeTarget) {
+                if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from "
+                        + mService.mInputMethodTarget + " to null since mInputMethodWindow is null");
+                setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim, 0);
+            }
+            return null;
+        }
+
+        // TODO(multidisplay): Needs some serious rethought when the target and IME are not on the
+        // same display. Or even when the current IME/target are not on the same screen as the next
+        // IME/target. For now only look for input windows on the main screen.
+        mUpdateImeTarget = updateImeTarget;
+        WindowState target = getWindow(mComputeImeTargetPredicate);
+
+
+        // Yet more tricksyness!  If this window is a "starting" window, we do actually want
+        // to be on top of it, but it is not -really- where input will go. So look down below
+        // for a real window to target...
+        if (target != null && target.mAttrs.type == TYPE_APPLICATION_STARTING) {
+            final AppWindowToken token = target.mAppToken;
+            if (token != null) {
+                final WindowState betterTarget = token.getImeTargetBelowWindow(target);
+                if (betterTarget != null) {
+                    target = betterTarget;
+                }
+            }
+        }
+
+        if (DEBUG_INPUT_METHOD && updateImeTarget) Slog.v(TAG_WM,
+                "Proposed new IME target: " + target);
+
+        // Now, a special case -- if the last target's window is in the process of exiting, and is
+        // above the new target, keep on the last target to avoid flicker. Consider for example a
+        // Dialog with the IME shown: when the Dialog is dismissed, we want to keep the IME above it
+        // until it is completely gone so it doesn't drop behind the dialog or its full-screen
+        // scrim.
+        final WindowState curTarget = mService.mInputMethodTarget;
+        if (curTarget != null && curTarget.isDisplayedLw() && curTarget.isClosing()
+                && (target == null
+                    || curTarget.mWinAnimator.mAnimLayer > target.mWinAnimator.mAnimLayer)) {
+            if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, "Current target higher, not changing");
+            return curTarget;
+        }
+
+        if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, "Desired input method target=" + target
+                + " updateImeTarget=" + updateImeTarget);
+
+        if (target == null) {
+            if (updateImeTarget) {
+                if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from " + curTarget
+                        + " to null." + (SHOW_STACK_CRAWLS ? " Callers="
+                        + Debug.getCallers(4) : ""));
+                setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim, 0);
+            }
+
+            return null;
+        }
+
+        if (updateImeTarget) {
+            AppWindowToken token = curTarget == null ? null : curTarget.mAppToken;
+            if (token != null) {
+
+                // Now some fun for dealing with window animations that modify the Z order. We need
+                // to look at all windows below the current target that are in this app, finding the
+                // highest visible one in layering.
+                WindowState highestTarget = null;
+                if (token.mAppAnimator.animating || token.mAppAnimator.animation != null) {
+                    highestTarget = token.getHighestAnimLayerWindow(curTarget);
+                }
+
+                if (highestTarget != null) {
+                    final AppTransition appTransition = mService.mAppTransition;
+                    if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, appTransition + " " + highestTarget
+                            + " animating=" + highestTarget.mWinAnimator.isAnimationSet()
+                            + " layer=" + highestTarget.mWinAnimator.mAnimLayer
+                            + " new layer=" + target.mWinAnimator.mAnimLayer);
+
+                    if (appTransition.isTransitionSet()) {
+                        // If we are currently setting up for an animation, hold everything until we
+                        // can find out what will happen.
+                        setInputMethodTarget(highestTarget, true, mInputMethodAnimLayerAdjustment);
+                        return highestTarget;
+                    } else if (highestTarget.mWinAnimator.isAnimationSet() &&
+                            highestTarget.mWinAnimator.mAnimLayer > target.mWinAnimator.mAnimLayer) {
+                        // If the window we are currently targeting is involved with an animation,
+                        // and it is on top of the next target we will be over, then hold off on
+                        // moving until that is done.
+                        setInputMethodTarget(highestTarget, true, mInputMethodAnimLayerAdjustment);
+                        return highestTarget;
+                    }
+                }
+            }
+
+            if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from " + curTarget + " to "
+                    + target + (SHOW_STACK_CRAWLS ? " Callers=" + Debug.getCallers(4) : ""));
+            setInputMethodTarget(target, false, target.mAppToken != null
+                    ? target.mAppToken.getAnimLayerAdjustment() : 0);
+        }
+
+        return target;
+    }
+
+    private void setInputMethodTarget(WindowState target, boolean targetWaitingAnim, int layerAdj) {
+        if (target == mService.mInputMethodTarget
+                && mService.mInputMethodTargetWaitingAnim == targetWaitingAnim
+                && mInputMethodAnimLayerAdjustment == layerAdj) {
+            return;
+        }
+
+        mService.mInputMethodTarget = target;
+        mService.mInputMethodTargetWaitingAnim = targetWaitingAnim;
+        setInputMethodAnimLayerAdjustment(layerAdj);
+        assignWindowLayers(false /* setLayoutNeeded */);
+    }
+
+    boolean getNeedsMenu(WindowState top, WindowManagerPolicy.WindowState bottom) {
+        if (top.mAttrs.needsMenuKey != NEEDS_MENU_UNSET) {
+            return top.mAttrs.needsMenuKey == NEEDS_MENU_SET_TRUE;
+        }
+
+        // Used to indicate we have reached the first window in the range we are interested in.
+        mTmpWindow = null;
+
+        // TODO: Figure-out a more efficient way to do this.
+        final WindowState candidate = getWindow(w -> {
+            if (w == top) {
+                // Reached the first window in the range we are interested in.
+                mTmpWindow = w;
+            }
+            if (mTmpWindow == null) {
+                return false;
+            }
+
+            if (w.mAttrs.needsMenuKey != NEEDS_MENU_UNSET) {
+                return true;
+            }
+            // If we reached the bottom of the range of windows we are considering,
+            // assume no menu is needed.
+            if (w == bottom) {
+                return true;
+            }
+            return false;
+        });
+
+        return candidate != null && candidate.mAttrs.needsMenuKey == NEEDS_MENU_SET_TRUE;
+    }
+
+    void setLayoutNeeded() {
+        if (DEBUG_LAYOUT) Slog.w(TAG_WM, "setLayoutNeeded: callers=" + Debug.getCallers(3));
+        mLayoutNeeded = true;
+    }
+
+    private void clearLayoutNeeded() {
+        if (DEBUG_LAYOUT) Slog.w(TAG_WM, "clearLayoutNeeded: callers=" + Debug.getCallers(3));
+        mLayoutNeeded = false;
+    }
+
+    boolean isLayoutNeeded() {
+        return mLayoutNeeded;
+    }
+
+    void dumpTokens(PrintWriter pw, boolean dumpAll) {
+        if (mTokenMap.isEmpty()) {
+            return;
+        }
+        pw.println("  Display #" + mDisplayId);
+        final Iterator<WindowToken> it = mTokenMap.values().iterator();
+        while (it.hasNext()) {
+            final WindowToken token = it.next();
+            pw.print("  ");
+            pw.print(token);
+            if (dumpAll) {
+                pw.println(':');
+                token.dump(pw, "    ");
+            } else {
+                pw.println();
+            }
+        }
+    }
+
+    void dumpWindowAnimators(PrintWriter pw, String subPrefix) {
+        final int[] index = new int[1];
+        forAllWindows(w -> {
+            final WindowStateAnimator wAnim = w.mWinAnimator;
+            pw.println(subPrefix + "Window #" + index[0] + ": " + wAnim);
+            index[0] = index[0] + 1;
+        }, false /* traverseTopToBottom */);
+    }
+
+    void enableSurfaceTrace(FileDescriptor fd) {
+        forAllWindows(w -> {
+            w.mWinAnimator.enableSurfaceTrace(fd);
+        }, true /* traverseTopToBottom */);
+    }
+
+    void disableSurfaceTrace() {
+        forAllWindows(w -> {
+            w.mWinAnimator.disableSurfaceTrace();
+        }, true /* traverseTopToBottom */);
+    }
+
+    /**
+     * Starts the Keyguard exit animation on all windows that don't belong to an app token.
+     */
+    void startKeyguardExitOnNonAppWindows(boolean onWallpaper, boolean goingToShade) {
+        final WindowManagerPolicy policy = mService.mPolicy;
+        forAllWindows(w -> {
+            if (w.mAppToken == null && policy.canBeHiddenByKeyguardLw(w)
+                    && w.wouldBeVisibleIfPolicyIgnored() && !w.isVisible()) {
+                w.mWinAnimator.setAnimation(
+                        policy.createHiddenByKeyguardExit(onWallpaper, goingToShade));
+            }
+        }, true /* traverseTopToBottom */);
+    }
+
+    boolean checkWaitingForWindows() {
+
+        mHaveBootMsg = false;
+        mHaveApp = false;
+        mHaveWallpaper = false;
+        mHaveKeyguard = true;
+
+        final WindowState visibleWindow = getWindow(w -> {
+            if (w.isVisibleLw() && !w.mObscured && !w.isDrawnLw()) {
+                return true;
+            }
+            if (w.isDrawnLw()) {
+                if (w.mAttrs.type == TYPE_BOOT_PROGRESS) {
+                    mHaveBootMsg = true;
+                } else if (w.mAttrs.type == TYPE_APPLICATION
+                        || w.mAttrs.type == TYPE_DRAWN_APPLICATION) {
+                    mHaveApp = true;
+                } else if (w.mAttrs.type == TYPE_WALLPAPER) {
+                    mHaveWallpaper = true;
+                } else if (w.mAttrs.type == TYPE_STATUS_BAR) {
+                    mHaveKeyguard = mService.mPolicy.isKeyguardDrawnLw();
+                }
+            }
+            return false;
+        });
+
+        if (visibleWindow != null) {
+            // We have a visible window.
+            return true;
+        }
+
+        // if the wallpaper service is disabled on the device, we're never going to have
+        // wallpaper, don't bother waiting for it
+        boolean wallpaperEnabled = mService.mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_enableWallpaperService)
+                && !mService.mOnlyCore;
+
+        if (DEBUG_SCREEN_ON || DEBUG_BOOT) Slog.i(TAG_WM,
+                "******** booted=" + mService.mSystemBooted
+                + " msg=" + mService.mShowingBootMessages
+                + " haveBoot=" + mHaveBootMsg + " haveApp=" + mHaveApp
+                + " haveWall=" + mHaveWallpaper + " wallEnabled=" + wallpaperEnabled
+                + " haveKeyguard=" + mHaveKeyguard);
+
+        // If we are turning on the screen to show the boot message, don't do it until the boot
+        // message is actually displayed.
+        if (!mService.mSystemBooted && !mHaveBootMsg) {
+            return true;
+        }
+
+        // If we are turning on the screen after the boot is completed normally, don't do so until
+        // we have the application and wallpaper.
+        if (mService.mSystemBooted
+                && ((!mHaveApp && !mHaveKeyguard) || (wallpaperEnabled && !mHaveWallpaper))) {
+            return true;
+        }
+
+        return false;
+    }
+
+    void updateWindowsForAnimator(WindowAnimator animator) {
+        mTmpWindowAnimator = animator;
+        forAllWindows(mUpdateWindowsForAnimator, true /* traverseTopToBottom */);
+    }
+
+    void updateWallpaperForAnimator(WindowAnimator animator) {
+        resetAnimationBackgroundAnimator();
+
+        // Used to indicate a detached wallpaper.
+        mTmpWindow = null;
+        mTmpWindowAnimator = animator;
+
+        forAllWindows(mUpdateWallpaperForAnimator, true /* traverseTopToBottom */);
+
+        if (animator.mWindowDetachedWallpaper != mTmpWindow) {
+            if (DEBUG_WALLPAPER) Slog.v(TAG, "Detached wallpaper changed from "
+                    + animator.mWindowDetachedWallpaper + " to " + mTmpWindow);
+            animator.mWindowDetachedWallpaper = mTmpWindow;
+            animator.mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE;
+        }
+    }
+
+    void prepareWindowSurfaces() {
+        forAllWindows(mPrepareWindowSurfaces, false /* traverseTopToBottom */);
+    }
+
+    boolean inputMethodClientHasFocus(IInputMethodClient client) {
+        final WindowState imFocus = computeImeTarget(false /* updateImeTarget */);
+        if (imFocus == null) {
+            return false;
+        }
+
+        if (DEBUG_INPUT_METHOD) {
+            Slog.i(TAG_WM, "Desired input method target: " + imFocus);
+            Slog.i(TAG_WM, "Current focus: " + mService.mCurrentFocus);
+            Slog.i(TAG_WM, "Last focus: " + mService.mLastFocus);
+        }
+
+        final IInputMethodClient imeClient = imFocus.mSession.mClient;
+
+        if (DEBUG_INPUT_METHOD) {
+            Slog.i(TAG_WM, "IM target client: " + imeClient);
+            if (imeClient != null) {
+                Slog.i(TAG_WM, "IM target client binder: " + imeClient.asBinder());
+                Slog.i(TAG_WM, "Requesting client binder: " + client.asBinder());
+            }
+        }
+
+        return imeClient != null && imeClient.asBinder() == client.asBinder();
+    }
+
+    boolean hasSecureWindowOnScreen() {
+        final WindowState win = getWindow(
+                w -> w.isOnScreen() && (w.mAttrs.flags & FLAG_SECURE) != 0);
+        return win != null;
+    }
+
+    void updateSystemUiVisibility(int visibility, int globalDiff) {
+        forAllWindows(w -> {
+            try {
+                final int curValue = w.mSystemUiVisibility;
+                final int diff = (curValue ^ visibility) & globalDiff;
+                final int newValue = (curValue & ~diff) | (visibility & diff);
+                if (newValue != curValue) {
+                    w.mSeq++;
+                    w.mSystemUiVisibility = newValue;
+                }
+                if (newValue != curValue || w.mAttrs.hasSystemUiListeners) {
+                    w.mClient.dispatchSystemUiVisibilityChanged(w.mSeq,
+                            visibility, newValue, diff);
+                }
+            } catch (RemoteException e) {
+                // so sorry
+            }
+        }, true /* traverseTopToBottom */);
+    }
+
+    void onWindowFreezeTimeout() {
+        Slog.w(TAG_WM, "Window freeze timeout expired.");
+        mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
+
+        forAllWindows(w -> {
+            if (!w.getOrientationChanging()) {
+                return;
+            }
+            w.orientationChangeTimedOut();
+            w.mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
+                    - mService.mDisplayFreezeTime);
+            Slog.w(TAG_WM, "Force clearing orientation change: " + w);
+        }, true /* traverseTopToBottom */);
+        mService.mWindowPlacerLocked.performSurfacePlacement();
+    }
+
+    void waitForAllWindowsDrawn() {
+        final WindowManagerPolicy policy = mService.mPolicy;
+        forAllWindows(w -> {
+            final boolean keyguard = policy.isKeyguardHostWindow(w.mAttrs);
+            if (w.isVisibleLw() && (w.mAppToken != null || keyguard)) {
+                w.mWinAnimator.mDrawState = DRAW_PENDING;
+                // Force add to mResizingWindows.
+                w.mLastContentInsets.set(-1, -1, -1, -1);
+                mService.mWaitingForDrawn.add(w);
+            }
+        }, true /* traverseTopToBottom */);
+    }
+
+    // TODO: Super crazy long method that should be broken down...
+    boolean applySurfaceChangesTransaction(boolean recoveringMemory) {
+
+        final int dw = mDisplayInfo.logicalWidth;
+        final int dh = mDisplayInfo.logicalHeight;
+        final WindowSurfacePlacer surfacePlacer = mService.mWindowPlacerLocked;
+
+        mTmpUpdateAllDrawn.clear();
+
+        int repeats = 0;
+        do {
+            repeats++;
+            if (repeats > 6) {
+                Slog.w(TAG, "Animation repeat aborted after too many iterations");
+                clearLayoutNeeded();
+                break;
+            }
+
+            if (DEBUG_LAYOUT_REPEATS) surfacePlacer.debugLayoutRepeats("On entry to LockedInner",
+                    pendingLayoutChanges);
+
+            // TODO(multi-display): For now adjusting wallpaper only on primary display to avoid
+            // the wallpaper window jumping across displays.
+            // Remove check for default display when there will be support for multiple wallpaper
+            // targets (on different displays).
+            if (isDefaultDisplay && (pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
+                mWallpaperController.adjustWallpaperWindows(this);
+            }
+
+            if (isDefaultDisplay && (pendingLayoutChanges & FINISH_LAYOUT_REDO_CONFIG) != 0) {
+                if (DEBUG_LAYOUT) Slog.v(TAG, "Computing new config from layout");
+                if (mService.updateOrientationFromAppTokensLocked(true, mDisplayId)) {
+                    setLayoutNeeded();
+                    mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, mDisplayId).sendToTarget();
+                }
+            }
+
+            if ((pendingLayoutChanges & FINISH_LAYOUT_REDO_LAYOUT) != 0) {
+                setLayoutNeeded();
+            }
+
+            // FIRST LOOP: Perform a layout, if needed.
+            if (repeats < LAYOUT_REPEAT_THRESHOLD) {
+                performLayout(repeats == 1, false /* updateInputWindows */);
+            } else {
+                Slog.w(TAG, "Layout repeat skipped after too many iterations");
+            }
+
+            // FIRST AND ONE HALF LOOP: Make WindowManagerPolicy think it is animating.
+            pendingLayoutChanges = 0;
+
+            if (isDefaultDisplay) {
+                mService.mPolicy.beginPostLayoutPolicyLw(dw, dh);
+                forAllWindows(mApplyPostLayoutPolicy, true /* traverseTopToBottom */);
+                pendingLayoutChanges |= mService.mPolicy.finishPostLayoutPolicyLw();
+                if (DEBUG_LAYOUT_REPEATS) surfacePlacer.debugLayoutRepeats(
+                        "after finishPostLayoutPolicyLw", pendingLayoutChanges);
+            }
+        } while (pendingLayoutChanges != 0);
+
+        mTmpApplySurfaceChangesTransactionState.reset();
+        resetDimming();
+
+        mTmpRecoveringMemory = recoveringMemory;
+        forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */);
+
+        mService.mDisplayManagerInternal.setDisplayProperties(mDisplayId,
+                mTmpApplySurfaceChangesTransactionState.displayHasContent,
+                mTmpApplySurfaceChangesTransactionState.preferredRefreshRate,
+                mTmpApplySurfaceChangesTransactionState.preferredModeId,
+                true /* inTraversal, must call performTraversalInTrans... below */);
+
+        stopDimmingIfNeeded();
+
+        final boolean wallpaperVisible = mWallpaperController.isWallpaperVisible();
+        if (wallpaperVisible != mLastWallpaperVisible) {
+            mLastWallpaperVisible = wallpaperVisible;
+            mService.mWallpaperVisibilityListeners.notifyWallpaperVisibilityChanged(this);
+        }
+
+        while (!mTmpUpdateAllDrawn.isEmpty()) {
+            final AppWindowToken atoken = mTmpUpdateAllDrawn.removeLast();
+            // See if any windows have been drawn, so they (and others associated with them)
+            // can now be shown.
+            atoken.updateAllDrawn();
+        }
+
+        return mTmpApplySurfaceChangesTransactionState.focusDisplayed;
+    }
+
+    void performLayout(boolean initial, boolean updateInputWindows) {
+        if (!isLayoutNeeded()) {
+            return;
+        }
+        clearLayoutNeeded();
+
+        final int dw = mDisplayInfo.logicalWidth;
+        final int dh = mDisplayInfo.logicalHeight;
+
+        if (DEBUG_LAYOUT) {
+            Slog.v(TAG, "-------------------------------------");
+            Slog.v(TAG, "performLayout: needed=" + isLayoutNeeded() + " dw=" + dw + " dh=" + dh);
+        }
+
+        mService.mPolicy.beginLayoutLw(isDefaultDisplay, dw, dh, mRotation,
+                getConfiguration().uiMode);
+        if (isDefaultDisplay) {
+            // Not needed on non-default displays.
+            mService.mSystemDecorLayer = mService.mPolicy.getSystemDecorLayerLw();
+            mService.mScreenRect.set(0, 0, dw, dh);
+        }
+
+        mService.mPolicy.getContentRectLw(mContentRect);
+
+        int seq = mService.mLayoutSeq + 1;
+        if (seq < 0) seq = 0;
+        mService.mLayoutSeq = seq;
+
+        // Used to indicate that we have processed the dream window and all additional windows are
+        // behind it.
+        mTmpWindow = null;
+        mTmpInitial = initial;
+
+        // First perform layout of any root windows (not attached to another window).
+        forAllWindows(mPerformLayout, true /* traverseTopToBottom */);
+
+        // Used to indicate that we have processed the dream window and all additional attached
+        // windows are behind it.
+        mTmpWindow2 = mTmpWindow;
+        mTmpWindow = null;
+
+        // Now perform layout of attached windows, which usually depend on the position of the
+        // window they are attached to. XXX does not deal with windows that are attached to windows
+        // that are themselves attached.
+        forAllWindows(mPerformLayoutAttached, true /* traverseTopToBottom */);
+
+        // Window frames may have changed. Tell the input dispatcher about it.
+        mService.mInputMonitor.layoutInputConsumers(dw, dh);
+        mService.mInputMonitor.setUpdateInputWindowsNeededLw();
+        if (updateInputWindows) {
+            mService.mInputMonitor.updateInputWindowsLw(false /*force*/);
+        }
+
+        mService.mPolicy.finishLayoutLw();
+        mService.mH.sendEmptyMessage(UPDATE_DOCKED_STACK_DIVIDER);
+    }
+
+    /**
+     * Takes a snapshot of the display.  In landscape mode this grabs the whole screen.
+     * In portrait mode, it grabs the full screenshot.
+     *
+     * @param width the width of the target bitmap
+     * @param height the height of the target bitmap
+     * @param includeFullDisplay true if the screen should not be cropped before capture
+     * @param frameScale the scale to apply to the frame, only used when width = -1 and height = -1
+     * @param config of the output bitmap
+     * @param wallpaperOnly true if only the wallpaper layer should be included in the screenshot
+     * @param includeDecor whether to include window decors, like the status or navigation bar
+     *                     background of the window
+     */
+    Bitmap screenshotApplications(IBinder appToken, int width, int height,
+            boolean includeFullDisplay, float frameScale, Bitmap.Config config,
+            boolean wallpaperOnly, boolean includeDecor) {
+        Bitmap bitmap = screenshotApplications(appToken, width, height, includeFullDisplay,
+                frameScale, wallpaperOnly, includeDecor, SurfaceControl::screenshot);
+        if (bitmap == null) {
+            return null;
+        }
+
+        if (DEBUG_SCREENSHOT) {
+            // TEST IF IT's ALL BLACK
+            int[] buffer = new int[bitmap.getWidth() * bitmap.getHeight()];
+            bitmap.getPixels(buffer, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(),
+                    bitmap.getHeight());
+            boolean allBlack = true;
+            final int firstColor = buffer[0];
+            for (int i = 0; i < buffer.length; i++) {
+                if (buffer[i] != firstColor) {
+                    allBlack = false;
+                    break;
+                }
+            }
+            if (allBlack) {
+                final WindowState appWin = mScreenshotApplicationState.appWin;
+                final int maxLayer = mScreenshotApplicationState.maxLayer;
+                final int minLayer = mScreenshotApplicationState.minLayer;
+                Slog.i(TAG_WM, "Screenshot " + appWin + " was monochrome(" +
+                        Integer.toHexString(firstColor) + ")! mSurfaceLayer=" +
+                        (appWin != null ?
+                                appWin.mWinAnimator.mSurfaceController.getLayer() : "null") +
+                        " minLayer=" + minLayer + " maxLayer=" + maxLayer);
+            }
+        }
+
+        // Create a copy of the screenshot that is immutable and backed in ashmem.
+        // This greatly reduces the overhead of passing the bitmap between processes.
+        Bitmap ret = bitmap.createAshmemBitmap(config);
+        bitmap.recycle();
+        return ret;
+    }
+
+    GraphicBuffer screenshotApplicationsToBuffer(IBinder appToken, int width, int height,
+            boolean includeFullDisplay, float frameScale, boolean wallpaperOnly,
+            boolean includeDecor) {
+        return screenshotApplications(appToken, width, height, includeFullDisplay, frameScale,
+                wallpaperOnly, includeDecor, SurfaceControl::screenshotToBuffer);
+    }
+
+    private <E> E screenshotApplications(IBinder appToken, int width, int height,
+            boolean includeFullDisplay, float frameScale, boolean wallpaperOnly,
+            boolean includeDecor, Screenshoter<E> screenshoter) {
+        int dw = mDisplayInfo.logicalWidth;
+        int dh = mDisplayInfo.logicalHeight;
+        if (dw == 0 || dh == 0) {
+            if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot of " + appToken
+                    + ": returning null. logical widthxheight=" + dw + "x" + dh);
+            return null;
+        }
+
+        E bitmap;
+
+        mScreenshotApplicationState.reset(appToken == null && !wallpaperOnly);
+        final Rect frame = new Rect();
+        final Rect stackBounds = new Rect();
+
+        final int aboveAppLayer = (mService.mPolicy.getWindowLayerFromTypeLw(TYPE_APPLICATION) + 1)
+                * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
+        final MutableBoolean mutableIncludeFullDisplay = new MutableBoolean(includeFullDisplay);
+        synchronized(mService.mWindowMap) {
+            if (!mService.mPolicy.isScreenOn()) {
+                if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Attempted to take screenshot while display"
+                        + " was off.");
+                return null;
+            }
+            // Figure out the part of the screen that is actually the app.
+            mScreenshotApplicationState.appWin = null;
+            forAllWindows(w -> {
+                if (!w.mHasSurface) {
+                    return false;
+                }
+                if (w.mLayer >= aboveAppLayer) {
+                    return false;
+                }
+                if (wallpaperOnly && !w.mIsWallpaper) {
+                    return false;
+                }
+                if (w.mIsImWindow) {
+                    return false;
+                } else if (w.mIsWallpaper) {
+                    // If this is the wallpaper layer and we're only looking for the wallpaper layer
+                    // then the target window state is this one.
+                    if (wallpaperOnly) {
+                        mScreenshotApplicationState.appWin = w;
+                    }
+
+                    if (mScreenshotApplicationState.appWin == null) {
+                        // We have not ran across the target window yet, so it is probably behind
+                        // the wallpaper. This can happen when the keyguard is up and all windows
+                        // are moved behind the wallpaper. We don't want to include the wallpaper
+                        // layer in the screenshot as it will cover-up the layer of the target
+                        // window.
+                        return false;
+                    }
+                    // Fall through. The target window is in front of the wallpaper. For this
+                    // case we want to include the wallpaper layer in the screenshot because
+                    // the target window might have some transparent areas.
+                } else if (appToken != null) {
+                    if (w.mAppToken == null || w.mAppToken.token != appToken) {
+                        // This app window is of no interest if it is not associated with the
+                        // screenshot app.
+                        return false;
+                    }
+                    mScreenshotApplicationState.appWin = w;
+                }
+
+                // Include this window.
+
+                final WindowStateAnimator winAnim = w.mWinAnimator;
+                int layer = winAnim.mSurfaceController.getLayer();
+                if (mScreenshotApplicationState.maxLayer < layer) {
+                    mScreenshotApplicationState.maxLayer = layer;
+                }
+                if (mScreenshotApplicationState.minLayer > layer) {
+                    mScreenshotApplicationState.minLayer = layer;
+                }
+
+                // Don't include wallpaper in bounds calculation
+                if (!w.mIsWallpaper && !mutableIncludeFullDisplay.value) {
+                    if (includeDecor) {
+                        final Task task = w.getTask();
+                        if (task != null) {
+                            task.getBounds(frame);
+                        } else {
+
+                            // No task bounds? Too bad! Ain't no screenshot then.
+                            return true;
+                        }
+                    } else {
+                        final Rect wf = w.mFrame;
+                        final Rect cr = w.mContentInsets;
+                        int left = wf.left + cr.left;
+                        int top = wf.top + cr.top;
+                        int right = wf.right - cr.right;
+                        int bottom = wf.bottom - cr.bottom;
+                        frame.union(left, top, right, bottom);
+                        w.getVisibleBounds(stackBounds);
+                        if (!Rect.intersects(frame, stackBounds)) {
+                            // Set frame empty if there's no intersection.
+                            frame.setEmpty();
+                        }
+                    }
+                }
+
+                final boolean foundTargetWs =
+                        (w.mAppToken != null && w.mAppToken.token == appToken)
+                                || (mScreenshotApplicationState.appWin != null && wallpaperOnly);
+                if (foundTargetWs && winAnim.getShown()) {
+                    mScreenshotApplicationState.screenshotReady = true;
+                }
+
+                if (w.isObscuringDisplay()){
+                    return true;
+                }
+                return false;
+            }, true /* traverseTopToBottom */);
+
+            final WindowState appWin = mScreenshotApplicationState.appWin;
+            final boolean screenshotReady = mScreenshotApplicationState.screenshotReady;
+            final int maxLayer = mScreenshotApplicationState.maxLayer;
+            final int minLayer = mScreenshotApplicationState.minLayer;
+
+            if (appToken != null && appWin == null) {
+                // Can't find a window to snapshot.
+                if (DEBUG_SCREENSHOT) Slog.i(TAG_WM,
+                        "Screenshot: Couldn't find a surface matching " + appToken);
+                return null;
+            }
+
+            if (!screenshotReady) {
+                Slog.i(TAG_WM, "Failed to capture screenshot of " + appToken +
+                        " appWin=" + (appWin == null ? "null" : (appWin + " drawState=" +
+                        appWin.mWinAnimator.mDrawState)));
+                return null;
+            }
+
+            // Screenshot is ready to be taken. Everything from here below will continue
+            // through the bottom of the loop and return a value. We only stay in the loop
+            // because we don't want to release the mWindowMap lock until the screenshot is
+            // taken.
+
+            if (maxLayer == 0) {
+                if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot of " + appToken
+                        + ": returning null maxLayer=" + maxLayer);
+                return null;
+            }
+
+            if (!mutableIncludeFullDisplay.value) {
+                // Constrain frame to the screen size.
+                if (!frame.intersect(0, 0, dw, dh)) {
+                    frame.setEmpty();
+                }
+            } else {
+                // Caller just wants entire display.
+                frame.set(0, 0, dw, dh);
+            }
+            if (frame.isEmpty()) {
+                return null;
+            }
+
+            if (width < 0) {
+                width = (int) (frame.width() * frameScale);
+            }
+            if (height < 0) {
+                height = (int) (frame.height() * frameScale);
+            }
+
+            // Tell surface flinger what part of the image to crop. Take the top
+            // right part of the application, and crop the larger dimension to fit.
+            Rect crop = new Rect(frame);
+            if (width / (float) frame.width() < height / (float) frame.height()) {
+                int cropWidth = (int)((float)width / (float)height * frame.height());
+                crop.right = crop.left + cropWidth;
+            } else {
+                int cropHeight = (int)((float)height / (float)width * frame.width());
+                crop.bottom = crop.top + cropHeight;
+            }
+
+            // The screenshot API does not apply the current screen rotation.
+            int rot = mDisplay.getRotation();
+
+            if (rot == ROTATION_90 || rot == ROTATION_270) {
+                rot = (rot == ROTATION_90) ? ROTATION_270 : ROTATION_90;
+            }
+
+            // Surfaceflinger is not aware of orientation, so convert our logical
+            // crop to surfaceflinger's portrait orientation.
+            convertCropForSurfaceFlinger(crop, rot, dw, dh);
+
+            if (DEBUG_SCREENSHOT) {
+                Slog.i(TAG_WM, "Screenshot: " + dw + "x" + dh + " from " + minLayer + " to "
+                        + maxLayer + " appToken=" + appToken);
+                forAllWindows(w -> {
+                    final WindowSurfaceController controller = w.mWinAnimator.mSurfaceController;
+                    Slog.i(TAG_WM, w + ": " + w.mLayer
+                            + " animLayer=" + w.mWinAnimator.mAnimLayer
+                            + " surfaceLayer=" + ((controller == null)
+                            ? "null" : controller.getLayer()));
+                }, false /* traverseTopToBottom */);
+            }
+
+            final ScreenRotationAnimation screenRotationAnimation =
+                    mService.mAnimator.getScreenRotationAnimationLocked(DEFAULT_DISPLAY);
+            final boolean inRotation = screenRotationAnimation != null &&
+                    screenRotationAnimation.isAnimating();
+            if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG_WM,
+                    "Taking screenshot while rotating");
+
+            // We force pending transactions to flush before taking
+            // the screenshot by pushing an empty synchronous transaction.
+            SurfaceControl.openTransaction();
+            SurfaceControl.closeTransactionSync();
+
+            bitmap = screenshoter.screenshot(crop, width, height, minLayer, maxLayer,
+                    inRotation, rot);
+            if (bitmap == null) {
+                Slog.w(TAG_WM, "Screenshot failure taking screenshot for (" + dw + "x" + dh
+                        + ") to layer " + maxLayer);
+                return null;
+            }
+        }
+        return bitmap;
+    }
+
+    // TODO: Can this use createRotationMatrix()?
+    private static void convertCropForSurfaceFlinger(Rect crop, int rot, int dw, int dh) {
+        if (rot == Surface.ROTATION_90) {
+            final int tmp = crop.top;
+            crop.top = dw - crop.right;
+            crop.right = crop.bottom;
+            crop.bottom = dw - crop.left;
+            crop.left = tmp;
+        } else if (rot == Surface.ROTATION_180) {
+            int tmp = crop.top;
+            crop.top = dh - crop.bottom;
+            crop.bottom = dh - tmp;
+            tmp = crop.right;
+            crop.right = dw - crop.left;
+            crop.left = dw - tmp;
+        } else if (rot == Surface.ROTATION_270) {
+            final int tmp = crop.top;
+            crop.top = crop.left;
+            crop.left = dh - crop.bottom;
+            crop.bottom = crop.right;
+            crop.right = dh - tmp;
+        }
+    }
+
+    void onSeamlessRotationTimeout() {
+        // Used to indicate the layout is needed.
+        mTmpWindow = null;
+
+        forAllWindows(w -> {
+            if (!w.mSeamlesslyRotated) {
+                return;
+            }
+            mTmpWindow = w;
+            w.setDisplayLayoutNeeded();
+            mService.markForSeamlessRotation(w, false);
+        }, true /* traverseTopToBottom */);
+
+        if (mTmpWindow != null) {
+            mService.mWindowPlacerLocked.performSurfacePlacement();
+        }
+    }
+
+    void setExitingTokensHasVisible(boolean hasVisible) {
+        for (int i = mExitingTokens.size() - 1; i >= 0; i--) {
+            mExitingTokens.get(i).hasVisible = hasVisible;
+        }
+
+        // Initialize state of exiting applications.
+        mTaskStackContainers.setExitingTokensHasVisible(hasVisible);
+    }
+
+    void removeExistingTokensIfPossible() {
+        for (int i = mExitingTokens.size() - 1; i >= 0; i--) {
+            final WindowToken token = mExitingTokens.get(i);
+            if (!token.hasVisible) {
+                mExitingTokens.remove(i);
+            }
+        }
+
+        // Time to remove any exiting applications?
+        mTaskStackContainers.removeExistingAppTokensIfPossible();
+    }
+
+    @Override
+    void onDescendantOverrideConfigurationChanged() {
+        setLayoutNeeded();
+        mService.requestTraversal();
+    }
+
+    boolean okToDisplay() {
+        if (mDisplayId == DEFAULT_DISPLAY) {
+            return !mService.mDisplayFrozen
+                    && mService.mDisplayEnabled && mService.mPolicy.isScreenOn();
+        }
+        return mDisplayInfo.state == Display.STATE_ON;
+    }
+
+    boolean okToAnimate() {
+        return okToDisplay() &&
+                (mDisplayId != DEFAULT_DISPLAY || mService.mPolicy.okToAnimate());
+    }
+
+    static final class TaskForResizePointSearchResult {
+        boolean searchDone;
+        Task taskForResize;
+
+        void reset() {
+            searchDone = false;
+            taskForResize = null;
+        }
+    }
+
+    private static final class ApplySurfaceChangesTransactionState {
+        boolean displayHasContent;
+        boolean obscured;
+        boolean syswin;
+        boolean focusDisplayed;
+        float preferredRefreshRate;
+        int preferredModeId;
+
+        void reset() {
+            displayHasContent = false;
+            obscured = false;
+            syswin = false;
+            focusDisplayed = false;
+            preferredRefreshRate = 0;
+            preferredModeId = 0;
+        }
+    }
+
+    private static final class ScreenshotApplicationState {
+        WindowState appWin;
+        int maxLayer;
+        int minLayer;
+        boolean screenshotReady;
+
+        void reset(boolean screenshotReady) {
+            appWin = null;
+            maxLayer = 0;
+            minLayer = 0;
+            this.screenshotReady = screenshotReady;
+            minLayer = (screenshotReady) ? 0 : Integer.MAX_VALUE;
+        }
+    }
+
+    /**
+     * Base class for any direct child window container of {@link #DisplayContent} need to inherit
+     * from. This is mainly a pass through class that allows {@link #DisplayContent} to have
+     * homogeneous children type which is currently required by sub-classes of
+     * {@link WindowContainer} class.
+     */
+    static class DisplayChildWindowContainer<E extends WindowContainer> extends WindowContainer<E> {
+
+        int size() {
+            return mChildren.size();
+        }
+
+        E get(int index) {
+            return mChildren.get(index);
+        }
+
+        @Override
+        boolean fillsParent() {
+            return true;
+        }
+
+        @Override
+        boolean isVisible() {
+            return true;
+        }
+    }
+
+    /**
+     * Window container class that contains all containers on this display relating to Apps.
+     * I.e Activities.
+     */
+    private final class TaskStackContainers extends DisplayChildWindowContainer<TaskStack> {
+
+        /**
+         * Adds the stack to this container.
+         * @see WindowManagerService#addStackToDisplay(int, int, boolean)
+         */
+        void addStackToDisplay(TaskStack stack, boolean onTop) {
+            if (stack.mStackId == HOME_STACK_ID) {
+                if (mHomeStack != null) {
+                    throw new IllegalArgumentException("attachStack: HOME_STACK_ID (0) not first.");
+                }
+                mHomeStack = stack;
+            }
+            addChild(stack, onTop);
+            stack.onDisplayChanged(DisplayContent.this);
+        }
+
+        /** Removes the stack from its container and prepare for changing the parent. */
+        void removeStackFromDisplay(TaskStack stack) {
+            removeChild(stack);
+            stack.onRemovedFromDisplay();
+        }
+
+        private void addChild(TaskStack stack, boolean toTop) {
+            final int addIndex = findPositionForStack(toTop ? mChildren.size() : 0, stack,
+                    true /* adding */);
+            addChild(stack, addIndex);
+            setLayoutNeeded();
+        }
+
+
+        @Override
+        boolean isOnTop() {
+            // Considered always on top
+            return true;
+        }
+
+        @Override
+        void positionChildAt(int position, TaskStack child, boolean includingParents) {
+            if (child.getWindowConfiguration().isAlwaysOnTop()
+                    && position != POSITION_TOP) {
+                // This stack is always-on-top, override the default behavior.
+                Slog.w(TAG_WM, "Ignoring move of always-on-top stack=" + this + " to bottom");
+
+                // Moving to its current position, as we must call super but we don't want to
+                // perform any meaningful action.
+                final int currentPosition = mChildren.indexOf(child);
+                super.positionChildAt(currentPosition, child, false /* includingParents */);
+                return;
+            }
+
+            final int targetPosition = findPositionForStack(position, child, false /* adding */);
+            super.positionChildAt(targetPosition, child, includingParents);
+
+            setLayoutNeeded();
+        }
+
+        /**
+         * When stack is added or repositioned, find a proper position for it.
+         * This will make sure that pinned stack always stays on top.
+         * @param requestedPosition Position requested by caller.
+         * @param stack Stack to be added or positioned.
+         * @param adding Flag indicates whether we're adding a new stack or positioning an existing.
+         * @return The proper position for the stack.
+         */
+        private int findPositionForStack(int requestedPosition, TaskStack stack, boolean adding) {
+            final int topChildPosition = mChildren.size() - 1;
+            boolean toTop = requestedPosition == POSITION_TOP;
+            toTop |= adding ? requestedPosition >= topChildPosition + 1
+                    : requestedPosition >= topChildPosition;
+            int targetPosition = requestedPosition;
+
+            if (toTop && stack.mStackId != PINNED_STACK_ID
+                    && getStackById(PINNED_STACK_ID) != null) {
+                // The pinned stack is always the top most stack (always-on-top) when it is present.
+                TaskStack topStack = mChildren.get(topChildPosition);
+                if (topStack.mStackId != PINNED_STACK_ID) {
+                    throw new IllegalStateException("Pinned stack isn't top stack??? " + mChildren);
+                }
+
+                // So, stack is moved just below the pinned stack.
+                // When we're adding a new stack the target is the current pinned stack position.
+                // When we're positioning an existing stack the target is the position below pinned
+                // stack, because WindowContainer#positionAt() first removes element and then adds
+                // it to specified place.
+                targetPosition = adding ? topChildPosition : topChildPosition - 1;
+            }
+
+            return targetPosition;
+        }
+
+        @Override
+        boolean forAllWindows(ToBooleanFunction<WindowState> callback,
+                boolean traverseTopToBottom) {
+            if (traverseTopToBottom) {
+                if (super.forAllWindows(callback, traverseTopToBottom)) {
+                    return true;
+                }
+                if (forAllExitingAppTokenWindows(callback, traverseTopToBottom)) {
+                    return true;
+                }
+            } else {
+                if (forAllExitingAppTokenWindows(callback, traverseTopToBottom)) {
+                    return true;
+                }
+                if (super.forAllWindows(callback, traverseTopToBottom)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        private boolean forAllExitingAppTokenWindows(ToBooleanFunction<WindowState> callback,
+                boolean traverseTopToBottom) {
+            // For legacy reasons we process the TaskStack.mExitingAppTokens first here before the
+            // app tokens.
+            // TODO: Investigate if we need to continue to do this or if we can just process them
+            // in-order.
+            if (traverseTopToBottom) {
+                for (int i = mChildren.size() - 1; i >= 0; --i) {
+                    final AppTokenList appTokens = mChildren.get(i).mExitingAppTokens;
+                    for (int j = appTokens.size() - 1; j >= 0; --j) {
+                        if (appTokens.get(j).forAllWindowsUnchecked(callback,
+                                traverseTopToBottom)) {
+                            return true;
+                        }
+                    }
+                }
+            } else {
+                final int count = mChildren.size();
+                for (int i = 0; i < count; ++i) {
+                    final AppTokenList appTokens = mChildren.get(i).mExitingAppTokens;
+                    final int appTokensCount = appTokens.size();
+                    for (int j = 0; j < appTokensCount; j++) {
+                        if (appTokens.get(j).forAllWindowsUnchecked(callback,
+                                traverseTopToBottom)) {
+                            return true;
+                        }
+                    }
+                }
+            }
+            return false;
+        }
+
+        void setExitingTokensHasVisible(boolean hasVisible) {
+            for (int i = mChildren.size() - 1; i >= 0; --i) {
+                final AppTokenList appTokens = mChildren.get(i).mExitingAppTokens;
+                for (int j = appTokens.size() - 1; j >= 0; --j) {
+                    appTokens.get(j).hasVisible = hasVisible;
+                }
+            }
+        }
+
+        void removeExistingAppTokensIfPossible() {
+            for (int i = mChildren.size() - 1; i >= 0; --i) {
+                final AppTokenList appTokens = mChildren.get(i).mExitingAppTokens;
+                for (int j = appTokens.size() - 1; j >= 0; --j) {
+                    final AppWindowToken token = appTokens.get(j);
+                    if (!token.hasVisible && !mService.mClosingApps.contains(token)
+                            && (!token.mIsExiting || token.isEmpty())) {
+                        // Make sure there is no animation running on this token, so any windows
+                        // associated with it will be removed as soon as their animations are
+                        // complete.
+                        token.mAppAnimator.clearAnimation();
+                        token.mAppAnimator.animating = false;
+                        if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG,
+                                "performLayout: App token exiting now removed" + token);
+                        token.removeIfPossible();
+                    }
+                }
+            }
+        }
+
+        @Override
+        int getOrientation() {
+            if (isStackVisible(DOCKED_STACK_ID) || isStackVisible(FREEFORM_WORKSPACE_STACK_ID)) {
+                // Apps and their containers are not allowed to specify an orientation while the
+                // docked or freeform stack is visible...except for the home stack/task if the
+                // docked stack is minimized and it actually set something.
+                if (mHomeStack != null && mHomeStack.isVisible()
+                        && mDividerControllerLocked.isMinimizedDock()) {
+                    final int orientation = mHomeStack.getOrientation();
+                    if (orientation != SCREEN_ORIENTATION_UNSET) {
+                        return orientation;
+                    }
+                }
+                return SCREEN_ORIENTATION_UNSPECIFIED;
+            }
+
+            final int orientation = super.getOrientation();
+            if (orientation != SCREEN_ORIENTATION_UNSET
+                    && orientation != SCREEN_ORIENTATION_BEHIND) {
+                if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
+                        "App is requesting an orientation, return " + orientation);
+                return orientation;
+            }
+
+            if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
+                    "No app is requesting an orientation, return " + mLastOrientation);
+            // The next app has not been requested to be visible, so we keep the current orientation
+            // to prevent freezing/unfreezing the display too early.
+            return mLastOrientation;
+        }
+    }
+
+    /**
+     * Window container class that contains all containers on this display that are not related to
+     * Apps. E.g. status bar.
+     */
+    private final class NonAppWindowContainers extends DisplayChildWindowContainer<WindowToken> {
+        /**
+         * Compares two child window tokens returns -1 if the first is lesser than the second in
+         * terms of z-order and 1 otherwise.
+         */
+        private final Comparator<WindowToken> mWindowComparator = (token1, token2) ->
+                // Tokens with higher base layer are z-ordered on-top.
+                mService.mPolicy.getWindowLayerFromTypeLw(token1.windowType,
+                        token1.mOwnerCanManageAppTokens)
+                < mService.mPolicy.getWindowLayerFromTypeLw(token2.windowType,
+                        token2.mOwnerCanManageAppTokens) ? -1 : 1;
+
+        private final Predicate<WindowState> mGetOrientingWindow = w -> {
+            if (!w.isVisibleLw() || !w.mPolicyVisibilityAfterAnim) {
+                return false;
+            }
+            final int req = w.mAttrs.screenOrientation;
+            if(req == SCREEN_ORIENTATION_UNSPECIFIED || req == SCREEN_ORIENTATION_BEHIND
+                    || req == SCREEN_ORIENTATION_UNSET) {
+                return false;
+            }
+            return true;
+        };
+
+        private final String mName;
+        NonAppWindowContainers(String name) {
+            mName = name;
+        }
+
+        void addChild(WindowToken token) {
+            addChild(token, mWindowComparator);
+        }
+
+        @Override
+        int getOrientation() {
+            final WindowManagerPolicy policy = mService.mPolicy;
+            // Find a window requesting orientation.
+            final WindowState win = getWindow(mGetOrientingWindow);
+
+            if (win != null) {
+                final int req = win.mAttrs.screenOrientation;
+                if (policy.isKeyguardHostWindow(win.mAttrs)) {
+                    mLastKeyguardForcedOrientation = req;
+                    if (mService.mKeyguardGoingAway) {
+                        // Keyguard can't affect the orientation if it is going away...
+                        mLastWindowForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+                        return SCREEN_ORIENTATION_UNSET;
+                    }
+                }
+                if (DEBUG_ORIENTATION) Slog.v(TAG_WM, win + " forcing orientation to " + req);
+                return (mLastWindowForcedOrientation = req);
+            }
+
+            mLastWindowForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+
+            if (policy.isKeyguardShowingAndNotOccluded()
+                    || mService.mAppTransition.getAppTransition() == TRANSIT_KEYGUARD_UNOCCLUDE) {
+                return mLastKeyguardForcedOrientation;
+            }
+
+            return SCREEN_ORIENTATION_UNSET;
+        }
+
+        @Override
+        String getName() {
+            return mName;
+        }
+    }
+
+    /**
+     * Interface to screenshot into various types, i.e. {@link Bitmap} and {@link GraphicBuffer}.
+     */
+    @FunctionalInterface
+    private interface Screenshoter<E> {
+        E screenshot(Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
+                boolean useIdentityTransform, int rotation);
+    }
+}
diff --git a/com/android/server/wm/DisplaySettings.java b/com/android/server/wm/DisplaySettings.java
new file mode 100644
index 0000000..7f79686
--- /dev/null
+++ b/com/android/server/wm/DisplaySettings.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.graphics.Rect;
+import android.os.Environment;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+/**
+ * Current persistent settings about a display
+ */
+public class DisplaySettings {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplaySettings" : TAG_WM;
+
+    private final AtomicFile mFile;
+    private final HashMap<String, Entry> mEntries = new HashMap<String, Entry>();
+
+    public static class Entry {
+        public final String name;
+        public int overscanLeft;
+        public int overscanTop;
+        public int overscanRight;
+        public int overscanBottom;
+
+        public Entry(String _name) {
+            name = _name;
+        }
+    }
+
+    public DisplaySettings() {
+        File dataDir = Environment.getDataDirectory();
+        File systemDir = new File(dataDir, "system");
+        mFile = new AtomicFile(new File(systemDir, "display_settings.xml"));
+    }
+
+    public void getOverscanLocked(String name, String uniqueId, Rect outRect) {
+        // Try to get the entry with the unique if possible.
+        // Else, fall back on the display name.
+        Entry entry;
+        if (uniqueId == null || (entry = mEntries.get(uniqueId)) == null) {
+            entry = mEntries.get(name);
+        }
+        if (entry != null) {
+            outRect.left = entry.overscanLeft;
+            outRect.top = entry.overscanTop;
+            outRect.right = entry.overscanRight;
+            outRect.bottom = entry.overscanBottom;
+        } else {
+            outRect.set(0, 0, 0, 0);
+        }
+    }
+
+    public void setOverscanLocked(String uniqueId, String name, int left, int top, int right,
+            int bottom) {
+        if (left == 0 && top == 0 && right == 0 && bottom == 0) {
+            // Right now all we are storing is overscan; if there is no overscan,
+            // we have no need for the entry.
+            mEntries.remove(uniqueId);
+            // Legacy name might have been in used, so we need to clear it.
+            mEntries.remove(name);
+            return;
+        }
+        Entry entry = mEntries.get(uniqueId);
+        if (entry == null) {
+            entry = new Entry(uniqueId);
+            mEntries.put(uniqueId, entry);
+        }
+        entry.overscanLeft = left;
+        entry.overscanTop = top;
+        entry.overscanRight = right;
+        entry.overscanBottom = bottom;
+    }
+
+    public void readSettingsLocked() {
+        FileInputStream stream;
+        try {
+            stream = mFile.openRead();
+        } catch (FileNotFoundException e) {
+            Slog.i(TAG, "No existing display settings " + mFile.getBaseFile()
+                    + "; starting empty");
+            return;
+        }
+        boolean success = false;
+        try {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(stream, StandardCharsets.UTF_8.name());
+            int type;
+            while ((type = parser.next()) != XmlPullParser.START_TAG
+                    && type != XmlPullParser.END_DOCUMENT) {
+                // Do nothing.
+            }
+
+            if (type != XmlPullParser.START_TAG) {
+                throw new IllegalStateException("no start tag found");
+            }
+
+            int outerDepth = parser.getDepth();
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                    continue;
+                }
+
+                String tagName = parser.getName();
+                if (tagName.equals("display")) {
+                    readDisplay(parser);
+                } else {
+                    Slog.w(TAG, "Unknown element under <display-settings>: "
+                            + parser.getName());
+                    XmlUtils.skipCurrentTag(parser);
+                }
+            }
+            success = true;
+        } catch (IllegalStateException e) {
+            Slog.w(TAG, "Failed parsing " + e);
+        } catch (NullPointerException e) {
+            Slog.w(TAG, "Failed parsing " + e);
+        } catch (NumberFormatException e) {
+            Slog.w(TAG, "Failed parsing " + e);
+        } catch (XmlPullParserException e) {
+            Slog.w(TAG, "Failed parsing " + e);
+        } catch (IOException e) {
+            Slog.w(TAG, "Failed parsing " + e);
+        } catch (IndexOutOfBoundsException e) {
+            Slog.w(TAG, "Failed parsing " + e);
+        } finally {
+            if (!success) {
+                mEntries.clear();
+            }
+            try {
+                stream.close();
+            } catch (IOException e) {
+            }
+        }
+    }
+
+    private int getIntAttribute(XmlPullParser parser, String name) {
+        try {
+            String str = parser.getAttributeValue(null, name);
+            return str != null ? Integer.parseInt(str) : 0;
+        } catch (NumberFormatException e) {
+            return 0;
+        }
+    }
+
+    private void readDisplay(XmlPullParser parser) throws NumberFormatException,
+            XmlPullParserException, IOException {
+        String name = parser.getAttributeValue(null, "name");
+        if (name != null) {
+            Entry entry = new Entry(name);
+            entry.overscanLeft = getIntAttribute(parser, "overscanLeft");
+            entry.overscanTop = getIntAttribute(parser, "overscanTop");
+            entry.overscanRight = getIntAttribute(parser, "overscanRight");
+            entry.overscanBottom = getIntAttribute(parser, "overscanBottom");
+            mEntries.put(name, entry);
+        }
+        XmlUtils.skipCurrentTag(parser);
+    }
+
+    public void writeSettingsLocked() {
+        FileOutputStream stream;
+        try {
+            stream = mFile.startWrite();
+        } catch (IOException e) {
+            Slog.w(TAG, "Failed to write display settings: " + e);
+            return;
+        }
+
+        try {
+            XmlSerializer out = new FastXmlSerializer();
+            out.setOutput(stream, StandardCharsets.UTF_8.name());
+            out.startDocument(null, true);
+            out.startTag(null, "display-settings");
+
+            for (Entry entry : mEntries.values()) {
+                out.startTag(null, "display");
+                out.attribute(null, "name", entry.name);
+                if (entry.overscanLeft != 0) {
+                    out.attribute(null, "overscanLeft", Integer.toString(entry.overscanLeft));
+                }
+                if (entry.overscanTop != 0) {
+                    out.attribute(null, "overscanTop", Integer.toString(entry.overscanTop));
+                }
+                if (entry.overscanRight != 0) {
+                    out.attribute(null, "overscanRight", Integer.toString(entry.overscanRight));
+                }
+                if (entry.overscanBottom != 0) {
+                    out.attribute(null, "overscanBottom", Integer.toString(entry.overscanBottom));
+                }
+                out.endTag(null, "display");
+            }
+
+            out.endTag(null, "display-settings");
+            out.endDocument();
+            mFile.finishWrite(stream);
+        } catch (IOException e) {
+            Slog.w(TAG, "Failed to write display settings, restoring backup.", e);
+            mFile.failWrite(stream);
+        }
+    }
+}
diff --git a/com/android/server/wm/DockedStackDividerController.java b/com/android/server/wm/DockedStackDividerController.java
new file mode 100644
index 0000000..030b986
--- /dev/null
+++ b/com/android/server/wm/DockedStackDividerController.java
@@ -0,0 +1,929 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+import static android.view.WindowManager.DOCKED_BOTTOM;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
+import static android.view.WindowManager.DOCKED_TOP;
+import static com.android.server.wm.AppTransition.DEFAULT_APP_TRANSITION_DURATION;
+import static com.android.server.wm.AppTransition.TOUCH_RESPONSE_INTERPOLATOR;
+import static com.android.server.wm.AppTransition.TRANSIT_NONE;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.H.NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED;
+import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;
+import static com.android.server.wm.proto.DockedStackDividerControllerProto.MINIMIZED_DOCK;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+import android.view.DisplayInfo;
+import android.view.IDockedStackListener;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+import android.view.inputmethod.InputMethodManagerInternal;
+
+import com.android.internal.policy.DividerSnapAlgorithm;
+import com.android.internal.policy.DockedDividerUtils;
+import com.android.server.LocalServices;
+import com.android.server.wm.DimLayer.DimLayerUser;
+import com.android.server.wm.WindowManagerService.H;
+
+import java.io.PrintWriter;
+
+/**
+ * Keeps information about the docked stack divider.
+ */
+public class DockedStackDividerController implements DimLayerUser {
+
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "DockedStackDividerController" : TAG_WM;
+
+    /**
+     * The fraction during the maximize/clip reveal animation the divider meets the edge of the clip
+     * revealing surface at the earliest.
+     */
+    private static final float CLIP_REVEAL_MEET_EARLIEST = 0.6f;
+
+    /**
+     * The fraction during the maximize/clip reveal animation the divider meets the edge of the clip
+     * revealing surface at the latest.
+     */
+    private static final float CLIP_REVEAL_MEET_LAST = 1f;
+
+    /**
+     * If the app translates at least CLIP_REVEAL_MEET_FRACTION_MIN * minimize distance, we start
+     * meet somewhere between {@link #CLIP_REVEAL_MEET_LAST} and {@link #CLIP_REVEAL_MEET_EARLIEST}.
+     */
+    private static final float CLIP_REVEAL_MEET_FRACTION_MIN = 0.4f;
+
+    /**
+     * If the app translates equals or more than CLIP_REVEAL_MEET_FRACTION_MIN * minimize distance,
+     * we meet at {@link #CLIP_REVEAL_MEET_EARLIEST}.
+     */
+    private static final float CLIP_REVEAL_MEET_FRACTION_MAX = 0.8f;
+
+    private static final Interpolator IME_ADJUST_ENTRY_INTERPOLATOR =
+            new PathInterpolator(0.2f, 0f, 0.1f, 1f);
+
+    private static final long IME_ADJUST_ANIM_DURATION = 280;
+
+    private static final long IME_ADJUST_DRAWN_TIMEOUT = 200;
+
+    private static final int DIVIDER_WIDTH_INACTIVE_DP = 4;
+
+    private final WindowManagerService mService;
+    private final DisplayContent mDisplayContent;
+    private int mDividerWindowWidth;
+    private int mDividerWindowWidthInactive;
+    private int mDividerInsets;
+    private int mTaskHeightInMinimizedMode;
+    private boolean mResizing;
+    private WindowState mWindow;
+    private final Rect mTmpRect = new Rect();
+    private final Rect mTmpRect2 = new Rect();
+    private final Rect mTmpRect3 = new Rect();
+    private final Rect mLastRect = new Rect();
+    private boolean mLastVisibility = false;
+    private final RemoteCallbackList<IDockedStackListener> mDockedStackListeners
+            = new RemoteCallbackList<>();
+    private final DimLayer mDimLayer;
+
+    private boolean mMinimizedDock;
+    private boolean mAnimatingForMinimizedDockedStack;
+    private boolean mAnimationStarted;
+    private long mAnimationStartTime;
+    private float mAnimationStart;
+    private float mAnimationTarget;
+    private long mAnimationDuration;
+    private boolean mAnimationStartDelayed;
+    private final Interpolator mMinimizedDockInterpolator;
+    private float mMaximizeMeetFraction;
+    private final Rect mTouchRegion = new Rect();
+    private boolean mAnimatingForIme;
+    private boolean mAdjustedForIme;
+    private int mImeHeight;
+    private WindowState mDelayedImeWin;
+    private boolean mAdjustedForDivider;
+    private float mDividerAnimationStart;
+    private float mDividerAnimationTarget;
+    float mLastAnimationProgress;
+    float mLastDividerProgress;
+    private final DividerSnapAlgorithm[] mSnapAlgorithmForRotation = new DividerSnapAlgorithm[4];
+    private boolean mImeHideRequested;
+
+    DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) {
+        mService = service;
+        mDisplayContent = displayContent;
+        final Context context = service.mContext;
+        mDimLayer = new DimLayer(displayContent.mService, this, displayContent.getDisplayId(),
+                "DockedStackDim");
+        mMinimizedDockInterpolator = AnimationUtils.loadInterpolator(
+                context, android.R.interpolator.fast_out_slow_in);
+        loadDimens();
+    }
+
+    int getSmallestWidthDpForBounds(Rect bounds) {
+        final DisplayInfo di = mDisplayContent.getDisplayInfo();
+
+        final int baseDisplayWidth = mDisplayContent.mBaseDisplayWidth;
+        final int baseDisplayHeight = mDisplayContent.mBaseDisplayHeight;
+        int minWidth = Integer.MAX_VALUE;
+
+        // Go through all screen orientations and find the orientation in which the task has the
+        // smallest width.
+        for (int rotation = 0; rotation < 4; rotation++) {
+            mTmpRect.set(bounds);
+            mDisplayContent.rotateBounds(di.rotation, rotation, mTmpRect);
+            final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+            mTmpRect2.set(0, 0,
+                    rotated ? baseDisplayHeight : baseDisplayWidth,
+                    rotated ? baseDisplayWidth : baseDisplayHeight);
+            final int orientation = mTmpRect2.width() <= mTmpRect2.height()
+                    ? ORIENTATION_PORTRAIT
+                    : ORIENTATION_LANDSCAPE;
+            final int dockSide = TaskStack.getDockSideUnchecked(mTmpRect, mTmpRect2, orientation);
+            final int position = DockedDividerUtils.calculatePositionForBounds(mTmpRect, dockSide,
+                    getContentWidth());
+
+            // Since we only care about feasible states, snap to the closest snap target, like it
+            // would happen when actually rotating the screen.
+            final int snappedPosition = mSnapAlgorithmForRotation[rotation]
+                    .calculateNonDismissingSnapTarget(position).position;
+            DockedDividerUtils.calculateBoundsForPosition(snappedPosition, dockSide, mTmpRect,
+                    mTmpRect2.width(), mTmpRect2.height(), getContentWidth());
+            mService.mPolicy.getStableInsetsLw(rotation, mTmpRect2.width(), mTmpRect2.height(),
+                    mTmpRect3);
+            mService.intersectDisplayInsetBounds(mTmpRect2, mTmpRect3, mTmpRect);
+            minWidth = Math.min(mTmpRect.width(), minWidth);
+        }
+        return (int) (minWidth / mDisplayContent.getDisplayMetrics().density);
+    }
+
+    void getHomeStackBoundsInDockedMode(Rect outBounds) {
+        final DisplayInfo di = mDisplayContent.getDisplayInfo();
+        mService.mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
+                mTmpRect);
+        int dividerSize = mDividerWindowWidth - 2 * mDividerInsets;
+        Configuration configuration = mDisplayContent.getConfiguration();
+        // The offset in the left (landscape)/top (portrait) is calculated with the minimized
+        // offset value with the divider size and any system insets in that direction.
+        if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
+            outBounds.set(0, mTaskHeightInMinimizedMode + dividerSize + mTmpRect.top,
+                    di.logicalWidth, di.logicalHeight);
+        } else {
+            // In landscape append the left position with the statusbar height to match the
+            // minimized size height in portrait mode.
+            outBounds.set(mTaskHeightInMinimizedMode + dividerSize + mTmpRect.left + mTmpRect.top,
+                    0, di.logicalWidth, di.logicalHeight);
+        }
+    }
+
+    boolean isHomeStackResizable() {
+        final TaskStack homeStack = mDisplayContent.getHomeStack();
+        if (homeStack == null) {
+            return false;
+        }
+        final Task homeTask = homeStack.findHomeTask();
+        return homeTask != null && homeTask.isResizeable();
+    }
+
+    private void initSnapAlgorithmForRotations() {
+        final Configuration baseConfig = mDisplayContent.getConfiguration();
+
+        // Initialize the snap algorithms for all 4 screen orientations.
+        final Configuration config = new Configuration();
+        for (int rotation = 0; rotation < 4; rotation++) {
+            final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+            final int dw = rotated
+                    ? mDisplayContent.mBaseDisplayHeight
+                    : mDisplayContent.mBaseDisplayWidth;
+            final int dh = rotated
+                    ? mDisplayContent.mBaseDisplayWidth
+                    : mDisplayContent.mBaseDisplayHeight;
+            mService.mPolicy.getStableInsetsLw(rotation, dw, dh, mTmpRect);
+            config.unset();
+            config.orientation = (dw <= dh) ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
+
+            final int displayId = mDisplayContent.getDisplayId();
+            final int appWidth = mService.mPolicy.getNonDecorDisplayWidth(dw, dh, rotation,
+                baseConfig.uiMode, displayId);
+            final int appHeight = mService.mPolicy.getNonDecorDisplayHeight(dw, dh, rotation,
+                baseConfig.uiMode, displayId);
+            mService.mPolicy.getNonDecorInsetsLw(rotation, dw, dh, mTmpRect);
+            final int leftInset = mTmpRect.left;
+            final int topInset = mTmpRect.top;
+
+            config.windowConfiguration.setAppBounds(leftInset /*left*/, topInset /*top*/,
+                    leftInset + appWidth /*right*/, topInset + appHeight /*bottom*/);
+
+            config.screenWidthDp = (int)
+                    (mService.mPolicy.getConfigDisplayWidth(dw, dh, rotation, baseConfig.uiMode,
+                            displayId) / mDisplayContent.getDisplayMetrics().density);
+            config.screenHeightDp = (int)
+                    (mService.mPolicy.getConfigDisplayHeight(dw, dh, rotation, baseConfig.uiMode,
+                            displayId) / mDisplayContent.getDisplayMetrics().density);
+            final Context rotationContext = mService.mContext.createConfigurationContext(config);
+            mSnapAlgorithmForRotation[rotation] = new DividerSnapAlgorithm(
+                    rotationContext.getResources(), dw, dh, getContentWidth(),
+                    config.orientation == ORIENTATION_PORTRAIT, mTmpRect);
+        }
+    }
+
+    private void loadDimens() {
+        final Context context = mService.mContext;
+        mDividerWindowWidth = context.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.docked_stack_divider_thickness);
+        mDividerInsets = context.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.docked_stack_divider_insets);
+        mDividerWindowWidthInactive = WindowManagerService.dipToPixel(
+                DIVIDER_WIDTH_INACTIVE_DP, mDisplayContent.getDisplayMetrics());
+        mTaskHeightInMinimizedMode = context.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.task_height_of_minimized_mode);
+        initSnapAlgorithmForRotations();
+    }
+
+    void onConfigurationChanged() {
+        loadDimens();
+    }
+
+    boolean isResizing() {
+        return mResizing;
+    }
+
+    int getContentWidth() {
+        return mDividerWindowWidth - 2 * mDividerInsets;
+    }
+
+    int getContentInsets() {
+        return mDividerInsets;
+    }
+
+    int getContentWidthInactive() {
+        return mDividerWindowWidthInactive;
+    }
+
+    void setResizing(boolean resizing) {
+        if (mResizing != resizing) {
+            mResizing = resizing;
+            resetDragResizingChangeReported();
+        }
+    }
+
+    void setTouchRegion(Rect touchRegion) {
+        mTouchRegion.set(touchRegion);
+    }
+
+    void getTouchRegion(Rect outRegion) {
+        outRegion.set(mTouchRegion);
+        outRegion.offset(mWindow.getFrameLw().left, mWindow.getFrameLw().top);
+    }
+
+    private void resetDragResizingChangeReported() {
+        mDisplayContent.forAllWindows(WindowState::resetDragResizingChangeReported,
+                true /* traverseTopToBottom */ );
+    }
+
+    void setWindow(WindowState window) {
+        mWindow = window;
+        reevaluateVisibility(false);
+    }
+
+    void reevaluateVisibility(boolean force) {
+        if (mWindow == null) {
+            return;
+        }
+        TaskStack stack = mDisplayContent.getDockedStackIgnoringVisibility();
+
+        // If the stack is invisible, we policy force hide it in WindowAnimator.shouldForceHide
+        final boolean visible = stack != null;
+        if (mLastVisibility == visible && !force) {
+            return;
+        }
+        mLastVisibility = visible;
+        notifyDockedDividerVisibilityChanged(visible);
+        if (!visible) {
+            setResizeDimLayer(false, INVALID_STACK_ID, 0f);
+        }
+    }
+
+    private boolean wasVisible() {
+        return mLastVisibility;
+    }
+
+    void setAdjustedForIme(
+            boolean adjustedForIme, boolean adjustedForDivider,
+            boolean animate, WindowState imeWin, int imeHeight) {
+        if (mAdjustedForIme != adjustedForIme || (adjustedForIme && mImeHeight != imeHeight)
+                || mAdjustedForDivider != adjustedForDivider) {
+            if (animate && !mAnimatingForMinimizedDockedStack) {
+                startImeAdjustAnimation(adjustedForIme, adjustedForDivider, imeWin);
+            } else {
+                // Animation might be delayed, so only notify if we don't run an animation.
+                notifyAdjustedForImeChanged(adjustedForIme || adjustedForDivider, 0 /* duration */);
+            }
+            mAdjustedForIme = adjustedForIme;
+            mImeHeight = imeHeight;
+            mAdjustedForDivider = adjustedForDivider;
+        }
+    }
+
+    int getImeHeightAdjustedFor() {
+        return mImeHeight;
+    }
+
+    void positionDockedStackedDivider(Rect frame) {
+        TaskStack stack = mDisplayContent.getDockedStackLocked();
+        if (stack == null) {
+            // Unfortunately we might end up with still having a divider, even though the underlying
+            // stack was already removed. This is because we are on AM thread and the removal of the
+            // divider was deferred to WM thread and hasn't happened yet. In that case let's just
+            // keep putting it in the same place it was before the stack was removed to have
+            // continuity and prevent it from jumping to the center. It will get hidden soon.
+            frame.set(mLastRect);
+            return;
+        } else {
+            stack.getDimBounds(mTmpRect);
+        }
+        int side = stack.getDockSide();
+        switch (side) {
+            case DOCKED_LEFT:
+                frame.set(mTmpRect.right - mDividerInsets, frame.top,
+                        mTmpRect.right + frame.width() - mDividerInsets, frame.bottom);
+                break;
+            case DOCKED_TOP:
+                frame.set(frame.left, mTmpRect.bottom - mDividerInsets,
+                        mTmpRect.right, mTmpRect.bottom + frame.height() - mDividerInsets);
+                break;
+            case DOCKED_RIGHT:
+                frame.set(mTmpRect.left - frame.width() + mDividerInsets, frame.top,
+                        mTmpRect.left + mDividerInsets, frame.bottom);
+                break;
+            case DOCKED_BOTTOM:
+                frame.set(frame.left, mTmpRect.top - frame.height() + mDividerInsets,
+                        frame.right, mTmpRect.top + mDividerInsets);
+                break;
+        }
+        mLastRect.set(frame);
+    }
+
+    private void notifyDockedDividerVisibilityChanged(boolean visible) {
+        final int size = mDockedStackListeners.beginBroadcast();
+        for (int i = 0; i < size; ++i) {
+            final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
+            try {
+                listener.onDividerVisibilityChanged(visible);
+            } catch (RemoteException e) {
+                Slog.e(TAG_WM, "Error delivering divider visibility changed event.", e);
+            }
+        }
+        mDockedStackListeners.finishBroadcast();
+    }
+
+    void notifyDockedStackExistsChanged(boolean exists) {
+        // TODO(multi-display): Perform all actions only for current display.
+        final int size = mDockedStackListeners.beginBroadcast();
+        for (int i = 0; i < size; ++i) {
+            final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
+            try {
+                listener.onDockedStackExistsChanged(exists);
+            } catch (RemoteException e) {
+                Slog.e(TAG_WM, "Error delivering docked stack exists changed event.", e);
+            }
+        }
+        mDockedStackListeners.finishBroadcast();
+        if (exists) {
+            InputMethodManagerInternal inputMethodManagerInternal =
+                    LocalServices.getService(InputMethodManagerInternal.class);
+            if (inputMethodManagerInternal != null) {
+
+                // Hide the current IME to avoid problems with animations from IME adjustment when
+                // attaching the docked stack.
+                inputMethodManagerInternal.hideCurrentInputMethod();
+                mImeHideRequested = true;
+            }
+            return;
+        }
+        setMinimizedDockedStack(false /* minimizedDock */, false /* animate */);
+    }
+
+    /**
+     * Resets the state that IME hide has been requested. See {@link #isImeHideRequested}.
+     */
+    void resetImeHideRequested() {
+        mImeHideRequested = false;
+    }
+
+    /**
+     * The docked stack divider controller makes sure the IME gets hidden when attaching the docked
+     * stack, to avoid animation problems. This flag indicates whether the request to hide the IME
+     * has been sent in an asynchronous manner, and the IME should be treated as hidden already.
+     *
+     * @return whether IME hide request has been sent
+     */
+    boolean isImeHideRequested() {
+        return mImeHideRequested;
+    }
+
+    private void notifyDockedStackMinimizedChanged(boolean minimizedDock, boolean animate,
+            boolean isHomeStackResizable) {
+        long animDuration = 0;
+        if (animate) {
+            final TaskStack stack = mDisplayContent.getStackById(DOCKED_STACK_ID);
+            final long transitionDuration = isAnimationMaximizing()
+                    ? mService.mAppTransition.getLastClipRevealTransitionDuration()
+                    : DEFAULT_APP_TRANSITION_DURATION;
+            mAnimationDuration = (long)
+                    (transitionDuration * mService.getTransitionAnimationScaleLocked());
+            mMaximizeMeetFraction = getClipRevealMeetFraction(stack);
+            animDuration = (long) (mAnimationDuration * mMaximizeMeetFraction);
+        }
+        mService.mH.removeMessages(NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED);
+        mService.mH.obtainMessage(NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED,
+                minimizedDock ? 1 : 0, 0).sendToTarget();
+        final int size = mDockedStackListeners.beginBroadcast();
+        for (int i = 0; i < size; ++i) {
+            final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
+            try {
+                listener.onDockedStackMinimizedChanged(minimizedDock, animDuration,
+                        isHomeStackResizable);
+            } catch (RemoteException e) {
+                Slog.e(TAG_WM, "Error delivering minimized dock changed event.", e);
+            }
+        }
+        mDockedStackListeners.finishBroadcast();
+    }
+
+    void notifyDockSideChanged(int newDockSide) {
+        final int size = mDockedStackListeners.beginBroadcast();
+        for (int i = 0; i < size; ++i) {
+            final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
+            try {
+                listener.onDockSideChanged(newDockSide);
+            } catch (RemoteException e) {
+                Slog.e(TAG_WM, "Error delivering dock side changed event.", e);
+            }
+        }
+        mDockedStackListeners.finishBroadcast();
+    }
+
+    private void notifyAdjustedForImeChanged(boolean adjustedForIme, long animDuration) {
+        final int size = mDockedStackListeners.beginBroadcast();
+        for (int i = 0; i < size; ++i) {
+            final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
+            try {
+                listener.onAdjustedForImeChanged(adjustedForIme, animDuration);
+            } catch (RemoteException e) {
+                Slog.e(TAG_WM, "Error delivering adjusted for ime changed event.", e);
+            }
+        }
+        mDockedStackListeners.finishBroadcast();
+    }
+
+    void registerDockedStackListener(IDockedStackListener listener) {
+        mDockedStackListeners.register(listener);
+        notifyDockedDividerVisibilityChanged(wasVisible());
+        notifyDockedStackExistsChanged(mDisplayContent.getDockedStackIgnoringVisibility() != null);
+        notifyDockedStackMinimizedChanged(mMinimizedDock, false /* animate */,
+                isHomeStackResizable());
+        notifyAdjustedForImeChanged(mAdjustedForIme, 0 /* animDuration */);
+
+    }
+
+    void setResizeDimLayer(boolean visible, int targetStackId, float alpha) {
+        mService.openSurfaceTransaction();
+        final TaskStack stack = mDisplayContent.getStackById(targetStackId);
+        final TaskStack dockedStack = mDisplayContent.getDockedStackLocked();
+        boolean visibleAndValid = visible && stack != null && dockedStack != null;
+        if (visibleAndValid) {
+            stack.getDimBounds(mTmpRect);
+            if (mTmpRect.height() > 0 && mTmpRect.width() > 0) {
+                mDimLayer.setBounds(mTmpRect);
+                mDimLayer.show(getResizeDimLayer(), alpha, 0 /* duration */);
+            } else {
+                visibleAndValid = false;
+            }
+        }
+        if (!visibleAndValid) {
+            mDimLayer.hide();
+        }
+        mService.closeSurfaceTransaction();
+    }
+
+    /**
+     * @return The layer used for dimming the apps when dismissing docked/fullscreen stack. Just
+     *         above all application surfaces.
+     */
+    private int getResizeDimLayer() {
+        return (mWindow != null) ? mWindow.mLayer - 1 : LAYER_OFFSET_DIM;
+    }
+
+    /**
+     * Notifies the docked stack divider controller of a visibility change that happens without
+     * an animation.
+     */
+    void notifyAppVisibilityChanged() {
+        checkMinimizeChanged(false /* animate */);
+    }
+
+    void notifyAppTransitionStarting(ArraySet<AppWindowToken> openingApps, int appTransition) {
+        final boolean wasMinimized = mMinimizedDock;
+        checkMinimizeChanged(true /* animate */);
+
+        // We were minimized, and now we are still minimized, but somebody is trying to launch an
+        // app in docked stack, better show recent apps so we actually get unminimized! However do
+        // not do this if keyguard is dismissed such as when the device is unlocking. This catches
+        // any case that was missed in ActivityStarter.postStartActivityUncheckedProcessing because
+        // we couldn't retrace the launch of the app in the docked stack to the launch from
+        // homescreen.
+        if (wasMinimized && mMinimizedDock && containsAppInDockedStack(openingApps)
+                && appTransition != TRANSIT_NONE &&
+                !AppTransition.isKeyguardGoingAwayTransit(appTransition)) {
+            mService.showRecentApps(true /* fromHome */);
+        }
+    }
+
+    /**
+     * @return true if {@param apps} contains an activity in the docked stack, false otherwise.
+     */
+    private boolean containsAppInDockedStack(ArraySet<AppWindowToken> apps) {
+        for (int i = apps.size() - 1; i >= 0; i--) {
+            final AppWindowToken token = apps.valueAt(i);
+            if (token.getTask() != null && token.getTask().mStack.mStackId == DOCKED_STACK_ID) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    boolean isMinimizedDock() {
+        return mMinimizedDock;
+    }
+
+    private void checkMinimizeChanged(boolean animate) {
+        if (mDisplayContent.getDockedStackIgnoringVisibility() == null) {
+            return;
+        }
+        final TaskStack homeStack = mDisplayContent.getHomeStack();
+        if (homeStack == null) {
+            return;
+        }
+        final Task homeTask = homeStack.findHomeTask();
+        if (homeTask == null || !isWithinDisplay(homeTask)) {
+            return;
+        }
+
+        // Do not minimize when dock is already minimized while keyguard is showing and not
+        // occluded such as unlocking the screen
+        if (mMinimizedDock && mService.mPolicy.isKeyguardShowingAndNotOccluded()) {
+            return;
+        }
+        final TaskStack fullscreenStack =
+                mDisplayContent.getStackById(FULLSCREEN_WORKSPACE_STACK_ID);
+        final boolean homeVisible = homeTask.getTopVisibleAppToken() != null;
+        final boolean homeBehind = fullscreenStack != null && fullscreenStack.isVisible();
+        setMinimizedDockedStack(homeVisible && !homeBehind, animate);
+    }
+
+    private boolean isWithinDisplay(Task task) {
+        task.mStack.getBounds(mTmpRect);
+        mDisplayContent.getLogicalDisplayRect(mTmpRect2);
+        return mTmpRect.intersect(mTmpRect2);
+    }
+
+    /**
+     * Sets whether the docked stack is currently in a minimized state, i.e. all the tasks in the
+     * docked stack are heavily clipped so you can only see a minimal peek state.
+     *
+     * @param minimizedDock Whether the docked stack is currently minimized.
+     * @param animate Whether to animate the change.
+     */
+    private void setMinimizedDockedStack(boolean minimizedDock, boolean animate) {
+        final boolean wasMinimized = mMinimizedDock;
+        mMinimizedDock = minimizedDock;
+        if (minimizedDock == wasMinimized) {
+            return;
+        }
+
+        final boolean imeChanged = clearImeAdjustAnimation();
+        boolean minimizedChange = false;
+        if (isHomeStackResizable()) {
+            notifyDockedStackMinimizedChanged(minimizedDock, true /* animate */,
+                    true /* isHomeStackResizable */);
+            minimizedChange = true;
+        } else {
+            if (minimizedDock) {
+                if (animate) {
+                    startAdjustAnimation(0f, 1f);
+                } else {
+                    minimizedChange |= setMinimizedDockedStack(true);
+                }
+            } else {
+                if (animate) {
+                    startAdjustAnimation(1f, 0f);
+                } else {
+                    minimizedChange |= setMinimizedDockedStack(false);
+                }
+            }
+        }
+        if (imeChanged || minimizedChange) {
+            if (imeChanged && !minimizedChange) {
+                Slog.d(TAG, "setMinimizedDockedStack: IME adjust changed due to minimizing,"
+                        + " minimizedDock=" + minimizedDock
+                        + " minimizedChange=" + minimizedChange);
+            }
+            mService.mWindowPlacerLocked.performSurfacePlacement();
+        }
+    }
+
+    private boolean clearImeAdjustAnimation() {
+        final boolean changed = mDisplayContent.clearImeAdjustAnimation();
+        mAnimatingForIme = false;
+        return changed;
+    }
+
+    private void startAdjustAnimation(float from, float to) {
+        mAnimatingForMinimizedDockedStack = true;
+        mAnimationStarted = false;
+        mAnimationStart = from;
+        mAnimationTarget = to;
+    }
+
+    private void startImeAdjustAnimation(
+            boolean adjustedForIme, boolean adjustedForDivider, WindowState imeWin) {
+
+        // If we're not in an animation, the starting point depends on whether we're adjusted
+        // or not. If we're already in an animation, we start from where the current animation
+        // left off, so that the motion doesn't look discontinuous.
+        if (!mAnimatingForIme) {
+            mAnimationStart = mAdjustedForIme ? 1 : 0;
+            mDividerAnimationStart = mAdjustedForDivider ? 1 : 0;
+            mLastAnimationProgress = mAnimationStart;
+            mLastDividerProgress = mDividerAnimationStart;
+        } else {
+            mAnimationStart = mLastAnimationProgress;
+            mDividerAnimationStart = mLastDividerProgress;
+        }
+        mAnimatingForIme = true;
+        mAnimationStarted = false;
+        mAnimationTarget = adjustedForIme ? 1 : 0;
+        mDividerAnimationTarget = adjustedForDivider ? 1 : 0;
+
+        mDisplayContent.beginImeAdjustAnimation();
+
+        // We put all tasks into drag resizing mode - wait until all of them have completed the
+        // drag resizing switch.
+        if (!mService.mWaitingForDrawn.isEmpty()) {
+            mService.mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT);
+            mService.mH.sendEmptyMessageDelayed(H.WAITING_FOR_DRAWN_TIMEOUT,
+                    IME_ADJUST_DRAWN_TIMEOUT);
+            mAnimationStartDelayed = true;
+            if (imeWin != null) {
+
+                // There might be an old window delaying the animation start - clear it.
+                if (mDelayedImeWin != null) {
+                    mDelayedImeWin.mWinAnimator.endDelayingAnimationStart();
+                }
+                mDelayedImeWin = imeWin;
+                imeWin.mWinAnimator.startDelayingAnimationStart();
+            }
+
+            // If we are already waiting for something to be drawn, clear out the old one so it
+            // still gets executed.
+            // TODO: Have a real system where we can wait on different windows to be drawn with
+            // different callbacks.
+            if (mService.mWaitingForDrawnCallback != null) {
+                mService.mWaitingForDrawnCallback.run();
+            }
+            mService.mWaitingForDrawnCallback = () -> {
+                mAnimationStartDelayed = false;
+                if (mDelayedImeWin != null) {
+                    mDelayedImeWin.mWinAnimator.endDelayingAnimationStart();
+                }
+                // If the adjust status changed since this was posted, only notify
+                // the new states and don't animate.
+                long duration = 0;
+                if (mAdjustedForIme == adjustedForIme
+                        && mAdjustedForDivider == adjustedForDivider) {
+                    duration = IME_ADJUST_ANIM_DURATION;
+                } else {
+                    Slog.w(TAG, "IME adjust changed while waiting for drawn:"
+                            + " adjustedForIme=" + adjustedForIme
+                            + " adjustedForDivider=" + adjustedForDivider
+                            + " mAdjustedForIme=" + mAdjustedForIme
+                            + " mAdjustedForDivider=" + mAdjustedForDivider);
+                }
+                notifyAdjustedForImeChanged(
+                        mAdjustedForIme || mAdjustedForDivider, duration);
+            };
+        } else {
+            notifyAdjustedForImeChanged(
+                    adjustedForIme || adjustedForDivider, IME_ADJUST_ANIM_DURATION);
+        }
+    }
+
+    private boolean setMinimizedDockedStack(boolean minimized) {
+        final TaskStack stack = mDisplayContent.getDockedStackIgnoringVisibility();
+        notifyDockedStackMinimizedChanged(minimized, false /* animate */, isHomeStackResizable());
+        return stack != null && stack.setAdjustedForMinimizedDock(minimized ? 1f : 0f);
+    }
+
+    private boolean isAnimationMaximizing() {
+        return mAnimationTarget == 0f;
+    }
+
+    public boolean animate(long now) {
+        if (mWindow == null) {
+            return false;
+        }
+        if (mAnimatingForMinimizedDockedStack) {
+            return animateForMinimizedDockedStack(now);
+        } else if (mAnimatingForIme) {
+            return animateForIme(now);
+        } else {
+            if (mDimLayer != null && mDimLayer.isDimming()) {
+                mDimLayer.setLayer(getResizeDimLayer());
+            }
+            return false;
+        }
+    }
+
+    private boolean animateForIme(long now) {
+        if (!mAnimationStarted || mAnimationStartDelayed) {
+            mAnimationStarted = true;
+            mAnimationStartTime = now;
+            mAnimationDuration = (long)
+                    (IME_ADJUST_ANIM_DURATION * mService.getWindowAnimationScaleLocked());
+        }
+        float t = Math.min(1f, (float) (now - mAnimationStartTime) / mAnimationDuration);
+        t = (mAnimationTarget == 1f ? IME_ADJUST_ENTRY_INTERPOLATOR : TOUCH_RESPONSE_INTERPOLATOR)
+                .getInterpolation(t);
+        final boolean updated =
+                mDisplayContent.animateForIme(t, mAnimationTarget, mDividerAnimationTarget);
+        if (updated) {
+            mService.mWindowPlacerLocked.performSurfacePlacement();
+        }
+        if (t >= 1.0f) {
+            mLastAnimationProgress = mAnimationTarget;
+            mLastDividerProgress = mDividerAnimationTarget;
+            mAnimatingForIme = false;
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    private boolean animateForMinimizedDockedStack(long now) {
+        final TaskStack stack = mDisplayContent.getStackById(DOCKED_STACK_ID);
+        if (!mAnimationStarted) {
+            mAnimationStarted = true;
+            mAnimationStartTime = now;
+            notifyDockedStackMinimizedChanged(mMinimizedDock, true /* animate */,
+                    isHomeStackResizable() /* isHomeStackResizable */);
+        }
+        float t = Math.min(1f, (float) (now - mAnimationStartTime) / mAnimationDuration);
+        t = (isAnimationMaximizing() ? TOUCH_RESPONSE_INTERPOLATOR : mMinimizedDockInterpolator)
+                .getInterpolation(t);
+        if (stack != null) {
+            if (stack.setAdjustedForMinimizedDock(getMinimizeAmount(stack, t))) {
+                mService.mWindowPlacerLocked.performSurfacePlacement();
+            }
+        }
+        if (t >= 1.0f) {
+            mAnimatingForMinimizedDockedStack = false;
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    float getInterpolatedAnimationValue(float t) {
+        return t * mAnimationTarget + (1 - t) * mAnimationStart;
+    }
+
+    float getInterpolatedDividerValue(float t) {
+        return t * mDividerAnimationTarget + (1 - t) * mDividerAnimationStart;
+    }
+
+    /**
+     * Gets the amount how much to minimize a stack depending on the interpolated fraction t.
+     */
+    private float getMinimizeAmount(TaskStack stack, float t) {
+        final float naturalAmount = getInterpolatedAnimationValue(t);
+        if (isAnimationMaximizing()) {
+            return adjustMaximizeAmount(stack, t, naturalAmount);
+        } else {
+            return naturalAmount;
+        }
+    }
+
+    /**
+     * When maximizing the stack during a clip reveal transition, this adjusts the minimize amount
+     * during the transition such that the edge of the clip reveal rect is met earlier in the
+     * transition so we don't create a visible "hole", but only if both the clip reveal and the
+     * docked stack divider start from about the same portion on the screen.
+     */
+    private float adjustMaximizeAmount(TaskStack stack, float t, float naturalAmount) {
+        if (mMaximizeMeetFraction == 1f) {
+            return naturalAmount;
+        }
+        final int minimizeDistance = stack.getMinimizeDistance();
+        float startPrime = mService.mAppTransition.getLastClipRevealMaxTranslation()
+                / (float) minimizeDistance;
+        final float amountPrime = t * mAnimationTarget + (1 - t) * startPrime;
+        final float t2 = Math.min(t / mMaximizeMeetFraction, 1);
+        return amountPrime * t2 + naturalAmount * (1 - t2);
+    }
+
+    /**
+     * Retrieves the animation fraction at which the docked stack has to meet the clip reveal
+     * edge. See {@link #adjustMaximizeAmount}.
+     */
+    private float getClipRevealMeetFraction(TaskStack stack) {
+        if (!isAnimationMaximizing() || stack == null ||
+                !mService.mAppTransition.hadClipRevealAnimation()) {
+            return 1f;
+        }
+        final int minimizeDistance = stack.getMinimizeDistance();
+        final float fraction = Math.abs(mService.mAppTransition.getLastClipRevealMaxTranslation())
+                / (float) minimizeDistance;
+        final float t = Math.max(0, Math.min(1, (fraction - CLIP_REVEAL_MEET_FRACTION_MIN)
+                / (CLIP_REVEAL_MEET_FRACTION_MAX - CLIP_REVEAL_MEET_FRACTION_MIN)));
+        return CLIP_REVEAL_MEET_EARLIEST
+                + (1 - t) * (CLIP_REVEAL_MEET_LAST - CLIP_REVEAL_MEET_EARLIEST);
+    }
+
+    @Override
+    public boolean dimFullscreen() {
+        return false;
+    }
+
+    @Override
+    public DisplayInfo getDisplayInfo() {
+        return mDisplayContent.getDisplayInfo();
+    }
+
+    @Override
+    public boolean isAttachedToDisplay() {
+        return mDisplayContent != null;
+    }
+
+    @Override
+    public void getDimBounds(Rect outBounds) {
+        // This dim layer user doesn't need this.
+    }
+
+    @Override
+    public String toShortString() {
+        return TAG;
+    }
+
+    WindowState getWindow() {
+        return mWindow;
+    }
+
+    void dump(String prefix, PrintWriter pw) {
+        pw.println(prefix + "DockedStackDividerController");
+        pw.println(prefix + "  mLastVisibility=" + mLastVisibility);
+        pw.println(prefix + "  mMinimizedDock=" + mMinimizedDock);
+        pw.println(prefix + "  mAdjustedForIme=" + mAdjustedForIme);
+        pw.println(prefix + "  mAdjustedForDivider=" + mAdjustedForDivider);
+        if (mDimLayer.isDimming()) {
+            pw.println(prefix + "  Dim layer is dimming: ");
+            mDimLayer.printTo(prefix + "    ", pw);
+        }
+    }
+
+    void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(MINIMIZED_DOCK, mMinimizedDock);
+        proto.end(token);
+    }
+}
diff --git a/com/android/server/wm/DragAndDropPermissionsHandler.java b/com/android/server/wm/DragAndDropPermissionsHandler.java
new file mode 100644
index 0000000..70478fe
--- /dev/null
+++ b/com/android/server/wm/DragAndDropPermissionsHandler.java
@@ -0,0 +1,133 @@
+/*
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package com.android.server.wm;
+
+import android.app.ActivityManager;
+import android.content.ClipData;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.internal.view.IDragAndDropPermissions;
+
+import java.util.ArrayList;
+
+class DragAndDropPermissionsHandler extends IDragAndDropPermissions.Stub
+        implements IBinder.DeathRecipient {
+
+    private final int mSourceUid;
+    private final String mTargetPackage;
+    private final int mMode;
+    private final int mSourceUserId;
+    private final int mTargetUserId;
+
+    private final ArrayList<Uri> mUris = new ArrayList<Uri>();
+
+    private IBinder mActivityToken = null;
+    private IBinder mPermissionOwnerToken = null;
+    private IBinder mTransientToken = null;
+
+    DragAndDropPermissionsHandler(ClipData clipData, int sourceUid, String targetPackage, int mode,
+                                  int sourceUserId, int targetUserId) {
+        mSourceUid = sourceUid;
+        mTargetPackage = targetPackage;
+        mMode = mode;
+        mSourceUserId = sourceUserId;
+        mTargetUserId = targetUserId;
+
+        clipData.collectUris(mUris);
+    }
+
+    @Override
+    public void take(IBinder activityToken) throws RemoteException {
+        if (mActivityToken != null || mPermissionOwnerToken != null) {
+            return;
+        }
+        mActivityToken = activityToken;
+
+        // Will throw if Activity is not found.
+        IBinder permissionOwner = ActivityManager.getService().
+                getUriPermissionOwnerForActivity(mActivityToken);
+
+        doTake(permissionOwner);
+    }
+
+    private void doTake(IBinder permissionOwner) throws RemoteException {
+        long origId = Binder.clearCallingIdentity();
+        try {
+            for (int i = 0; i < mUris.size(); i++) {
+                ActivityManager.getService().grantUriPermissionFromOwner(
+                        permissionOwner, mSourceUid, mTargetPackage, mUris.get(i), mMode,
+                        mSourceUserId, mTargetUserId);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    @Override
+    public void takeTransient(IBinder transientToken) throws RemoteException {
+        if (mActivityToken != null || mPermissionOwnerToken != null) {
+            return;
+        }
+        mPermissionOwnerToken = ActivityManager.getService().newUriPermissionOwner("drop");
+        mTransientToken = transientToken;
+        mTransientToken.linkToDeath(this, 0);
+
+        doTake(mPermissionOwnerToken);
+    }
+
+    @Override
+    public void release() throws RemoteException {
+        if (mActivityToken == null && mPermissionOwnerToken == null) {
+            return;
+        }
+
+        IBinder permissionOwner = null;
+        if (mActivityToken != null) {
+            try {
+                permissionOwner = ActivityManager.getService().
+                        getUriPermissionOwnerForActivity(mActivityToken);
+            } catch (Exception e) {
+                // Activity is destroyed, permissions already revoked.
+                return;
+            } finally {
+                mActivityToken = null;
+            }
+        } else {
+            permissionOwner = mPermissionOwnerToken;
+            mPermissionOwnerToken = null;
+            mTransientToken.unlinkToDeath(this, 0);
+            mTransientToken = null;
+        }
+
+        for (int i = 0; i < mUris.size(); ++i) {
+            ActivityManager.getService().revokeUriPermissionFromOwner(
+                    permissionOwner, mUris.get(i), mMode, mSourceUserId);
+        }
+    }
+
+    @Override
+    public void binderDied() {
+        try {
+            release();
+        } catch (RemoteException e) {
+            // Cannot happen, local call.
+        }
+    }
+}
diff --git a/com/android/server/wm/DragResizeMode.java b/com/android/server/wm/DragResizeMode.java
new file mode 100644
index 0000000..8ab0406
--- /dev/null
+++ b/com/android/server/wm/DragResizeMode.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
+
+/**
+ * Describes the mode in which a window is drag resizing.
+ */
+class DragResizeMode {
+
+    /**
+     * Freeform mode: Client surface is fullscreen, and client is responsible to draw window at
+     * the correct position.
+     */
+    static final int DRAG_RESIZE_MODE_FREEFORM = 0;
+
+    /**
+     * Mode for resizing the docked (and adjacent) stack: Client surface is fullscreen, but window
+     * is drawn at (0, 0), window manager is responsible for positioning the surface when draging.
+     */
+    static final int DRAG_RESIZE_MODE_DOCKED_DIVIDER = 1;
+
+    static boolean isModeAllowedForStack(int stackId, int mode) {
+        switch (mode) {
+            case DRAG_RESIZE_MODE_FREEFORM:
+                return stackId == FREEFORM_WORKSPACE_STACK_ID;
+            case DRAG_RESIZE_MODE_DOCKED_DIVIDER:
+                return stackId == DOCKED_STACK_ID
+                        || stackId == FULLSCREEN_WORKSPACE_STACK_ID
+                        || stackId == HOME_STACK_ID
+                        || stackId == RECENTS_STACK_ID;
+            default:
+                return false;
+        }
+    }
+}
diff --git a/com/android/server/wm/DragState.java b/com/android/server/wm/DragState.java
new file mode 100644
index 0000000..3fdafc7
--- /dev/null
+++ b/com/android/server/wm/DragState.java
@@ -0,0 +1,625 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.hardware.input.InputManager;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.IUserManager;
+import android.util.Slog;
+import android.view.Display;
+import android.view.DragEvent;
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.PointerIcon;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.animation.ScaleAnimation;
+import android.view.animation.Transformation;
+import android.view.animation.TranslateAnimation;
+
+import com.android.server.input.InputApplicationHandle;
+import com.android.server.input.InputWindowHandle;
+import com.android.server.wm.WindowManagerService.DragInputEventReceiver;
+import com.android.server.wm.WindowManagerService.H;
+
+import com.android.internal.view.IDragAndDropPermissions;
+
+import java.util.ArrayList;
+
+/**
+ * Drag/drop state
+ */
+class DragState {
+    private static final long MIN_ANIMATION_DURATION_MS = 195;
+    private static final long MAX_ANIMATION_DURATION_MS = 375;
+
+    private static final int DRAG_FLAGS_URI_ACCESS = View.DRAG_FLAG_GLOBAL_URI_READ |
+            View.DRAG_FLAG_GLOBAL_URI_WRITE;
+
+    private static final int DRAG_FLAGS_URI_PERMISSIONS = DRAG_FLAGS_URI_ACCESS |
+            View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION |
+            View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION;
+
+    final WindowManagerService mService;
+    IBinder mToken;
+    SurfaceControl mSurfaceControl;
+    int mFlags;
+    IBinder mLocalWin;
+    int mPid;
+    int mUid;
+    int mSourceUserId;
+    boolean mCrossProfileCopyAllowed;
+    ClipData mData;
+    ClipDescription mDataDescription;
+    int mTouchSource;
+    boolean mDragResult;
+    float mOriginalAlpha;
+    float mOriginalX, mOriginalY;
+    float mCurrentX, mCurrentY;
+    float mThumbOffsetX, mThumbOffsetY;
+    InputInterceptor mInputInterceptor;
+    WindowState mTargetWindow;
+    ArrayList<WindowState> mNotifiedWindows;
+    boolean mDragInProgress;
+    DisplayContent mDisplayContent;
+
+    private Animation mAnimation;
+    final Transformation mTransformation = new Transformation();
+    private final Interpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
+    private Point mDisplaySize = new Point();
+
+    DragState(WindowManagerService service, IBinder token, SurfaceControl surface,
+            int flags, IBinder localWin) {
+        mService = service;
+        mToken = token;
+        mSurfaceControl = surface;
+        mFlags = flags;
+        mLocalWin = localWin;
+        mNotifiedWindows = new ArrayList<WindowState>();
+    }
+
+    void reset() {
+        if (mSurfaceControl != null) {
+            mSurfaceControl.destroy();
+        }
+        mSurfaceControl = null;
+        mFlags = 0;
+        mLocalWin = null;
+        mToken = null;
+        mData = null;
+        mThumbOffsetX = mThumbOffsetY = 0;
+        mNotifiedWindows = null;
+    }
+
+    class InputInterceptor {
+        InputChannel mServerChannel, mClientChannel;
+        DragInputEventReceiver mInputEventReceiver;
+        InputApplicationHandle mDragApplicationHandle;
+        InputWindowHandle mDragWindowHandle;
+
+        InputInterceptor(Display display) {
+            InputChannel[] channels = InputChannel.openInputChannelPair("drag");
+            mServerChannel = channels[0];
+            mClientChannel = channels[1];
+            mService.mInputManager.registerInputChannel(mServerChannel, null);
+            mInputEventReceiver = mService.new DragInputEventReceiver(mClientChannel,
+                    mService.mH.getLooper());
+
+            mDragApplicationHandle = new InputApplicationHandle(null);
+            mDragApplicationHandle.name = "drag";
+            mDragApplicationHandle.dispatchingTimeoutNanos =
+                    WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+
+            mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null, null,
+                    display.getDisplayId());
+            mDragWindowHandle.name = "drag";
+            mDragWindowHandle.inputChannel = mServerChannel;
+            mDragWindowHandle.layer = getDragLayerLw();
+            mDragWindowHandle.layoutParamsFlags = 0;
+            mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
+            mDragWindowHandle.dispatchingTimeoutNanos =
+                    WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+            mDragWindowHandle.visible = true;
+            mDragWindowHandle.canReceiveKeys = false;
+            mDragWindowHandle.hasFocus = true;
+            mDragWindowHandle.hasWallpaper = false;
+            mDragWindowHandle.paused = false;
+            mDragWindowHandle.ownerPid = Process.myPid();
+            mDragWindowHandle.ownerUid = Process.myUid();
+            mDragWindowHandle.inputFeatures = 0;
+            mDragWindowHandle.scaleFactor = 1.0f;
+
+            // The drag window cannot receive new touches.
+            mDragWindowHandle.touchableRegion.setEmpty();
+
+            // The drag window covers the entire display
+            mDragWindowHandle.frameLeft = 0;
+            mDragWindowHandle.frameTop = 0;
+            mDragWindowHandle.frameRight = mDisplaySize.x;
+            mDragWindowHandle.frameBottom = mDisplaySize.y;
+
+            // Pause rotations before a drag.
+            if (DEBUG_ORIENTATION) {
+                Slog.d(TAG_WM, "Pausing rotation during drag");
+            }
+            mService.pauseRotationLocked();
+        }
+
+        void tearDown() {
+            mService.mInputManager.unregisterInputChannel(mServerChannel);
+            mInputEventReceiver.dispose();
+            mInputEventReceiver = null;
+            mClientChannel.dispose();
+            mServerChannel.dispose();
+            mClientChannel = null;
+            mServerChannel = null;
+
+            mDragWindowHandle = null;
+            mDragApplicationHandle = null;
+
+            // Resume rotations after a drag.
+            if (DEBUG_ORIENTATION) {
+                Slog.d(TAG_WM, "Resuming rotation after drag");
+            }
+            mService.resumeRotationLocked();
+        }
+    }
+
+    InputChannel getInputChannel() {
+        return mInputInterceptor == null ? null : mInputInterceptor.mServerChannel;
+    }
+
+    InputWindowHandle getInputWindowHandle() {
+        return mInputInterceptor == null ? null : mInputInterceptor.mDragWindowHandle;
+    }
+
+    /**
+     * @param display The Display that the window being dragged is on.
+     */
+    void register(Display display) {
+        display.getRealSize(mDisplaySize);
+        if (DEBUG_DRAG) Slog.d(TAG_WM, "registering drag input channel");
+        if (mInputInterceptor != null) {
+            Slog.e(TAG_WM, "Duplicate register of drag input channel");
+        } else {
+            mInputInterceptor = new InputInterceptor(display);
+            mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
+        }
+    }
+
+    void unregister() {
+        if (DEBUG_DRAG) Slog.d(TAG_WM, "unregistering drag input channel");
+        if (mInputInterceptor == null) {
+            Slog.e(TAG_WM, "Unregister of nonexistent drag input channel");
+        } else {
+            // Input channel should be disposed on the thread where the input is being handled.
+            mService.mH.obtainMessage(
+                    H.TEAR_DOWN_DRAG_AND_DROP_INPUT, mInputInterceptor).sendToTarget();
+            mInputInterceptor = null;
+            mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
+        }
+    }
+
+    int getDragLayerLw() {
+        return mService.mPolicy.getWindowLayerFromTypeLw(WindowManager.LayoutParams.TYPE_DRAG)
+                * WindowManagerService.TYPE_LAYER_MULTIPLIER
+                + WindowManagerService.TYPE_LAYER_OFFSET;
+    }
+
+    /* call out to each visible window/session informing it about the drag
+     */
+    void broadcastDragStartedLw(final float touchX, final float touchY) {
+        mOriginalX = mCurrentX = touchX;
+        mOriginalY = mCurrentY = touchY;
+
+        // Cache a base-class instance of the clip metadata so that parceling
+        // works correctly in calling out to the apps.
+        mDataDescription = (mData != null) ? mData.getDescription() : null;
+        mNotifiedWindows.clear();
+        mDragInProgress = true;
+
+        mSourceUserId = UserHandle.getUserId(mUid);
+
+        final IUserManager userManager =
+                (IUserManager) ServiceManager.getService(Context.USER_SERVICE);
+        try {
+            mCrossProfileCopyAllowed = !userManager.getUserRestrictions(mSourceUserId).getBoolean(
+                    UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
+        } catch (RemoteException e) {
+            Slog.e(TAG_WM, "Remote Exception calling UserManager: " + e);
+            mCrossProfileCopyAllowed = false;
+        }
+
+        if (DEBUG_DRAG) {
+            Slog.d(TAG_WM, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")");
+        }
+
+        mDisplayContent.forAllWindows(w -> {
+            sendDragStartedLw(w, touchX, touchY, mDataDescription);
+        }, false /* traverseTopToBottom */ );
+    }
+
+    /* helper - send a ACTION_DRAG_STARTED event, if the
+     * designated window is potentially a drop recipient.  There are race situations
+     * around DRAG_ENDED broadcast, so we make sure that once we've declared that
+     * the drag has ended, we never send out another DRAG_STARTED for this drag action.
+     *
+     * This method clones the 'event' parameter if it's being delivered to the same
+     * process, so it's safe for the caller to call recycle() on the event afterwards.
+     */
+    private void sendDragStartedLw(WindowState newWin, float touchX, float touchY,
+            ClipDescription desc) {
+        if (mDragInProgress && isValidDropTarget(newWin)) {
+            DragEvent event = obtainDragEvent(newWin, DragEvent.ACTION_DRAG_STARTED,
+                    touchX, touchY, null, desc, null, null, false);
+            try {
+                newWin.mClient.dispatchDragEvent(event);
+                // track each window that we've notified that the drag is starting
+                mNotifiedWindows.add(newWin);
+            } catch (RemoteException e) {
+                Slog.w(TAG_WM, "Unable to drag-start window " + newWin);
+            } finally {
+                // if the callee was local, the dispatch has already recycled the event
+                if (Process.myPid() != newWin.mSession.mPid) {
+                    event.recycle();
+                }
+            }
+        }
+    }
+
+    private boolean isValidDropTarget(WindowState targetWin) {
+        if (targetWin == null) {
+            return false;
+        }
+        if (!targetWin.isPotentialDragTarget()) {
+            return false;
+        }
+        if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0 || !targetWindowSupportsGlobalDrag(targetWin)) {
+            // Drag is limited to the current window.
+            if (mLocalWin != targetWin.mClient.asBinder()) {
+                return false;
+            }
+        }
+
+        return mCrossProfileCopyAllowed ||
+                mSourceUserId == UserHandle.getUserId(targetWin.getOwningUid());
+    }
+
+    private boolean targetWindowSupportsGlobalDrag(WindowState targetWin) {
+        // Global drags are limited to system windows, and windows for apps that are targeting N and
+        // above.
+        return targetWin.mAppToken == null
+                || targetWin.mAppToken.mTargetSdk >= Build.VERSION_CODES.N;
+    }
+
+    /* helper - send a ACTION_DRAG_STARTED event only if the window has not
+     * previously been notified, i.e. it became visible after the drag operation
+     * was begun.  This is a rare case.
+     */
+    void sendDragStartedIfNeededLw(WindowState newWin) {
+        if (mDragInProgress) {
+            // If we have sent the drag-started, we needn't do so again
+            if (isWindowNotified(newWin)) {
+                return;
+            }
+            if (DEBUG_DRAG) {
+                Slog.d(TAG_WM, "need to send DRAG_STARTED to new window " + newWin);
+            }
+            sendDragStartedLw(newWin, mCurrentX, mCurrentY, mDataDescription);
+        }
+    }
+
+    private boolean isWindowNotified(WindowState newWin) {
+        for (WindowState ws : mNotifiedWindows) {
+            if (ws == newWin) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void broadcastDragEndedLw() {
+        final int myPid = Process.myPid();
+
+        if (DEBUG_DRAG) {
+            Slog.d(TAG_WM, "broadcasting DRAG_ENDED");
+        }
+        for (WindowState ws : mNotifiedWindows) {
+            float x = 0;
+            float y = 0;
+            if (!mDragResult && (ws.mSession.mPid == mPid)) {
+                // Report unconsumed drop location back to the app that started the drag.
+                x = mCurrentX;
+                y = mCurrentY;
+            }
+            DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED,
+                    x, y, null, null, null, null, mDragResult);
+            try {
+                ws.mClient.dispatchDragEvent(evt);
+            } catch (RemoteException e) {
+                Slog.w(TAG_WM, "Unable to drag-end window " + ws);
+            }
+            // if the current window is in the same process,
+            // the dispatch has already recycled the event
+            if (myPid != ws.mSession.mPid) {
+                evt.recycle();
+            }
+        }
+        mNotifiedWindows.clear();
+        mDragInProgress = false;
+    }
+
+    void endDragLw() {
+        if (mAnimation != null) {
+            return;
+        }
+        if (!mDragResult) {
+            mAnimation = createReturnAnimationLocked();
+            mService.scheduleAnimationLocked();
+            return;  // Will call cleanUpDragLw when the animation is done.
+        }
+        cleanUpDragLw();
+    }
+
+    void cancelDragLw() {
+        if (mAnimation != null) {
+            return;
+        }
+        mAnimation = createCancelAnimationLocked();
+        mService.scheduleAnimationLocked();
+    }
+
+    private void cleanUpDragLw() {
+        broadcastDragEndedLw();
+        if (isFromSource(InputDevice.SOURCE_MOUSE)) {
+            mService.restorePointerIconLocked(mDisplayContent, mCurrentX, mCurrentY);
+        }
+
+        // stop intercepting input
+        unregister();
+
+        // free our resources and drop all the object references
+        reset();
+        mService.mDragState = null;
+    }
+
+    void notifyMoveLw(float x, float y) {
+        if (mAnimation != null) {
+            return;
+        }
+        mCurrentX = x;
+        mCurrentY = y;
+
+        // Move the surface to the given touch
+        if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
+                TAG_WM, ">>> OPEN TRANSACTION notifyMoveLw");
+        mService.openSurfaceTransaction();
+        try {
+            mSurfaceControl.setPosition(x - mThumbOffsetX, y - mThumbOffsetY);
+            if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, "  DRAG "
+                    + mSurfaceControl + ": pos=(" +
+                    (int)(x - mThumbOffsetX) + "," + (int)(y - mThumbOffsetY) + ")");
+        } finally {
+            mService.closeSurfaceTransaction();
+            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
+                    TAG_WM, "<<< CLOSE TRANSACTION notifyMoveLw");
+        }
+        notifyLocationLw(x, y);
+    }
+
+    void notifyLocationLw(float x, float y) {
+        // Tell the affected window
+        WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y);
+        if (touchedWin != null && !isWindowNotified(touchedWin)) {
+            // The drag point is over a window which was not notified about a drag start.
+            // Pretend it's over empty space.
+            touchedWin = null;
+        }
+
+        try {
+            final int myPid = Process.myPid();
+
+            // have we dragged over a new window?
+            if ((touchedWin != mTargetWindow) && (mTargetWindow != null)) {
+                if (DEBUG_DRAG) {
+                    Slog.d(TAG_WM, "sending DRAG_EXITED to " + mTargetWindow);
+                }
+                // force DRAG_EXITED_EVENT if appropriate
+                DragEvent evt = obtainDragEvent(mTargetWindow, DragEvent.ACTION_DRAG_EXITED,
+                        0, 0, null, null, null, null, false);
+                mTargetWindow.mClient.dispatchDragEvent(evt);
+                if (myPid != mTargetWindow.mSession.mPid) {
+                    evt.recycle();
+                }
+            }
+            if (touchedWin != null) {
+                if (false && DEBUG_DRAG) {
+                    Slog.d(TAG_WM, "sending DRAG_LOCATION to " + touchedWin);
+                }
+                DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DRAG_LOCATION,
+                        x, y, null, null, null, null, false);
+                touchedWin.mClient.dispatchDragEvent(evt);
+                if (myPid != touchedWin.mSession.mPid) {
+                    evt.recycle();
+                }
+            }
+        } catch (RemoteException e) {
+            Slog.w(TAG_WM, "can't send drag notification to windows");
+        }
+        mTargetWindow = touchedWin;
+    }
+
+    // Find the drop target and tell it about the data.  Returns 'true' if we can immediately
+    // dispatch the global drag-ended message, 'false' if we need to wait for a
+    // result from the recipient.
+    boolean notifyDropLw(float x, float y) {
+        if (mAnimation != null) {
+            return false;
+        }
+        mCurrentX = x;
+        mCurrentY = y;
+
+        WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y);
+
+        if (!isWindowNotified(touchedWin)) {
+            // "drop" outside a valid window -- no recipient to apply a
+            // timeout to, and we can send the drag-ended message immediately.
+            mDragResult = false;
+            return true;
+        }
+
+        if (DEBUG_DRAG) {
+            Slog.d(TAG_WM, "sending DROP to " + touchedWin);
+        }
+
+        final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid());
+
+        DragAndDropPermissionsHandler dragAndDropPermissions = null;
+        if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 &&
+                (mFlags & DRAG_FLAGS_URI_ACCESS) != 0) {
+            dragAndDropPermissions = new DragAndDropPermissionsHandler(
+                    mData,
+                    mUid,
+                    touchedWin.getOwningPackage(),
+                    mFlags & DRAG_FLAGS_URI_PERMISSIONS,
+                    mSourceUserId,
+                    targetUserId);
+        }
+        if (mSourceUserId != targetUserId){
+            mData.fixUris(mSourceUserId);
+        }
+        final int myPid = Process.myPid();
+        final IBinder token = touchedWin.mClient.asBinder();
+        DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DROP, x, y,
+                null, null, mData, dragAndDropPermissions, false);
+        try {
+            touchedWin.mClient.dispatchDragEvent(evt);
+
+            // 5 second timeout for this window to respond to the drop
+            mService.mH.removeMessages(H.DRAG_END_TIMEOUT, token);
+            Message msg = mService.mH.obtainMessage(H.DRAG_END_TIMEOUT, token);
+            mService.mH.sendMessageDelayed(msg, 5000);
+        } catch (RemoteException e) {
+            Slog.w(TAG_WM, "can't send drop notification to win " + touchedWin);
+            return true;
+        } finally {
+            if (myPid != touchedWin.mSession.mPid) {
+                evt.recycle();
+            }
+        }
+        mToken = token;
+        return false;
+    }
+
+    private static DragEvent obtainDragEvent(WindowState win, int action,
+            float x, float y, Object localState,
+            ClipDescription description, ClipData data,
+            IDragAndDropPermissions dragAndDropPermissions,
+            boolean result) {
+        final float winX = win.translateToWindowX(x);
+        final float winY = win.translateToWindowY(y);
+        return DragEvent.obtain(action, winX, winY, localState, description, data,
+                dragAndDropPermissions, result);
+    }
+
+    boolean stepAnimationLocked(long currentTimeMs) {
+        if (mAnimation == null) {
+            return false;
+        }
+
+        mTransformation.clear();
+        if (!mAnimation.getTransformation(currentTimeMs, mTransformation)) {
+            cleanUpDragLw();
+            return false;
+        }
+
+        mTransformation.getMatrix().postTranslate(
+                mCurrentX - mThumbOffsetX, mCurrentY - mThumbOffsetY);
+        final float tmpFloats[] = mService.mTmpFloats;
+        mTransformation.getMatrix().getValues(tmpFloats);
+        mSurfaceControl.setPosition(tmpFloats[Matrix.MTRANS_X], tmpFloats[Matrix.MTRANS_Y]);
+        mSurfaceControl.setAlpha(mTransformation.getAlpha());
+        mSurfaceControl.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y],
+                tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
+        return true;
+    }
+
+    private Animation createReturnAnimationLocked() {
+        final AnimationSet set = new AnimationSet(false);
+        final float translateX = mOriginalX - mCurrentX;
+        final float translateY = mOriginalY - mCurrentY;
+        set.addAnimation(new TranslateAnimation( 0, translateX, 0, translateY));
+        set.addAnimation(new AlphaAnimation(mOriginalAlpha, mOriginalAlpha / 2));
+        // Adjust the duration to the travel distance.
+        final double travelDistance = Math.sqrt(translateX * translateX + translateY * translateY);
+        final double displayDiagonal =
+                Math.sqrt(mDisplaySize.x * mDisplaySize.x + mDisplaySize.y * mDisplaySize.y);
+        final long duration = MIN_ANIMATION_DURATION_MS + (long) (travelDistance / displayDiagonal
+                * (MAX_ANIMATION_DURATION_MS - MIN_ANIMATION_DURATION_MS));
+        set.setDuration(duration);
+        set.setInterpolator(mCubicEaseOutInterpolator);
+        set.initialize(0, 0, 0, 0);
+        set.start();  // Will start on the first call to getTransformation.
+        return set;
+    }
+
+    private Animation createCancelAnimationLocked() {
+        final AnimationSet set = new AnimationSet(false);
+        set.addAnimation(new ScaleAnimation(1, 0, 1, 0, mThumbOffsetX, mThumbOffsetY));
+        set.addAnimation(new AlphaAnimation(mOriginalAlpha, 0));
+        set.setDuration(MIN_ANIMATION_DURATION_MS);
+        set.setInterpolator(mCubicEaseOutInterpolator);
+        set.initialize(0, 0, 0, 0);
+        set.start();  // Will start on the first call to getTransformation.
+        return set;
+    }
+
+    private boolean isFromSource(int source) {
+        return (mTouchSource & source) == source;
+    }
+
+    void overridePointerIconLw(int touchSource) {
+        mTouchSource = touchSource;
+        if (isFromSource(InputDevice.SOURCE_MOUSE)) {
+            InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_GRABBING);
+        }
+    }
+}
diff --git a/com/android/server/wm/EmulatorDisplayOverlay.java b/com/android/server/wm/EmulatorDisplayOverlay.java
new file mode 100644
index 0000000..3186d3d
--- /dev/null
+++ b/com/android/server/wm/EmulatorDisplayOverlay.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.view.Display;
+import android.view.Surface;
+import android.view.Surface.OutOfResourcesException;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+
+class EmulatorDisplayOverlay {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "EmulatorDisplayOverlay" : TAG_WM;
+
+    // Display dimensions
+    private Point mScreenSize;
+
+    private final SurfaceControl mSurfaceControl;
+    private final Surface mSurface = new Surface();
+    private int mLastDW;
+    private int mLastDH;
+    private boolean mDrawNeeded;
+    private Drawable mOverlay;
+    private int mRotation;
+    private boolean mVisible;
+
+    public EmulatorDisplayOverlay(Context context, Display display, SurfaceSession session,
+            int zOrder) {
+        mScreenSize = new Point();
+        display.getSize(mScreenSize);
+
+        SurfaceControl ctrl = null;
+        try {
+            if (DEBUG_SURFACE_TRACE) {
+                ctrl = new WindowSurfaceController.SurfaceTrace(session, "EmulatorDisplayOverlay",
+                        mScreenSize.x, mScreenSize.y, PixelFormat.TRANSLUCENT,
+                        SurfaceControl.HIDDEN);
+            } else {
+                ctrl = new SurfaceControl(session, "EmulatorDisplayOverlay", mScreenSize.x,
+                        mScreenSize.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+            }
+            ctrl.setLayerStack(display.getLayerStack());
+            ctrl.setLayer(zOrder);
+            ctrl.setPosition(0, 0);
+            ctrl.show();
+            mSurface.copyFrom(ctrl);
+        } catch (OutOfResourcesException e) {
+        }
+        mSurfaceControl = ctrl;
+        mDrawNeeded = true;
+        mOverlay = context.getDrawable(
+                com.android.internal.R.drawable.emulator_circular_window_overlay);
+    }
+
+    private void drawIfNeeded() {
+        if (!mDrawNeeded || !mVisible) {
+            return;
+        }
+        mDrawNeeded = false;
+
+        Rect dirty = new Rect(0, 0, mScreenSize.x, mScreenSize.y);
+        Canvas c = null;
+        try {
+            c = mSurface.lockCanvas(dirty);
+        } catch (IllegalArgumentException e) {
+        } catch (OutOfResourcesException e) {
+        }
+        if (c == null) {
+            return;
+        }
+        c.drawColor(Color.TRANSPARENT, PorterDuff.Mode.SRC);
+        mSurfaceControl.setPosition(0, 0);
+        // Always draw the overlay with square dimensions
+        int size = Math.max(mScreenSize.x, mScreenSize.y);
+        mOverlay.setBounds(0, 0, size, size);
+        mOverlay.draw(c);
+        mSurface.unlockCanvasAndPost(c);
+    }
+
+    // Note: caller responsible for being inside
+    // Surface.openTransaction() / closeTransaction()
+    public void setVisibility(boolean on) {
+        if (mSurfaceControl == null) {
+            return;
+        }
+        mVisible = on;
+        drawIfNeeded();
+        if (on) {
+            mSurfaceControl.show();
+        } else {
+            mSurfaceControl.hide();
+        }
+    }
+
+    void positionSurface(int dw, int dh, int rotation) {
+        if (mLastDW == dw && mLastDH == dh && mRotation == rotation) {
+            return;
+        }
+        mLastDW = dw;
+        mLastDH = dh;
+        mDrawNeeded = true;
+        mRotation = rotation;
+        drawIfNeeded();
+    }
+
+}
diff --git a/com/android/server/wm/InputConsumerImpl.java b/com/android/server/wm/InputConsumerImpl.java
new file mode 100644
index 0000000..36753b7
--- /dev/null
+++ b/com/android/server/wm/InputConsumerImpl.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.os.Process;
+import android.view.Display;
+import android.view.InputChannel;
+import android.view.WindowManager;
+import com.android.server.input.InputApplicationHandle;
+import com.android.server.input.InputWindowHandle;
+
+class InputConsumerImpl {
+    final WindowManagerService mService;
+    final InputChannel mServerChannel, mClientChannel;
+    final InputApplicationHandle mApplicationHandle;
+    final InputWindowHandle mWindowHandle;
+
+    InputConsumerImpl(WindowManagerService service, String name, InputChannel inputChannel) {
+        mService = service;
+
+        InputChannel[] channels = InputChannel.openInputChannelPair(name);
+        mServerChannel = channels[0];
+        if (inputChannel != null) {
+            channels[1].transferTo(inputChannel);
+            channels[1].dispose();
+            mClientChannel = inputChannel;
+        } else {
+            mClientChannel = channels[1];
+        }
+        mService.mInputManager.registerInputChannel(mServerChannel, null);
+
+        mApplicationHandle = new InputApplicationHandle(null);
+        mApplicationHandle.name = name;
+        mApplicationHandle.dispatchingTimeoutNanos =
+                WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+
+        mWindowHandle = new InputWindowHandle(mApplicationHandle, null, null,
+                Display.DEFAULT_DISPLAY);
+        mWindowHandle.name = name;
+        mWindowHandle.inputChannel = mServerChannel;
+        mWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
+        mWindowHandle.layer = getLayerLw(mWindowHandle.layoutParamsType);
+        mWindowHandle.layoutParamsFlags = 0;
+        mWindowHandle.dispatchingTimeoutNanos =
+                WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+        mWindowHandle.visible = true;
+        mWindowHandle.canReceiveKeys = false;
+        mWindowHandle.hasFocus = false;
+        mWindowHandle.hasWallpaper = false;
+        mWindowHandle.paused = false;
+        mWindowHandle.ownerPid = Process.myPid();
+        mWindowHandle.ownerUid = Process.myUid();
+        mWindowHandle.inputFeatures = 0;
+        mWindowHandle.scaleFactor = 1.0f;
+    }
+
+    void layout(int dw, int dh) {
+        mWindowHandle.touchableRegion.set(0, 0, dw, dh);
+        mWindowHandle.frameLeft = 0;
+        mWindowHandle.frameTop = 0;
+        mWindowHandle.frameRight = dw;
+        mWindowHandle.frameBottom = dh;
+    }
+
+    private int getLayerLw(int windowType) {
+        return mService.mPolicy.getWindowLayerFromTypeLw(windowType)
+                * WindowManagerService.TYPE_LAYER_MULTIPLIER
+                + WindowManagerService.TYPE_LAYER_OFFSET;
+    }
+
+    void disposeChannelsLw() {
+        mService.mInputManager.unregisterInputChannel(mServerChannel);
+        mClientChannel.dispose();
+        mServerChannel.dispose();
+    }
+}
diff --git a/com/android/server/wm/InputMonitor.java b/com/android/server/wm/InputMonitor.java
new file mode 100644
index 0000000..5057f63
--- /dev/null
+++ b/com/android/server/wm/InputMonitor.java
@@ -0,0 +1,700 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION;
+import static android.view.WindowManager.INPUT_CONSUMER_PIP;
+import static android.view.WindowManager.INPUT_CONSUMER_WALLPAPER;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.app.ActivityManager;
+import android.graphics.Rect;
+import android.os.Debug;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+import android.view.InputChannel;
+import android.view.InputEventReceiver;
+import android.view.KeyEvent;
+import android.view.WindowManager;
+
+import android.view.WindowManagerPolicy;
+
+import com.android.server.input.InputApplicationHandle;
+import com.android.server.input.InputManagerService;
+import com.android.server.input.InputWindowHandle;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Set;
+import java.util.function.Consumer;
+
+final class InputMonitor implements InputManagerService.WindowManagerCallbacks {
+    private final WindowManagerService mService;
+
+    // Current window with input focus for keys and other non-touch events.  May be null.
+    private WindowState mInputFocus;
+
+    // When true, prevents input dispatch from proceeding until set to false again.
+    private boolean mInputDispatchFrozen;
+
+    // The reason the input is currently frozen or null if the input isn't frozen.
+    private String mInputFreezeReason = null;
+
+    // When true, input dispatch proceeds normally.  Otherwise all events are dropped.
+    // Initially false, so that input does not get dispatched until boot is finished at
+    // which point the ActivityManager will enable dispatching.
+    private boolean mInputDispatchEnabled;
+
+    // When true, need to call updateInputWindowsLw().
+    private boolean mUpdateInputWindowsNeeded = true;
+
+    // Array of window handles to provide to the input dispatcher.
+    private InputWindowHandle[] mInputWindowHandles;
+    private int mInputWindowHandleCount;
+    private InputWindowHandle mFocusedInputWindowHandle;
+
+    private boolean mAddInputConsumerHandle;
+    private boolean mAddPipInputConsumerHandle;
+    private boolean mAddWallpaperInputConsumerHandle;
+    private boolean mDisableWallpaperTouchEvents;
+    private final Rect mTmpRect = new Rect();
+    private final UpdateInputForAllWindowsConsumer mUpdateInputForAllWindowsConsumer =
+            new UpdateInputForAllWindowsConsumer();
+
+    // Set to true when the first input device configuration change notification
+    // is received to indicate that the input devices are ready.
+    private final Object mInputDevicesReadyMonitor = new Object();
+    private boolean mInputDevicesReady;
+
+    /**
+     * The set of input consumer added to the window manager by name, which consumes input events
+     * for the windows below it.
+     */
+    private final ArrayMap<String, InputConsumerImpl> mInputConsumers = new ArrayMap();
+
+    private static final class EventReceiverInputConsumer extends InputConsumerImpl
+            implements WindowManagerPolicy.InputConsumer {
+        private InputMonitor mInputMonitor;
+        private final InputEventReceiver mInputEventReceiver;
+
+        EventReceiverInputConsumer(WindowManagerService service, InputMonitor monitor,
+                                   Looper looper, String name,
+                                   InputEventReceiver.Factory inputEventReceiverFactory) {
+            super(service, name, null);
+            mInputMonitor = monitor;
+            mInputEventReceiver = inputEventReceiverFactory.createInputEventReceiver(
+                    mClientChannel, looper);
+        }
+
+        @Override
+        public void dismiss() {
+            synchronized (mService.mWindowMap) {
+                if (mInputMonitor.destroyInputConsumer(mWindowHandle.name)) {
+                    mInputEventReceiver.dispose();
+                }
+            }
+        }
+    }
+
+    public InputMonitor(WindowManagerService service) {
+        mService = service;
+    }
+
+    private void addInputConsumer(String name, InputConsumerImpl consumer) {
+        mInputConsumers.put(name, consumer);
+        updateInputWindowsLw(true /* force */);
+    }
+
+    boolean destroyInputConsumer(String name) {
+        if (disposeInputConsumer(mInputConsumers.remove(name))) {
+            updateInputWindowsLw(true /* force */);
+            return true;
+        }
+        return false;
+    }
+
+    private boolean disposeInputConsumer(InputConsumerImpl consumer) {
+        if (consumer != null) {
+            consumer.disposeChannelsLw();
+            return true;
+        }
+        return false;
+    }
+
+    InputConsumerImpl getInputConsumer(String name, int displayId) {
+        // TODO(multi-display): Allow input consumers on non-default displays?
+        return (displayId == DEFAULT_DISPLAY) ? mInputConsumers.get(name) : null;
+    }
+
+    void layoutInputConsumers(int dw, int dh) {
+        for (int i = mInputConsumers.size() - 1; i >= 0; i--) {
+            mInputConsumers.valueAt(i).layout(dw, dh);
+        }
+    }
+
+    WindowManagerPolicy.InputConsumer createInputConsumer(Looper looper, String name,
+            InputEventReceiver.Factory inputEventReceiverFactory) {
+        if (mInputConsumers.containsKey(name)) {
+            throw new IllegalStateException("Existing input consumer found with name: " + name);
+        }
+
+        final EventReceiverInputConsumer consumer = new EventReceiverInputConsumer(mService,
+                this, looper, name, inputEventReceiverFactory);
+        addInputConsumer(name, consumer);
+        return consumer;
+    }
+
+    void createInputConsumer(String name, InputChannel inputChannel) {
+        if (mInputConsumers.containsKey(name)) {
+            throw new IllegalStateException("Existing input consumer found with name: " + name);
+        }
+
+        final InputConsumerImpl consumer = new InputConsumerImpl(mService, name, inputChannel);
+        switch (name) {
+            case INPUT_CONSUMER_WALLPAPER:
+                consumer.mWindowHandle.hasWallpaper = true;
+                break;
+            case INPUT_CONSUMER_PIP:
+                // The touchable region of the Pip input window is cropped to the bounds of the
+                // stack, and we need FLAG_NOT_TOUCH_MODAL to ensure other events fall through
+                consumer.mWindowHandle.layoutParamsFlags |= FLAG_NOT_TOUCH_MODAL;
+                break;
+        }
+        addInputConsumer(name, consumer);
+    }
+
+    /* Notifies the window manager about a broken input channel.
+     *
+     * Called by the InputManager.
+     */
+    @Override
+    public void notifyInputChannelBroken(InputWindowHandle inputWindowHandle) {
+        if (inputWindowHandle == null) {
+            return;
+        }
+
+        synchronized (mService.mWindowMap) {
+            WindowState windowState = (WindowState) inputWindowHandle.windowState;
+            if (windowState != null) {
+                Slog.i(TAG_WM, "WINDOW DIED " + windowState);
+                windowState.removeIfPossible();
+            }
+        }
+    }
+
+    /* Notifies the window manager about an application that is not responding.
+     * Returns a new timeout to continue waiting in nanoseconds, or 0 to abort dispatch.
+     *
+     * Called by the InputManager.
+     */
+    @Override
+    public long notifyANR(InputApplicationHandle inputApplicationHandle,
+            InputWindowHandle inputWindowHandle, String reason) {
+        AppWindowToken appWindowToken = null;
+        WindowState windowState = null;
+        boolean aboveSystem = false;
+        synchronized (mService.mWindowMap) {
+            if (inputWindowHandle != null) {
+                windowState = (WindowState) inputWindowHandle.windowState;
+                if (windowState != null) {
+                    appWindowToken = windowState.mAppToken;
+                }
+            }
+            if (appWindowToken == null && inputApplicationHandle != null) {
+                appWindowToken = (AppWindowToken)inputApplicationHandle.appWindowToken;
+            }
+
+            if (windowState != null) {
+                Slog.i(TAG_WM, "Input event dispatching timed out "
+                        + "sending to " + windowState.mAttrs.getTitle()
+                        + ".  Reason: " + reason);
+                // Figure out whether this window is layered above system windows.
+                // We need to do this here to help the activity manager know how to
+                // layer its ANR dialog.
+                int systemAlertLayer = mService.mPolicy.getWindowLayerFromTypeLw(
+                        TYPE_APPLICATION_OVERLAY, windowState.mOwnerCanAddInternalSystemWindow);
+                aboveSystem = windowState.mBaseLayer > systemAlertLayer;
+            } else if (appWindowToken != null) {
+                Slog.i(TAG_WM, "Input event dispatching timed out "
+                        + "sending to application " + appWindowToken.stringName
+                        + ".  Reason: " + reason);
+            } else {
+                Slog.i(TAG_WM, "Input event dispatching timed out "
+                        + ".  Reason: " + reason);
+            }
+
+            mService.saveANRStateLocked(appWindowToken, windowState, reason);
+        }
+
+        // All the calls below need to happen without the WM lock held since they call into AM.
+        mService.mAmInternal.saveANRState(reason);
+
+        if (appWindowToken != null && appWindowToken.appToken != null) {
+            // Notify the activity manager about the timeout and let it decide whether
+            // to abort dispatching or keep waiting.
+            final AppWindowContainerController controller = appWindowToken.getController();
+            final boolean abort = controller != null
+                    && controller.keyDispatchingTimedOut(reason,
+                            (windowState != null) ? windowState.mSession.mPid : -1);
+            if (!abort) {
+                // The activity manager declined to abort dispatching.
+                // Wait a bit longer and timeout again later.
+                return appWindowToken.mInputDispatchingTimeoutNanos;
+            }
+        } else if (windowState != null) {
+            try {
+                // Notify the activity manager about the timeout and let it decide whether
+                // to abort dispatching or keep waiting.
+                long timeout = ActivityManager.getService().inputDispatchingTimedOut(
+                        windowState.mSession.mPid, aboveSystem, reason);
+                if (timeout >= 0) {
+                    // The activity manager declined to abort dispatching.
+                    // Wait a bit longer and timeout again later.
+                    return timeout * 1000000L; // nanoseconds
+                }
+            } catch (RemoteException ex) {
+            }
+        }
+        return 0; // abort dispatching
+    }
+
+    private void addInputWindowHandle(final InputWindowHandle windowHandle) {
+        if (mInputWindowHandles == null) {
+            mInputWindowHandles = new InputWindowHandle[16];
+        }
+        if (mInputWindowHandleCount >= mInputWindowHandles.length) {
+            mInputWindowHandles = Arrays.copyOf(mInputWindowHandles,
+                    mInputWindowHandleCount * 2);
+        }
+        mInputWindowHandles[mInputWindowHandleCount++] = windowHandle;
+    }
+
+    void addInputWindowHandle(final InputWindowHandle inputWindowHandle,
+            final WindowState child, int flags, final int type, final boolean isVisible,
+            final boolean hasFocus, final boolean hasWallpaper) {
+        // Add a window to our list of input windows.
+        inputWindowHandle.name = child.toString();
+        flags = child.getTouchableRegion(inputWindowHandle.touchableRegion, flags);
+        inputWindowHandle.layoutParamsFlags = flags;
+        inputWindowHandle.layoutParamsType = type;
+        inputWindowHandle.dispatchingTimeoutNanos = child.getInputDispatchingTimeoutNanos();
+        inputWindowHandle.visible = isVisible;
+        inputWindowHandle.canReceiveKeys = child.canReceiveKeys();
+        inputWindowHandle.hasFocus = hasFocus;
+        inputWindowHandle.hasWallpaper = hasWallpaper;
+        inputWindowHandle.paused = child.mAppToken != null ? child.mAppToken.paused : false;
+        inputWindowHandle.layer = child.mLayer;
+        inputWindowHandle.ownerPid = child.mSession.mPid;
+        inputWindowHandle.ownerUid = child.mSession.mUid;
+        inputWindowHandle.inputFeatures = child.mAttrs.inputFeatures;
+
+        final Rect frame = child.mFrame;
+        inputWindowHandle.frameLeft = frame.left;
+        inputWindowHandle.frameTop = frame.top;
+        inputWindowHandle.frameRight = frame.right;
+        inputWindowHandle.frameBottom = frame.bottom;
+
+        if (child.mGlobalScale != 1) {
+            // If we are scaling the window, input coordinates need
+            // to be inversely scaled to map from what is on screen
+            // to what is actually being touched in the UI.
+            inputWindowHandle.scaleFactor = 1.0f/child.mGlobalScale;
+        } else {
+            inputWindowHandle.scaleFactor = 1;
+        }
+
+        if (DEBUG_INPUT) {
+            Slog.d(TAG_WM, "addInputWindowHandle: "
+                    + child + ", " + inputWindowHandle);
+        }
+        addInputWindowHandle(inputWindowHandle);
+        if (hasFocus) {
+            mFocusedInputWindowHandle = inputWindowHandle;
+        }
+    }
+
+    private void clearInputWindowHandlesLw() {
+        while (mInputWindowHandleCount != 0) {
+            mInputWindowHandles[--mInputWindowHandleCount] = null;
+        }
+        mFocusedInputWindowHandle = null;
+    }
+
+    void setUpdateInputWindowsNeededLw() {
+        mUpdateInputWindowsNeeded = true;
+    }
+
+    /* Updates the cached window information provided to the input dispatcher. */
+    void updateInputWindowsLw(boolean force) {
+        if (!force && !mUpdateInputWindowsNeeded) {
+            return;
+        }
+        mUpdateInputWindowsNeeded = false;
+
+        if (false) Slog.d(TAG_WM, ">>>>>> ENTERED updateInputWindowsLw");
+
+        // Populate the input window list with information about all of the windows that
+        // could potentially receive input.
+        // As an optimization, we could try to prune the list of windows but this turns
+        // out to be difficult because only the native code knows for sure which window
+        // currently has touch focus.
+
+        // If there's a drag in flight, provide a pseudo-window to catch drag input
+        final boolean inDrag = (mService.mDragState != null);
+        if (inDrag) {
+            if (DEBUG_DRAG) {
+                Log.d(TAG_WM, "Inserting drag window");
+            }
+            final InputWindowHandle dragWindowHandle = mService.mDragState.getInputWindowHandle();
+            if (dragWindowHandle != null) {
+                addInputWindowHandle(dragWindowHandle);
+            } else {
+                Slog.w(TAG_WM, "Drag is in progress but there is no "
+                        + "drag window handle.");
+            }
+        }
+
+        final boolean inPositioning = (mService.mTaskPositioner != null);
+        if (inPositioning) {
+            if (DEBUG_TASK_POSITIONING) {
+                Log.d(TAG_WM, "Inserting window handle for repositioning");
+            }
+            final InputWindowHandle dragWindowHandle = mService.mTaskPositioner.mDragWindowHandle;
+            if (dragWindowHandle != null) {
+                addInputWindowHandle(dragWindowHandle);
+            } else {
+                Slog.e(TAG_WM,
+                        "Repositioning is in progress but there is no drag window handle.");
+            }
+        }
+
+        // Add all windows on the default display.
+        mUpdateInputForAllWindowsConsumer.updateInputWindows(inDrag);
+
+        if (false) Slog.d(TAG_WM, "<<<<<<< EXITED updateInputWindowsLw");
+    }
+
+    /* Notifies that the input device configuration has changed. */
+    @Override
+    public void notifyConfigurationChanged() {
+        // TODO(multi-display): Notify proper displays that are associated with this input device.
+        mService.sendNewConfiguration(DEFAULT_DISPLAY);
+
+        synchronized (mInputDevicesReadyMonitor) {
+            if (!mInputDevicesReady) {
+                mInputDevicesReady = true;
+                mInputDevicesReadyMonitor.notifyAll();
+            }
+        }
+    }
+
+    /* Waits until the built-in input devices have been configured. */
+    public boolean waitForInputDevicesReady(long timeoutMillis) {
+        synchronized (mInputDevicesReadyMonitor) {
+            if (!mInputDevicesReady) {
+                try {
+                    mInputDevicesReadyMonitor.wait(timeoutMillis);
+                } catch (InterruptedException ex) {
+                }
+            }
+            return mInputDevicesReady;
+        }
+    }
+
+    /* Notifies that the lid switch changed state. */
+    @Override
+    public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) {
+        mService.mPolicy.notifyLidSwitchChanged(whenNanos, lidOpen);
+    }
+
+    /* Notifies that the camera lens cover state has changed. */
+    @Override
+    public void notifyCameraLensCoverSwitchChanged(long whenNanos, boolean lensCovered) {
+        mService.mPolicy.notifyCameraLensCoverSwitchChanged(whenNanos, lensCovered);
+    }
+
+    /* Provides an opportunity for the window manager policy to intercept early key
+     * processing as soon as the key has been read from the device. */
+    @Override
+    public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
+        return mService.mPolicy.interceptKeyBeforeQueueing(event, policyFlags);
+    }
+
+    /* Provides an opportunity for the window manager policy to intercept early motion event
+     * processing when the device is in a non-interactive state since these events are normally
+     * dropped. */
+    @Override
+    public int interceptMotionBeforeQueueingNonInteractive(long whenNanos, int policyFlags) {
+        return mService.mPolicy.interceptMotionBeforeQueueingNonInteractive(
+                whenNanos, policyFlags);
+    }
+
+    /* Provides an opportunity for the window manager policy to process a key before
+     * ordinary dispatch. */
+    @Override
+    public long interceptKeyBeforeDispatching(
+            InputWindowHandle focus, KeyEvent event, int policyFlags) {
+        WindowState windowState = focus != null ? (WindowState) focus.windowState : null;
+        return mService.mPolicy.interceptKeyBeforeDispatching(windowState, event, policyFlags);
+    }
+
+    /* Provides an opportunity for the window manager policy to process a key that
+     * the application did not handle. */
+    @Override
+    public KeyEvent dispatchUnhandledKey(
+            InputWindowHandle focus, KeyEvent event, int policyFlags) {
+        WindowState windowState = focus != null ? (WindowState) focus.windowState : null;
+        return mService.mPolicy.dispatchUnhandledKey(windowState, event, policyFlags);
+    }
+
+    /* Callback to get pointer layer. */
+    @Override
+    public int getPointerLayer() {
+        return mService.mPolicy.getWindowLayerFromTypeLw(WindowManager.LayoutParams.TYPE_POINTER)
+                * WindowManagerService.TYPE_LAYER_MULTIPLIER
+                + WindowManagerService.TYPE_LAYER_OFFSET;
+    }
+
+    /* Called when the current input focus changes.
+     * Layer assignment is assumed to be complete by the time this is called.
+     */
+    public void setInputFocusLw(WindowState newWindow, boolean updateInputWindows) {
+        if (DEBUG_FOCUS_LIGHT || DEBUG_INPUT) {
+            Slog.d(TAG_WM, "Input focus has changed to " + newWindow);
+        }
+
+        if (newWindow != mInputFocus) {
+            if (newWindow != null && newWindow.canReceiveKeys()) {
+                // Displaying a window implicitly causes dispatching to be unpaused.
+                // This is to protect against bugs if someone pauses dispatching but
+                // forgets to resume.
+                newWindow.mToken.paused = false;
+            }
+
+            mInputFocus = newWindow;
+            setUpdateInputWindowsNeededLw();
+
+            if (updateInputWindows) {
+                updateInputWindowsLw(false /*force*/);
+            }
+        }
+    }
+
+    public void setFocusedAppLw(AppWindowToken newApp) {
+        // Focused app has changed.
+        if (newApp == null) {
+            mService.mInputManager.setFocusedApplication(null);
+        } else {
+            final InputApplicationHandle handle = newApp.mInputApplicationHandle;
+            handle.name = newApp.toString();
+            handle.dispatchingTimeoutNanos = newApp.mInputDispatchingTimeoutNanos;
+
+            mService.mInputManager.setFocusedApplication(handle);
+        }
+    }
+
+    public void pauseDispatchingLw(WindowToken window) {
+        if (! window.paused) {
+            if (DEBUG_INPUT) {
+                Slog.v(TAG_WM, "Pausing WindowToken " + window);
+            }
+
+            window.paused = true;
+            updateInputWindowsLw(true /*force*/);
+        }
+    }
+
+    public void resumeDispatchingLw(WindowToken window) {
+        if (window.paused) {
+            if (DEBUG_INPUT) {
+                Slog.v(TAG_WM, "Resuming WindowToken " + window);
+            }
+
+            window.paused = false;
+            updateInputWindowsLw(true /*force*/);
+        }
+    }
+
+    public void freezeInputDispatchingLw() {
+        if (!mInputDispatchFrozen) {
+            if (DEBUG_INPUT) {
+                Slog.v(TAG_WM, "Freezing input dispatching");
+            }
+
+            mInputDispatchFrozen = true;
+
+            if (DEBUG_INPUT || true) {
+                mInputFreezeReason = Debug.getCallers(6);
+            }
+            updateInputDispatchModeLw();
+        }
+    }
+
+    public void thawInputDispatchingLw() {
+        if (mInputDispatchFrozen) {
+            if (DEBUG_INPUT) {
+                Slog.v(TAG_WM, "Thawing input dispatching");
+            }
+
+            mInputDispatchFrozen = false;
+            mInputFreezeReason = null;
+            updateInputDispatchModeLw();
+        }
+    }
+
+    public void setEventDispatchingLw(boolean enabled) {
+        if (mInputDispatchEnabled != enabled) {
+            if (DEBUG_INPUT) {
+                Slog.v(TAG_WM, "Setting event dispatching to " + enabled);
+            }
+
+            mInputDispatchEnabled = enabled;
+            updateInputDispatchModeLw();
+        }
+    }
+
+    private void updateInputDispatchModeLw() {
+        mService.mInputManager.setInputDispatchMode(mInputDispatchEnabled, mInputDispatchFrozen);
+    }
+
+    void dump(PrintWriter pw, String prefix) {
+        if (mInputFreezeReason != null) {
+            pw.println(prefix + "mInputFreezeReason=" + mInputFreezeReason);
+        }
+        final Set<String> inputConsumerKeys = mInputConsumers.keySet();
+        if (!inputConsumerKeys.isEmpty()) {
+            pw.println(prefix + "InputConsumers:");
+            for (String key : inputConsumerKeys) {
+                pw.println(prefix + "  name=" + key);
+            }
+        }
+    }
+
+    private final class UpdateInputForAllWindowsConsumer implements Consumer<WindowState> {
+
+        InputConsumerImpl navInputConsumer;
+        InputConsumerImpl pipInputConsumer;
+        InputConsumerImpl wallpaperInputConsumer;
+        Rect pipTouchableBounds;
+        boolean inDrag;
+        WallpaperController wallpaperController;
+
+        private void updateInputWindows(boolean inDrag) {
+
+            // TODO: multi-display
+            navInputConsumer = getInputConsumer(INPUT_CONSUMER_NAVIGATION, DEFAULT_DISPLAY);
+            pipInputConsumer = getInputConsumer(INPUT_CONSUMER_PIP, DEFAULT_DISPLAY);
+            wallpaperInputConsumer = getInputConsumer(INPUT_CONSUMER_WALLPAPER, DEFAULT_DISPLAY);
+            mAddInputConsumerHandle = navInputConsumer != null;
+            mAddPipInputConsumerHandle = pipInputConsumer != null;
+            mAddWallpaperInputConsumerHandle = wallpaperInputConsumer != null;
+            mTmpRect.setEmpty();
+            pipTouchableBounds = mAddPipInputConsumerHandle ? mTmpRect : null;
+            mDisableWallpaperTouchEvents = false;
+            this.inDrag = inDrag;
+            wallpaperController = mService.mRoot.mWallpaperController;
+
+            mService.mRoot.forAllWindows(this, true /* traverseTopToBottom */);
+            if (mAddWallpaperInputConsumerHandle) {
+                // No visible wallpaper found, add the wallpaper input consumer at the end.
+                addInputWindowHandle(wallpaperInputConsumer.mWindowHandle);
+            }
+
+            // Send windows to native code.
+            mService.mInputManager.setInputWindows(mInputWindowHandles, mFocusedInputWindowHandle);
+
+            clearInputWindowHandlesLw();
+        }
+
+        @Override
+        public void accept(WindowState w) {
+            final InputChannel inputChannel = w.mInputChannel;
+            final InputWindowHandle inputWindowHandle = w.mInputWindowHandle;
+            if (inputChannel == null || inputWindowHandle == null || w.mRemoved
+                    || w.canReceiveTouchInput()) {
+                // Skip this window because it cannot possibly receive input.
+                return;
+            }
+
+            final int flags = w.mAttrs.flags;
+            final int privateFlags = w.mAttrs.privateFlags;
+            final int type = w.mAttrs.type;
+            final boolean hasFocus = w == mInputFocus;
+            final boolean isVisible = w.isVisibleLw();
+
+            if (w.getStackId() == PINNED_STACK_ID) {
+                if (mAddPipInputConsumerHandle
+                        && (inputWindowHandle.layer <= pipInputConsumer.mWindowHandle.layer)) {
+                    // Update the bounds of the Pip input consumer to match the Pinned stack
+                    w.getStack().getBounds(pipTouchableBounds);
+                    pipInputConsumer.mWindowHandle.touchableRegion.set(pipTouchableBounds);
+                    addInputWindowHandle(pipInputConsumer.mWindowHandle);
+                    mAddPipInputConsumerHandle = false;
+                }
+                // TODO: Fix w.canReceiveTouchInput() to handle this case
+                if (!hasFocus) {
+                    // Skip this pinned stack window if it does not have focus
+                    return;
+                }
+            }
+
+            if (mAddInputConsumerHandle
+                    && inputWindowHandle.layer <= navInputConsumer.mWindowHandle.layer) {
+                addInputWindowHandle(navInputConsumer.mWindowHandle);
+                mAddInputConsumerHandle = false;
+            }
+
+            if (mAddWallpaperInputConsumerHandle) {
+                if (w.mAttrs.type == TYPE_WALLPAPER && w.isVisibleLw()) {
+                    // Add the wallpaper input consumer above the first visible wallpaper.
+                    addInputWindowHandle(wallpaperInputConsumer.mWindowHandle);
+                    mAddWallpaperInputConsumerHandle = false;
+                }
+            }
+
+            if ((privateFlags & PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS) != 0) {
+                mDisableWallpaperTouchEvents = true;
+            }
+            final boolean hasWallpaper = wallpaperController.isWallpaperTarget(w)
+                    && (privateFlags & PRIVATE_FLAG_KEYGUARD) == 0
+                    && !mDisableWallpaperTouchEvents;
+
+            // If there's a drag in progress and 'child' is a potential drop target,
+            // make sure it's been told about the drag
+            if (inDrag && isVisible && w.getDisplayContent().isDefaultDisplay) {
+                mService.mDragState.sendDragStartedIfNeededLw(w);
+            }
+
+            addInputWindowHandle(
+                    inputWindowHandle, w, flags, type, isVisible, hasFocus, hasWallpaper);
+        }
+    }
+}
diff --git a/com/android/server/wm/KeyguardDisableHandler.java b/com/android/server/wm/KeyguardDisableHandler.java
new file mode 100644
index 0000000..2eb186b
--- /dev/null
+++ b/com/android/server/wm/KeyguardDisableHandler.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.app.ActivityManager;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.TokenWatcher;
+import android.util.Log;
+import android.util.Pair;
+import android.view.WindowManagerPolicy;
+
+public class KeyguardDisableHandler extends Handler {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "KeyguardDisableHandler" : TAG_WM;
+
+    private static final int ALLOW_DISABLE_YES = 1;
+    private static final int ALLOW_DISABLE_NO = 0;
+    private static final int ALLOW_DISABLE_UNKNOWN = -1; // check with DevicePolicyManager
+    private int mAllowDisableKeyguard = ALLOW_DISABLE_UNKNOWN; // sync'd by mKeyguardTokenWatcher
+
+    // Message.what constants
+    static final int KEYGUARD_DISABLE = 1;
+    static final int KEYGUARD_REENABLE = 2;
+    static final int KEYGUARD_POLICY_CHANGED = 3;
+
+    final Context mContext;
+    final WindowManagerPolicy mPolicy;
+    KeyguardTokenWatcher mKeyguardTokenWatcher;
+
+    public KeyguardDisableHandler(final Context context, final WindowManagerPolicy policy) {
+        mContext = context;
+        mPolicy = policy;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public void handleMessage(Message msg) {
+        if (mKeyguardTokenWatcher == null) {
+            mKeyguardTokenWatcher = new KeyguardTokenWatcher(this);
+        }
+
+        switch (msg.what) {
+            case KEYGUARD_DISABLE:
+                final Pair<IBinder, String> pair = (Pair<IBinder, String>)msg.obj;
+                mKeyguardTokenWatcher.acquire(pair.first, pair.second);
+                break;
+
+            case KEYGUARD_REENABLE:
+                mKeyguardTokenWatcher.release((IBinder)msg.obj);
+                break;
+
+            case KEYGUARD_POLICY_CHANGED:
+                mAllowDisableKeyguard = ALLOW_DISABLE_UNKNOWN;
+                if (mKeyguardTokenWatcher.isAcquired()) {
+                    // If we are currently disabled we need to know if the keyguard
+                    // should be re-enabled, so determine the allow state immediately.
+                    mKeyguardTokenWatcher.updateAllowState();
+                    if (mAllowDisableKeyguard != ALLOW_DISABLE_YES) {
+                        mPolicy.enableKeyguard(true);
+                    }
+                } else {
+                    // lazily evaluate this next time we're asked to disable keyguard
+                    mPolicy.enableKeyguard(true);
+                }
+                break;
+        }
+    }
+
+    class KeyguardTokenWatcher extends TokenWatcher {
+
+        public KeyguardTokenWatcher(final Handler handler) {
+            super(handler, TAG);
+        }
+
+        public void updateAllowState() {
+            // We fail safe and prevent disabling keyguard in the unlikely event this gets
+            // called before DevicePolicyManagerService has started.
+            DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(
+                    Context.DEVICE_POLICY_SERVICE);
+            if (dpm != null) {
+                try {
+                    mAllowDisableKeyguard = dpm.getPasswordQuality(null,
+                            ActivityManager.getService().getCurrentUser().id)
+                            == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED ?
+                                    ALLOW_DISABLE_YES : ALLOW_DISABLE_NO;
+                } catch (RemoteException re) {
+                    // Nothing much we can do
+                }
+            }
+        }
+
+        @Override
+        public void acquired() {
+            if (mAllowDisableKeyguard == ALLOW_DISABLE_UNKNOWN) {
+                updateAllowState();
+            }
+            if (mAllowDisableKeyguard == ALLOW_DISABLE_YES) {
+                mPolicy.enableKeyguard(false);
+            } else {
+                Log.v(TAG, "Not disabling keyguard since device policy is enforced");
+            }
+        }
+
+        @Override
+        public void released() {
+            mPolicy.enableKeyguard(true);
+        }
+    }
+}
diff --git a/com/android/server/wm/PinnedStackController.java b/com/android/server/wm/PinnedStackController.java
new file mode 100644
index 0000000..1e7140a
--- /dev/null
+++ b/com/android/server/wm/PinnedStackController.java
@@ -0,0 +1,519 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.util.TypedValue.COMPLEX_UNIT_DIP;
+
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.proto.PinnedStackControllerProto.DEFAULT_BOUNDS;
+import static com.android.server.wm.proto.PinnedStackControllerProto.MOVEMENT_BOUNDS;
+
+import android.app.RemoteAction;
+import android.content.pm.ParceledListSlice;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.Size;
+import android.util.Slog;
+import android.util.TypedValue;
+import android.util.proto.ProtoOutputStream;
+import android.view.DisplayInfo;
+import android.view.Gravity;
+import android.view.IPinnedStackController;
+import android.view.IPinnedStackListener;
+
+import com.android.internal.policy.PipSnapAlgorithm;
+import com.android.server.UiThread;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Holds the common state of the pinned stack between the system and SystemUI. If SystemUI ever
+ * needs to be restarted, it will be notified with the last known state.
+ *
+ * Changes to the pinned stack also flow through this controller, and generally, the system only
+ * changes the pinned stack bounds through this controller in two ways:
+ *
+ * 1) When first entering PiP: the controller returns the valid bounds given, taking aspect ratio
+ *    and IME state into account.
+ * 2) When rotating the device: the controller calculates the new bounds in the new orientation,
+ *    taking the minimized and IME state into account. In this case, we currently ignore the
+ *    SystemUI adjustments (ie. expanded for menu, interaction, etc).
+ *
+ * Other changes in the system, including adjustment of IME, configuration change, and more are
+ * handled by SystemUI (similar to the docked stack divider).
+ */
+class PinnedStackController {
+
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "PinnedStackController" : TAG_WM;
+
+    private final WindowManagerService mService;
+    private final DisplayContent mDisplayContent;
+    private final Handler mHandler = UiThread.getHandler();
+
+    private IPinnedStackListener mPinnedStackListener;
+    private final PinnedStackListenerDeathHandler mPinnedStackListenerDeathHandler =
+            new PinnedStackListenerDeathHandler();
+
+    private final PinnedStackControllerCallback mCallbacks = new PinnedStackControllerCallback();
+    private final PipSnapAlgorithm mSnapAlgorithm;
+
+    // States that affect how the PIP can be manipulated
+    private boolean mIsMinimized;
+    private boolean mIsImeShowing;
+    private int mImeHeight;
+
+    // The set of actions and aspect-ratio for the that are currently allowed on the PiP activity
+    private ArrayList<RemoteAction> mActions = new ArrayList<>();
+    private float mAspectRatio = -1f;
+
+    // Used to calculate stack bounds across rotations
+    private final DisplayInfo mDisplayInfo = new DisplayInfo();
+    private final Rect mStableInsets = new Rect();
+
+    // The size and position information that describes where the pinned stack will go by default.
+    private int mDefaultMinSize;
+    private int mDefaultStackGravity;
+    private float mDefaultAspectRatio;
+    private Point mScreenEdgeInsets;
+    private int mCurrentMinSize;
+
+    // The aspect ratio bounds of the PIP.
+    private float mMinAspectRatio;
+    private float mMaxAspectRatio;
+
+    // Temp vars for calculation
+    private final DisplayMetrics mTmpMetrics = new DisplayMetrics();
+    private final Rect mTmpInsets = new Rect();
+    private final Rect mTmpRect = new Rect();
+    private final Rect mTmpAnimatingBoundsRect = new Rect();
+    private final Point mTmpDisplaySize = new Point();
+
+    /**
+     * The callback object passed to listeners for them to notify the controller of state changes.
+     */
+    private class PinnedStackControllerCallback extends IPinnedStackController.Stub {
+
+        @Override
+        public void setIsMinimized(final boolean isMinimized) {
+            mHandler.post(() -> {
+                mIsMinimized = isMinimized;
+                mSnapAlgorithm.setMinimized(isMinimized);
+            });
+        }
+
+        @Override
+        public void setMinEdgeSize(int minEdgeSize) {
+            mHandler.post(() -> {
+                mCurrentMinSize = Math.max(mDefaultMinSize, minEdgeSize);
+            });
+        }
+
+        @Override
+        public int getDisplayRotation() {
+            synchronized (mService.mWindowMap) {
+                return mDisplayInfo.rotation;
+            }
+        }
+    }
+
+    /**
+     * Handler for the case where the listener dies.
+     */
+    private class PinnedStackListenerDeathHandler implements IBinder.DeathRecipient {
+
+        @Override
+        public void binderDied() {
+            // Clean up the state if the listener dies
+            mPinnedStackListener = null;
+        }
+    }
+
+    PinnedStackController(WindowManagerService service, DisplayContent displayContent) {
+        mService = service;
+        mDisplayContent = displayContent;
+        mSnapAlgorithm = new PipSnapAlgorithm(service.mContext);
+        mDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo());
+        reloadResources();
+        // Initialize the aspect ratio to the default aspect ratio.  Don't do this in reload
+        // resources as it would clobber mAspectRatio when entering PiP from fullscreen which
+        // triggers a configuration change and the resources to be reloaded.
+        mAspectRatio = mDefaultAspectRatio;
+    }
+
+    void onConfigurationChanged() {
+        reloadResources();
+    }
+
+    /**
+     * Reloads all the resources for the current configuration.
+     */
+    private void reloadResources() {
+        final Resources res = mService.mContext.getResources();
+        mDefaultMinSize = res.getDimensionPixelSize(
+                com.android.internal.R.dimen.default_minimal_size_pip_resizable_task);
+        mCurrentMinSize = mDefaultMinSize;
+        mDefaultAspectRatio = res.getFloat(
+                com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio);
+        final String screenEdgeInsetsDpString = res.getString(
+                com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets);
+        final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
+                ? Size.parseSize(screenEdgeInsetsDpString)
+                : null;
+        mDefaultStackGravity = res.getInteger(
+                com.android.internal.R.integer.config_defaultPictureInPictureGravity);
+        mDisplayContent.getDisplay().getRealMetrics(mTmpMetrics);
+        mScreenEdgeInsets = screenEdgeInsetsDp == null ? new Point()
+                : new Point(dpToPx(screenEdgeInsetsDp.getWidth(), mTmpMetrics),
+                        dpToPx(screenEdgeInsetsDp.getHeight(), mTmpMetrics));
+        mMinAspectRatio = res.getFloat(
+                com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
+        mMaxAspectRatio = res.getFloat(
+                com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio);
+    }
+
+    /**
+     * Registers a pinned stack listener.
+     */
+    void registerPinnedStackListener(IPinnedStackListener listener) {
+        try {
+            listener.asBinder().linkToDeath(mPinnedStackListenerDeathHandler, 0);
+            listener.onListenerRegistered(mCallbacks);
+            mPinnedStackListener = listener;
+            notifyImeVisibilityChanged(mIsImeShowing, mImeHeight);
+            // The movement bounds notification needs to be sent before the minimized state, since
+            // SystemUI may use the bounds to retore the minimized position
+            notifyMovementBoundsChanged(false /* fromImeAdjustment */);
+            notifyActionsChanged(mActions);
+            notifyMinimizeChanged(mIsMinimized);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to register pinned stack listener", e);
+        }
+    }
+
+    /**
+     * @return whether the given {@param aspectRatio} is valid.
+     */
+    public boolean isValidPictureInPictureAspectRatio(float aspectRatio) {
+        return Float.compare(mMinAspectRatio, aspectRatio) <= 0 &&
+                Float.compare(aspectRatio, mMaxAspectRatio) <= 0;
+    }
+
+    /**
+     * Returns the current bounds (or the default bounds if there are no current bounds) with the
+     * specified aspect ratio.
+     */
+    Rect transformBoundsToAspectRatio(Rect stackBounds, float aspectRatio,
+            boolean useCurrentMinEdgeSize) {
+        // Save the snap fraction, calculate the aspect ratio based on screen size
+        final float snapFraction = mSnapAlgorithm.getSnapFraction(stackBounds,
+                getMovementBounds(stackBounds));
+
+        final int minEdgeSize = useCurrentMinEdgeSize ? mCurrentMinSize : mDefaultMinSize;
+        final Size size = mSnapAlgorithm.getSizeForAspectRatio(aspectRatio, minEdgeSize,
+                mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
+        final int left = (int) (stackBounds.centerX() - size.getWidth() / 2f);
+        final int top = (int) (stackBounds.centerY() - size.getHeight() / 2f);
+        stackBounds.set(left, top, left + size.getWidth(), top + size.getHeight());
+        mSnapAlgorithm.applySnapFraction(stackBounds, getMovementBounds(stackBounds), snapFraction);
+        if (mIsMinimized) {
+            applyMinimizedOffset(stackBounds, getMovementBounds(stackBounds));
+        }
+        return stackBounds;
+    }
+
+    /**
+     * @return the default bounds to show the PIP when there is no active PIP.
+     */
+    Rect getDefaultBounds() {
+        synchronized (mService.mWindowMap) {
+            final Rect insetBounds = new Rect();
+            getInsetBounds(insetBounds);
+
+            final Rect defaultBounds = new Rect();
+            final Size size = mSnapAlgorithm.getSizeForAspectRatio(mDefaultAspectRatio,
+                    mDefaultMinSize, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
+            Gravity.apply(mDefaultStackGravity, size.getWidth(), size.getHeight(), insetBounds,
+                    0, mIsImeShowing ? mImeHeight : 0, defaultBounds);
+            return defaultBounds;
+        }
+    }
+
+    /**
+     * In the case where the display rotation is changed but there is no stack, we can't depend on
+     * onTaskStackBoundsChanged() to be called.  But we still should update our known display info
+     * with the new state so that we can update SystemUI.
+     */
+    synchronized void onDisplayInfoChanged() {
+        mDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo());
+        notifyMovementBoundsChanged(false /* fromImeAdjustment */);
+    }
+
+    /**
+     * Updates the display info, calculating and returning the new stack and movement bounds in the
+     * new orientation of the device if necessary.
+     */
+    boolean onTaskStackBoundsChanged(Rect targetBounds, Rect outBounds) {
+        synchronized (mService.mWindowMap) {
+            final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
+            if (mDisplayInfo.equals(displayInfo)) {
+                // We are already in the right orientation, ignore
+                outBounds.setEmpty();
+                return false;
+            } else if (targetBounds.isEmpty()) {
+                // The stack is null, we are just initializing the stack, so just store the display
+                // info and ignore
+                mDisplayInfo.copyFrom(displayInfo);
+                outBounds.setEmpty();
+                return false;
+            }
+
+            mTmpRect.set(targetBounds);
+            final Rect postChangeStackBounds = mTmpRect;
+
+            // Calculate the snap fraction of the current stack along the old movement bounds
+            final Rect preChangeMovementBounds = getMovementBounds(postChangeStackBounds);
+            final float snapFraction = mSnapAlgorithm.getSnapFraction(postChangeStackBounds,
+                    preChangeMovementBounds);
+            mDisplayInfo.copyFrom(displayInfo);
+
+            // Calculate the stack bounds in the new orientation to the same same fraction along the
+            // rotated movement bounds.
+            final Rect postChangeMovementBounds = getMovementBounds(postChangeStackBounds,
+                    false /* adjustForIme */);
+            mSnapAlgorithm.applySnapFraction(postChangeStackBounds, postChangeMovementBounds,
+                    snapFraction);
+            if (mIsMinimized) {
+                applyMinimizedOffset(postChangeStackBounds, postChangeMovementBounds);
+            }
+
+            notifyMovementBoundsChanged(false /* fromImeAdjustment */);
+
+            outBounds.set(postChangeStackBounds);
+            return true;
+        }
+    }
+
+    /**
+     * Sets the Ime state and height.
+     */
+    void setAdjustedForIme(boolean adjustedForIme, int imeHeight) {
+        // Return early if there is no state change
+        if (mIsImeShowing == adjustedForIme && mImeHeight == imeHeight) {
+            return;
+        }
+
+        mIsImeShowing = adjustedForIme;
+        mImeHeight = imeHeight;
+        notifyImeVisibilityChanged(adjustedForIme, imeHeight);
+        notifyMovementBoundsChanged(true /* fromImeAdjustment */);
+    }
+
+    /**
+     * Sets the current aspect ratio.
+     */
+    void setAspectRatio(float aspectRatio) {
+        if (Float.compare(mAspectRatio, aspectRatio) != 0) {
+            mAspectRatio = aspectRatio;
+            notifyMovementBoundsChanged(false /* fromImeAdjustment */);
+        }
+    }
+
+    /**
+     * @return the current aspect ratio.
+     */
+    float getAspectRatio() {
+        return mAspectRatio;
+    }
+
+    /**
+     * Sets the current set of actions.
+     */
+    void setActions(List<RemoteAction> actions) {
+        mActions.clear();
+        if (actions != null) {
+            mActions.addAll(actions);
+        }
+        notifyActionsChanged(mActions);
+    }
+
+    /**
+     * Notifies listeners that the PIP needs to be adjusted for the IME.
+     */
+    private void notifyImeVisibilityChanged(boolean imeVisible, int imeHeight) {
+        if (mPinnedStackListener != null) {
+            try {
+                mPinnedStackListener.onImeVisibilityChanged(imeVisible, imeHeight);
+            } catch (RemoteException e) {
+                Slog.e(TAG_WM, "Error delivering bounds changed event.", e);
+            }
+        }
+    }
+
+    /**
+     * Notifies listeners that the PIP minimized state has changed.
+     */
+    private void notifyMinimizeChanged(boolean isMinimized) {
+        if (mPinnedStackListener != null) {
+            try {
+                mPinnedStackListener.onMinimizedStateChanged(isMinimized);
+            } catch (RemoteException e) {
+                Slog.e(TAG_WM, "Error delivering minimize changed event.", e);
+            }
+        }
+    }
+
+    /**
+     * Notifies listeners that the PIP actions have changed.
+     */
+    private void notifyActionsChanged(List<RemoteAction> actions) {
+        if (mPinnedStackListener != null) {
+            try {
+                mPinnedStackListener.onActionsChanged(new ParceledListSlice(actions));
+            } catch (RemoteException e) {
+                Slog.e(TAG_WM, "Error delivering actions changed event.", e);
+            }
+        }
+    }
+
+    /**
+     * Notifies listeners that the PIP movement bounds have changed.
+     */
+    private void notifyMovementBoundsChanged(boolean fromImeAdjustement) {
+        synchronized (mService.mWindowMap) {
+            if (mPinnedStackListener != null) {
+                try {
+                    final Rect insetBounds = new Rect();
+                    getInsetBounds(insetBounds);
+                    final Rect normalBounds = getDefaultBounds();
+                    if (isValidPictureInPictureAspectRatio(mAspectRatio)) {
+                        transformBoundsToAspectRatio(normalBounds, mAspectRatio,
+                                false /* useCurrentMinEdgeSize */);
+                    }
+                    final Rect animatingBounds = mTmpAnimatingBoundsRect;
+                    final TaskStack pinnedStack = mDisplayContent.getStackById(PINNED_STACK_ID);
+                    if (pinnedStack != null) {
+                        pinnedStack.getAnimationOrCurrentBounds(animatingBounds);
+                    } else {
+                        animatingBounds.set(normalBounds);
+                    }
+                    mPinnedStackListener.onMovementBoundsChanged(insetBounds, normalBounds,
+                            animatingBounds, fromImeAdjustement, mDisplayInfo.rotation);
+                } catch (RemoteException e) {
+                    Slog.e(TAG_WM, "Error delivering actions changed event.", e);
+                }
+            }
+        }
+    }
+
+    /**
+     * @return the bounds on the screen that the PIP can be visible in.
+     */
+    private void getInsetBounds(Rect outRect) {
+        synchronized (mService.mWindowMap) {
+            mService.mPolicy.getStableInsetsLw(mDisplayInfo.rotation, mDisplayInfo.logicalWidth,
+                    mDisplayInfo.logicalHeight, mTmpInsets);
+            outRect.set(mTmpInsets.left + mScreenEdgeInsets.x, mTmpInsets.top + mScreenEdgeInsets.y,
+                    mDisplayInfo.logicalWidth - mTmpInsets.right - mScreenEdgeInsets.x,
+                    mDisplayInfo.logicalHeight - mTmpInsets.bottom - mScreenEdgeInsets.y);
+        }
+    }
+
+    /**
+     * @return the movement bounds for the given {@param stackBounds} and the current state of the
+     *         controller.
+     */
+    private Rect getMovementBounds(Rect stackBounds) {
+        synchronized (mService.mWindowMap) {
+            return getMovementBounds(stackBounds, true /* adjustForIme */);
+        }
+    }
+
+    /**
+     * @return the movement bounds for the given {@param stackBounds} and the current state of the
+     *         controller.
+     */
+    private Rect getMovementBounds(Rect stackBounds, boolean adjustForIme) {
+        synchronized (mService.mWindowMap) {
+            final Rect movementBounds = new Rect();
+            getInsetBounds(movementBounds);
+
+            // Apply the movement bounds adjustments based on the current state
+            mSnapAlgorithm.getMovementBounds(stackBounds, movementBounds, movementBounds,
+                    (adjustForIme && mIsImeShowing) ? mImeHeight : 0);
+            return movementBounds;
+        }
+    }
+
+    /**
+     * Applies the minimized offsets to the given stack bounds.
+     */
+    private void applyMinimizedOffset(Rect stackBounds, Rect movementBounds) {
+        synchronized (mService.mWindowMap) {
+            mTmpDisplaySize.set(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
+            mService.getStableInsetsLocked(mDisplayContent.getDisplayId(), mStableInsets);
+            mSnapAlgorithm.applyMinimizedOffset(stackBounds, movementBounds, mTmpDisplaySize,
+                    mStableInsets);
+        }
+    }
+
+    /**
+     * @return the pixels for a given dp value.
+     */
+    private int dpToPx(float dpValue, DisplayMetrics dm) {
+        return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, dm);
+    }
+
+    void dump(String prefix, PrintWriter pw) {
+        pw.println(prefix + "PinnedStackController");
+        pw.print(prefix + "  defaultBounds="); getDefaultBounds().printShortString(pw);
+        pw.println();
+        mService.getStackBounds(PINNED_STACK_ID, mTmpRect);
+        pw.print(prefix + "  movementBounds="); getMovementBounds(mTmpRect).printShortString(pw);
+        pw.println();
+        pw.println(prefix + "  mIsImeShowing=" + mIsImeShowing);
+        pw.println(prefix + "  mIsMinimized=" + mIsMinimized);
+        if (mActions.isEmpty()) {
+            pw.println(prefix + "  mActions=[]");
+        } else {
+            pw.println(prefix + "  mActions=[");
+            for (int i = 0; i < mActions.size(); i++) {
+                RemoteAction action = mActions.get(i);
+                pw.print(prefix + "    Action[" + i + "]: ");
+                action.dump("", pw);
+            }
+            pw.println(prefix + "  ]");
+        }
+    }
+
+    void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        getDefaultBounds().writeToProto(proto, DEFAULT_BOUNDS);
+        mService.getStackBounds(PINNED_STACK_ID, mTmpRect);
+        getMovementBounds(mTmpRect).writeToProto(proto, MOVEMENT_BOUNDS);
+        proto.end(token);
+    }
+}
diff --git a/com/android/server/wm/PinnedStackWindowController.java b/com/android/server/wm/PinnedStackWindowController.java
new file mode 100644
index 0000000..590ac6e
--- /dev/null
+++ b/com/android/server/wm/PinnedStackWindowController.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+
+import static com.android.server.wm.BoundsAnimationController.NO_PIP_MODE_CHANGED_CALLBACKS;
+import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_END;
+import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_START;
+import static com.android.server.wm.BoundsAnimationController.SchedulePipModeChangedState;
+
+import android.app.RemoteAction;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+
+import com.android.server.UiThread;
+
+import java.util.List;
+
+/**
+ * Controller for the pinned stack container. See {@link StackWindowController}.
+ */
+public class PinnedStackWindowController extends StackWindowController {
+
+    private Rect mTmpFromBounds = new Rect();
+    private Rect mTmpToBounds = new Rect();
+
+    public PinnedStackWindowController(int stackId, PinnedStackWindowListener listener,
+            int displayId, boolean onTop, Rect outBounds) {
+        super(stackId, listener, displayId, onTop, outBounds, WindowManagerService.getInstance());
+    }
+
+    /**
+     * @return the {@param currentStackBounds} transformed to the give {@param aspectRatio}.  If
+     *         {@param currentStackBounds} is null, then the {@param aspectRatio} is applied to the
+     *         default bounds.
+     */
+    public Rect getPictureInPictureBounds(float aspectRatio, Rect stackBounds) {
+        synchronized (mWindowMap) {
+            if (!mService.mSupportsPictureInPicture || mContainer == null) {
+                return null;
+            }
+
+            final DisplayContent displayContent = mContainer.getDisplayContent();
+            if (displayContent == null) {
+                return null;
+            }
+
+            final PinnedStackController pinnedStackController =
+                    displayContent.getPinnedStackController();
+            if (stackBounds == null) {
+                // Calculate the aspect ratio bounds from the default bounds
+                stackBounds = pinnedStackController.getDefaultBounds();
+            }
+
+            if (pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio)) {
+                return pinnedStackController.transformBoundsToAspectRatio(stackBounds, aspectRatio,
+                        true /* useCurrentMinEdgeSize */);
+            } else {
+                return stackBounds;
+            }
+        }
+    }
+
+    /**
+     * Animates the pinned stack.
+     */
+    public void animateResizePinnedStack(Rect toBounds, Rect sourceHintBounds,
+            int animationDuration, boolean fromFullscreen) {
+        synchronized (mWindowMap) {
+            if (mContainer == null) {
+                throw new IllegalArgumentException("Pinned stack container not found :(");
+            }
+
+            // Get the from-bounds
+            final Rect fromBounds = new Rect();
+            mContainer.getBounds(fromBounds);
+
+            // Get non-null fullscreen to-bounds for animating if the bounds are null
+            @SchedulePipModeChangedState int schedulePipModeChangedState =
+                NO_PIP_MODE_CHANGED_CALLBACKS;
+            final boolean toFullscreen = toBounds == null;
+            if (toFullscreen) {
+                if (fromFullscreen) {
+                    throw new IllegalArgumentException("Should not defer scheduling PiP mode"
+                            + " change on animation to fullscreen.");
+                }
+                schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_START;
+
+                mService.getStackBounds(FULLSCREEN_WORKSPACE_STACK_ID, mTmpToBounds);
+                if (!mTmpToBounds.isEmpty()) {
+                    // If there is a fullscreen bounds, use that
+                    toBounds = new Rect(mTmpToBounds);
+                } else {
+                    // Otherwise, use the display bounds
+                    toBounds = new Rect();
+                    mContainer.getDisplayContent().getLogicalDisplayRect(toBounds);
+                }
+            } else if (fromFullscreen) {
+                schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END;
+            }
+
+            mContainer.setAnimationFinalBounds(sourceHintBounds, toBounds, toFullscreen);
+
+            final Rect finalToBounds = toBounds;
+            final @SchedulePipModeChangedState int finalSchedulePipModeChangedState =
+                schedulePipModeChangedState;
+            mService.mBoundsAnimationController.getHandler().post(() -> {
+                if (mContainer == null) {
+                    return;
+                }
+                mService.mBoundsAnimationController.animateBounds(mContainer, fromBounds,
+                        finalToBounds, animationDuration, finalSchedulePipModeChangedState,
+                        fromFullscreen, toFullscreen);
+            });
+        }
+    }
+
+    /**
+     * Sets the current picture-in-picture aspect ratio.
+     */
+    public void setPictureInPictureAspectRatio(float aspectRatio) {
+        synchronized (mWindowMap) {
+            if (!mService.mSupportsPictureInPicture || mContainer == null) {
+                return;
+            }
+
+            final PinnedStackController pinnedStackController =
+                    mContainer.getDisplayContent().getPinnedStackController();
+
+            if (Float.compare(aspectRatio, pinnedStackController.getAspectRatio()) != 0) {
+                mContainer.getAnimationOrCurrentBounds(mTmpFromBounds);
+                mTmpToBounds.set(mTmpFromBounds);
+                getPictureInPictureBounds(aspectRatio, mTmpToBounds);
+                if (!mTmpToBounds.equals(mTmpFromBounds)) {
+                    animateResizePinnedStack(mTmpToBounds, null /* sourceHintBounds */,
+                            -1 /* duration */, false /* fromFullscreen */);
+                }
+                pinnedStackController.setAspectRatio(
+                        pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio)
+                                ? aspectRatio : -1f);
+            }
+        }
+    }
+
+    /**
+     * Sets the current picture-in-picture actions.
+     */
+    public void setPictureInPictureActions(List<RemoteAction> actions) {
+        synchronized (mWindowMap) {
+            if (!mService.mSupportsPictureInPicture || mContainer == null) {
+                return;
+            }
+
+            mContainer.getDisplayContent().getPinnedStackController().setActions(actions);
+        }
+    }
+
+    /**
+     * @return whether the multi-window mode change should be deferred as a part of a transition
+     * from fullscreen to non-fullscreen bounds.
+     */
+    public boolean deferScheduleMultiWindowModeChanged() {
+        synchronized(mWindowMap) {
+            return mContainer.deferScheduleMultiWindowModeChanged();
+        }
+    }
+
+    /**
+     * @return whether the bounds are currently animating to fullscreen.
+     */
+    public boolean isAnimatingBoundsToFullscreen() {
+        synchronized (mWindowMap) {
+            return mContainer.isAnimatingBoundsToFullscreen();
+        }
+    }
+
+    /**
+     * @return whether the stack can be resized from the bounds animation.
+     */
+    public boolean pinnedStackResizeDisallowed() {
+        synchronized (mWindowMap) {
+            return mContainer.pinnedStackResizeDisallowed();
+        }
+    }
+
+    /**
+     * The following calls are made from WM to AM.
+     */
+
+    /** Calls directly into activity manager so window manager lock shouldn't held. */
+    public void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds,
+            boolean forceUpdate) {
+        if (mListener != null) {
+            PinnedStackWindowListener listener = (PinnedStackWindowListener) mListener;
+            listener.updatePictureInPictureModeForPinnedStackAnimation(targetStackBounds,
+                    forceUpdate);
+        }
+    }
+}
diff --git a/com/android/server/wm/PinnedStackWindowListener.java b/com/android/server/wm/PinnedStackWindowListener.java
new file mode 100644
index 0000000..33e8a60
--- /dev/null
+++ b/com/android/server/wm/PinnedStackWindowListener.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import android.graphics.Rect;
+
+/**
+ * Interface used by the creator of {@link PinnedStackWindowController} to listen to changes with
+ * the stack container.
+ */
+public interface PinnedStackWindowListener extends StackWindowListener {
+
+    /**
+     * Called when the stack container pinned stack animation will change the picture-in-picture
+     * mode. This is a direct call into ActivityManager.
+     */
+    default void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds,
+            boolean forceUpdate) {}
+}
diff --git a/com/android/server/wm/PointerEventDispatcher.java b/com/android/server/wm/PointerEventDispatcher.java
new file mode 100644
index 0000000..484987e
--- /dev/null
+++ b/com/android/server/wm/PointerEventDispatcher.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.MotionEvent;
+import android.view.WindowManagerPolicy.PointerEventListener;
+
+import com.android.server.UiThread;
+
+import java.util.ArrayList;
+
+public class PointerEventDispatcher extends InputEventReceiver {
+    ArrayList<PointerEventListener> mListeners = new ArrayList<PointerEventListener>();
+    PointerEventListener[] mListenersArray = new PointerEventListener[0];
+
+    public PointerEventDispatcher(InputChannel inputChannel) {
+        super(inputChannel, UiThread.getHandler().getLooper());
+    }
+
+    @Override
+    public void onInputEvent(InputEvent event, int displayId) {
+        try {
+            if (event instanceof MotionEvent
+                    && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+                final MotionEvent motionEvent = (MotionEvent) event;
+                PointerEventListener[] listeners;
+                synchronized (mListeners) {
+                    if (mListenersArray == null) {
+                        mListenersArray = new PointerEventListener[mListeners.size()];
+                        mListeners.toArray(mListenersArray);
+                    }
+                    listeners = mListenersArray;
+                }
+                for (int i = 0; i < listeners.length; ++i) {
+                    listeners[i].onPointerEvent(motionEvent, displayId);
+                }
+            }
+        } finally {
+            finishInputEvent(event, false);
+        }
+    }
+
+    /**
+     * Add the specified listener to the list.
+     * @param listener The listener to add.
+     */
+    public void registerInputEventListener(PointerEventListener listener) {
+        synchronized (mListeners) {
+            if (mListeners.contains(listener)) {
+                throw new IllegalStateException("registerInputEventListener: trying to register" +
+                        listener + " twice.");
+            }
+            mListeners.add(listener);
+            mListenersArray = null;
+        }
+    }
+
+    /**
+     * Remove the specified listener from the list.
+     * @param listener The listener to remove.
+     */
+    public void unregisterInputEventListener(PointerEventListener listener) {
+        synchronized (mListeners) {
+            if (!mListeners.contains(listener)) {
+                throw new IllegalStateException("registerInputEventListener: " + listener +
+                        " not registered.");
+            }
+            mListeners.remove(listener);
+            mListenersArray = null;
+        }
+    }
+}
diff --git a/com/android/server/wm/RemoteEventTrace.java b/com/android/server/wm/RemoteEventTrace.java
new file mode 100644
index 0000000..9f65ba3
--- /dev/null
+++ b/com/android/server/wm/RemoteEventTrace.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.DataOutputStream;
+
+import android.util.Slog;
+import android.os.Debug;
+
+// Counterpart to remote surface trace for events which are not tied to a particular surface.
+class RemoteEventTrace {
+    private static final String TAG = "RemoteEventTrace";
+
+    // We terminate all our messages with a recognizable marker, to avoid issues
+    // with partial reads (which ADB makes impossible to avoid).
+    static final byte[] sigil = {(byte)0xfc, (byte)0xfc, (byte)0xfc, (byte)0xfc};
+
+    private final WindowManagerService mService;
+    private final DataOutputStream mOut;
+
+    RemoteEventTrace(WindowManagerService service, FileDescriptor fd) {
+        mService = service;
+        mOut = new DataOutputStream(new FileOutputStream(fd, false));
+    }
+
+    void openSurfaceTransaction() {
+        try {
+            mOut.writeUTF("OpenTransaction");
+            writeSigil();
+        } catch (Exception e) {
+            logException(e);
+            mService.disableSurfaceTrace();
+        }
+    }
+
+    void closeSurfaceTransaction() {
+        try {
+            mOut.writeUTF("CloseTransaction");
+            writeSigil();
+        } catch (Exception e) {
+            logException(e);
+            mService.disableSurfaceTrace();
+        }
+    }
+
+    private void writeSigil() throws Exception {
+        mOut.write(RemoteEventTrace.sigil, 0, 4);
+    }
+
+    static void logException(Exception e) {
+        Slog.i(TAG, "Exception writing to SurfaceTrace (client vanished?): " + e.toString());
+    }
+}
diff --git a/com/android/server/wm/RemoteSurfaceTrace.java b/com/android/server/wm/RemoteSurfaceTrace.java
new file mode 100644
index 0000000..a12c2c4
--- /dev/null
+++ b/com/android/server/wm/RemoteSurfaceTrace.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.util.Slog;
+import android.view.SurfaceControl;
+
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.DataOutputStream;
+
+// A surface control subclass which logs events to a FD in binary format.
+// This can be used in our CTS tests to enable a pattern similar to mocking
+// the surface control.
+//
+// See cts/hostsidetests/../../SurfaceTraceReceiver.java for parsing side.
+class RemoteSurfaceTrace extends SurfaceControlWithBackground {
+    static final String TAG = "RemoteSurfaceTrace";
+
+    final FileDescriptor mWriteFd;
+    final DataOutputStream mOut;
+
+    final WindowManagerService mService;
+    final WindowState mWindow;
+
+    RemoteSurfaceTrace(FileDescriptor fd, SurfaceControlWithBackground wrapped,
+            WindowState window) {
+        super(wrapped);
+
+        mWriteFd = fd;
+        mOut = new DataOutputStream(new FileOutputStream(fd, false));
+
+        mWindow = window;
+        mService = mWindow.mService;
+    }
+
+    @Override
+    public void setAlpha(float alpha) {
+        writeFloatEvent("Alpha", alpha);
+        super.setAlpha(alpha);
+    }
+
+    @Override
+    public void setLayer(int zorder) {
+        writeIntEvent("Layer", zorder);
+        super.setLayer(zorder);
+    }
+
+    @Override
+    public void setPosition(float x, float y) {
+        writeFloatEvent("Position", x, y);
+        super.setPosition(x, y);
+    }
+
+    @Override
+    public void setGeometryAppliesWithResize() {
+        writeEvent("GeometryAppliesWithResize");
+        super.setGeometryAppliesWithResize();
+    }
+
+    @Override
+    public void setSize(int w, int h) {
+        writeIntEvent("Size", w, h);
+        super.setSize(w, h);
+    }
+
+    @Override
+    public void setWindowCrop(Rect crop) {
+        writeRectEvent("Crop", crop);
+        super.setWindowCrop(crop);
+    }
+
+    @Override
+    public void setFinalCrop(Rect crop) {
+        writeRectEvent("FinalCrop", crop);
+        super.setFinalCrop(crop);
+    }
+
+    @Override
+    public void setLayerStack(int layerStack) {
+        writeIntEvent("LayerStack", layerStack);
+        super.setLayerStack(layerStack);
+    }
+
+    @Override
+    public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
+        writeFloatEvent("Matrix", dsdx, dtdx, dsdy, dtdy);
+        super.setMatrix(dsdx, dtdx, dsdy, dtdy);
+    }
+
+    @Override
+    public void hide() {
+        writeEvent("Hide");
+        super.hide();
+    }
+
+    @Override
+    public void show() {
+        writeEvent("Show");
+        super.show();
+    }
+
+    private void writeEvent(String tag) {
+        try {
+            mOut.writeUTF(tag);
+            mOut.writeUTF(mWindow.getWindowTag().toString());
+            writeSigil();
+        } catch (Exception e) {
+            RemoteEventTrace.logException(e);
+            mService.disableSurfaceTrace();
+        }
+    }
+
+    private void writeIntEvent(String tag, int... values) {
+        try {
+            mOut.writeUTF(tag);
+            mOut.writeUTF(mWindow.getWindowTag().toString());
+            for (int value: values) {
+                mOut.writeInt(value);
+            }
+            writeSigil();
+        } catch (Exception e) {
+            RemoteEventTrace.logException(e);
+            mService.disableSurfaceTrace();
+        }
+    }
+
+    private void writeFloatEvent(String tag, float... values) {
+        try {
+            mOut.writeUTF(tag);
+            mOut.writeUTF(mWindow.getWindowTag().toString());
+            for (float value: values) {
+                mOut.writeFloat(value);
+            }
+            writeSigil();
+        } catch (Exception e) {
+            RemoteEventTrace.logException(e);
+            mService.disableSurfaceTrace();
+        }
+    }
+
+    private void writeRectEvent(String tag, Rect value) {
+        writeFloatEvent(tag, value.left, value.top, value.right, value.bottom);
+    }
+
+    private void writeSigil() throws Exception {
+        mOut.write(RemoteEventTrace.sigil, 0, 4);
+    }
+}
diff --git a/com/android/server/wm/RootWindowContainer.java b/com/android/server/wm/RootWindowContainer.java
new file mode 100644
index 0000000..8a74976
--- /dev/null
+++ b/com/android/server/wm/RootWindowContainer.java
@@ -0,0 +1,1097 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.hardware.power.V1_0.PowerHint;
+import android.os.Binder;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.ArraySet;
+import android.util.EventLog;
+import android.util.Slog;
+import android.util.SparseIntArray;
+import android.util.proto.ProtoOutputStream;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.WindowManager;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.server.EventLogTags;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.app.AppOpsManager.OP_NONE;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE;
+import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
+import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
+import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
+import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
+import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_KEEP_SCREEN_ON;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_POWER;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_KEEP_SCREEN_ON;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.H.REPORT_LOSING_FOCUS;
+import static com.android.server.wm.WindowManagerService.H.SEND_NEW_CONFIGURATION;
+import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES;
+import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
+import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_NONE;
+import static com.android.server.wm.WindowManagerService.H.WINDOW_FREEZE_TIMEOUT;
+import static com.android.server.wm.WindowManagerService.logSurface;
+import static com.android.server.wm.WindowSurfacePlacer.SET_FORCE_HIDING_CHANGED;
+import static com.android.server.wm.WindowSurfacePlacer.SET_ORIENTATION_CHANGE_COMPLETE;
+import static com.android.server.wm.WindowSurfacePlacer.SET_TURN_ON_SCREEN;
+import static com.android.server.wm.WindowSurfacePlacer.SET_UPDATE_ROTATION;
+import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_ACTION_PENDING;
+import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_MAY_CHANGE;
+import static com.android.server.wm.proto.WindowManagerServiceProto.DISPLAYS;
+import static com.android.server.wm.proto.WindowManagerServiceProto.WINDOWS;
+
+/** Root {@link WindowContainer} for the device. */
+class RootWindowContainer extends WindowContainer<DisplayContent> {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "RootWindowContainer" : TAG_WM;
+
+    private static final int SET_SCREEN_BRIGHTNESS_OVERRIDE = 1;
+    private static final int SET_USER_ACTIVITY_TIMEOUT = 2;
+
+    WindowManagerService mService;
+
+    private boolean mWallpaperForceHidingChanged = false;
+    private Object mLastWindowFreezeSource = null;
+    private Session mHoldScreen = null;
+    private float mScreenBrightness = -1;
+    private long mUserActivityTimeout = -1;
+    private boolean mUpdateRotation = false;
+    // Following variables are for debugging screen wakelock only.
+    // Last window that requires screen wakelock
+    WindowState mHoldScreenWindow = null;
+    // Last window that obscures all windows below
+    WindowState mObscuringWindow = null;
+    // Only set while traversing the default display based on its content.
+    // Affects the behavior of mirroring on secondary displays.
+    private boolean mObscureApplicationContentOnSecondaryDisplays = false;
+
+    private boolean mSustainedPerformanceModeEnabled = false;
+    private boolean mSustainedPerformanceModeCurrent = false;
+
+    boolean mWallpaperMayChange = false;
+    // During an orientation change, we track whether all windows have rendered
+    // at the new orientation, and this will be false from changing orientation until that occurs.
+    // For seamless rotation cases this always stays true, as the windows complete their orientation
+    // changes 1 by 1 without disturbing global state.
+    boolean mOrientationChangeComplete = true;
+    boolean mWallpaperActionPending = false;
+
+    private final ArrayList<Integer> mChangedStackList = new ArrayList();
+
+    // State for the RemoteSurfaceTrace system used in testing. If this is enabled SurfaceControl
+    // instances will be replaced with an instance that writes a binary representation of all
+    // commands to mSurfaceTraceFd.
+    boolean mSurfaceTraceEnabled;
+    ParcelFileDescriptor mSurfaceTraceFd;
+    RemoteEventTrace mRemoteEventTrace;
+
+    private final WindowLayersController mLayersController;
+    final WallpaperController mWallpaperController;
+
+    private final Handler mHandler;
+
+    private String mCloseSystemDialogsReason;
+    private final Consumer<WindowState> mCloseSystemDialogsConsumer = w -> {
+        if (w.mHasSurface) {
+            try {
+                w.mClient.closeSystemDialogs(mCloseSystemDialogsReason);
+            } catch (RemoteException e) {
+            }
+        }
+    };
+
+    private static final Consumer<WindowState> sRemoveReplacedWindowsConsumer = w -> {
+        final AppWindowToken aToken = w.mAppToken;
+        if (aToken != null) {
+            aToken.removeReplacedWindowIfNeeded(w);
+        }
+    };
+
+    RootWindowContainer(WindowManagerService service) {
+        mService = service;
+        mHandler = new MyHandler(service.mH.getLooper());
+        mLayersController = new WindowLayersController(mService);
+        mWallpaperController = new WallpaperController(mService);
+    }
+
+    WindowState computeFocusedWindow() {
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final DisplayContent dc = mChildren.get(i);
+            final WindowState win = dc.findFocusedWindow();
+            if (win != null) {
+                return win;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get an array with display ids ordered by focus priority - last items should be given
+     * focus first. Sparse array just maps position to displayId.
+     */
+    void getDisplaysInFocusOrder(SparseIntArray displaysInFocusOrder) {
+        displaysInFocusOrder.clear();
+
+        final int size = mChildren.size();
+        for (int i = 0; i < size; ++i) {
+            final DisplayContent displayContent = mChildren.get(i);
+            if (displayContent.isRemovalDeferred()) {
+                // Don't report displays that are going to be removed soon.
+                continue;
+            }
+            displaysInFocusOrder.put(i, displayContent.getDisplayId());
+        }
+    }
+
+    /**
+     * Retrieve the DisplayContent for the specified displayId. Will create a new DisplayContent if
+     * there is a Display for the displayId.
+     *
+     * @param displayId The display the caller is interested in.
+     * @return The DisplayContent associated with displayId or null if there is no Display for it.
+     */
+    DisplayContent getDisplayContentOrCreate(int displayId) {
+        DisplayContent dc = getDisplayContent(displayId);
+
+        if (dc == null) {
+            final Display display = mService.mDisplayManager.getDisplay(displayId);
+            if (display != null) {
+                final long callingIdentity = Binder.clearCallingIdentity();
+                try {
+                    dc = createDisplayContent(display);
+                } finally {
+                    Binder.restoreCallingIdentity(callingIdentity);
+                }
+            }
+        }
+        return dc;
+    }
+
+    DisplayContent getDisplayContent(int displayId) {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final DisplayContent current = mChildren.get(i);
+            if (current.getDisplayId() == displayId) {
+                return current;
+            }
+        }
+        return null;
+    }
+
+    private DisplayContent createDisplayContent(final Display display) {
+        final DisplayContent dc = new DisplayContent(display, mService, mLayersController,
+                mWallpaperController);
+        final int displayId = display.getDisplayId();
+
+        if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Adding display=" + display);
+
+        final DisplayInfo displayInfo = dc.getDisplayInfo();
+        final Rect rect = new Rect();
+        mService.mDisplaySettings.getOverscanLocked(displayInfo.name, displayInfo.uniqueId, rect);
+        displayInfo.overscanLeft = rect.left;
+        displayInfo.overscanTop = rect.top;
+        displayInfo.overscanRight = rect.right;
+        displayInfo.overscanBottom = rect.bottom;
+        if (mService.mDisplayManagerInternal != null) {
+            mService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(
+                    displayId, displayInfo);
+            mService.configureDisplayPolicyLocked(dc);
+
+            // Tap Listeners are supported for:
+            // 1. All physical displays (multi-display).
+            // 2. VirtualDisplays on VR, AA (and everything else).
+            if (mService.canDispatchPointerEvents()) {
+                if (DEBUG_DISPLAY) {
+                    Slog.d(TAG,
+                            "Registering PointerEventListener for DisplayId: " + displayId);
+                }
+                dc.mTapDetector = new TaskTapPointerEventListener(mService, dc);
+                mService.registerPointerEventListener(dc.mTapDetector);
+                if (displayId == DEFAULT_DISPLAY) {
+                    mService.registerPointerEventListener(mService.mMousePositionTracker);
+                }
+            }
+        }
+
+        return dc;
+    }
+
+    boolean isLayoutNeeded() {
+        final int numDisplays = mChildren.size();
+        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+            final DisplayContent displayContent = mChildren.get(displayNdx);
+            if (displayContent.isLayoutNeeded()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    void getWindowsByName(ArrayList<WindowState> output, String name) {
+        int objectId = 0;
+        // See if this is an object ID.
+        try {
+            objectId = Integer.parseInt(name, 16);
+            name = null;
+        } catch (RuntimeException e) {
+        }
+
+        getWindowsByName(output, name, objectId);
+    }
+
+    private void getWindowsByName(ArrayList<WindowState> output, String name, int objectId) {
+        forAllWindows((w) -> {
+            if (name != null) {
+                if (w.mAttrs.getTitle().toString().contains(name)) {
+                    output.add(w);
+                }
+            } else if (System.identityHashCode(w) == objectId) {
+                output.add(w);
+            }
+        }, true /* traverseTopToBottom */);
+    }
+
+    /**
+     * Returns the app window token for the input binder if it exist in the system.
+     * NOTE: Only one AppWindowToken is allowed to exist in the system for a binder token, since
+     * AppWindowToken represents an activity which can only exist on one display.
+     */
+    AppWindowToken getAppWindowToken(IBinder binder) {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final DisplayContent dc = mChildren.get(i);
+            final AppWindowToken atoken = dc.getAppWindowToken(binder);
+            if (atoken != null) {
+                return atoken;
+            }
+        }
+        return null;
+    }
+
+    /** Returns the display object the input window token is currently mapped on. */
+    DisplayContent getWindowTokenDisplay(WindowToken token) {
+        if (token == null) {
+            return null;
+        }
+
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final DisplayContent dc = mChildren.get(i);
+            final WindowToken current = dc.getWindowToken(token.token);
+            if (current == token) {
+                return dc;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Set new display override config and return array of ids of stacks that were changed during
+     * update. If called for the default display, global configuration will also be updated.
+     */
+    int[] setDisplayOverrideConfigurationIfNeeded(Configuration newConfiguration, int displayId) {
+        final DisplayContent displayContent = getDisplayContent(displayId);
+        if (displayContent == null) {
+            throw new IllegalArgumentException("Display not found for id: " + displayId);
+        }
+
+        final Configuration currentConfig = displayContent.getOverrideConfiguration();
+        final boolean configChanged = currentConfig.diff(newConfiguration) != 0;
+        if (!configChanged) {
+            return null;
+        }
+        displayContent.onOverrideConfigurationChanged(newConfiguration);
+
+        if (displayId == DEFAULT_DISPLAY) {
+            // Override configuration of the default display duplicates global config. In this case
+            // we also want to update the global config.
+            return setGlobalConfigurationIfNeeded(newConfiguration);
+        } else {
+            return updateStackBoundsAfterConfigChange(displayId);
+        }
+    }
+
+    private int[] setGlobalConfigurationIfNeeded(Configuration newConfiguration) {
+        final boolean configChanged = getConfiguration().diff(newConfiguration) != 0;
+        if (!configChanged) {
+            return null;
+        }
+        onConfigurationChanged(newConfiguration);
+        return updateStackBoundsAfterConfigChange();
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newParentConfig) {
+        prepareFreezingTaskBounds();
+        super.onConfigurationChanged(newParentConfig);
+
+        mService.mPolicy.onConfigurationChanged();
+    }
+
+    /**
+     * Callback used to trigger bounds update after configuration change and get ids of stacks whose
+     * bounds were updated.
+     */
+    private int[] updateStackBoundsAfterConfigChange() {
+        mChangedStackList.clear();
+
+        final int numDisplays = mChildren.size();
+        for (int i = 0; i < numDisplays; ++i) {
+            final DisplayContent dc = mChildren.get(i);
+            dc.updateStackBoundsAfterConfigChange(mChangedStackList);
+        }
+
+        return mChangedStackList.isEmpty() ? null : ArrayUtils.convertToIntArray(mChangedStackList);
+    }
+
+    /** Same as {@link #updateStackBoundsAfterConfigChange()} but only for a specific display. */
+    private int[] updateStackBoundsAfterConfigChange(int displayId) {
+        mChangedStackList.clear();
+
+        final DisplayContent dc = getDisplayContent(displayId);
+        dc.updateStackBoundsAfterConfigChange(mChangedStackList);
+
+        return mChangedStackList.isEmpty() ? null : ArrayUtils.convertToIntArray(mChangedStackList);
+    }
+
+    private void prepareFreezingTaskBounds() {
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            mChildren.get(i).prepareFreezingTaskBounds();
+        }
+    }
+
+    TaskStack getStackById(int stackId) {
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final DisplayContent dc = mChildren.get(i);
+            final TaskStack stack = dc.getStackById(stackId);
+            if (stack != null) {
+                return stack;
+            }
+        }
+        return null;
+    }
+
+    void setSecureSurfaceState(int userId, boolean disabled) {
+        forAllWindows((w) -> {
+            if (w.mHasSurface && userId == UserHandle.getUserId(w.mOwnerUid)) {
+                w.mWinAnimator.setSecureLocked(disabled);
+            }
+        }, true /* traverseTopToBottom */);
+    }
+
+    void updateAppOpsState() {
+        forAllWindows((w) -> {
+            if (w.mAppOp == OP_NONE) {
+                return;
+            }
+            final int mode = mService.mAppOps.checkOpNoThrow(w.mAppOp, w.getOwningUid(),
+                    w.getOwningPackage());
+            w.setAppOpVisibilityLw(mode == MODE_ALLOWED || mode == MODE_DEFAULT);
+        }, false /* traverseTopToBottom */);
+    }
+
+    boolean canShowStrictModeViolation(int pid) {
+        final WindowState win = getWindow((w) -> w.mSession.mPid == pid && w.isVisibleLw());
+        return win != null;
+    }
+
+    void closeSystemDialogs(String reason) {
+        mCloseSystemDialogsReason = reason;
+        forAllWindows(mCloseSystemDialogsConsumer, false /* traverseTopToBottom */);
+    }
+
+    void removeReplacedWindows() {
+        if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION removeReplacedWindows");
+        mService.openSurfaceTransaction();
+        try {
+            forAllWindows(sRemoveReplacedWindowsConsumer, true /* traverseTopToBottom */);
+        } finally {
+            mService.closeSurfaceTransaction();
+            if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION removeReplacedWindows");
+        }
+    }
+
+    boolean hasPendingLayoutChanges(WindowAnimator animator) {
+        boolean hasChanges = false;
+
+        final int count = mChildren.size();
+        for (int i = 0; i < count; ++i) {
+            final DisplayContent dc = mChildren.get(i);
+            final int pendingChanges = animator.getPendingLayoutChanges(dc.getDisplayId());
+            if ((pendingChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
+                animator.mBulkUpdateParams |= SET_WALLPAPER_ACTION_PENDING;
+            }
+            if (pendingChanges != 0) {
+                hasChanges = true;
+            }
+        }
+
+        return hasChanges;
+    }
+
+    boolean reclaimSomeSurfaceMemory(WindowStateAnimator winAnimator, String operation,
+            boolean secure) {
+        final WindowSurfaceController surfaceController = winAnimator.mSurfaceController;
+        boolean leakedSurface = false;
+        boolean killedApps = false;
+
+        EventLog.writeEvent(EventLogTags.WM_NO_SURFACE_MEMORY, winAnimator.mWin.toString(),
+                winAnimator.mSession.mPid, operation);
+
+        final long callingIdentity = Binder.clearCallingIdentity();
+        try {
+            // There was some problem...first, do a sanity check of the window list to make sure
+            // we haven't left any dangling surfaces around.
+
+            Slog.i(TAG_WM, "Out of memory for surface!  Looking for leaks...");
+            final int numDisplays = mChildren.size();
+            for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+                leakedSurface |= mChildren.get(displayNdx).destroyLeakedSurfaces();
+            }
+
+            if (!leakedSurface) {
+                Slog.w(TAG_WM, "No leaked surfaces; killing applications!");
+                final SparseIntArray pidCandidates = new SparseIntArray();
+                for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+                    mChildren.get(displayNdx).forAllWindows((w) -> {
+                        if (mService.mForceRemoves.contains(w)) {
+                            return;
+                        }
+                        final WindowStateAnimator wsa = w.mWinAnimator;
+                        if (wsa.mSurfaceController != null) {
+                            pidCandidates.append(wsa.mSession.mPid, wsa.mSession.mPid);
+                        }
+                    }, false /* traverseTopToBottom */);
+
+                    if (pidCandidates.size() > 0) {
+                        int[] pids = new int[pidCandidates.size()];
+                        for (int i = 0; i < pids.length; i++) {
+                            pids[i] = pidCandidates.keyAt(i);
+                        }
+                        try {
+                            if (mService.mActivityManager.killPids(pids, "Free memory", secure)) {
+                                killedApps = true;
+                            }
+                        } catch (RemoteException e) {
+                        }
+                    }
+                }
+            }
+
+            if (leakedSurface || killedApps) {
+                // We managed to reclaim some memory, so get rid of the trouble surface and ask the
+                // app to request another one.
+                Slog.w(TAG_WM,
+                        "Looks like we have reclaimed some memory, clearing surface for retry.");
+                if (surfaceController != null) {
+                    if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) logSurface(winAnimator.mWin,
+                            "RECOVER DESTROY", false);
+                    winAnimator.destroySurface();
+                    if (winAnimator.mWin.mAppToken != null
+                            && winAnimator.mWin.mAppToken.getController() != null) {
+                        winAnimator.mWin.mAppToken.getController().removeStartingWindow();
+                    }
+                }
+
+                try {
+                    winAnimator.mWin.mClient.dispatchGetNewSurface();
+                } catch (RemoteException e) {
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingIdentity);
+        }
+
+        return leakedSurface || killedApps;
+    }
+
+    // "Something has changed!  Let's make it correct now."
+    // TODO: Super crazy long method that should be broken down...
+    void performSurfacePlacement(boolean recoveringMemory) {
+        if (DEBUG_WINDOW_TRACE) Slog.v(TAG, "performSurfacePlacementInner: entry. Called by "
+                + Debug.getCallers(3));
+
+        int i;
+        boolean updateInputWindowsNeeded = false;
+
+        if (mService.mFocusMayChange) {
+            mService.mFocusMayChange = false;
+            updateInputWindowsNeeded = mService.updateFocusedWindowLocked(
+                    UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/);
+        }
+
+        // Initialize state of exiting tokens.
+        final int numDisplays = mChildren.size();
+        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+            final DisplayContent displayContent = mChildren.get(displayNdx);
+            displayContent.setExitingTokensHasVisible(false);
+        }
+
+        mHoldScreen = null;
+        mScreenBrightness = -1;
+        mUserActivityTimeout = -1;
+        mObscureApplicationContentOnSecondaryDisplays = false;
+        mSustainedPerformanceModeCurrent = false;
+        mService.mTransactionSequence++;
+
+        // TODO(multi-display):
+        final DisplayContent defaultDisplay = mService.getDefaultDisplayContentLocked();
+        final DisplayInfo defaultInfo = defaultDisplay.getDisplayInfo();
+        final int defaultDw = defaultInfo.logicalWidth;
+        final int defaultDh = defaultInfo.logicalHeight;
+
+        if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
+                ">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces");
+        mService.openSurfaceTransaction();
+        try {
+            applySurfaceChangesTransaction(recoveringMemory, defaultDw, defaultDh);
+        } catch (RuntimeException e) {
+            Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
+        } finally {
+            mService.closeSurfaceTransaction();
+            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
+                    "<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
+        }
+
+        final WindowSurfacePlacer surfacePlacer = mService.mWindowPlacerLocked;
+
+        // If we are ready to perform an app transition, check through all of the app tokens to be
+        // shown and see if they are ready to go.
+        if (mService.mAppTransition.isReady()) {
+            defaultDisplay.pendingLayoutChanges |=
+                    surfacePlacer.handleAppTransitionReadyLocked();
+            if (DEBUG_LAYOUT_REPEATS)
+                surfacePlacer.debugLayoutRepeats("after handleAppTransitionReadyLocked",
+                        defaultDisplay.pendingLayoutChanges);
+        }
+
+        if (!mService.mAnimator.mAppWindowAnimating && mService.mAppTransition.isRunning()) {
+            // We have finished the animation of an app transition. To do this, we have delayed a
+            // lot of operations like showing and hiding apps, moving apps in Z-order, etc. The app
+            // token list reflects the correct Z-order, but the window list may now be out of sync
+            // with it. So here we will just rebuild the entire app window list. Fun!
+            defaultDisplay.pendingLayoutChanges |=
+                    mService.handleAnimatingStoppedAndTransitionLocked();
+            if (DEBUG_LAYOUT_REPEATS)
+                surfacePlacer.debugLayoutRepeats("after handleAnimStopAndXitionLock",
+                        defaultDisplay.pendingLayoutChanges);
+        }
+
+        if (mWallpaperForceHidingChanged && defaultDisplay.pendingLayoutChanges == 0
+                && !mService.mAppTransition.isReady()) {
+            // At this point, there was a window with a wallpaper that was force hiding other
+            // windows behind it, but now it is going away. This may be simple -- just animate away
+            // the wallpaper and its window -- or it may be hard -- the wallpaper now needs to be
+            // shown behind something that was hidden.
+            defaultDisplay.pendingLayoutChanges |= FINISH_LAYOUT_REDO_LAYOUT;
+            if (DEBUG_LAYOUT_REPEATS) surfacePlacer.debugLayoutRepeats(
+                    "after animateAwayWallpaperLocked", defaultDisplay.pendingLayoutChanges);
+        }
+        mWallpaperForceHidingChanged = false;
+
+        if (mWallpaperMayChange) {
+            if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Wallpaper may change!  Adjusting");
+            defaultDisplay.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+            if (DEBUG_LAYOUT_REPEATS) surfacePlacer.debugLayoutRepeats("WallpaperMayChange",
+                    defaultDisplay.pendingLayoutChanges);
+        }
+
+        if (mService.mFocusMayChange) {
+            mService.mFocusMayChange = false;
+            if (mService.updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES,
+                    false /*updateInputWindows*/)) {
+                updateInputWindowsNeeded = true;
+                defaultDisplay.pendingLayoutChanges |= FINISH_LAYOUT_REDO_ANIM;
+            }
+        }
+
+        if (isLayoutNeeded()) {
+            defaultDisplay.pendingLayoutChanges |= FINISH_LAYOUT_REDO_LAYOUT;
+            if (DEBUG_LAYOUT_REPEATS) surfacePlacer.debugLayoutRepeats("mLayoutNeeded",
+                    defaultDisplay.pendingLayoutChanges);
+        }
+
+        final ArraySet<DisplayContent> touchExcludeRegionUpdateDisplays = handleResizingWindows();
+
+        if (DEBUG_ORIENTATION && mService.mDisplayFrozen) Slog.v(TAG,
+                "With display frozen, orientationChangeComplete=" + mOrientationChangeComplete);
+        if (mOrientationChangeComplete) {
+            if (mService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
+                mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
+                mService.mLastFinishedFreezeSource = mLastWindowFreezeSource;
+                mService.mH.removeMessages(WINDOW_FREEZE_TIMEOUT);
+            }
+            mService.stopFreezingDisplayLocked();
+        }
+
+        // Destroy the surface of any windows that are no longer visible.
+        boolean wallpaperDestroyed = false;
+        i = mService.mDestroySurface.size();
+        if (i > 0) {
+            do {
+                i--;
+                WindowState win = mService.mDestroySurface.get(i);
+                win.mDestroying = false;
+                if (mService.mInputMethodWindow == win) {
+                    mService.setInputMethodWindowLocked(null);
+                }
+                if (win.getDisplayContent().mWallpaperController.isWallpaperTarget(win)) {
+                    wallpaperDestroyed = true;
+                }
+                win.destroySurfaceUnchecked();
+            } while (i > 0);
+            mService.mDestroySurface.clear();
+        }
+
+        // Time to remove any exiting tokens?
+        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+            final DisplayContent displayContent = mChildren.get(displayNdx);
+            displayContent.removeExistingTokensIfPossible();
+        }
+
+        if (wallpaperDestroyed) {
+            defaultDisplay.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+            defaultDisplay.setLayoutNeeded();
+        }
+
+        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+            final DisplayContent displayContent = mChildren.get(displayNdx);
+            if (displayContent.pendingLayoutChanges != 0) {
+                displayContent.setLayoutNeeded();
+            }
+        }
+
+        // Finally update all input windows now that the window changes have stabilized.
+        mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
+
+        mService.setHoldScreenLocked(mHoldScreen);
+        if (!mService.mDisplayFrozen) {
+            final int brightness = mScreenBrightness < 0 || mScreenBrightness > 1.0f
+                    ? -1 : toBrightnessOverride(mScreenBrightness);
+
+            // Post these on a handler such that we don't call into power manager service while
+            // holding the window manager lock to avoid lock contention with power manager lock.
+            mHandler.obtainMessage(SET_SCREEN_BRIGHTNESS_OVERRIDE, brightness, 0).sendToTarget();
+            mHandler.obtainMessage(SET_USER_ACTIVITY_TIMEOUT, mUserActivityTimeout).sendToTarget();
+        }
+
+        if (mSustainedPerformanceModeCurrent != mSustainedPerformanceModeEnabled) {
+            mSustainedPerformanceModeEnabled = mSustainedPerformanceModeCurrent;
+            mService.mPowerManagerInternal.powerHint(
+                    PowerHint.SUSTAINED_PERFORMANCE,
+                    (mSustainedPerformanceModeEnabled ? 1 : 0));
+        }
+
+        if (mService.mTurnOnScreen) {
+            if (mService.mAllowTheaterModeWakeFromLayout
+                    || Settings.Global.getInt(mService.mContext.getContentResolver(),
+                    Settings.Global.THEATER_MODE_ON, 0) == 0) {
+                if (DEBUG_VISIBILITY || DEBUG_POWER) {
+                    Slog.v(TAG, "Turning screen on after layout!");
+                }
+                mService.mPowerManager.wakeUp(SystemClock.uptimeMillis(),
+                        "android.server.wm:TURN_ON");
+            }
+            mService.mTurnOnScreen = false;
+        }
+
+        if (mUpdateRotation) {
+            if (DEBUG_ORIENTATION) Slog.d(TAG, "Performing post-rotate rotation");
+            // TODO(multi-display): Update rotation for different displays separately.
+            final int displayId = defaultDisplay.getDisplayId();
+            if (defaultDisplay.updateRotationUnchecked(false /* inTransaction */)) {
+                mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget();
+            } else {
+                mUpdateRotation = false;
+            }
+            // Update rotation of VR virtual display separately. Currently this is the only kind of
+            // secondary display that can be rotated because of the single-display limitations in
+            // PhoneWindowManager.
+            final DisplayContent vrDisplay = mService.mVr2dDisplayId != INVALID_DISPLAY
+                    ? getDisplayContent(mService.mVr2dDisplayId) : null;
+            if (vrDisplay != null && vrDisplay.updateRotationUnchecked(false /* inTransaction */)) {
+                mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, mService.mVr2dDisplayId)
+                        .sendToTarget();
+            }
+        }
+
+        if (mService.mWaitingForDrawnCallback != null ||
+                (mOrientationChangeComplete && !defaultDisplay.isLayoutNeeded()
+                        && !mUpdateRotation)) {
+            mService.checkDrawnWindowsLocked();
+        }
+
+        final int N = mService.mPendingRemove.size();
+        if (N > 0) {
+            if (mService.mPendingRemoveTmp.length < N) {
+                mService.mPendingRemoveTmp = new WindowState[N+10];
+            }
+            mService.mPendingRemove.toArray(mService.mPendingRemoveTmp);
+            mService.mPendingRemove.clear();
+            ArrayList<DisplayContent> displayList = new ArrayList();
+            for (i = 0; i < N; i++) {
+                final WindowState w = mService.mPendingRemoveTmp[i];
+                w.removeImmediately();
+                final DisplayContent displayContent = w.getDisplayContent();
+                if (displayContent != null && !displayList.contains(displayContent)) {
+                    displayList.add(displayContent);
+                }
+            }
+
+            for (int j = displayList.size() - 1; j >= 0; --j) {
+                final DisplayContent dc = displayList.get(j);
+                dc.assignWindowLayers(true /*setLayoutNeeded*/);
+            }
+        }
+
+        // Remove all deferred displays stacks, tasks, and activities.
+        for (int displayNdx = mChildren.size() - 1; displayNdx >= 0; --displayNdx) {
+            mChildren.get(displayNdx).checkCompleteDeferredRemoval();
+        }
+
+        if (updateInputWindowsNeeded) {
+            mService.mInputMonitor.updateInputWindowsLw(false /*force*/);
+        }
+        mService.setFocusTaskRegionLocked(null);
+        if (touchExcludeRegionUpdateDisplays != null) {
+            final DisplayContent focusedDc = mService.mFocusedApp != null
+                    ? mService.mFocusedApp.getDisplayContent() : null;
+            for (DisplayContent dc : touchExcludeRegionUpdateDisplays) {
+                // The focused DisplayContent was recalcuated in setFocusTaskRegionLocked
+                if (focusedDc != dc) {
+                    dc.setTouchExcludeRegion(null /* focusedTask */);
+                }
+            }
+        }
+
+        // Check to see if we are now in a state where the screen should
+        // be enabled, because the window obscured flags have changed.
+        mService.enableScreenIfNeededLocked();
+
+        mService.scheduleAnimationLocked();
+        mService.mWindowPlacerLocked.destroyPendingSurfaces();
+
+        if (DEBUG_WINDOW_TRACE) Slog.e(TAG,
+                "performSurfacePlacementInner exit: animating=" + mService.mAnimator.isAnimating());
+    }
+
+    private void applySurfaceChangesTransaction(boolean recoveringMemory, int defaultDw,
+            int defaultDh) {
+        mHoldScreenWindow = null;
+        mObscuringWindow = null;
+
+        // TODO(multi-display): Support these features on secondary screens.
+        if (mService.mWatermark != null) {
+            mService.mWatermark.positionSurface(defaultDw, defaultDh);
+        }
+        if (mService.mStrictModeFlash != null) {
+            mService.mStrictModeFlash.positionSurface(defaultDw, defaultDh);
+        }
+        if (mService.mCircularDisplayMask != null) {
+            mService.mCircularDisplayMask.positionSurface(defaultDw, defaultDh,
+                    mService.getDefaultDisplayRotation());
+        }
+        if (mService.mEmulatorDisplayOverlay != null) {
+            mService.mEmulatorDisplayOverlay.positionSurface(defaultDw, defaultDh,
+                    mService.getDefaultDisplayRotation());
+        }
+
+        boolean focusDisplayed = false;
+
+        final int count = mChildren.size();
+        for (int j = 0; j < count; ++j) {
+            final DisplayContent dc = mChildren.get(j);
+            focusDisplayed |= dc.applySurfaceChangesTransaction(recoveringMemory);
+        }
+
+        if (focusDisplayed) {
+            mService.mH.sendEmptyMessage(REPORT_LOSING_FOCUS);
+        }
+
+        // Give the display manager a chance to adjust properties like display rotation if it needs
+        // to.
+        mService.mDisplayManagerInternal.performTraversalInTransactionFromWindowManager();
+    }
+
+    /**
+     * Handles resizing windows during surface placement.
+     *
+     * @return A set of any DisplayContent whose touch exclude region needs to be recalculated due
+     *         to a tap-exclude window resizing, or null if no such DisplayContents were found.
+     */
+    private ArraySet<DisplayContent> handleResizingWindows() {
+        ArraySet<DisplayContent> touchExcludeRegionUpdateSet = null;
+        for (int i = mService.mResizingWindows.size() - 1; i >= 0; i--) {
+            WindowState win = mService.mResizingWindows.get(i);
+            if (win.mAppFreezing) {
+                // Don't remove this window until rotation has completed.
+                continue;
+            }
+            win.reportResized();
+            mService.mResizingWindows.remove(i);
+            if (WindowManagerService.excludeWindowTypeFromTapOutTask(win.mAttrs.type)) {
+                final DisplayContent dc = win.getDisplayContent();
+                if (touchExcludeRegionUpdateSet == null) {
+                    touchExcludeRegionUpdateSet = new ArraySet<>();
+                }
+                touchExcludeRegionUpdateSet.add(dc);
+            }
+        }
+        return touchExcludeRegionUpdateSet;
+    }
+
+    /**
+     * @param w WindowState this method is applied to.
+     * @param obscured True if there is a window on top of this obscuring the display.
+     * @param syswin System window?
+     * @return True when the display contains content to show the user. When false, the display
+     *          manager may choose to mirror or blank the display.
+     */
+    boolean handleNotObscuredLocked(WindowState w, boolean obscured, boolean syswin) {
+        final WindowManager.LayoutParams attrs = w.mAttrs;
+        final int attrFlags = attrs.flags;
+        final boolean canBeSeen = w.isDisplayedLw();
+        final int privateflags = attrs.privateFlags;
+        boolean displayHasContent = false;
+
+        if (w.mHasSurface && canBeSeen) {
+            if ((attrFlags & FLAG_KEEP_SCREEN_ON) != 0) {
+                mHoldScreen = w.mSession;
+                mHoldScreenWindow = w;
+            } else if (DEBUG_KEEP_SCREEN_ON && w == mService.mLastWakeLockHoldingWindow) {
+                Slog.d(TAG_KEEP_SCREEN_ON, "handleNotObscuredLocked: " + w + " was holding "
+                        + "screen wakelock but no longer has FLAG_KEEP_SCREEN_ON!!! called by"
+                        + Debug.getCallers(10));
+            }
+            if (!syswin && w.mAttrs.screenBrightness >= 0 && mScreenBrightness < 0) {
+                mScreenBrightness = w.mAttrs.screenBrightness;
+            }
+            if (!syswin && w.mAttrs.userActivityTimeout >= 0 && mUserActivityTimeout < 0) {
+                mUserActivityTimeout = w.mAttrs.userActivityTimeout;
+            }
+
+            final int type = attrs.type;
+            // This function assumes that the contents of the default display are processed first
+            // before secondary displays.
+            final DisplayContent displayContent = w.getDisplayContent();
+            if (displayContent != null && displayContent.isDefaultDisplay) {
+                // While a dream or keyguard is showing, obscure ordinary application content on
+                // secondary displays (by forcibly enabling mirroring unless there is other content
+                // we want to show) but still allow opaque keyguard dialogs to be shown.
+                if (type == TYPE_DREAM || (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
+                    mObscureApplicationContentOnSecondaryDisplays = true;
+                }
+                displayHasContent = true;
+            } else if (displayContent != null &&
+                    (!mObscureApplicationContentOnSecondaryDisplays
+                            || (obscured && type == TYPE_KEYGUARD_DIALOG))) {
+                // Allow full screen keyguard presentation dialogs to be seen.
+                displayHasContent = true;
+            }
+            if ((privateflags & PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE) != 0) {
+                mSustainedPerformanceModeCurrent = true;
+            }
+        }
+
+        return displayHasContent;
+    }
+
+    boolean copyAnimToLayoutParams() {
+        boolean doRequest = false;
+
+        final int bulkUpdateParams = mService.mAnimator.mBulkUpdateParams;
+        if ((bulkUpdateParams & SET_UPDATE_ROTATION) != 0) {
+            mUpdateRotation = true;
+            doRequest = true;
+        }
+        if ((bulkUpdateParams & SET_WALLPAPER_MAY_CHANGE) != 0) {
+            mWallpaperMayChange = true;
+            doRequest = true;
+        }
+        if ((bulkUpdateParams & SET_FORCE_HIDING_CHANGED) != 0) {
+            mWallpaperForceHidingChanged = true;
+            doRequest = true;
+        }
+        if ((bulkUpdateParams & SET_ORIENTATION_CHANGE_COMPLETE) == 0) {
+            mOrientationChangeComplete = false;
+        } else {
+            mOrientationChangeComplete = true;
+            mLastWindowFreezeSource = mService.mAnimator.mLastWindowFreezeSource;
+            if (mService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
+                doRequest = true;
+            }
+        }
+        if ((bulkUpdateParams & SET_TURN_ON_SCREEN) != 0) {
+            mService.mTurnOnScreen = true;
+        }
+        if ((bulkUpdateParams & SET_WALLPAPER_ACTION_PENDING) != 0) {
+            mWallpaperActionPending = true;
+        }
+
+        return doRequest;
+    }
+
+    private static int toBrightnessOverride(float value) {
+        return (int)(value * PowerManager.BRIGHTNESS_ON);
+    }
+
+    private final class MyHandler extends Handler {
+
+        public MyHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case SET_SCREEN_BRIGHTNESS_OVERRIDE:
+                    mService.mPowerManagerInternal.setScreenBrightnessOverrideFromWindowManager(
+                            msg.arg1);
+                    break;
+                case SET_USER_ACTIVITY_TIMEOUT:
+                    mService.mPowerManagerInternal.setUserActivityTimeoutOverrideFromWindowManager(
+                            (Long) msg.obj);
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    void enableSurfaceTrace(ParcelFileDescriptor pfd) {
+        final FileDescriptor fd = pfd.getFileDescriptor();
+        if (mSurfaceTraceEnabled) {
+            disableSurfaceTrace();
+        }
+        mSurfaceTraceEnabled = true;
+        mRemoteEventTrace = new RemoteEventTrace(mService, fd);
+        mSurfaceTraceFd = pfd;
+        for (int displayNdx = mChildren.size() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent dc = mChildren.get(displayNdx);
+            dc.enableSurfaceTrace(fd);
+        }
+    }
+
+    void disableSurfaceTrace() {
+        mSurfaceTraceEnabled = false;
+        mRemoteEventTrace = null;
+        mSurfaceTraceFd = null;
+        for (int displayNdx = mChildren.size() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent dc = mChildren.get(displayNdx);
+            dc.disableSurfaceTrace();
+        }
+    }
+
+    void dumpDisplayContents(PrintWriter pw) {
+        pw.println("WINDOW MANAGER DISPLAY CONTENTS (dumpsys window displays)");
+        if (mService.mDisplayReady) {
+            final int count = mChildren.size();
+            for (int i = 0; i < count; ++i) {
+                final DisplayContent displayContent = mChildren.get(i);
+                displayContent.dump("  ", pw);
+            }
+        } else {
+            pw.println("  NO DISPLAY");
+        }
+    }
+
+    void dumpLayoutNeededDisplayIds(PrintWriter pw) {
+        if (!isLayoutNeeded()) {
+            return;
+        }
+        pw.print("  mLayoutNeeded on displays=");
+        final int count = mChildren.size();
+        for (int displayNdx = 0; displayNdx < count; ++displayNdx) {
+            final DisplayContent displayContent = mChildren.get(displayNdx);
+            if (displayContent.isLayoutNeeded()) {
+                pw.print(displayContent.getDisplayId());
+            }
+        }
+        pw.println();
+    }
+
+    void dumpWindowsNoHeader(PrintWriter pw, boolean dumpAll, ArrayList<WindowState> windows) {
+        final int[] index = new int[1];
+        forAllWindows((w) -> {
+            if (windows == null || windows.contains(w)) {
+                pw.println("  Window #" + index[0] + " " + w + ":");
+                w.dump(pw, "    ", dumpAll || windows != null);
+                index[0] = index[0] + 1;
+            }
+        }, true /* traverseTopToBottom */);
+    }
+
+    void dumpTokens(PrintWriter pw, boolean dumpAll) {
+        pw.println("  All tokens:");
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            mChildren.get(i).dumpTokens(pw, dumpAll);
+        }
+    }
+
+    void writeToProto(ProtoOutputStream proto) {
+        if (mService.mDisplayReady) {
+            final int count = mChildren.size();
+            for (int i = 0; i < count; ++i) {
+                final DisplayContent displayContent = mChildren.get(i);
+                displayContent.writeToProto(proto, DISPLAYS);
+            }
+        }
+        forAllWindows((w) -> {
+            w.writeIdentifierToProto(proto, WINDOWS);
+        }, true);
+    }
+
+    @Override
+    String getName() {
+        return "ROOT";
+    }
+}
diff --git a/com/android/server/wm/ScreenRotationAnimation.java b/com/android/server/wm/ScreenRotationAnimation.java
new file mode 100644
index 0000000..d5b6d24
--- /dev/null
+++ b/com/android/server/wm/ScreenRotationAnimation.java
@@ -0,0 +1,1032 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER;
+import static com.android.server.wm.WindowStateAnimator.WINDOW_FREEZE_LAYER;
+import static com.android.server.wm.WindowSurfaceController.SurfaceTrace;
+import static com.android.server.wm.proto.ScreenRotationAnimationProto.ANIMATION_RUNNING;
+import static com.android.server.wm.proto.ScreenRotationAnimationProto.STARTED;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.Surface;
+import android.view.Surface.OutOfResourcesException;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Transformation;
+
+import java.io.PrintWriter;
+
+class ScreenRotationAnimation {
+    static final String TAG = TAG_WITH_CLASS_NAME ? "ScreenRotationAnimation" : TAG_WM;
+    static final boolean DEBUG_STATE = false;
+    static final boolean DEBUG_TRANSFORMS = false;
+    static final boolean TWO_PHASE_ANIMATION = false;
+    static final boolean USE_CUSTOM_BLACK_FRAME = false;
+
+    /*
+     * Layers for screen rotation animation. We put these layers above
+     * WINDOW_FREEZE_LAYER so that screen freeze will cover all windows.
+     */
+    static final int SCREEN_FREEZE_LAYER_BASE       = WINDOW_FREEZE_LAYER + TYPE_LAYER_MULTIPLIER;
+    static final int SCREEN_FREEZE_LAYER_ENTER      = SCREEN_FREEZE_LAYER_BASE;
+    static final int SCREEN_FREEZE_LAYER_SCREENSHOT = SCREEN_FREEZE_LAYER_BASE + 1;
+    static final int SCREEN_FREEZE_LAYER_EXIT       = SCREEN_FREEZE_LAYER_BASE + 2;
+    static final int SCREEN_FREEZE_LAYER_CUSTOM     = SCREEN_FREEZE_LAYER_BASE + 3;
+
+    final Context mContext;
+    final DisplayContent mDisplayContent;
+    SurfaceControl mSurfaceControl;
+    BlackFrame mCustomBlackFrame;
+    BlackFrame mExitingBlackFrame;
+    BlackFrame mEnteringBlackFrame;
+    int mWidth, mHeight;
+
+    int mOriginalRotation;
+    int mOriginalWidth, mOriginalHeight;
+    int mCurRotation;
+    Rect mOriginalDisplayRect = new Rect();
+    Rect mCurrentDisplayRect = new Rect();
+
+    // For all animations, "exit" is for the UI elements that are going
+    // away (that is the snapshot of the old screen), and "enter" is for
+    // the new UI elements that are appearing (that is the active windows
+    // in their final orientation).
+
+    // The starting animation for the exiting and entering elements.  This
+    // animation applies a transformation while the rotation is in progress.
+    // It is started immediately, before the new entering UI is ready.
+    Animation mStartExitAnimation;
+    final Transformation mStartExitTransformation = new Transformation();
+    Animation mStartEnterAnimation;
+    final Transformation mStartEnterTransformation = new Transformation();
+    Animation mStartFrameAnimation;
+    final Transformation mStartFrameTransformation = new Transformation();
+
+    // The finishing animation for the exiting and entering elements.  This
+    // animation needs to undo the transformation of the starting animation.
+    // It starts running once the new rotation UI elements are ready to be
+    // displayed.
+    Animation mFinishExitAnimation;
+    final Transformation mFinishExitTransformation = new Transformation();
+    Animation mFinishEnterAnimation;
+    final Transformation mFinishEnterTransformation = new Transformation();
+    Animation mFinishFrameAnimation;
+    final Transformation mFinishFrameTransformation = new Transformation();
+
+    // The current active animation to move from the old to the new rotated
+    // state.  Which animation is run here will depend on the old and new
+    // rotations.
+    Animation mRotateExitAnimation;
+    final Transformation mRotateExitTransformation = new Transformation();
+    Animation mRotateEnterAnimation;
+    final Transformation mRotateEnterTransformation = new Transformation();
+    Animation mRotateFrameAnimation;
+    final Transformation mRotateFrameTransformation = new Transformation();
+
+    // A previously running rotate animation.  This will be used if we need
+    // to switch to a new rotation before finishing the previous one.
+    Animation mLastRotateExitAnimation;
+    final Transformation mLastRotateExitTransformation = new Transformation();
+    Animation mLastRotateEnterAnimation;
+    final Transformation mLastRotateEnterTransformation = new Transformation();
+    Animation mLastRotateFrameAnimation;
+    final Transformation mLastRotateFrameTransformation = new Transformation();
+
+    // Complete transformations being applied.
+    final Transformation mExitTransformation = new Transformation();
+    final Transformation mEnterTransformation = new Transformation();
+    final Transformation mFrameTransformation = new Transformation();
+
+    boolean mStarted;
+    boolean mAnimRunning;
+    boolean mFinishAnimReady;
+    long mFinishAnimStartTime;
+    boolean mForceDefaultOrientation;
+
+    final Matrix mFrameInitialMatrix = new Matrix();
+    final Matrix mSnapshotInitialMatrix = new Matrix();
+    final Matrix mSnapshotFinalMatrix = new Matrix();
+    final Matrix mExitFrameFinalMatrix = new Matrix();
+    final Matrix mTmpMatrix = new Matrix();
+    final float[] mTmpFloats = new float[9];
+    private boolean mMoreRotateEnter;
+    private boolean mMoreRotateExit;
+    private boolean mMoreRotateFrame;
+    private boolean mMoreFinishEnter;
+    private boolean mMoreFinishExit;
+    private boolean mMoreFinishFrame;
+    private boolean mMoreStartEnter;
+    private boolean mMoreStartExit;
+    private boolean mMoreStartFrame;
+    long mHalfwayPoint;
+
+    private final WindowManagerService mService;
+
+    public void printTo(String prefix, PrintWriter pw) {
+        pw.print(prefix); pw.print("mSurface="); pw.print(mSurfaceControl);
+                pw.print(" mWidth="); pw.print(mWidth);
+                pw.print(" mHeight="); pw.println(mHeight);
+        if (USE_CUSTOM_BLACK_FRAME) {
+            pw.print(prefix); pw.print("mCustomBlackFrame="); pw.println(mCustomBlackFrame);
+            if (mCustomBlackFrame != null) {
+                mCustomBlackFrame.printTo(prefix + "  ", pw);
+            }
+        }
+        pw.print(prefix); pw.print("mExitingBlackFrame="); pw.println(mExitingBlackFrame);
+        if (mExitingBlackFrame != null) {
+            mExitingBlackFrame.printTo(prefix + "  ", pw);
+        }
+        pw.print(prefix); pw.print("mEnteringBlackFrame="); pw.println(mEnteringBlackFrame);
+        if (mEnteringBlackFrame != null) {
+            mEnteringBlackFrame.printTo(prefix + "  ", pw);
+        }
+        pw.print(prefix); pw.print("mCurRotation="); pw.print(mCurRotation);
+                pw.print(" mOriginalRotation="); pw.println(mOriginalRotation);
+        pw.print(prefix); pw.print("mOriginalWidth="); pw.print(mOriginalWidth);
+                pw.print(" mOriginalHeight="); pw.println(mOriginalHeight);
+        pw.print(prefix); pw.print("mStarted="); pw.print(mStarted);
+                pw.print(" mAnimRunning="); pw.print(mAnimRunning);
+                pw.print(" mFinishAnimReady="); pw.print(mFinishAnimReady);
+                pw.print(" mFinishAnimStartTime="); pw.println(mFinishAnimStartTime);
+        pw.print(prefix); pw.print("mStartExitAnimation="); pw.print(mStartExitAnimation);
+                pw.print(" "); mStartExitTransformation.printShortString(pw); pw.println();
+        pw.print(prefix); pw.print("mStartEnterAnimation="); pw.print(mStartEnterAnimation);
+                pw.print(" "); mStartEnterTransformation.printShortString(pw); pw.println();
+        pw.print(prefix); pw.print("mStartFrameAnimation="); pw.print(mStartFrameAnimation);
+                pw.print(" "); mStartFrameTransformation.printShortString(pw); pw.println();
+        pw.print(prefix); pw.print("mFinishExitAnimation="); pw.print(mFinishExitAnimation);
+                pw.print(" "); mFinishExitTransformation.printShortString(pw); pw.println();
+        pw.print(prefix); pw.print("mFinishEnterAnimation="); pw.print(mFinishEnterAnimation);
+                pw.print(" "); mFinishEnterTransformation.printShortString(pw); pw.println();
+        pw.print(prefix); pw.print("mFinishFrameAnimation="); pw.print(mFinishFrameAnimation);
+                pw.print(" "); mFinishFrameTransformation.printShortString(pw); pw.println();
+        pw.print(prefix); pw.print("mRotateExitAnimation="); pw.print(mRotateExitAnimation);
+                pw.print(" "); mRotateExitTransformation.printShortString(pw); pw.println();
+        pw.print(prefix); pw.print("mRotateEnterAnimation="); pw.print(mRotateEnterAnimation);
+                pw.print(" "); mRotateEnterTransformation.printShortString(pw); pw.println();
+        pw.print(prefix); pw.print("mRotateFrameAnimation="); pw.print(mRotateFrameAnimation);
+                pw.print(" "); mRotateFrameTransformation.printShortString(pw); pw.println();
+        pw.print(prefix); pw.print("mExitTransformation=");
+                mExitTransformation.printShortString(pw); pw.println();
+        pw.print(prefix); pw.print("mEnterTransformation=");
+                mEnterTransformation.printShortString(pw); pw.println();
+        pw.print(prefix); pw.print("mFrameTransformation=");
+                mFrameTransformation.printShortString(pw); pw.println();
+        pw.print(prefix); pw.print("mFrameInitialMatrix=");
+                mFrameInitialMatrix.printShortString(pw);
+                pw.println();
+        pw.print(prefix); pw.print("mSnapshotInitialMatrix=");
+                mSnapshotInitialMatrix.printShortString(pw);
+                pw.print(" mSnapshotFinalMatrix="); mSnapshotFinalMatrix.printShortString(pw);
+                pw.println();
+        pw.print(prefix); pw.print("mExitFrameFinalMatrix=");
+                mExitFrameFinalMatrix.printShortString(pw);
+                pw.println();
+        pw.print(prefix); pw.print("mForceDefaultOrientation="); pw.print(mForceDefaultOrientation);
+        if (mForceDefaultOrientation) {
+            pw.print(" mOriginalDisplayRect="); pw.print(mOriginalDisplayRect.toShortString());
+            pw.print(" mCurrentDisplayRect="); pw.println(mCurrentDisplayRect.toShortString());
+        }
+    }
+
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(STARTED, mStarted);
+        proto.write(ANIMATION_RUNNING, mAnimRunning);
+        proto.end(token);
+    }
+
+    public ScreenRotationAnimation(Context context, DisplayContent displayContent,
+            SurfaceSession session, boolean inTransaction, boolean forceDefaultOrientation,
+            boolean isSecure, WindowManagerService service) {
+        mService = service;
+        mContext = context;
+        mDisplayContent = displayContent;
+        displayContent.getLogicalDisplayRect(mOriginalDisplayRect);
+
+        // Screenshot does NOT include rotation!
+        final Display display = displayContent.getDisplay();
+        int originalRotation = display.getRotation();
+        final int originalWidth;
+        final int originalHeight;
+        DisplayInfo displayInfo = displayContent.getDisplayInfo();
+        if (forceDefaultOrientation) {
+            // Emulated orientation.
+            mForceDefaultOrientation = true;
+            originalWidth = displayContent.mBaseDisplayWidth;
+            originalHeight = displayContent.mBaseDisplayHeight;
+        } else {
+            // Normal situation
+            originalWidth = displayInfo.logicalWidth;
+            originalHeight = displayInfo.logicalHeight;
+        }
+        if (originalRotation == Surface.ROTATION_90
+                || originalRotation == Surface.ROTATION_270) {
+            mWidth = originalHeight;
+            mHeight = originalWidth;
+        } else {
+            mWidth = originalWidth;
+            mHeight = originalHeight;
+        }
+
+        mOriginalRotation = originalRotation;
+        mOriginalWidth = originalWidth;
+        mOriginalHeight = originalHeight;
+
+        if (!inTransaction) {
+            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
+                    ">>> OPEN TRANSACTION ScreenRotationAnimation");
+            mService.openSurfaceTransaction();
+        }
+
+        try {
+            try {
+                int flags = SurfaceControl.HIDDEN;
+                if (isSecure) {
+                    flags |= SurfaceControl.SECURE;
+                }
+
+                if (DEBUG_SURFACE_TRACE) {
+                    mSurfaceControl = new SurfaceTrace(session, "ScreenshotSurface",
+                            mWidth, mHeight,
+                            PixelFormat.OPAQUE, flags);
+                    Slog.w(TAG, "ScreenRotationAnimation ctor: displayOffset="
+                            + mOriginalDisplayRect.toShortString());
+                } else {
+                    mSurfaceControl = new SurfaceControl(session, "ScreenshotSurface",
+                            mWidth, mHeight,
+                            PixelFormat.OPAQUE, flags);
+                }
+                // capture a screenshot into the surface we just created
+                Surface sur = new Surface();
+                sur.copyFrom(mSurfaceControl);
+                // TODO(multidisplay): we should use the proper display
+                SurfaceControl.screenshot(SurfaceControl.getBuiltInDisplay(
+                        SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN), sur);
+                mSurfaceControl.setLayerStack(display.getLayerStack());
+                mSurfaceControl.setLayer(SCREEN_FREEZE_LAYER_SCREENSHOT);
+                mSurfaceControl.setAlpha(0);
+                mSurfaceControl.show();
+                sur.destroy();
+            } catch (OutOfResourcesException e) {
+                Slog.w(TAG, "Unable to allocate freeze surface", e);
+            }
+
+            if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG_WM,
+                    "  FREEZE " + mSurfaceControl + ": CREATE");
+
+            setRotationInTransaction(originalRotation);
+        } finally {
+            if (!inTransaction) {
+                mService.closeSurfaceTransaction();
+                if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
+                        "<<< CLOSE TRANSACTION ScreenRotationAnimation");
+            }
+        }
+    }
+
+    boolean hasScreenshot() {
+        return mSurfaceControl != null;
+    }
+
+    private void setSnapshotTransformInTransaction(Matrix matrix, float alpha) {
+        if (mSurfaceControl != null) {
+            matrix.getValues(mTmpFloats);
+            float x = mTmpFloats[Matrix.MTRANS_X];
+            float y = mTmpFloats[Matrix.MTRANS_Y];
+            if (mForceDefaultOrientation) {
+                mDisplayContent.getLogicalDisplayRect(mCurrentDisplayRect);
+                x -= mCurrentDisplayRect.left;
+                y -= mCurrentDisplayRect.top;
+            }
+            mSurfaceControl.setPosition(x, y);
+            mSurfaceControl.setMatrix(
+                    mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
+                    mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
+            mSurfaceControl.setAlpha(alpha);
+            if (DEBUG_TRANSFORMS) {
+                float[] srcPnts = new float[] { 0, 0, mWidth, mHeight };
+                float[] dstPnts = new float[4];
+                matrix.mapPoints(dstPnts, srcPnts);
+                Slog.i(TAG, "Original  : (" + srcPnts[0] + "," + srcPnts[1]
+                        + ")-(" + srcPnts[2] + "," + srcPnts[3] + ")");
+                Slog.i(TAG, "Transformed: (" + dstPnts[0] + "," + dstPnts[1]
+                        + ")-(" + dstPnts[2] + "," + dstPnts[3] + ")");
+            }
+        }
+    }
+
+    public static void createRotationMatrix(int rotation, int width, int height,
+            Matrix outMatrix) {
+        switch (rotation) {
+            case Surface.ROTATION_0:
+                outMatrix.reset();
+                break;
+            case Surface.ROTATION_90:
+                outMatrix.setRotate(90, 0, 0);
+                outMatrix.postTranslate(height, 0);
+                break;
+            case Surface.ROTATION_180:
+                outMatrix.setRotate(180, 0, 0);
+                outMatrix.postTranslate(width, height);
+                break;
+            case Surface.ROTATION_270:
+                outMatrix.setRotate(270, 0, 0);
+                outMatrix.postTranslate(0, width);
+                break;
+        }
+    }
+
+    // Must be called while in a transaction.
+    private void setRotationInTransaction(int rotation) {
+        mCurRotation = rotation;
+
+        // Compute the transformation matrix that must be applied
+        // to the snapshot to make it stay in the same original position
+        // with the current screen rotation.
+        int delta = DisplayContent.deltaRotation(rotation, Surface.ROTATION_0);
+        createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix);
+
+        if (DEBUG_STATE) Slog.v(TAG, "**** ROTATION: " + delta);
+        setSnapshotTransformInTransaction(mSnapshotInitialMatrix, 1.0f);
+    }
+
+    // Must be called while in a transaction.
+    public boolean setRotationInTransaction(int rotation, SurfaceSession session,
+            long maxAnimationDuration, float animationScale, int finalWidth, int finalHeight) {
+        setRotationInTransaction(rotation);
+        if (TWO_PHASE_ANIMATION) {
+            return startAnimation(session, maxAnimationDuration, animationScale,
+                    finalWidth, finalHeight, false, 0, 0);
+        }
+
+        // Don't start animation yet.
+        return false;
+    }
+
+    /**
+     * Returns true if animating.
+     */
+    private boolean startAnimation(SurfaceSession session, long maxAnimationDuration,
+            float animationScale, int finalWidth, int finalHeight, boolean dismissing,
+            int exitAnim, int enterAnim) {
+        if (mSurfaceControl == null) {
+            // Can't do animation.
+            return false;
+        }
+        if (mStarted) {
+            return true;
+        }
+
+        mStarted = true;
+
+        boolean firstStart = false;
+
+        // Figure out how the screen has moved from the original rotation.
+        int delta = DisplayContent.deltaRotation(mCurRotation, mOriginalRotation);
+
+        if (TWO_PHASE_ANIMATION && mFinishExitAnimation == null
+                && (!dismissing || delta != Surface.ROTATION_0)) {
+            if (DEBUG_STATE) Slog.v(TAG, "Creating start and finish animations");
+            firstStart = true;
+            mStartExitAnimation = AnimationUtils.loadAnimation(mContext,
+                    com.android.internal.R.anim.screen_rotate_start_exit);
+            mStartEnterAnimation = AnimationUtils.loadAnimation(mContext,
+                    com.android.internal.R.anim.screen_rotate_start_enter);
+            if (USE_CUSTOM_BLACK_FRAME) {
+                mStartFrameAnimation = AnimationUtils.loadAnimation(mContext,
+                        com.android.internal.R.anim.screen_rotate_start_frame);
+            }
+            mFinishExitAnimation = AnimationUtils.loadAnimation(mContext,
+                    com.android.internal.R.anim.screen_rotate_finish_exit);
+            mFinishEnterAnimation = AnimationUtils.loadAnimation(mContext,
+                    com.android.internal.R.anim.screen_rotate_finish_enter);
+            if (USE_CUSTOM_BLACK_FRAME) {
+                mFinishFrameAnimation = AnimationUtils.loadAnimation(mContext,
+                        com.android.internal.R.anim.screen_rotate_finish_frame);
+            }
+        }
+
+        if (DEBUG_STATE) Slog.v(TAG, "Rotation delta: " + delta + " finalWidth="
+                + finalWidth + " finalHeight=" + finalHeight
+                + " origWidth=" + mOriginalWidth + " origHeight=" + mOriginalHeight);
+
+        final boolean customAnim;
+        if (exitAnim != 0 && enterAnim != 0) {
+            customAnim = true;
+            mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, exitAnim);
+            mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, enterAnim);
+        } else {
+            customAnim = false;
+            switch (delta) {
+                case Surface.ROTATION_0:
+                    mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
+                            com.android.internal.R.anim.screen_rotate_0_exit);
+                    mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
+                            com.android.internal.R.anim.screen_rotate_0_enter);
+                    if (USE_CUSTOM_BLACK_FRAME) {
+                        mRotateFrameAnimation = AnimationUtils.loadAnimation(mContext,
+                                com.android.internal.R.anim.screen_rotate_0_frame);
+                    }
+                    break;
+                case Surface.ROTATION_90:
+                    mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
+                            com.android.internal.R.anim.screen_rotate_plus_90_exit);
+                    mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
+                            com.android.internal.R.anim.screen_rotate_plus_90_enter);
+                    if (USE_CUSTOM_BLACK_FRAME) {
+                        mRotateFrameAnimation = AnimationUtils.loadAnimation(mContext,
+                                com.android.internal.R.anim.screen_rotate_plus_90_frame);
+                    }
+                    break;
+                case Surface.ROTATION_180:
+                    mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
+                            com.android.internal.R.anim.screen_rotate_180_exit);
+                    mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
+                            com.android.internal.R.anim.screen_rotate_180_enter);
+                    if (USE_CUSTOM_BLACK_FRAME) {
+                        mRotateFrameAnimation = AnimationUtils.loadAnimation(mContext,
+                                com.android.internal.R.anim.screen_rotate_180_frame);
+                    }
+                    break;
+                case Surface.ROTATION_270:
+                    mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
+                            com.android.internal.R.anim.screen_rotate_minus_90_exit);
+                    mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
+                            com.android.internal.R.anim.screen_rotate_minus_90_enter);
+                    if (USE_CUSTOM_BLACK_FRAME) {
+                        mRotateFrameAnimation = AnimationUtils.loadAnimation(mContext,
+                                com.android.internal.R.anim.screen_rotate_minus_90_frame);
+                    }
+                    break;
+            }
+        }
+
+        // Initialize the animations.  This is a hack, redefining what "parent"
+        // means to allow supplying the last and next size.  In this definition
+        // "%p" is the original (let's call it "previous") size, and "%" is the
+        // screen's current/new size.
+        if (TWO_PHASE_ANIMATION && firstStart) {
+            // Compute partial steps between original and final sizes.  These
+            // are used for the dimensions of the exiting and entering elements,
+            // so they are never stretched too significantly.
+            final int halfWidth = (finalWidth + mOriginalWidth) / 2;
+            final int halfHeight = (finalHeight + mOriginalHeight) / 2;
+
+            if (DEBUG_STATE) Slog.v(TAG, "Initializing start and finish animations");
+            mStartEnterAnimation.initialize(finalWidth, finalHeight,
+                    halfWidth, halfHeight);
+            mStartExitAnimation.initialize(halfWidth, halfHeight,
+                    mOriginalWidth, mOriginalHeight);
+            mFinishEnterAnimation.initialize(finalWidth, finalHeight,
+                    halfWidth, halfHeight);
+            mFinishExitAnimation.initialize(halfWidth, halfHeight,
+                    mOriginalWidth, mOriginalHeight);
+            if (USE_CUSTOM_BLACK_FRAME) {
+                mStartFrameAnimation.initialize(finalWidth, finalHeight,
+                        mOriginalWidth, mOriginalHeight);
+                mFinishFrameAnimation.initialize(finalWidth, finalHeight,
+                        mOriginalWidth, mOriginalHeight);
+            }
+        }
+        mRotateEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
+        mRotateExitAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
+        if (USE_CUSTOM_BLACK_FRAME) {
+            mRotateFrameAnimation.initialize(finalWidth, finalHeight, mOriginalWidth,
+                    mOriginalHeight);
+        }
+        mAnimRunning = false;
+        mFinishAnimReady = false;
+        mFinishAnimStartTime = -1;
+
+        if (TWO_PHASE_ANIMATION && firstStart) {
+            mStartExitAnimation.restrictDuration(maxAnimationDuration);
+            mStartExitAnimation.scaleCurrentDuration(animationScale);
+            mStartEnterAnimation.restrictDuration(maxAnimationDuration);
+            mStartEnterAnimation.scaleCurrentDuration(animationScale);
+            mFinishExitAnimation.restrictDuration(maxAnimationDuration);
+            mFinishExitAnimation.scaleCurrentDuration(animationScale);
+            mFinishEnterAnimation.restrictDuration(maxAnimationDuration);
+            mFinishEnterAnimation.scaleCurrentDuration(animationScale);
+            if (USE_CUSTOM_BLACK_FRAME) {
+                mStartFrameAnimation.restrictDuration(maxAnimationDuration);
+                mStartFrameAnimation.scaleCurrentDuration(animationScale);
+                mFinishFrameAnimation.restrictDuration(maxAnimationDuration);
+                mFinishFrameAnimation.scaleCurrentDuration(animationScale);
+            }
+        }
+        mRotateExitAnimation.restrictDuration(maxAnimationDuration);
+        mRotateExitAnimation.scaleCurrentDuration(animationScale);
+        mRotateEnterAnimation.restrictDuration(maxAnimationDuration);
+        mRotateEnterAnimation.scaleCurrentDuration(animationScale);
+        if (USE_CUSTOM_BLACK_FRAME) {
+            mRotateFrameAnimation.restrictDuration(maxAnimationDuration);
+            mRotateFrameAnimation.scaleCurrentDuration(animationScale);
+        }
+
+        final int layerStack = mDisplayContent.getDisplay().getLayerStack();
+        if (USE_CUSTOM_BLACK_FRAME && mCustomBlackFrame == null) {
+            if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
+                    TAG_WM,
+                    ">>> OPEN TRANSACTION ScreenRotationAnimation.startAnimation");
+            mService.openSurfaceTransaction();
+
+            // Compute the transformation matrix that must be applied
+            // the the black frame to make it stay in the initial position
+            // before the new screen rotation.  This is different than the
+            // snapshot transformation because the snapshot is always based
+            // of the native orientation of the screen, not the orientation
+            // we were last in.
+            createRotationMatrix(delta, mOriginalWidth, mOriginalHeight, mFrameInitialMatrix);
+
+            try {
+                Rect outer = new Rect(-mOriginalWidth*1, -mOriginalHeight*1,
+                        mOriginalWidth*2, mOriginalHeight*2);
+                Rect inner = new Rect(0, 0, mOriginalWidth, mOriginalHeight);
+                mCustomBlackFrame = new BlackFrame(session, outer, inner,
+                        SCREEN_FREEZE_LAYER_CUSTOM, layerStack, false);
+                mCustomBlackFrame.setMatrix(mFrameInitialMatrix);
+            } catch (OutOfResourcesException e) {
+                Slog.w(TAG, "Unable to allocate black surface", e);
+            } finally {
+                mService.closeSurfaceTransaction();
+                if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
+                        TAG_WM,
+                        "<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
+            }
+        }
+
+        if (!customAnim && mExitingBlackFrame == null) {
+            if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
+                    TAG_WM,
+                    ">>> OPEN TRANSACTION ScreenRotationAnimation.startAnimation");
+            mService.openSurfaceTransaction();
+            try {
+                // Compute the transformation matrix that must be applied
+                // the the black frame to make it stay in the initial position
+                // before the new screen rotation.  This is different than the
+                // snapshot transformation because the snapshot is always based
+                // of the native orientation of the screen, not the orientation
+                // we were last in.
+                createRotationMatrix(delta, mOriginalWidth, mOriginalHeight, mFrameInitialMatrix);
+
+                final Rect outer;
+                final Rect inner;
+                if (mForceDefaultOrientation) {
+                    // Going from a smaller Display to a larger Display, add curtains to sides
+                    // or top and bottom. Going from a larger to smaller display will result in
+                    // no BlackSurfaces being constructed.
+                    outer = mCurrentDisplayRect;
+                    inner = mOriginalDisplayRect;
+                } else {
+                    outer = new Rect(-mOriginalWidth*1, -mOriginalHeight*1,
+                            mOriginalWidth*2, mOriginalHeight*2);
+                    inner = new Rect(0, 0, mOriginalWidth, mOriginalHeight);
+                }
+                mExitingBlackFrame = new BlackFrame(session, outer, inner,
+                        SCREEN_FREEZE_LAYER_EXIT, layerStack, mForceDefaultOrientation);
+                mExitingBlackFrame.setMatrix(mFrameInitialMatrix);
+            } catch (OutOfResourcesException e) {
+                Slog.w(TAG, "Unable to allocate black surface", e);
+            } finally {
+                mService.closeSurfaceTransaction();
+                if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
+                        TAG_WM,
+                        "<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
+            }
+        }
+
+        if (customAnim && mEnteringBlackFrame == null) {
+            if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
+                    TAG_WM,
+                    ">>> OPEN TRANSACTION ScreenRotationAnimation.startAnimation");
+            mService.openSurfaceTransaction();
+
+            try {
+                Rect outer = new Rect(-finalWidth*1, -finalHeight*1,
+                        finalWidth*2, finalHeight*2);
+                Rect inner = new Rect(0, 0, finalWidth, finalHeight);
+                mEnteringBlackFrame = new BlackFrame(session, outer, inner,
+                        SCREEN_FREEZE_LAYER_ENTER, layerStack, false);
+            } catch (OutOfResourcesException e) {
+                Slog.w(TAG, "Unable to allocate black surface", e);
+            } finally {
+                mService.closeSurfaceTransaction();
+                if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
+                        TAG_WM,
+                        "<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns true if animating.
+     */
+    public boolean dismiss(SurfaceSession session, long maxAnimationDuration,
+            float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
+        if (DEBUG_STATE) Slog.v(TAG, "Dismiss!");
+        if (mSurfaceControl == null) {
+            // Can't do animation.
+            return false;
+        }
+        if (!mStarted) {
+            startAnimation(session, maxAnimationDuration, animationScale, finalWidth, finalHeight,
+                    true, exitAnim, enterAnim);
+        }
+        if (!mStarted) {
+            return false;
+        }
+        if (DEBUG_STATE) Slog.v(TAG, "Setting mFinishAnimReady = true");
+        mFinishAnimReady = true;
+        return true;
+    }
+
+    public void kill() {
+        if (DEBUG_STATE) Slog.v(TAG, "Kill!");
+        if (mSurfaceControl != null) {
+            if (SHOW_TRANSACTIONS ||
+                    SHOW_SURFACE_ALLOC) Slog.i(TAG_WM,
+                            "  FREEZE " + mSurfaceControl + ": DESTROY");
+            mSurfaceControl.destroy();
+            mSurfaceControl = null;
+        }
+        if (mCustomBlackFrame != null) {
+            mCustomBlackFrame.kill();
+            mCustomBlackFrame = null;
+        }
+        if (mExitingBlackFrame != null) {
+            mExitingBlackFrame.kill();
+            mExitingBlackFrame = null;
+        }
+        if (mEnteringBlackFrame != null) {
+            mEnteringBlackFrame.kill();
+            mEnteringBlackFrame = null;
+        }
+        if (TWO_PHASE_ANIMATION) {
+            if (mStartExitAnimation != null) {
+                mStartExitAnimation.cancel();
+                mStartExitAnimation = null;
+            }
+            if (mStartEnterAnimation != null) {
+                mStartEnterAnimation.cancel();
+                mStartEnterAnimation = null;
+            }
+            if (mFinishExitAnimation != null) {
+                mFinishExitAnimation.cancel();
+                mFinishExitAnimation = null;
+            }
+            if (mFinishEnterAnimation != null) {
+                mFinishEnterAnimation.cancel();
+                mFinishEnterAnimation = null;
+            }
+        }
+        if (USE_CUSTOM_BLACK_FRAME) {
+            if (mStartFrameAnimation != null) {
+                mStartFrameAnimation.cancel();
+                mStartFrameAnimation = null;
+            }
+            if (mRotateFrameAnimation != null) {
+                mRotateFrameAnimation.cancel();
+                mRotateFrameAnimation = null;
+            }
+            if (mFinishFrameAnimation != null) {
+                mFinishFrameAnimation.cancel();
+                mFinishFrameAnimation = null;
+            }
+        }
+        if (mRotateExitAnimation != null) {
+            mRotateExitAnimation.cancel();
+            mRotateExitAnimation = null;
+        }
+        if (mRotateEnterAnimation != null) {
+            mRotateEnterAnimation.cancel();
+            mRotateEnterAnimation = null;
+        }
+    }
+
+    public boolean isAnimating() {
+        return hasAnimations() || (TWO_PHASE_ANIMATION && mFinishAnimReady);
+    }
+
+    public boolean isRotating() {
+        return mCurRotation != mOriginalRotation;
+    }
+
+    private boolean hasAnimations() {
+        return (TWO_PHASE_ANIMATION &&
+                    (mStartEnterAnimation != null || mStartExitAnimation != null
+                    || mFinishEnterAnimation != null || mFinishExitAnimation != null))
+                || (USE_CUSTOM_BLACK_FRAME &&
+                        (mStartFrameAnimation != null || mRotateFrameAnimation != null
+                        || mFinishFrameAnimation != null))
+                || mRotateEnterAnimation != null || mRotateExitAnimation != null;
+    }
+
+    private boolean stepAnimation(long now) {
+        if (now > mHalfwayPoint) {
+            mHalfwayPoint = Long.MAX_VALUE;
+        }
+        if (mFinishAnimReady && mFinishAnimStartTime < 0) {
+            if (DEBUG_STATE) Slog.v(TAG, "Step: finish anim now ready");
+            mFinishAnimStartTime = now;
+        }
+
+        if (TWO_PHASE_ANIMATION) {
+            mMoreStartExit = false;
+            if (mStartExitAnimation != null) {
+                mMoreStartExit = mStartExitAnimation.getTransformation(now, mStartExitTransformation);
+                if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped start exit: " + mStartExitTransformation);
+            }
+
+            mMoreStartEnter = false;
+            if (mStartEnterAnimation != null) {
+                mMoreStartEnter = mStartEnterAnimation.getTransformation(now, mStartEnterTransformation);
+                if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped start enter: " + mStartEnterTransformation);
+            }
+        }
+        if (USE_CUSTOM_BLACK_FRAME) {
+            mMoreStartFrame = false;
+            if (mStartFrameAnimation != null) {
+                mMoreStartFrame = mStartFrameAnimation.getTransformation(now, mStartFrameTransformation);
+                if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped start frame: " + mStartFrameTransformation);
+            }
+        }
+
+        long finishNow = mFinishAnimReady ? (now - mFinishAnimStartTime) : 0;
+        if (DEBUG_STATE) Slog.v(TAG, "Step: finishNow=" + finishNow);
+
+        if (TWO_PHASE_ANIMATION) {
+            mMoreFinishExit = false;
+            if (mFinishExitAnimation != null) {
+                mMoreFinishExit = mFinishExitAnimation.getTransformation(finishNow, mFinishExitTransformation);
+                if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped finish exit: " + mFinishExitTransformation);
+            }
+
+            mMoreFinishEnter = false;
+            if (mFinishEnterAnimation != null) {
+                mMoreFinishEnter = mFinishEnterAnimation.getTransformation(finishNow, mFinishEnterTransformation);
+                if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped finish enter: " + mFinishEnterTransformation);
+            }
+        }
+        if (USE_CUSTOM_BLACK_FRAME) {
+            mMoreFinishFrame = false;
+            if (mFinishFrameAnimation != null) {
+                mMoreFinishFrame = mFinishFrameAnimation.getTransformation(finishNow, mFinishFrameTransformation);
+                if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped finish frame: " + mFinishFrameTransformation);
+            }
+        }
+
+        mMoreRotateExit = false;
+        if (mRotateExitAnimation != null) {
+            mMoreRotateExit = mRotateExitAnimation.getTransformation(now, mRotateExitTransformation);
+            if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped rotate exit: " + mRotateExitTransformation);
+        }
+
+        mMoreRotateEnter = false;
+        if (mRotateEnterAnimation != null) {
+            mMoreRotateEnter = mRotateEnterAnimation.getTransformation(now, mRotateEnterTransformation);
+            if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped rotate enter: " + mRotateEnterTransformation);
+        }
+
+        if (USE_CUSTOM_BLACK_FRAME) {
+            mMoreRotateFrame = false;
+            if (mRotateFrameAnimation != null) {
+                mMoreRotateFrame = mRotateFrameAnimation.getTransformation(now, mRotateFrameTransformation);
+                if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped rotate frame: " + mRotateFrameTransformation);
+            }
+        }
+
+        if (!mMoreRotateExit && (!TWO_PHASE_ANIMATION || (!mMoreStartExit && !mMoreFinishExit))) {
+            if (TWO_PHASE_ANIMATION) {
+                if (mStartExitAnimation != null) {
+                    if (DEBUG_STATE) Slog.v(TAG, "Exit animations done, clearing start exit anim!");
+                    mStartExitAnimation.cancel();
+                    mStartExitAnimation = null;
+                    mStartExitTransformation.clear();
+                }
+                if (mFinishExitAnimation != null) {
+                    if (DEBUG_STATE) Slog.v(TAG, "Exit animations done, clearing finish exit anim!");
+                    mFinishExitAnimation.cancel();
+                    mFinishExitAnimation = null;
+                    mFinishExitTransformation.clear();
+                }
+            }
+            if (mRotateExitAnimation != null) {
+                if (DEBUG_STATE) Slog.v(TAG, "Exit animations done, clearing rotate exit anim!");
+                mRotateExitAnimation.cancel();
+                mRotateExitAnimation = null;
+                mRotateExitTransformation.clear();
+            }
+        }
+
+        if (!mMoreRotateEnter && (!TWO_PHASE_ANIMATION || (!mMoreStartEnter && !mMoreFinishEnter))) {
+            if (TWO_PHASE_ANIMATION) {
+                if (mStartEnterAnimation != null) {
+                    if (DEBUG_STATE) Slog.v(TAG, "Enter animations done, clearing start enter anim!");
+                    mStartEnterAnimation.cancel();
+                    mStartEnterAnimation = null;
+                    mStartEnterTransformation.clear();
+                }
+                if (mFinishEnterAnimation != null) {
+                    if (DEBUG_STATE) Slog.v(TAG, "Enter animations done, clearing finish enter anim!");
+                    mFinishEnterAnimation.cancel();
+                    mFinishEnterAnimation = null;
+                    mFinishEnterTransformation.clear();
+                }
+            }
+            if (mRotateEnterAnimation != null) {
+                if (DEBUG_STATE) Slog.v(TAG, "Enter animations done, clearing rotate enter anim!");
+                mRotateEnterAnimation.cancel();
+                mRotateEnterAnimation = null;
+                mRotateEnterTransformation.clear();
+            }
+        }
+
+        if (USE_CUSTOM_BLACK_FRAME && !mMoreStartFrame && !mMoreRotateFrame && !mMoreFinishFrame) {
+            if (mStartFrameAnimation != null) {
+                if (DEBUG_STATE) Slog.v(TAG, "Frame animations done, clearing start frame anim!");
+                mStartFrameAnimation.cancel();
+                mStartFrameAnimation = null;
+                mStartFrameTransformation.clear();
+            }
+            if (mFinishFrameAnimation != null) {
+                if (DEBUG_STATE) Slog.v(TAG, "Frame animations done, clearing finish frame anim!");
+                mFinishFrameAnimation.cancel();
+                mFinishFrameAnimation = null;
+                mFinishFrameTransformation.clear();
+            }
+            if (mRotateFrameAnimation != null) {
+                if (DEBUG_STATE) Slog.v(TAG, "Frame animations done, clearing rotate frame anim!");
+                mRotateFrameAnimation.cancel();
+                mRotateFrameAnimation = null;
+                mRotateFrameTransformation.clear();
+            }
+        }
+
+        mExitTransformation.set(mRotateExitTransformation);
+        mEnterTransformation.set(mRotateEnterTransformation);
+        if (TWO_PHASE_ANIMATION) {
+            mExitTransformation.compose(mStartExitTransformation);
+            mExitTransformation.compose(mFinishExitTransformation);
+
+            mEnterTransformation.compose(mStartEnterTransformation);
+            mEnterTransformation.compose(mFinishEnterTransformation);
+        }
+
+        if (DEBUG_TRANSFORMS) Slog.v(TAG, "Final exit: " + mExitTransformation);
+        if (DEBUG_TRANSFORMS) Slog.v(TAG, "Final enter: " + mEnterTransformation);
+
+        if (USE_CUSTOM_BLACK_FRAME) {
+            //mFrameTransformation.set(mRotateExitTransformation);
+            //mFrameTransformation.compose(mStartExitTransformation);
+            //mFrameTransformation.compose(mFinishExitTransformation);
+            mFrameTransformation.set(mRotateFrameTransformation);
+            mFrameTransformation.compose(mStartFrameTransformation);
+            mFrameTransformation.compose(mFinishFrameTransformation);
+            mFrameTransformation.getMatrix().preConcat(mFrameInitialMatrix);
+            if (DEBUG_TRANSFORMS) Slog.v(TAG, "Final frame: " + mFrameTransformation);
+        }
+
+        final boolean more = (TWO_PHASE_ANIMATION
+                    && (mMoreStartEnter || mMoreStartExit || mMoreFinishEnter || mMoreFinishExit))
+                || (USE_CUSTOM_BLACK_FRAME
+                        && (mMoreStartFrame || mMoreRotateFrame || mMoreFinishFrame))
+                || mMoreRotateEnter || mMoreRotateExit
+                || !mFinishAnimReady;
+
+        mSnapshotFinalMatrix.setConcat(mExitTransformation.getMatrix(), mSnapshotInitialMatrix);
+
+        if (DEBUG_STATE) Slog.v(TAG, "Step: more=" + more);
+
+        return more;
+    }
+
+    void updateSurfacesInTransaction() {
+        if (!mStarted) {
+            return;
+        }
+
+        if (mSurfaceControl != null) {
+            if (!mMoreStartExit && !mMoreFinishExit && !mMoreRotateExit) {
+                if (DEBUG_STATE) Slog.v(TAG, "Exit animations done, hiding screenshot surface");
+                mSurfaceControl.hide();
+            }
+        }
+
+        if (mCustomBlackFrame != null) {
+            if (!mMoreStartFrame && !mMoreFinishFrame && !mMoreRotateFrame) {
+                if (DEBUG_STATE) Slog.v(TAG, "Frame animations done, hiding black frame");
+                mCustomBlackFrame.hide();
+            } else {
+                mCustomBlackFrame.setMatrix(mFrameTransformation.getMatrix());
+            }
+        }
+
+        if (mExitingBlackFrame != null) {
+            if (!mMoreStartExit && !mMoreFinishExit && !mMoreRotateExit) {
+                if (DEBUG_STATE) Slog.v(TAG, "Frame animations done, hiding exiting frame");
+                mExitingBlackFrame.hide();
+            } else {
+                mExitFrameFinalMatrix.setConcat(mExitTransformation.getMatrix(), mFrameInitialMatrix);
+                mExitingBlackFrame.setMatrix(mExitFrameFinalMatrix);
+                if (mForceDefaultOrientation) {
+                    mExitingBlackFrame.setAlpha(mExitTransformation.getAlpha());
+                }
+            }
+        }
+
+        if (mEnteringBlackFrame != null) {
+            if (!mMoreStartEnter && !mMoreFinishEnter && !mMoreRotateEnter) {
+                if (DEBUG_STATE) Slog.v(TAG, "Frame animations done, hiding entering frame");
+                mEnteringBlackFrame.hide();
+            } else {
+                mEnteringBlackFrame.setMatrix(mEnterTransformation.getMatrix());
+            }
+        }
+
+        setSnapshotTransformInTransaction(mSnapshotFinalMatrix, mExitTransformation.getAlpha());
+    }
+
+    public boolean stepAnimationLocked(long now) {
+        if (!hasAnimations()) {
+            if (DEBUG_STATE) Slog.v(TAG, "Step: no animations running");
+            mFinishAnimReady = false;
+            return false;
+        }
+
+        if (!mAnimRunning) {
+            if (DEBUG_STATE) Slog.v(TAG, "Step: starting start, finish, rotate");
+            if (TWO_PHASE_ANIMATION) {
+                if (mStartEnterAnimation != null) {
+                    mStartEnterAnimation.setStartTime(now);
+                }
+                if (mStartExitAnimation != null) {
+                    mStartExitAnimation.setStartTime(now);
+                }
+                if (mFinishEnterAnimation != null) {
+                    mFinishEnterAnimation.setStartTime(0);
+                }
+                if (mFinishExitAnimation != null) {
+                    mFinishExitAnimation.setStartTime(0);
+                }
+            }
+            if (USE_CUSTOM_BLACK_FRAME) {
+                if (mStartFrameAnimation != null) {
+                    mStartFrameAnimation.setStartTime(now);
+                }
+                if (mFinishFrameAnimation != null) {
+                    mFinishFrameAnimation.setStartTime(0);
+                }
+                if (mRotateFrameAnimation != null) {
+                    mRotateFrameAnimation.setStartTime(now);
+                }
+            }
+            if (mRotateEnterAnimation != null) {
+                mRotateEnterAnimation.setStartTime(now);
+            }
+            if (mRotateExitAnimation != null) {
+                mRotateExitAnimation.setStartTime(now);
+            }
+            mAnimRunning = true;
+            mHalfwayPoint = now + mRotateEnterAnimation.getDuration() / 2;
+        }
+
+        return stepAnimation(now);
+    }
+
+    public Transformation getEnterTransformation() {
+        return mEnterTransformation;
+    }
+}
diff --git a/com/android/server/wm/Session.java b/com/android/server/wm/Session.java
new file mode 100644
index 0000000..1781247
--- /dev/null
+++ b/com/android/server/wm/Session.java
@@ -0,0 +1,722 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.Manifest.permission.DEVICE_POWER;
+import static android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.content.ClipData;
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.util.MergedConfiguration;
+import android.util.Slog;
+import android.view.Display;
+import android.view.IWindow;
+import android.view.IWindowId;
+import android.view.IWindowSession;
+import android.view.IWindowSessionCallback;
+import android.view.InputChannel;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.WindowManager;
+
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethodClient;
+import com.android.internal.view.IInputMethodManager;
+import com.android.server.wm.WindowManagerService.H;
+
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * This class represents an active client session.  There is generally one
+ * Session object per process that is interacting with the window manager.
+ */
+// Needs to be public and not final so we can mock during tests...sucks I know :(
+public class Session extends IWindowSession.Stub
+        implements IBinder.DeathRecipient {
+    final WindowManagerService mService;
+    final IWindowSessionCallback mCallback;
+    final IInputMethodClient mClient;
+    final int mUid;
+    final int mPid;
+    private final String mStringName;
+    SurfaceSession mSurfaceSession;
+    private int mNumWindow = 0;
+    // Set of visible application overlay window surfaces connected to this session.
+    private final Set<WindowSurfaceController> mAppOverlaySurfaces = new HashSet<>();
+    // Set of visible alert window surfaces connected to this session.
+    private final Set<WindowSurfaceController> mAlertWindowSurfaces = new HashSet<>();
+    final boolean mCanAddInternalSystemWindow;
+    final boolean mCanHideNonSystemOverlayWindows;
+    final boolean mCanAcquireSleepToken;
+    private AlertWindowNotification mAlertWindowNotification;
+    private boolean mShowingAlertWindowNotificationAllowed;
+    private boolean mClientDead = false;
+    private float mLastReportedAnimatorScale;
+    private String mPackageName;
+    private String mRelayoutTag;
+
+    public Session(WindowManagerService service, IWindowSessionCallback callback,
+            IInputMethodClient client, IInputContext inputContext) {
+        mService = service;
+        mCallback = callback;
+        mClient = client;
+        mUid = Binder.getCallingUid();
+        mPid = Binder.getCallingPid();
+        mLastReportedAnimatorScale = service.getCurrentAnimatorScale();
+        mCanAddInternalSystemWindow = service.mContext.checkCallingOrSelfPermission(
+                INTERNAL_SYSTEM_WINDOW) == PERMISSION_GRANTED;
+        mCanHideNonSystemOverlayWindows = service.mContext.checkCallingOrSelfPermission(
+                HIDE_NON_SYSTEM_OVERLAY_WINDOWS) == PERMISSION_GRANTED;
+        mCanAcquireSleepToken = service.mContext.checkCallingOrSelfPermission(DEVICE_POWER)
+                == PERMISSION_GRANTED;
+        mShowingAlertWindowNotificationAllowed = mService.mShowAlertWindowNotifications;
+        StringBuilder sb = new StringBuilder();
+        sb.append("Session{");
+        sb.append(Integer.toHexString(System.identityHashCode(this)));
+        sb.append(" ");
+        sb.append(mPid);
+        if (mUid < Process.FIRST_APPLICATION_UID) {
+            sb.append(":");
+            sb.append(mUid);
+        } else {
+            sb.append(":u");
+            sb.append(UserHandle.getUserId(mUid));
+            sb.append('a');
+            sb.append(UserHandle.getAppId(mUid));
+        }
+        sb.append("}");
+        mStringName = sb.toString();
+
+        synchronized (mService.mWindowMap) {
+            if (mService.mInputMethodManager == null && mService.mHaveInputMethods) {
+                IBinder b = ServiceManager.getService(
+                        Context.INPUT_METHOD_SERVICE);
+                mService.mInputMethodManager = IInputMethodManager.Stub.asInterface(b);
+            }
+        }
+        long ident = Binder.clearCallingIdentity();
+        try {
+            // Note: it is safe to call in to the input method manager
+            // here because we are not holding our lock.
+            if (mService.mInputMethodManager != null) {
+                mService.mInputMethodManager.addClient(client, inputContext,
+                        mUid, mPid);
+            } else {
+                client.setUsingInputMethod(false);
+            }
+            client.asBinder().linkToDeath(this, 0);
+        } catch (RemoteException e) {
+            // The caller has died, so we can just forget about this.
+            try {
+                if (mService.mInputMethodManager != null) {
+                    mService.mInputMethodManager.removeClient(client);
+                }
+            } catch (RemoteException ee) {
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        try {
+            return super.onTransact(code, data, reply, flags);
+        } catch (RuntimeException e) {
+            // Log all 'real' exceptions thrown to the caller
+            if (!(e instanceof SecurityException)) {
+                Slog.wtf(TAG_WM, "Window Session Crash", e);
+            }
+            throw e;
+        }
+    }
+
+    @Override
+    public void binderDied() {
+        // Note: it is safe to call in to the input method manager
+        // here because we are not holding our lock.
+        try {
+            if (mService.mInputMethodManager != null) {
+                mService.mInputMethodManager.removeClient(mClient);
+            }
+        } catch (RemoteException e) {
+        }
+        synchronized(mService.mWindowMap) {
+            mClient.asBinder().unlinkToDeath(this, 0);
+            mClientDead = true;
+            killSessionLocked();
+        }
+    }
+
+    @Override
+    public int add(IWindow window, int seq, WindowManager.LayoutParams attrs,
+            int viewVisibility, Rect outContentInsets, Rect outStableInsets,
+            InputChannel outInputChannel) {
+        return addToDisplay(window, seq, attrs, viewVisibility, Display.DEFAULT_DISPLAY,
+                outContentInsets, outStableInsets, null /* outOutsets */, outInputChannel);
+    }
+
+    @Override
+    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
+            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
+            Rect outOutsets, InputChannel outInputChannel) {
+        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
+                outContentInsets, outStableInsets, outOutsets, outInputChannel);
+    }
+
+    @Override
+    public int addWithoutInputChannel(IWindow window, int seq, WindowManager.LayoutParams attrs,
+            int viewVisibility, Rect outContentInsets, Rect outStableInsets) {
+        return addToDisplayWithoutInputChannel(window, seq, attrs, viewVisibility,
+                Display.DEFAULT_DISPLAY, outContentInsets, outStableInsets);
+    }
+
+    @Override
+    public int addToDisplayWithoutInputChannel(IWindow window, int seq, WindowManager.LayoutParams attrs,
+            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets) {
+        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
+            outContentInsets, outStableInsets, null /* outOutsets */, null);
+    }
+
+    @Override
+    public void remove(IWindow window) {
+        mService.removeWindow(this, window);
+    }
+
+    @Override
+    public void prepareToReplaceWindows(IBinder appToken, boolean childrenOnly) {
+        mService.setWillReplaceWindows(appToken, childrenOnly);
+    }
+
+    @Override
+    public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,
+            int requestedWidth, int requestedHeight, int viewFlags,
+            int flags, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
+            Rect outVisibleInsets, Rect outStableInsets, Rect outsets, Rect outBackdropFrame,
+            MergedConfiguration mergedConfiguration, Surface outSurface) {
+        if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from "
+                + Binder.getCallingPid());
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mRelayoutTag);
+        int res = mService.relayoutWindow(this, window, seq, attrs,
+                requestedWidth, requestedHeight, viewFlags, flags,
+                outFrame, outOverscanInsets, outContentInsets, outVisibleInsets,
+                outStableInsets, outsets, outBackdropFrame, mergedConfiguration, outSurface);
+        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+        if (false) Slog.d(TAG_WM, "<<<<<< EXITING relayout to "
+                + Binder.getCallingPid());
+        return res;
+    }
+
+    @Override
+    public boolean outOfMemory(IWindow window) {
+        return mService.outOfMemoryWindow(this, window);
+    }
+
+    @Override
+    public void setTransparentRegion(IWindow window, Region region) {
+        mService.setTransparentRegionWindow(this, window, region);
+    }
+
+    @Override
+    public void setInsets(IWindow window, int touchableInsets,
+            Rect contentInsets, Rect visibleInsets, Region touchableArea) {
+        mService.setInsetsWindow(this, window, touchableInsets, contentInsets,
+                visibleInsets, touchableArea);
+    }
+
+    @Override
+    public void getDisplayFrame(IWindow window, Rect outDisplayFrame) {
+        mService.getWindowDisplayFrame(this, window, outDisplayFrame);
+    }
+
+    @Override
+    public void finishDrawing(IWindow window) {
+        if (WindowManagerService.localLOGV) Slog.v(
+            TAG_WM, "IWindow finishDrawing called for " + window);
+        mService.finishDrawingWindow(this, window);
+    }
+
+    @Override
+    public void setInTouchMode(boolean mode) {
+        synchronized(mService.mWindowMap) {
+            mService.mInTouchMode = mode;
+        }
+    }
+
+    @Override
+    public boolean getInTouchMode() {
+        synchronized(mService.mWindowMap) {
+            return mService.mInTouchMode;
+        }
+    }
+
+    @Override
+    public boolean performHapticFeedback(IWindow window, int effectId,
+            boolean always) {
+        synchronized(mService.mWindowMap) {
+            long ident = Binder.clearCallingIdentity();
+            try {
+                return mService.mPolicy.performHapticFeedbackLw(
+                        mService.windowForClientLocked(this, window, true),
+                        effectId, always);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    /* Drag/drop */
+    @Override
+    public IBinder prepareDrag(IWindow window, int flags,
+            int width, int height, Surface outSurface) {
+        return mService.prepareDragSurface(window, mSurfaceSession, flags,
+                width, height, outSurface);
+    }
+
+    @Override
+    public boolean performDrag(IWindow window, IBinder dragToken,
+            int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
+            ClipData data) {
+        if (DEBUG_DRAG) {
+            Slog.d(TAG_WM, "perform drag: win=" + window + " data=" + data);
+        }
+
+        synchronized (mService.mWindowMap) {
+            if (mService.mDragState == null) {
+                Slog.w(TAG_WM, "No drag prepared");
+                throw new IllegalStateException("performDrag() without prepareDrag()");
+            }
+
+            if (dragToken != mService.mDragState.mToken) {
+                Slog.w(TAG_WM, "Performing mismatched drag");
+                throw new IllegalStateException("performDrag() does not match prepareDrag()");
+            }
+
+            WindowState callingWin = mService.windowForClientLocked(null, window, false);
+            if (callingWin == null) {
+                Slog.w(TAG_WM, "Bad requesting window " + window);
+                return false;  // !!! TODO: throw here?
+            }
+
+            // !!! TODO: if input is not still focused on the initiating window, fail
+            // the drag initiation (e.g. an alarm window popped up just as the application
+            // called performDrag()
+
+            mService.mH.removeMessages(H.DRAG_START_TIMEOUT, window.asBinder());
+
+            // !!! TODO: extract the current touch (x, y) in screen coordinates.  That
+            // will let us eliminate the (touchX,touchY) parameters from the API.
+
+            // !!! FIXME: put all this heavy stuff onto the mH looper, as well as
+            // the actual drag event dispatch stuff in the dragstate
+
+            final DisplayContent displayContent = callingWin.getDisplayContent();
+            if (displayContent == null) {
+               return false;
+            }
+            Display display = displayContent.getDisplay();
+            mService.mDragState.register(display);
+            if (!mService.mInputManager.transferTouchFocus(callingWin.mInputChannel,
+                    mService.mDragState.getInputChannel())) {
+                Slog.e(TAG_WM, "Unable to transfer touch focus");
+                mService.mDragState.unregister();
+                mService.mDragState.reset();
+                mService.mDragState = null;
+                return false;
+            }
+
+            mService.mDragState.mDisplayContent = displayContent;
+            mService.mDragState.mData = data;
+            mService.mDragState.broadcastDragStartedLw(touchX, touchY);
+            mService.mDragState.overridePointerIconLw(touchSource);
+
+            // remember the thumb offsets for later
+            mService.mDragState.mThumbOffsetX = thumbCenterX;
+            mService.mDragState.mThumbOffsetY = thumbCenterY;
+
+            // Make the surface visible at the proper location
+            final SurfaceControl surfaceControl = mService.mDragState.mSurfaceControl;
+            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
+                    TAG_WM, ">>> OPEN TRANSACTION performDrag");
+            mService.openSurfaceTransaction();
+            try {
+                surfaceControl.setPosition(touchX - thumbCenterX,
+                        touchY - thumbCenterY);
+                surfaceControl.setLayer(mService.mDragState.getDragLayerLw());
+                surfaceControl.setLayerStack(display.getLayerStack());
+                surfaceControl.show();
+            } finally {
+                mService.closeSurfaceTransaction();
+                if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
+                        TAG_WM, "<<< CLOSE TRANSACTION performDrag");
+            }
+
+            mService.mDragState.notifyLocationLw(touchX, touchY);
+        }
+
+        return true;    // success!
+    }
+
+    @Override
+    public boolean startMovingTask(IWindow window, float startX, float startY) {
+        if (DEBUG_TASK_POSITIONING) Slog.d(
+                TAG_WM, "startMovingTask: {" + startX + "," + startY + "}");
+
+        long ident = Binder.clearCallingIdentity();
+        try {
+            return mService.startMovingTask(window, startX, startY);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
+    public void reportDropResult(IWindow window, boolean consumed) {
+        IBinder token = window.asBinder();
+        if (DEBUG_DRAG) {
+            Slog.d(TAG_WM, "Drop result=" + consumed + " reported by " + token);
+        }
+
+        synchronized (mService.mWindowMap) {
+            long ident = Binder.clearCallingIdentity();
+            try {
+                if (mService.mDragState == null) {
+                    // Most likely the drop recipient ANRed and we ended the drag
+                    // out from under it.  Log the issue and move on.
+                    Slog.w(TAG_WM, "Drop result given but no drag in progress");
+                    return;
+                }
+
+                if (mService.mDragState.mToken != token) {
+                    // We're in a drag, but the wrong window has responded.
+                    Slog.w(TAG_WM, "Invalid drop-result claim by " + window);
+                    throw new IllegalStateException("reportDropResult() by non-recipient");
+                }
+
+                // The right window has responded, even if it's no longer around,
+                // so be sure to halt the timeout even if the later WindowState
+                // lookup fails.
+                mService.mH.removeMessages(H.DRAG_END_TIMEOUT, window.asBinder());
+                WindowState callingWin = mService.windowForClientLocked(null, window, false);
+                if (callingWin == null) {
+                    Slog.w(TAG_WM, "Bad result-reporting window " + window);
+                    return;  // !!! TODO: throw here?
+                }
+
+                mService.mDragState.mDragResult = consumed;
+                mService.mDragState.endDragLw();
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    @Override
+    public void cancelDragAndDrop(IBinder dragToken) {
+        if (DEBUG_DRAG) {
+            Slog.d(TAG_WM, "cancelDragAndDrop");
+        }
+
+        synchronized (mService.mWindowMap) {
+            long ident = Binder.clearCallingIdentity();
+            try {
+                if (mService.mDragState == null) {
+                    Slog.w(TAG_WM, "cancelDragAndDrop() without prepareDrag()");
+                    throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()");
+                }
+
+                if (mService.mDragState.mToken != dragToken) {
+                    Slog.w(TAG_WM,
+                            "cancelDragAndDrop() does not match prepareDrag()");
+                    throw new IllegalStateException(
+                            "cancelDragAndDrop() does not match prepareDrag()");
+                }
+
+                mService.mDragState.mDragResult = false;
+                mService.mDragState.cancelDragLw();
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    @Override
+    public void dragRecipientEntered(IWindow window) {
+        if (DEBUG_DRAG) {
+            Slog.d(TAG_WM, "Drag into new candidate view @ " + window.asBinder());
+        }
+    }
+
+    @Override
+    public void dragRecipientExited(IWindow window) {
+        if (DEBUG_DRAG) {
+            Slog.d(TAG_WM, "Drag from old candidate view @ " + window.asBinder());
+        }
+    }
+
+    @Override
+    public void setWallpaperPosition(IBinder window, float x, float y, float xStep, float yStep) {
+        synchronized(mService.mWindowMap) {
+            long ident = Binder.clearCallingIdentity();
+            try {
+                mService.mRoot.mWallpaperController.setWindowWallpaperPosition(
+                        mService.windowForClientLocked(this, window, true),
+                        x, y, xStep, yStep);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    @Override
+    public void wallpaperOffsetsComplete(IBinder window) {
+        synchronized (mService.mWindowMap) {
+            mService.mRoot.mWallpaperController.wallpaperOffsetsComplete(window);
+        }
+    }
+
+    @Override
+    public void setWallpaperDisplayOffset(IBinder window, int x, int y) {
+        synchronized(mService.mWindowMap) {
+            long ident = Binder.clearCallingIdentity();
+            try {
+                mService.mRoot.mWallpaperController.setWindowWallpaperDisplayOffset(
+                        mService.windowForClientLocked(this, window, true), x, y);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    @Override
+    public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,
+            int z, Bundle extras, boolean sync) {
+        synchronized(mService.mWindowMap) {
+            long ident = Binder.clearCallingIdentity();
+            try {
+                return mService.mRoot.mWallpaperController.sendWindowWallpaperCommand(
+                        mService.windowForClientLocked(this, window, true),
+                        action, x, y, z, extras, sync);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    @Override
+    public void wallpaperCommandComplete(IBinder window, Bundle result) {
+        synchronized (mService.mWindowMap) {
+            mService.mRoot.mWallpaperController.wallpaperCommandComplete(window);
+        }
+    }
+
+    @Override
+    public void onRectangleOnScreenRequested(IBinder token, Rect rectangle) {
+        synchronized(mService.mWindowMap) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mService.onRectangleOnScreenRequested(token, rectangle);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
+    @Override
+    public IWindowId getWindowId(IBinder window) {
+        return mService.getWindowId(window);
+    }
+
+    @Override
+    public void pokeDrawLock(IBinder window) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mService.pokeDrawLock(this, window);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public void updatePointerIcon(IWindow window) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mService.updatePointerIcon(window);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    void windowAddedLocked(String packageName) {
+        mPackageName = packageName;
+        mRelayoutTag = "relayoutWindow: " + mPackageName;
+        if (mSurfaceSession == null) {
+            if (WindowManagerService.localLOGV) Slog.v(
+                TAG_WM, "First window added to " + this + ", creating SurfaceSession");
+            mSurfaceSession = new SurfaceSession();
+            if (SHOW_TRANSACTIONS) Slog.i(
+                    TAG_WM, "  NEW SURFACE SESSION " + mSurfaceSession);
+            mService.mSessions.add(this);
+            if (mLastReportedAnimatorScale != mService.getCurrentAnimatorScale()) {
+                mService.dispatchNewAnimatorScaleLocked(this);
+            }
+        }
+        mNumWindow++;
+    }
+
+    void windowRemovedLocked() {
+        mNumWindow--;
+        killSessionLocked();
+    }
+
+
+    void onWindowSurfaceVisibilityChanged(WindowSurfaceController surfaceController,
+            boolean visible, int type) {
+
+        if (!isSystemAlertWindowType(type)) {
+            return;
+        }
+
+        boolean changed;
+
+        if (!mCanAddInternalSystemWindow) {
+            // We want to track non-system signature apps adding alert windows so we can post an
+            // on-going notification for the user to control their visibility.
+            if (visible) {
+                changed = mAlertWindowSurfaces.add(surfaceController);
+            } else {
+                changed = mAlertWindowSurfaces.remove(surfaceController);
+            }
+
+            if (changed) {
+                if (mAlertWindowSurfaces.isEmpty()) {
+                    cancelAlertWindowNotification();
+                } else if (mAlertWindowNotification == null){
+                    mAlertWindowNotification = new AlertWindowNotification(mService, mPackageName);
+                    if (mShowingAlertWindowNotificationAllowed) {
+                        mAlertWindowNotification.post();
+                    }
+                }
+            }
+        }
+
+        if (type != TYPE_APPLICATION_OVERLAY) {
+            return;
+        }
+
+        if (visible) {
+            changed = mAppOverlaySurfaces.add(surfaceController);
+        } else {
+            changed = mAppOverlaySurfaces.remove(surfaceController);
+        }
+
+        if (changed) {
+            // Notify activity manager of changes to app overlay windows so it can adjust the
+            // importance score for the process.
+            setHasOverlayUi(!mAppOverlaySurfaces.isEmpty());
+        }
+    }
+
+    void setShowingAlertWindowNotificationAllowed(boolean allowed) {
+        mShowingAlertWindowNotificationAllowed = allowed;
+        if (mAlertWindowNotification != null) {
+            if (allowed) {
+                mAlertWindowNotification.post();
+            } else {
+                mAlertWindowNotification.cancel();
+            }
+        }
+    }
+
+    private void killSessionLocked() {
+        if (mNumWindow > 0 || !mClientDead) {
+            return;
+        }
+
+        mService.mSessions.remove(this);
+        if (mSurfaceSession == null) {
+            return;
+        }
+
+        if (WindowManagerService.localLOGV) Slog.v(TAG_WM, "Last window removed from " + this
+                + ", destroying " + mSurfaceSession);
+        if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, "  KILL SURFACE SESSION " + mSurfaceSession);
+        try {
+            mSurfaceSession.kill();
+        } catch (Exception e) {
+            Slog.w(TAG_WM, "Exception thrown when killing surface session " + mSurfaceSession
+                    + " in session " + this + ": " + e.toString());
+        }
+        mSurfaceSession = null;
+        mAlertWindowSurfaces.clear();
+        mAppOverlaySurfaces.clear();
+        setHasOverlayUi(false);
+        cancelAlertWindowNotification();
+    }
+
+    private void setHasOverlayUi(boolean hasOverlayUi) {
+        mService.mH.obtainMessage(H.SET_HAS_OVERLAY_UI, mPid, hasOverlayUi ? 1 : 0).sendToTarget();
+    }
+
+    private void cancelAlertWindowNotification() {
+        if (mAlertWindowNotification == null) {
+            return;
+        }
+        mAlertWindowNotification.cancel();
+        mAlertWindowNotification = null;
+    }
+
+    void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix); pw.print("mNumWindow="); pw.print(mNumWindow);
+                pw.print(" mCanAddInternalSystemWindow="); pw.print(mCanAddInternalSystemWindow);
+                pw.print(" mAppOverlaySurfaces="); pw.print(mAppOverlaySurfaces);
+                pw.print(" mAlertWindowSurfaces="); pw.print(mAlertWindowSurfaces);
+                pw.print(" mClientDead="); pw.print(mClientDead);
+                pw.print(" mSurfaceSession="); pw.println(mSurfaceSession);
+        pw.print(prefix); pw.print("mPackageName="); pw.println(mPackageName);
+    }
+
+    @Override
+    public String toString() {
+        return mStringName;
+    }
+}
diff --git a/com/android/server/wm/SnapshotStartingData.java b/com/android/server/wm/SnapshotStartingData.java
new file mode 100644
index 0000000..35f35db
--- /dev/null
+++ b/com/android/server/wm/SnapshotStartingData.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import android.app.ActivityManager.TaskSnapshot;
+import android.graphics.GraphicBuffer;
+import android.view.WindowManagerPolicy.StartingSurface;
+
+/**
+ * Represents starting data for snapshot starting windows.
+ */
+class SnapshotStartingData extends StartingData {
+
+    private final WindowManagerService mService;
+    private final TaskSnapshot mSnapshot;
+
+    SnapshotStartingData(WindowManagerService service, TaskSnapshot snapshot) {
+        super(service);
+        mService = service;
+        mSnapshot = snapshot;
+    }
+
+    @Override
+    StartingSurface createStartingSurface(AppWindowToken atoken) {
+        return mService.mTaskSnapshotController.createStartingSurface(atoken, mSnapshot);
+    }
+}
diff --git a/com/android/server/wm/SplashScreenStartingData.java b/com/android/server/wm/SplashScreenStartingData.java
new file mode 100644
index 0000000..4b14f86
--- /dev/null
+++ b/com/android/server/wm/SplashScreenStartingData.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.view.WindowManagerPolicy.StartingSurface;
+
+/**
+ * Represents starting data for splash screens, i.e. "traditional" starting windows.
+ */
+class SplashScreenStartingData extends StartingData {
+
+    private final String mPkg;
+    private final int mTheme;
+    private final CompatibilityInfo mCompatInfo;
+    private final CharSequence mNonLocalizedLabel;
+    private final int mLabelRes;
+    private final int mIcon;
+    private final int mLogo;
+    private final int mWindowFlags;
+    private final Configuration mMergedOverrideConfiguration;
+
+    SplashScreenStartingData(WindowManagerService service, String pkg, int theme,
+            CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
+            int logo, int windowFlags, Configuration mergedOverrideConfiguration) {
+        super(service);
+        mPkg = pkg;
+        mTheme = theme;
+        mCompatInfo = compatInfo;
+        mNonLocalizedLabel = nonLocalizedLabel;
+        mLabelRes = labelRes;
+        mIcon = icon;
+        mLogo = logo;
+        mWindowFlags = windowFlags;
+        mMergedOverrideConfiguration = mergedOverrideConfiguration;
+    }
+
+    @Override
+    StartingSurface createStartingSurface(AppWindowToken atoken) {
+        return mService.mPolicy.addSplashScreen(atoken.token, mPkg, mTheme, mCompatInfo,
+                mNonLocalizedLabel, mLabelRes, mIcon, mLogo, mWindowFlags,
+                mMergedOverrideConfiguration, atoken.getDisplayContent().getDisplayId());
+    }
+}
diff --git a/com/android/server/wm/StackWindowController.java b/com/android/server/wm/StackWindowController.java
new file mode 100644
index 0000000..a50ed71
--- /dev/null
+++ b/com/android/server/wm/StackWindowController.java
@@ -0,0 +1,407 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+
+import android.app.ActivityManager.StackId;
+import android.app.WindowConfiguration;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.DisplayInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.ref.WeakReference;
+
+import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+/**
+ * Controller for the stack container. This is created by activity manager to link activity stacks
+ * to the stack container they use in window manager.
+ *
+ * Test class: {@link StackWindowControllerTests}
+ */
+public class StackWindowController
+        extends WindowContainerController<TaskStack, StackWindowListener> {
+
+    final int mStackId;
+
+    private final H mHandler;
+
+    // Temp bounds only used in adjustConfigurationForBounds()
+    private final Rect mTmpRect = new Rect();
+    private final Rect mTmpStableInsets = new Rect();
+    private final Rect mTmpNonDecorInsets = new Rect();
+    private final Rect mTmpDisplayBounds = new Rect();
+
+    public StackWindowController(int stackId, StackWindowListener listener, int displayId,
+            boolean onTop, Rect outBounds) {
+        this(stackId, listener, displayId, onTop, outBounds, WindowManagerService.getInstance());
+    }
+
+    @VisibleForTesting
+    public StackWindowController(int stackId, StackWindowListener listener,
+            int displayId, boolean onTop, Rect outBounds, WindowManagerService service) {
+        super(listener, service);
+        mStackId = stackId;
+        mHandler = new H(new WeakReference<>(this), service.mH.getLooper());
+
+        synchronized (mWindowMap) {
+            final DisplayContent dc = mRoot.getDisplayContent(displayId);
+            if (dc == null) {
+                throw new IllegalArgumentException("Trying to add stackId=" + stackId
+                        + " to unknown displayId=" + displayId);
+            }
+
+            final TaskStack stack = dc.addStackToDisplay(stackId, onTop);
+            stack.setController(this);
+            getRawBounds(outBounds);
+        }
+    }
+
+    @Override
+    public void removeContainer() {
+        synchronized (mWindowMap) {
+            if (mContainer != null) {
+                mContainer.removeIfPossible();
+                super.removeContainer();
+            }
+        }
+    }
+
+    public boolean isVisible() {
+        synchronized (mWindowMap) {
+            return mContainer != null && mContainer.isVisible();
+        }
+    }
+
+    public void reparent(int displayId, Rect outStackBounds, boolean onTop) {
+        synchronized (mWindowMap) {
+            if (mContainer == null) {
+                throw new IllegalArgumentException("Trying to move unknown stackId=" + mStackId
+                        + " to displayId=" + displayId);
+            }
+
+            final DisplayContent targetDc = mRoot.getDisplayContent(displayId);
+            if (targetDc == null) {
+                throw new IllegalArgumentException("Trying to move stackId=" + mStackId
+                        + " to unknown displayId=" + displayId);
+            }
+
+            targetDc.moveStackToDisplay(mContainer, onTop);
+            getRawBounds(outStackBounds);
+        }
+    }
+
+    public void positionChildAt(TaskWindowContainerController child, int position, Rect bounds,
+            Configuration overrideConfig) {
+        synchronized (mWindowMap) {
+            if (DEBUG_STACK) Slog.i(TAG_WM, "positionChildAt: positioning task=" + child
+                    + " at " + position);
+            if (child.mContainer == null) {
+                if (DEBUG_STACK) Slog.i(TAG_WM,
+                        "positionChildAt: could not find task=" + this);
+                return;
+            }
+            if (mContainer == null) {
+                if (DEBUG_STACK) Slog.i(TAG_WM,
+                        "positionChildAt: could not find stack for task=" + mContainer);
+                return;
+            }
+            child.mContainer.positionAt(position, bounds, overrideConfig);
+            mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
+        }
+    }
+
+    public void positionChildAtTop(TaskWindowContainerController child, boolean includingParents) {
+        if (child == null) {
+            // TODO: Fix the call-points that cause this to happen.
+            return;
+        }
+
+        synchronized(mWindowMap) {
+            final Task childTask = child.mContainer;
+            if (childTask == null) {
+                Slog.e(TAG_WM, "positionChildAtTop: task=" + child + " not found");
+                return;
+            }
+            mContainer.positionChildAt(POSITION_TOP, childTask, includingParents);
+
+            if (mService.mAppTransition.isTransitionSet()) {
+                childTask.setSendingToBottom(false);
+            }
+            mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
+        }
+    }
+
+    public void positionChildAtBottom(TaskWindowContainerController child) {
+        if (child == null) {
+            // TODO: Fix the call-points that cause this to happen.
+            return;
+        }
+
+        synchronized(mWindowMap) {
+            final Task childTask = child.mContainer;
+            if (childTask == null) {
+                Slog.e(TAG_WM, "positionChildAtBottom: task=" + child + " not found");
+                return;
+            }
+            mContainer.positionChildAt(POSITION_BOTTOM, childTask, false /* includingParents */);
+
+            if (mService.mAppTransition.isTransitionSet()) {
+                childTask.setSendingToBottom(true);
+            }
+            mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
+        }
+    }
+
+    /**
+     * Re-sizes a stack and its containing tasks.
+     *
+     * @param bounds New stack bounds. Passing in null sets the bounds to fullscreen.
+     * @param configs Configurations for tasks in the resized stack, keyed by task id.
+     * @param taskBounds Bounds for tasks in the resized stack, keyed by task id.
+     * @return True if the stack is now fullscreen.
+     */
+    public boolean resize(Rect bounds, SparseArray<Configuration> configs,
+            SparseArray<Rect> taskBounds, SparseArray<Rect> taskTempInsetBounds) {
+        synchronized (mWindowMap) {
+            if (mContainer == null) {
+                throw new IllegalArgumentException("resizeStack: stack " + this + " not found.");
+            }
+            // We might trigger a configuration change. Save the current task bounds for freezing.
+            mContainer.prepareFreezingTaskBounds();
+            if (mContainer.setBounds(bounds, configs, taskBounds, taskTempInsetBounds)
+                    && mContainer.isVisible()) {
+                mContainer.getDisplayContent().setLayoutNeeded();
+                mService.mWindowPlacerLocked.performSurfacePlacement();
+            }
+            return mContainer.getRawFullscreen();
+        }
+    }
+
+    /**
+     * @see TaskStack.getStackDockedModeBoundsLocked(Rect, Rect, Rect, boolean)
+     */
+   public void getStackDockedModeBounds(Rect currentTempTaskBounds, Rect outStackBounds,
+           Rect outTempTaskBounds, boolean ignoreVisibility) {
+        synchronized (mWindowMap) {
+            if (mContainer != null) {
+                mContainer.getStackDockedModeBoundsLocked(currentTempTaskBounds, outStackBounds,
+                        outTempTaskBounds, ignoreVisibility);
+                return;
+            }
+            outStackBounds.setEmpty();
+            outTempTaskBounds.setEmpty();
+        }
+    }
+
+    public void prepareFreezingTaskBounds() {
+        synchronized (mWindowMap) {
+            if (mContainer == null) {
+                throw new IllegalArgumentException("prepareFreezingTaskBounds: stack " + this
+                        + " not found.");
+            }
+            mContainer.prepareFreezingTaskBounds();
+        }
+    }
+
+    private void getRawBounds(Rect outBounds) {
+        if (mContainer.getRawFullscreen()) {
+            outBounds.setEmpty();
+        } else {
+            mContainer.getRawBounds(outBounds);
+        }
+    }
+
+    public void getBounds(Rect outBounds) {
+        synchronized (mWindowMap) {
+            if (mContainer != null) {
+                mContainer.getBounds(outBounds);
+                return;
+            }
+            outBounds.setEmpty();
+        }
+    }
+
+    public void getBoundsForNewConfiguration(Rect outBounds) {
+        synchronized(mWindowMap) {
+            mContainer.getBoundsForNewConfiguration(outBounds);
+        }
+    }
+
+    /**
+     * Adjusts the screen size in dp's for the {@param config} for the given params.
+     */
+    public void adjustConfigurationForBounds(Rect bounds, Rect insetBounds,
+            Rect nonDecorBounds, Rect stableBounds, boolean overrideWidth,
+            boolean overrideHeight, float density, Configuration config,
+            Configuration parentConfig) {
+        synchronized (mWindowMap) {
+            final TaskStack stack = mContainer;
+            final DisplayContent displayContent = stack.getDisplayContent();
+            final DisplayInfo di = displayContent.getDisplayInfo();
+
+            // Get the insets and display bounds
+            mService.mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
+                    mTmpStableInsets);
+            mService.mPolicy.getNonDecorInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
+                    mTmpNonDecorInsets);
+            mTmpDisplayBounds.set(0, 0, di.logicalWidth, di.logicalHeight);
+
+            int width;
+            int height;
+
+            final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds();
+
+            config.windowConfiguration.setAppBounds(!bounds.isEmpty() ? bounds : null);
+            boolean intersectParentBounds = false;
+
+            if (stack.getWindowConfiguration().tasksAreFloating()) {
+                // Floating tasks should not be resized to the screen's bounds.
+
+                if (mStackId == PINNED_STACK_ID && bounds.width() == mTmpDisplayBounds.width() &&
+                        bounds.height() == mTmpDisplayBounds.height()) {
+                    // If the bounds we are animating is the same as the fullscreen stack
+                    // dimensions, then apply the same inset calculations that we normally do for
+                    // the fullscreen stack, without intersecting it with the display bounds
+                    stableBounds.inset(mTmpStableInsets);
+                    nonDecorBounds.inset(mTmpNonDecorInsets);
+                    // Move app bounds to zero to apply intersection with parent correctly. They are
+                    // used only for evaluating width and height, so it's OK to move them around.
+                    config.windowConfiguration.getAppBounds().offsetTo(0, 0);
+                    intersectParentBounds = true;
+                }
+                width = (int) (stableBounds.width() / density);
+                height = (int) (stableBounds.height() / density);
+            } else {
+                // For calculating screenWidthDp, screenWidthDp, we use the stable inset screen
+                // area, i.e. the screen area without the system bars.
+                // Additionally task dimensions should not be bigger than its parents dimensions.
+                // The non decor inset are areas that could never be removed in Honeycomb. See
+                // {@link WindowManagerPolicy#getNonDecorInsetsLw}.
+                intersectDisplayBoundsExcludeInsets(nonDecorBounds,
+                        insetBounds != null ? insetBounds : bounds, mTmpNonDecorInsets,
+                        mTmpDisplayBounds, overrideWidth, overrideHeight);
+                intersectDisplayBoundsExcludeInsets(stableBounds,
+                        insetBounds != null ? insetBounds : bounds, mTmpStableInsets,
+                        mTmpDisplayBounds, overrideWidth, overrideHeight);
+                width = Math.min((int) (stableBounds.width() / density),
+                        parentConfig.screenWidthDp);
+                height = Math.min((int) (stableBounds.height() / density),
+                        parentConfig.screenHeightDp);
+                intersectParentBounds = true;
+            }
+
+            if (intersectParentBounds && config.windowConfiguration.getAppBounds() != null) {
+                config.windowConfiguration.getAppBounds().intersect(parentAppBounds);
+            }
+
+            config.screenWidthDp = width;
+            config.screenHeightDp = height;
+            config.smallestScreenWidthDp = getSmallestWidthForTaskBounds(
+                    insetBounds != null ? insetBounds : bounds, density);
+        }
+    }
+
+    /**
+     * Intersects the specified {@code inOutBounds} with the display frame that excludes the stable
+     * inset areas.
+     *
+     * @param inOutBounds The inOutBounds to subtract the stable inset areas from.
+     */
+    private void intersectDisplayBoundsExcludeInsets(Rect inOutBounds, Rect inInsetBounds,
+            Rect stableInsets, Rect displayBounds, boolean overrideWidth, boolean overrideHeight) {
+        mTmpRect.set(inInsetBounds);
+        mService.intersectDisplayInsetBounds(displayBounds, stableInsets, mTmpRect);
+        int leftInset = mTmpRect.left - inInsetBounds.left;
+        int topInset = mTmpRect.top - inInsetBounds.top;
+        int rightInset = overrideWidth ? 0 : inInsetBounds.right - mTmpRect.right;
+        int bottomInset = overrideHeight ? 0 : inInsetBounds.bottom - mTmpRect.bottom;
+        inOutBounds.inset(leftInset, topInset, rightInset, bottomInset);
+    }
+
+    /**
+     * Calculates the smallest width for a task given the {@param bounds}.
+     *
+     * @return the smallest width to be used in the Configuration, in dips
+     */
+    private int getSmallestWidthForTaskBounds(Rect bounds, float density) {
+        final DisplayContent displayContent = mContainer.getDisplayContent();
+        final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+
+        if (bounds == null || (bounds.width() == displayInfo.logicalWidth &&
+                bounds.height() == displayInfo.logicalHeight)) {
+            // If the bounds are fullscreen, return the value of the fullscreen configuration
+            return displayContent.getConfiguration().smallestScreenWidthDp;
+        } else if (mContainer.getWindowConfiguration().tasksAreFloating()) {
+            // For floating tasks, calculate the smallest width from the bounds of the task
+            return (int) (Math.min(bounds.width(), bounds.height()) / density);
+        } else {
+            // Iterating across all screen orientations, and return the minimum of the task
+            // width taking into account that the bounds might change because the snap algorithm
+            // snaps to a different value
+            return displayContent.getDockedDividerController()
+                    .getSmallestWidthDpForBounds(bounds);
+        }
+    }
+
+    void requestResize(Rect bounds) {
+        mHandler.obtainMessage(H.REQUEST_RESIZE, bounds).sendToTarget();
+    }
+
+    @Override
+    public String toString() {
+        return "{StackWindowController stackId=" + mStackId + "}";
+    }
+
+    private static final class H extends Handler {
+
+        static final int REQUEST_RESIZE = 0;
+
+        private final WeakReference<StackWindowController> mController;
+
+        H(WeakReference<StackWindowController> controller, Looper looper) {
+            super(looper);
+            mController = controller;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            final StackWindowController controller = mController.get();
+            final StackWindowListener listener = (controller != null)
+                    ? controller.mListener : null;
+            if (listener == null) {
+                return;
+            }
+            switch (msg.what) {
+                case REQUEST_RESIZE:
+                    listener.requestResize((Rect) msg.obj);
+                    break;
+            }
+        }
+    }
+}
diff --git a/com/android/server/wm/StackWindowListener.java b/com/android/server/wm/StackWindowListener.java
new file mode 100644
index 0000000..c763c17
--- /dev/null
+++ b/com/android/server/wm/StackWindowListener.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import android.graphics.Rect;
+
+/**
+ * Interface used by the creator of {@link StackWindowController} to listen to changes with
+ * the stack container.
+ */
+public interface StackWindowListener extends WindowContainerListener {
+
+    /** Called when the stack container would like its controller to resize. */
+    void requestResize(Rect bounds);
+}
diff --git a/com/android/server/wm/StartingData.java b/com/android/server/wm/StartingData.java
new file mode 100644
index 0000000..8c564bb
--- /dev/null
+++ b/com/android/server/wm/StartingData.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.view.WindowManagerPolicy.StartingSurface;
+
+/**
+ * Represents the model about how a starting window should be constructed.
+ */
+public abstract class StartingData {
+
+    protected final WindowManagerService mService;
+
+    protected StartingData(WindowManagerService service) {
+        mService = service;
+    }
+
+    /**
+     * Creates the actual starting window surface. DO NOT HOLD THE WINDOW MANAGER LOCK WHEN CALLING
+     * THIS METHOD.
+     *
+     * @param atoken the app to add the starting window to
+     * @return a class implementing {@link StartingSurface} for easy removal with
+     *         {@link StartingSurface#remove}
+     */
+    abstract StartingSurface createStartingSurface(AppWindowToken atoken);
+}
\ No newline at end of file
diff --git a/com/android/server/wm/StrictModeFlash.java b/com/android/server/wm/StrictModeFlash.java
new file mode 100644
index 0000000..d1547eb
--- /dev/null
+++ b/com/android/server/wm/StrictModeFlash.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.view.Display;
+import android.view.Surface.OutOfResourcesException;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+
+class StrictModeFlash {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "StrictModeFlash" : TAG_WM;
+
+    private final SurfaceControl mSurfaceControl;
+    private final Surface mSurface = new Surface();
+    private int mLastDW;
+    private int mLastDH;
+    private boolean mDrawNeeded;
+    private final int mThickness = 20;
+
+    public StrictModeFlash(Display display, SurfaceSession session) {
+        SurfaceControl ctrl = null;
+        try {
+            ctrl = new SurfaceControl(session, "StrictModeFlash",
+                1, 1, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+            ctrl.setLayerStack(display.getLayerStack());
+            ctrl.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER * 101);  // one more than Watermark? arbitrary.
+            ctrl.setPosition(0, 0);
+            ctrl.show();
+            mSurface.copyFrom(ctrl);
+        } catch (OutOfResourcesException e) {
+        }
+        mSurfaceControl = ctrl;
+        mDrawNeeded = true;
+    }
+
+    private void drawIfNeeded() {
+        if (!mDrawNeeded) {
+            return;
+        }
+        mDrawNeeded = false;
+        final int dw = mLastDW;
+        final int dh = mLastDH;
+
+        Rect dirty = new Rect(0, 0, dw, dh);
+        Canvas c = null;
+        try {
+            c = mSurface.lockCanvas(dirty);
+        } catch (IllegalArgumentException e) {
+        } catch (Surface.OutOfResourcesException e) {
+        }
+        if (c == null) {
+            return;
+        }
+
+        // Top
+        c.clipRect(new Rect(0, 0, dw, mThickness), Region.Op.REPLACE);
+        c.drawColor(Color.RED);
+        // Left
+        c.clipRect(new Rect(0, 0, mThickness, dh), Region.Op.REPLACE);
+        c.drawColor(Color.RED);
+        // Right
+        c.clipRect(new Rect(dw - mThickness, 0, dw, dh), Region.Op.REPLACE);
+        c.drawColor(Color.RED);
+        // Bottom
+        c.clipRect(new Rect(0, dh - mThickness, dw, dh), Region.Op.REPLACE);
+        c.drawColor(Color.RED);
+
+        mSurface.unlockCanvasAndPost(c);
+    }
+
+    // Note: caller responsible for being inside
+    // Surface.openTransaction() / closeTransaction()
+    public void setVisibility(boolean on) {
+        if (mSurfaceControl == null) {
+            return;
+        }
+        drawIfNeeded();
+        if (on) {
+            mSurfaceControl.show();
+        } else {
+            mSurfaceControl.hide();
+        }
+    }
+
+    void positionSurface(int dw, int dh) {
+        if (mLastDW == dw && mLastDH == dh) {
+            return;
+        }
+        mLastDW = dw;
+        mLastDH = dh;
+        mSurfaceControl.setSize(dw, dh);
+        mDrawNeeded = true;
+    }
+
+}
diff --git a/com/android/server/wm/SurfaceControlWithBackground.java b/com/android/server/wm/SurfaceControlWithBackground.java
new file mode 100644
index 0000000..b0eaf14
--- /dev/null
+++ b/com/android/server/wm/SurfaceControlWithBackground.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.view.Surface;
+import android.view.Surface.OutOfResourcesException;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManagerPolicy.NAV_BAR_BOTTOM;
+import static android.view.WindowManagerPolicy.NAV_BAR_LEFT;
+import static android.view.WindowManagerPolicy.NAV_BAR_RIGHT;
+
+/**
+ * SurfaceControl extension that has black background behind navigation bar area for fullscreen
+ * letterboxed apps.
+ */
+class SurfaceControlWithBackground extends SurfaceControl {
+    // SurfaceControl that holds the background.
+    private SurfaceControl mBackgroundControl;
+
+    // Flag that defines whether the background should be shown.
+    private boolean mVisible;
+
+    // Way to communicate with corresponding window.
+    private WindowSurfaceController mWindowSurfaceController;
+
+    // Rect to hold task bounds when computing metrics for background.
+    private Rect mTmpContainerRect = new Rect();
+
+    // Last metrics applied to the main SurfaceControl.
+    private float mLastWidth, mLastHeight;
+    private float mLastDsDx = 1, mLastDsDy = 1;
+    private float mLastX, mLastY;
+
+    // SurfaceFlinger doesn't support crop rectangles where width or height is non-positive.
+    // If we just set an empty crop it will behave as if there is no crop at all.
+    // To fix this we explicitly hide the surface and won't let it to be shown.
+    private boolean mHiddenForCrop = false;
+
+    public SurfaceControlWithBackground(SurfaceControlWithBackground other) {
+        super(other);
+        mBackgroundControl = other.mBackgroundControl;
+        mVisible = other.mVisible;
+        mWindowSurfaceController = other.mWindowSurfaceController;
+    }
+
+    public SurfaceControlWithBackground(SurfaceSession s, String name, int w, int h, int format,
+            int flags, int windowType, int ownerUid,
+            WindowSurfaceController windowSurfaceController) throws OutOfResourcesException {
+        super(s, name, w, h, format, flags, windowType, ownerUid);
+
+        // We should only show background behind app windows that are letterboxed in a task.
+        if ((windowType != TYPE_BASE_APPLICATION && windowType != TYPE_APPLICATION_STARTING)
+                || !windowSurfaceController.mAnimator.mWin.isLetterboxedAppWindow()) {
+            return;
+        }
+        mWindowSurfaceController = windowSurfaceController;
+        mLastWidth = w;
+        mLastHeight = h;
+        mWindowSurfaceController.getContainerRect(mTmpContainerRect);
+        mBackgroundControl = new SurfaceControl(s, "Background for - " + name,
+                mTmpContainerRect.width(), mTmpContainerRect.height(), PixelFormat.OPAQUE,
+                flags | SurfaceControl.FX_SURFACE_DIM);
+    }
+
+    @Override
+    public void setAlpha(float alpha) {
+        super.setAlpha(alpha);
+
+        if (mBackgroundControl == null) {
+            return;
+        }
+        mBackgroundControl.setAlpha(alpha);
+    }
+
+    @Override
+    public void setLayer(int zorder) {
+        super.setLayer(zorder);
+
+        if (mBackgroundControl == null) {
+            return;
+        }
+        // TODO: Use setRelativeLayer(Integer.MIN_VALUE) when it's fixed.
+        mBackgroundControl.setLayer(zorder - 1);
+    }
+
+    @Override
+    public void setPosition(float x, float y) {
+        super.setPosition(x, y);
+
+        if (mBackgroundControl == null) {
+            return;
+        }
+        mLastX = x;
+        mLastY = y;
+        updateBgPosition();
+    }
+
+    private void updateBgPosition() {
+        mWindowSurfaceController.getContainerRect(mTmpContainerRect);
+        final Rect winFrame = mWindowSurfaceController.mAnimator.mWin.mFrame;
+        final float offsetX = (mTmpContainerRect.left - winFrame.left) * mLastDsDx;
+        final float offsetY = (mTmpContainerRect.top - winFrame.top) * mLastDsDy;
+        mBackgroundControl.setPosition(mLastX + offsetX, mLastY + offsetY);
+    }
+
+    @Override
+    public void setSize(int w, int h) {
+        super.setSize(w, h);
+
+        if (mBackgroundControl == null) {
+            return;
+        }
+        mLastWidth = w;
+        mLastHeight = h;
+        mWindowSurfaceController.getContainerRect(mTmpContainerRect);
+        mBackgroundControl.setSize(mTmpContainerRect.width(), mTmpContainerRect.height());
+    }
+
+    @Override
+    public void setWindowCrop(Rect crop) {
+        super.setWindowCrop(crop);
+
+        if (mBackgroundControl == null) {
+            return;
+        }
+        calculateBgCrop(crop);
+        mBackgroundControl.setWindowCrop(mTmpContainerRect);
+        mHiddenForCrop = mTmpContainerRect.isEmpty();
+        updateBackgroundVisibility();
+    }
+
+    @Override
+    public void setFinalCrop(Rect crop) {
+        super.setFinalCrop(crop);
+
+        if (mBackgroundControl == null) {
+            return;
+        }
+        mWindowSurfaceController.getContainerRect(mTmpContainerRect);
+        mBackgroundControl.setFinalCrop(mTmpContainerRect);
+    }
+
+    /**
+     * Compute background crop based on current animation progress for main surface control and
+     * update {@link #mTmpContainerRect} with new values.
+     */
+    private void calculateBgCrop(Rect crop) {
+        // Track overall progress of animation by computing cropped portion of status bar.
+        final Rect contentInsets = mWindowSurfaceController.mAnimator.mWin.mContentInsets;
+        float d = contentInsets.top == 0 ? 0 : (float) crop.top / contentInsets.top;
+        if (d > 1.f) {
+            // We're running expand animation from launcher, won't compute custom bg crop here.
+            mTmpContainerRect.setEmpty();
+            return;
+        }
+
+        // Compute new scaled width and height for background that will depend on current animation
+        // progress. Those consist of current crop rect for the main surface + scaled areas outside
+        // of letterboxed area.
+        // TODO: Because the progress is computed with low precision we're getting smaller values
+        // for background width/height then screen size at the end of the animation. Will round when
+        // the value is smaller then some empiric epsilon. However, this should be fixed by
+        // computing correct frames for letterboxed windows in WindowState.
+        d = d < 0.025f ? 0 : d;
+        mWindowSurfaceController.getContainerRect(mTmpContainerRect);
+        int backgroundWidth = 0, backgroundHeight = 0;
+        // Compute additional offset for the background when app window is positioned not at (0,0).
+        // E.g. landscape with navigation bar on the left.
+        final Rect winFrame = mWindowSurfaceController.mAnimator.mWin.mFrame;
+        int offsetX = (int)((winFrame.left - mTmpContainerRect.left) * mLastDsDx),
+                offsetY = (int) ((winFrame.top - mTmpContainerRect.top) * mLastDsDy);
+
+        // Position and size background.
+        final int bgPosition = mWindowSurfaceController.mAnimator.mService.getNavBarPosition();
+
+        switch (bgPosition) {
+            case NAV_BAR_LEFT:
+                backgroundWidth = (int) ((mTmpContainerRect.width() - mLastWidth) * (1 - d) + 0.5);
+                backgroundHeight = crop.height();
+                offsetX += crop.left - backgroundWidth;
+                offsetY += crop.top;
+                break;
+            case NAV_BAR_RIGHT:
+                backgroundWidth = (int) ((mTmpContainerRect.width() - mLastWidth) * (1 - d) + 0.5);
+                backgroundHeight = crop.height();
+                offsetX += crop.right;
+                offsetY += crop.top;
+                break;
+            case NAV_BAR_BOTTOM:
+                backgroundWidth = crop.width();
+                backgroundHeight = (int) ((mTmpContainerRect.height() - mLastHeight) * (1 - d)
+                        + 0.5);
+                offsetX += crop.left;
+                offsetY += crop.bottom;
+                break;
+        }
+        mTmpContainerRect.set(offsetX, offsetY, offsetX + backgroundWidth,
+                offsetY + backgroundHeight);
+    }
+
+    @Override
+    public void setLayerStack(int layerStack) {
+        super.setLayerStack(layerStack);
+
+        if (mBackgroundControl == null) {
+            return;
+        }
+        mBackgroundControl.setLayerStack(layerStack);
+    }
+
+    @Override
+    public void setOpaque(boolean isOpaque) {
+        super.setOpaque(isOpaque);
+        updateBackgroundVisibility();
+    }
+
+    @Override
+    public void setSecure(boolean isSecure) {
+        super.setSecure(isSecure);
+    }
+
+    @Override
+    public void setMatrix(float dsdx, float dtdx, float dtdy, float dsdy) {
+        super.setMatrix(dsdx, dtdx, dtdy, dsdy);
+
+        if (mBackgroundControl == null) {
+            return;
+        }
+        mBackgroundControl.setMatrix(dsdx, dtdx, dtdy, dsdy);
+        mLastDsDx = dsdx;
+        mLastDsDy = dsdy;
+        updateBgPosition();
+    }
+
+    @Override
+    public void hide() {
+        super.hide();
+        mVisible = false;
+        updateBackgroundVisibility();
+    }
+
+    @Override
+    public void show() {
+        super.show();
+        mVisible = true;
+        updateBackgroundVisibility();
+    }
+
+    @Override
+    public void destroy() {
+        super.destroy();
+
+        if (mBackgroundControl == null) {
+            return;
+        }
+        mBackgroundControl.destroy();
+    }
+
+    @Override
+    public void release() {
+        super.release();
+
+        if (mBackgroundControl == null) {
+            return;
+        }
+        mBackgroundControl.release();
+    }
+
+    @Override
+    public void setTransparentRegionHint(Region region) {
+        super.setTransparentRegionHint(region);
+
+        if (mBackgroundControl == null) {
+            return;
+        }
+        mBackgroundControl.setTransparentRegionHint(region);
+    }
+
+    @Override
+    public void deferTransactionUntil(IBinder handle, long frame) {
+        super.deferTransactionUntil(handle, frame);
+
+        if (mBackgroundControl == null) {
+            return;
+        }
+        mBackgroundControl.deferTransactionUntil(handle, frame);
+    }
+
+    @Override
+    public void deferTransactionUntil(Surface barrier, long frame) {
+        super.deferTransactionUntil(barrier, frame);
+
+        if (mBackgroundControl == null) {
+            return;
+        }
+        mBackgroundControl.deferTransactionUntil(barrier, frame);
+    }
+
+    private void updateBackgroundVisibility() {
+        if (mBackgroundControl == null) {
+            return;
+        }
+        final AppWindowToken appWindowToken = mWindowSurfaceController.mAnimator.mWin.mAppToken;
+        if (!mHiddenForCrop && mVisible && appWindowToken != null && appWindowToken.fillsParent()) {
+            mBackgroundControl.show();
+        } else {
+            mBackgroundControl.hide();
+        }
+    }
+}
diff --git a/com/android/server/wm/Task.java b/com/android/server/wm/Task.java
new file mode 100644
index 0000000..55b6c91
--- /dev/null
+++ b/com/android/server/wm/Task.java
@@ -0,0 +1,769 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.ActivityManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+
+import static com.android.server.EventLogTags.WM_TASK_REMOVED;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.proto.TaskProto.APP_WINDOW_TOKENS;
+import static com.android.server.wm.proto.TaskProto.BOUNDS;
+import static com.android.server.wm.proto.TaskProto.FILLS_PARENT;
+import static com.android.server.wm.proto.TaskProto.ID;
+import static com.android.server.wm.proto.TaskProto.TEMP_INSET_BOUNDS;
+
+import android.app.ActivityManager.StackId;
+import android.app.ActivityManager.TaskDescription;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.util.EventLog;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+import android.view.DisplayInfo;
+import android.view.Surface;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+import java.util.function.Consumer;
+
+class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerUser {
+    static final String TAG = TAG_WITH_CLASS_NAME ? "Task" : TAG_WM;
+    // Return value from {@link setBounds} indicating no change was made to the Task bounds.
+    private static final int BOUNDS_CHANGE_NONE = 0;
+    // Return value from {@link setBounds} indicating the position of the Task bounds changed.
+    private static final int BOUNDS_CHANGE_POSITION = 1;
+    // Return value from {@link setBounds} indicating the size of the Task bounds changed.
+    private static final int BOUNDS_CHANGE_SIZE = 1 << 1;
+
+    // TODO: Track parent marks like this in WindowContainer.
+    TaskStack mStack;
+    final int mTaskId;
+    final int mUserId;
+    private boolean mDeferRemoval = false;
+    final WindowManagerService mService;
+
+    // Content limits relative to the DisplayContent this sits in.
+    private Rect mBounds = new Rect();
+    final Rect mPreparedFrozenBounds = new Rect();
+    final Configuration mPreparedFrozenMergedConfig = new Configuration();
+
+    // Bounds used to calculate the insets.
+    private final Rect mTempInsetBounds = new Rect();
+
+    // Device rotation as of the last time {@link #mBounds} was set.
+    private int mRotation;
+
+    // Whether mBounds is fullscreen
+    private boolean mFillsParent = true;
+
+    // For comparison with DisplayContent bounds.
+    private Rect mTmpRect = new Rect();
+    // For handling display rotations.
+    private Rect mTmpRect2 = new Rect();
+
+    // Resize mode of the task. See {@link ActivityInfo#resizeMode}
+    private int mResizeMode;
+
+    // Whether the task supports picture-in-picture.
+    // See {@link ActivityInfo#FLAG_SUPPORTS_PICTURE_IN_PICTURE}
+    private boolean mSupportsPictureInPicture;
+
+    // Whether the task is currently being drag-resized
+    private boolean mDragResizing;
+    private int mDragResizeMode;
+
+    private TaskDescription mTaskDescription;
+
+    // If set to true, the task will report that it is not in the floating
+    // state regardless of it's stack affiliation. As the floating state drives
+    // production of content insets this can be used to preserve them across
+    // stack moves and we in fact do so when moving from full screen to pinned.
+    private boolean mPreserveNonFloatingState = false;
+
+    Task(int taskId, TaskStack stack, int userId, WindowManagerService service, Rect bounds,
+            int resizeMode, boolean supportsPictureInPicture, TaskDescription taskDescription,
+            TaskWindowContainerController controller) {
+        mTaskId = taskId;
+        mStack = stack;
+        mUserId = userId;
+        mService = service;
+        mResizeMode = resizeMode;
+        mSupportsPictureInPicture = supportsPictureInPicture;
+        setController(controller);
+        setBounds(bounds, getOverrideConfiguration());
+        mTaskDescription = taskDescription;
+
+        // Tasks have no set orientation value (including SCREEN_ORIENTATION_UNSPECIFIED).
+        setOrientation(SCREEN_ORIENTATION_UNSET);
+    }
+
+    DisplayContent getDisplayContent() {
+        return mStack != null ? mStack.getDisplayContent() : null;
+    }
+
+    private int getAdjustedAddPosition(int suggestedPosition) {
+        final int size = mChildren.size();
+        if (suggestedPosition >= size) {
+            return Math.min(size, suggestedPosition);
+        }
+
+        for (int pos = 0; pos < size && pos < suggestedPosition; ++pos) {
+            // TODO: Confirm that this is the behavior we want long term.
+            if (mChildren.get(pos).removed) {
+                // suggestedPosition assumes removed tokens are actually gone.
+                ++suggestedPosition;
+            }
+        }
+        return Math.min(size, suggestedPosition);
+    }
+
+    @Override
+    void addChild(AppWindowToken wtoken, int position) {
+        position = getAdjustedAddPosition(position);
+        super.addChild(wtoken, position);
+        mDeferRemoval = false;
+    }
+
+    @Override
+    void positionChildAt(int position, AppWindowToken child, boolean includingParents) {
+        position = getAdjustedAddPosition(position);
+        super.positionChildAt(position, child, includingParents);
+        mDeferRemoval = false;
+    }
+
+    private boolean hasWindowsAlive() {
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            if (mChildren.get(i).hasWindowsAlive()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @VisibleForTesting
+    boolean shouldDeferRemoval() {
+        // TODO: This should probably return false if mChildren.isEmpty() regardless if the stack
+        // is animating...
+        return hasWindowsAlive() && mStack.isAnimating();
+    }
+
+    @Override
+    void removeIfPossible() {
+        if (shouldDeferRemoval()) {
+            if (DEBUG_STACK) Slog.i(TAG, "removeTask: deferring removing taskId=" + mTaskId);
+            mDeferRemoval = true;
+            return;
+        }
+        removeImmediately();
+    }
+
+    @Override
+    void removeImmediately() {
+        if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing taskId=" + mTaskId);
+        EventLog.writeEvent(WM_TASK_REMOVED, mTaskId, "removeTask");
+        mDeferRemoval = false;
+
+        // Make sure to remove dim layer user first before removing task its from parent.
+        DisplayContent content = getDisplayContent();
+        if (content != null) {
+            content.mDimLayerController.removeDimLayerUser(this);
+        }
+
+        super.removeImmediately();
+    }
+
+    void reparent(TaskStack stack, int position, boolean moveParents) {
+        if (stack == mStack) {
+            throw new IllegalArgumentException(
+                    "task=" + this + " already child of stack=" + mStack);
+        }
+        if (DEBUG_STACK) Slog.i(TAG, "reParentTask: removing taskId=" + mTaskId
+                + " from stack=" + mStack);
+        EventLog.writeEvent(WM_TASK_REMOVED, mTaskId, "reParentTask");
+        final DisplayContent prevDisplayContent = getDisplayContent();
+
+        // If we are moving from the fullscreen stack to the pinned stack
+        // then we want to preserve our insets so that there will not
+        // be a jump in the area covered by system decorations. We rely
+        // on the pinned animation to later unset this value.
+        if (stack.mStackId == PINNED_STACK_ID) {
+            mPreserveNonFloatingState = true;
+        } else {
+            mPreserveNonFloatingState = false;
+        }
+
+        getParent().removeChild(this);
+        stack.addTask(this, position, showForAllUsers(), moveParents);
+
+        // Relayout display(s).
+        final DisplayContent displayContent = stack.getDisplayContent();
+        displayContent.setLayoutNeeded();
+        if (prevDisplayContent != displayContent) {
+            onDisplayChanged(displayContent);
+            prevDisplayContent.setLayoutNeeded();
+        }
+    }
+
+    /** @see com.android.server.am.ActivityManagerService#positionTaskInStack(int, int, int). */
+    void positionAt(int position, Rect bounds, Configuration overrideConfig) {
+        mStack.positionChildAt(position, this, false /* includingParents */);
+        resizeLocked(bounds, overrideConfig, false /* force */);
+    }
+
+    @Override
+    void onParentSet() {
+        // Update task bounds if needed.
+        updateDisplayInfo(getDisplayContent());
+
+        if (getWindowConfiguration().windowsAreScaleable()) {
+            // We force windows out of SCALING_MODE_FREEZE so that we can continue to animate them
+            // while a resize is pending.
+            forceWindowsScaleable(true /* force */);
+        } else {
+            forceWindowsScaleable(false /* force */);
+        }
+    }
+
+    @Override
+    void removeChild(AppWindowToken token) {
+        if (!mChildren.contains(token)) {
+            Slog.e(TAG, "removeChild: token=" + this + " not found.");
+            return;
+        }
+
+        super.removeChild(token);
+
+        if (mChildren.isEmpty()) {
+            EventLog.writeEvent(WM_TASK_REMOVED, mTaskId, "removeAppToken: last token");
+            if (mDeferRemoval) {
+                removeIfPossible();
+            }
+        }
+    }
+
+    void setSendingToBottom(boolean toBottom) {
+        for (int appTokenNdx = 0; appTokenNdx < mChildren.size(); appTokenNdx++) {
+            mChildren.get(appTokenNdx).sendingToBottom = toBottom;
+        }
+    }
+
+    /** Set the task bounds. Passing in null sets the bounds to fullscreen. */
+    // TODO: There is probably not a need to pass in overrideConfig anymore since any change to it
+    // will be automatically propagated from the AM. Also, mBound is going to be in
+    // WindowConfiguration long term.
+    private int setBounds(Rect bounds, Configuration overrideConfig) {
+        if (overrideConfig == null) {
+            overrideConfig = Configuration.EMPTY;
+        }
+        if (bounds == null && !Configuration.EMPTY.equals(overrideConfig)) {
+            throw new IllegalArgumentException("null bounds but non empty configuration: "
+                    + overrideConfig);
+        }
+        if (bounds != null && Configuration.EMPTY.equals(overrideConfig)) {
+            throw new IllegalArgumentException("non null bounds, but empty configuration");
+        }
+        boolean oldFullscreen = mFillsParent;
+        int rotation = Surface.ROTATION_0;
+        final DisplayContent displayContent = mStack.getDisplayContent();
+        if (displayContent != null) {
+            displayContent.getLogicalDisplayRect(mTmpRect);
+            rotation = displayContent.getDisplayInfo().rotation;
+            mFillsParent = bounds == null;
+            if (mFillsParent) {
+                bounds = mTmpRect;
+            }
+        }
+
+        if (bounds == null) {
+            // Can't set to fullscreen if we don't have a display to get bounds from...
+            return BOUNDS_CHANGE_NONE;
+        }
+        if (mBounds.equals(bounds) && oldFullscreen == mFillsParent && mRotation == rotation) {
+            return BOUNDS_CHANGE_NONE;
+        }
+
+        int boundsChange = BOUNDS_CHANGE_NONE;
+        if (mBounds.left != bounds.left || mBounds.top != bounds.top) {
+            boundsChange |= BOUNDS_CHANGE_POSITION;
+        }
+        if (mBounds.width() != bounds.width() || mBounds.height() != bounds.height()) {
+            boundsChange |= BOUNDS_CHANGE_SIZE;
+        }
+
+        mBounds.set(bounds);
+
+        mRotation = rotation;
+        if (displayContent != null) {
+            displayContent.mDimLayerController.updateDimLayer(this);
+        }
+        onOverrideConfigurationChanged(mFillsParent ? Configuration.EMPTY : overrideConfig);
+        return boundsChange;
+    }
+
+    /**
+     * Sets the bounds used to calculate the insets. See
+     * {@link android.app.IActivityManager#resizeDockedStack} why this is needed.
+     */
+    void setTempInsetBounds(Rect tempInsetBounds) {
+        if (tempInsetBounds != null) {
+            mTempInsetBounds.set(tempInsetBounds);
+        } else {
+            mTempInsetBounds.setEmpty();
+        }
+    }
+
+    /**
+     * Gets the bounds used to calculate the insets. See
+     * {@link android.app.IActivityManager#resizeDockedStack} why this is needed.
+     */
+    void getTempInsetBounds(Rect out) {
+        out.set(mTempInsetBounds);
+    }
+
+    void setResizeable(int resizeMode) {
+        mResizeMode = resizeMode;
+    }
+
+    boolean isResizeable() {
+        return ActivityInfo.isResizeableMode(mResizeMode) || mSupportsPictureInPicture
+                || mService.mForceResizableTasks;
+    }
+
+    /**
+     * Tests if the orientation should be preserved upon user interactive resizig operations.
+
+     * @return true if orientation should not get changed upon resizing operation.
+     */
+    boolean preserveOrientationOnResize() {
+        return mResizeMode == RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY
+                || mResizeMode == RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY
+                || mResizeMode == RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
+    }
+
+    boolean cropWindowsToStackBounds() {
+        return isResizeable();
+    }
+
+    boolean resizeLocked(Rect bounds, Configuration overrideConfig, boolean forced) {
+        int boundsChanged = setBounds(bounds, overrideConfig);
+        if (forced) {
+            boundsChanged |= BOUNDS_CHANGE_SIZE;
+        }
+        if (boundsChanged == BOUNDS_CHANGE_NONE) {
+            return false;
+        }
+        if ((boundsChanged & BOUNDS_CHANGE_SIZE) == BOUNDS_CHANGE_SIZE) {
+            onResize();
+        } else {
+            onMovedByResize();
+        }
+        return true;
+    }
+
+    /**
+     * Prepares the task bounds to be frozen with the current size. See
+     * {@link AppWindowToken#freezeBounds}.
+     */
+    void prepareFreezingBounds() {
+        mPreparedFrozenBounds.set(mBounds);
+        mPreparedFrozenMergedConfig.setTo(getConfiguration());
+    }
+
+    /**
+     * Align the task to the adjusted bounds.
+     *
+     * @param adjustedBounds Adjusted bounds to which the task should be aligned.
+     * @param tempInsetBounds Insets bounds for the task.
+     * @param alignBottom True if the task's bottom should be aligned to the adjusted
+     *                    bounds's bottom; false if the task's top should be aligned
+     *                    the adjusted bounds's top.
+     */
+    void alignToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds, boolean alignBottom) {
+        if (!isResizeable() || Configuration.EMPTY.equals(getOverrideConfiguration())) {
+            return;
+        }
+
+        getBounds(mTmpRect2);
+        if (alignBottom) {
+            int offsetY = adjustedBounds.bottom - mTmpRect2.bottom;
+            mTmpRect2.offset(0, offsetY);
+        } else {
+            mTmpRect2.offsetTo(adjustedBounds.left, adjustedBounds.top);
+        }
+        setTempInsetBounds(tempInsetBounds);
+        resizeLocked(mTmpRect2, getOverrideConfiguration(), false /* forced */);
+    }
+
+    /** Return true if the current bound can get outputted to the rest of the system as-is. */
+    private boolean useCurrentBounds() {
+        final DisplayContent displayContent = getDisplayContent();
+        return mFillsParent
+                || !inSplitScreenSecondaryWindowingMode()
+                || displayContent == null
+                || displayContent.getDockedStackIgnoringVisibility() != null;
+    }
+
+    /** Original bounds of the task if applicable, otherwise fullscreen rect. */
+    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.
+            out.set(mBounds);
+            return;
+        }
+
+        // The bounds has been adjusted to accommodate for a docked stack, but the docked stack is
+        // not currently visible. Go ahead a represent it as fullscreen to the rest of the system.
+        mStack.getDisplayContent().getLogicalDisplayRect(out);
+    }
+
+    /**
+     * Calculate the maximum visible area of this task. If the task has only one app,
+     * the result will be visible frame of that app. If the task has more than one apps,
+     * we search from top down if the next app got different visible area.
+     *
+     * This effort is to handle the case where some task (eg. GMail composer) might pop up
+     * a dialog that's different in size from the activity below, in which case we should
+     * be dimming the entire task area behind the dialog.
+     *
+     * @param out Rect containing the max visible bounds.
+     * @return true if the task has some visible app windows; false otherwise.
+     */
+    boolean getMaxVisibleBounds(Rect out) {
+        boolean foundTop = false;
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final AppWindowToken token = mChildren.get(i);
+            // skip hidden (or about to hide) apps
+            if (token.mIsExiting || token.isClientHidden() || token.hiddenRequested) {
+                continue;
+            }
+            final WindowState win = token.findMainWindow();
+            if (win == null) {
+                continue;
+            }
+            if (!foundTop) {
+                out.set(win.mVisibleFrame);
+                foundTop = true;
+                continue;
+            }
+            if (win.mVisibleFrame.left < out.left) {
+                out.left = win.mVisibleFrame.left;
+            }
+            if (win.mVisibleFrame.top < out.top) {
+                out.top = win.mVisibleFrame.top;
+            }
+            if (win.mVisibleFrame.right > out.right) {
+                out.right = win.mVisibleFrame.right;
+            }
+            if (win.mVisibleFrame.bottom > out.bottom) {
+                out.bottom = win.mVisibleFrame.bottom;
+            }
+        }
+        return foundTop;
+    }
+
+    /** Bounds of the task to be used for dimming, as well as touch related tests. */
+    @Override
+    public void getDimBounds(Rect out) {
+        final DisplayContent displayContent = mStack.getDisplayContent();
+        // It doesn't matter if we in particular are part of the resize, since we couldn't have
+        // a DimLayer anyway if we weren't visible.
+        final boolean dockedResizing = displayContent != null
+                && displayContent.mDividerControllerLocked.isResizing();
+        if (useCurrentBounds()) {
+            if (inFreeformWorkspace() && getMaxVisibleBounds(out)) {
+                return;
+            }
+
+            if (!mFillsParent) {
+                // When minimizing the docked stack when going home, we don't adjust the task bounds
+                // so we need to intersect the task bounds with the stack bounds here.
+                //
+                // If we are Docked Resizing with snap points, the task bounds could be smaller than the stack
+                // bounds and so we don't even want to use them. Even if the app should not be resized the Dim
+                // should keep up with the divider.
+                if (dockedResizing) {
+                    mStack.getBounds(out);
+                } else {
+                    mStack.getBounds(mTmpRect);
+                    mTmpRect.intersect(mBounds);
+                }
+                out.set(mTmpRect);
+            } else {
+                out.set(mBounds);
+            }
+            return;
+        }
+
+        // The bounds has been adjusted to accommodate for a docked stack, but the docked stack is
+        // not currently visible. Go ahead a represent it as fullscreen to the rest of the system.
+        if (displayContent != null) {
+            displayContent.getLogicalDisplayRect(out);
+        }
+    }
+
+    void setDragResizing(boolean dragResizing, int dragResizeMode) {
+        if (mDragResizing != dragResizing) {
+            if (!DragResizeMode.isModeAllowedForStack(mStack.mStackId, dragResizeMode)) {
+                throw new IllegalArgumentException("Drag resize mode not allow for stack stackId="
+                        + mStack.mStackId + " dragResizeMode=" + dragResizeMode);
+            }
+            mDragResizing = dragResizing;
+            mDragResizeMode = dragResizeMode;
+            resetDragResizingChangeReported();
+        }
+    }
+
+    boolean isDragResizing() {
+        return mDragResizing;
+    }
+
+    int getDragResizeMode() {
+        return mDragResizeMode;
+    }
+
+    void updateDisplayInfo(final DisplayContent displayContent) {
+        if (displayContent == null) {
+            return;
+        }
+        if (mFillsParent) {
+            setBounds(null, Configuration.EMPTY);
+            return;
+        }
+        final int newRotation = displayContent.getDisplayInfo().rotation;
+        if (mRotation == newRotation) {
+            return;
+        }
+
+        // Device rotation changed.
+        // - We don't want the task to move around on the screen when this happens, so update the
+        //   task bounds so it stays in the same place.
+        // - Rotate the bounds and notify activity manager if the task can be resized independently
+        //   from its stack. The stack will take care of task rotation for the other case.
+        mTmpRect2.set(mBounds);
+
+        if (!getWindowConfiguration().canResizeTask()) {
+            setBounds(mTmpRect2, getOverrideConfiguration());
+            return;
+        }
+
+        displayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
+        if (setBounds(mTmpRect2, getOverrideConfiguration()) != BOUNDS_CHANGE_NONE) {
+            final TaskWindowContainerController controller = getController();
+            if (controller != null) {
+                controller.requestResize(mBounds, RESIZE_MODE_SYSTEM_SCREEN_ROTATION);
+            }
+        }
+    }
+
+    /** Cancels any running app transitions associated with the task. */
+    void cancelTaskWindowTransition() {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            mChildren.get(i).mAppAnimator.clearAnimation();
+        }
+    }
+
+    /** Cancels any running thumbnail transitions associated with the task. */
+    void cancelTaskThumbnailTransition() {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            mChildren.get(i).mAppAnimator.clearThumbnail();
+        }
+    }
+
+    boolean showForAllUsers() {
+        final int tokensCount = mChildren.size();
+        return (tokensCount != 0) && mChildren.get(tokensCount - 1).mShowForAllUsers;
+    }
+
+    boolean inFreeformWorkspace() {
+        return mStack != null && mStack.mStackId == FREEFORM_WORKSPACE_STACK_ID;
+    }
+
+    boolean inPinnedWorkspace() {
+        return mStack != null && mStack.mStackId == PINNED_STACK_ID;
+    }
+
+    /**
+     * When we are in a floating stack (Freeform, Pinned, ...) we calculate
+     * insets differently. However if we are animating to the fullscreen stack
+     * we need to begin calculating insets as if we were fullscreen, otherwise
+     * we will have a jump at the end.
+     */
+    boolean isFloating() {
+        return getWindowConfiguration().tasksAreFloating()
+                && !mStack.isAnimatingBoundsToFullscreen() && !mPreserveNonFloatingState;
+    }
+
+    WindowState getTopVisibleAppMainWindow() {
+        final AppWindowToken token = getTopVisibleAppToken();
+        return token != null ? token.findMainWindow() : null;
+    }
+
+    AppWindowToken getTopFullscreenAppToken() {
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final AppWindowToken token = mChildren.get(i);
+            final WindowState win = token.findMainWindow();
+            if (win != null && win.mAttrs.isFullscreen()) {
+                return token;
+            }
+        }
+        return null;
+    }
+
+    AppWindowToken getTopVisibleAppToken() {
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final AppWindowToken token = mChildren.get(i);
+            // skip hidden (or about to hide) apps
+            if (!token.mIsExiting && !token.isClientHidden() && !token.hiddenRequested) {
+                return token;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public boolean dimFullscreen() {
+        return isFullscreen();
+    }
+
+    @Override
+    public int getLayerForDim(WindowStateAnimator animator, int layerOffset, int defaultLayer) {
+        // If the dim layer is for a starting window, move the dim layer back in the z-order behind
+        // the lowest activity window to ensure it does not occlude the main window if it is
+        // translucent
+        final AppWindowToken appToken = animator.mWin.mAppToken;
+        if (animator.mAttrType == TYPE_APPLICATION_STARTING && hasChild(appToken) ) {
+            return Math.min(defaultLayer, appToken.getLowestAnimLayer() - layerOffset);
+        }
+        return defaultLayer;
+    }
+
+    boolean isFullscreen() {
+        if (useCurrentBounds()) {
+            return mFillsParent;
+        }
+        // The bounds has been adjusted to accommodate for a docked stack, but the docked stack
+        // is not currently visible. Go ahead a represent it as fullscreen to the rest of the
+        // system.
+        return true;
+    }
+
+    @Override
+    public DisplayInfo getDisplayInfo() {
+        return getDisplayContent().getDisplayInfo();
+    }
+
+    @Override
+    public boolean isAttachedToDisplay() {
+        return getDisplayContent() != null;
+    }
+
+    void forceWindowsScaleable(boolean force) {
+        mService.openSurfaceTransaction();
+        try {
+            for (int i = mChildren.size() - 1; i >= 0; i--) {
+                mChildren.get(i).forceWindowsScaleableInTransaction(force);
+            }
+        } finally {
+            mService.closeSurfaceTransaction();
+        }
+    }
+
+    void setTaskDescription(TaskDescription taskDescription) {
+        mTaskDescription = taskDescription;
+    }
+
+    TaskDescription getTaskDescription() {
+        return mTaskDescription;
+    }
+
+    @Override
+    boolean fillsParent() {
+        return mFillsParent || !getWindowConfiguration().canResizeTask();
+    }
+
+    @Override
+    TaskWindowContainerController getController() {
+        return (TaskWindowContainerController) super.getController();
+    }
+
+    @Override
+    void forAllTasks(Consumer<Task> callback) {
+        callback.accept(this);
+    }
+
+    @Override
+    public String toString() {
+        return "{taskId=" + mTaskId + " appTokens=" + mChildren + " mdr=" + mDeferRemoval + "}";
+    }
+
+    String getName() {
+        return toShortString();
+    }
+
+    void clearPreserveNonFloatingState() {
+        mPreserveNonFloatingState = false;
+    }
+
+    @Override
+    public String toShortString() {
+        return "Task=" + mTaskId;
+    }
+
+    void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(ID, mTaskId);
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final AppWindowToken appWindowToken = mChildren.get(i);
+            appWindowToken.writeToProto(proto, APP_WINDOW_TOKENS);
+        }
+        proto.write(FILLS_PARENT, mFillsParent);
+        mBounds.writeToProto(proto, BOUNDS);
+        mTempInsetBounds.writeToProto(proto, TEMP_INSET_BOUNDS);
+        proto.end(token);
+    }
+
+    public void dump(String prefix, PrintWriter pw) {
+        final String doublePrefix = prefix + "  ";
+
+        pw.println(prefix + "taskId=" + mTaskId);
+        pw.println(doublePrefix + "mFillsParent=" + mFillsParent);
+        pw.println(doublePrefix + "mBounds=" + mBounds.toShortString());
+        pw.println(doublePrefix + "mdr=" + mDeferRemoval);
+        pw.println(doublePrefix + "appTokens=" + mChildren);
+        pw.println(doublePrefix + "mTempInsetBounds=" + mTempInsetBounds.toShortString());
+
+        final String triplePrefix = doublePrefix + "  ";
+
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final AppWindowToken wtoken = mChildren.get(i);
+            pw.println(triplePrefix + "Activity #" + i + " " + wtoken);
+            wtoken.dump(pw, triplePrefix);
+        }
+    }
+}
diff --git a/com/android/server/wm/TaskPositioner.java b/com/android/server/wm/TaskPositioner.java
new file mode 100644
index 0000000..c58212c
--- /dev/null
+++ b/com/android/server/wm/TaskPositioner.java
@@ -0,0 +1,710 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.RESIZE_MODE_USER;
+import static android.app.ActivityManager.RESIZE_MODE_USER_FORCED;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.dipToPixel;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
+import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP;
+import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP;
+
+import android.annotation.IntDef;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Looper;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.Trace;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.view.BatchedInputEventReceiver;
+import android.view.Choreographer;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.MotionEvent;
+import android.view.WindowManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.input.InputApplicationHandle;
+import com.android.server.input.InputWindowHandle;
+import com.android.server.wm.WindowManagerService.H;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+class TaskPositioner implements DimLayer.DimLayerUser {
+    private static final boolean DEBUG_ORIENTATION_VIOLATIONS = false;
+    private static final String TAG_LOCAL = "TaskPositioner";
+    private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM;
+
+    // The margin the pointer position has to be within the side of the screen to be
+    // considered at the side of the screen.
+    static final int SIDE_MARGIN_DIP = 100;
+
+    @IntDef(flag = true,
+            value = {
+                    CTRL_NONE,
+                    CTRL_LEFT,
+                    CTRL_RIGHT,
+                    CTRL_TOP,
+                    CTRL_BOTTOM
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface CtrlType {}
+
+    private static final int CTRL_NONE   = 0x0;
+    private static final int CTRL_LEFT   = 0x1;
+    private static final int CTRL_RIGHT  = 0x2;
+    private static final int CTRL_TOP    = 0x4;
+    private static final int CTRL_BOTTOM = 0x8;
+
+    public static final float RESIZING_HINT_ALPHA = 0.5f;
+
+    public static final int RESIZING_HINT_DURATION_MS = 0;
+
+    // The minimal aspect ratio which needs to be met to count as landscape (or 1/.. for portrait).
+    // Note: We do not use the 1.33 from the CDD here since the user is allowed to use what ever
+    // aspect he desires.
+    @VisibleForTesting
+    static final float MIN_ASPECT = 1.2f;
+
+    private final WindowManagerService mService;
+    private WindowPositionerEventReceiver mInputEventReceiver;
+    private Display mDisplay;
+    private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+    private DimLayer mDimLayer;
+    @CtrlType
+    private int mCurrentDimSide;
+    private Rect mTmpRect = new Rect();
+    private int mSideMargin;
+    private int mMinVisibleWidth;
+    private int mMinVisibleHeight;
+
+    private Task mTask;
+    private boolean mResizing;
+    private boolean mPreserveOrientation;
+    private boolean mStartOrientationWasLandscape;
+    private final Rect mWindowOriginalBounds = new Rect();
+    private final Rect mWindowDragBounds = new Rect();
+    private final Point mMaxVisibleSize = new Point();
+    private float mStartDragX;
+    private float mStartDragY;
+    @CtrlType
+    private int mCtrlType = CTRL_NONE;
+    private boolean mDragEnded = false;
+
+    InputChannel mServerChannel;
+    InputChannel mClientChannel;
+    InputApplicationHandle mDragApplicationHandle;
+    InputWindowHandle mDragWindowHandle;
+
+    private final class WindowPositionerEventReceiver extends BatchedInputEventReceiver {
+        public WindowPositionerEventReceiver(
+                InputChannel inputChannel, Looper looper, Choreographer choreographer) {
+            super(inputChannel, looper, choreographer);
+        }
+
+        @Override
+        public void onInputEvent(InputEvent event, int displayId) {
+            if (!(event instanceof MotionEvent)
+                    || (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) {
+                return;
+            }
+            final MotionEvent motionEvent = (MotionEvent) event;
+            boolean handled = false;
+
+            try {
+                if (mDragEnded) {
+                    // The drag has ended but the clean-up message has not been processed by
+                    // window manager. Drop events that occur after this until window manager
+                    // has a chance to clean-up the input handle.
+                    handled = true;
+                    return;
+                }
+
+                final float newX = motionEvent.getRawX();
+                final float newY = motionEvent.getRawY();
+
+                switch (motionEvent.getAction()) {
+                    case MotionEvent.ACTION_DOWN: {
+                        if (DEBUG_TASK_POSITIONING) {
+                            Slog.w(TAG, "ACTION_DOWN @ {" + newX + ", " + newY + "}");
+                        }
+                    } break;
+
+                    case MotionEvent.ACTION_MOVE: {
+                        if (DEBUG_TASK_POSITIONING){
+                            Slog.w(TAG, "ACTION_MOVE @ {" + newX + ", " + newY + "}");
+                        }
+                        synchronized (mService.mWindowMap) {
+                            mDragEnded = notifyMoveLocked(newX, newY);
+                            mTask.getDimBounds(mTmpRect);
+                        }
+                        if (!mTmpRect.equals(mWindowDragBounds)) {
+                            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
+                                    "wm.TaskPositioner.resizeTask");
+                            try {
+                                mService.mActivityManager.resizeTask(
+                                        mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER);
+                            } catch (RemoteException e) {
+                            }
+                            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+                        }
+                    } break;
+
+                    case MotionEvent.ACTION_UP: {
+                        if (DEBUG_TASK_POSITIONING) {
+                            Slog.w(TAG, "ACTION_UP @ {" + newX + ", " + newY + "}");
+                        }
+                        mDragEnded = true;
+                    } break;
+
+                    case MotionEvent.ACTION_CANCEL: {
+                        if (DEBUG_TASK_POSITIONING) {
+                            Slog.w(TAG, "ACTION_CANCEL @ {" + newX + ", " + newY + "}");
+                        }
+                        mDragEnded = true;
+                    } break;
+                }
+
+                if (mDragEnded) {
+                    final boolean wasResizing = mResizing;
+                    synchronized (mService.mWindowMap) {
+                        endDragLocked();
+                        mTask.getDimBounds(mTmpRect);
+                    }
+                    try {
+                        if (wasResizing && !mTmpRect.equals(mWindowDragBounds)) {
+                            // We were using fullscreen surface during resizing. Request
+                            // resizeTask() one last time to restore surface to window size.
+                            mService.mActivityManager.resizeTask(
+                                    mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER_FORCED);
+                        }
+
+                        if (mCurrentDimSide != CTRL_NONE) {
+                            final int createMode = mCurrentDimSide == CTRL_LEFT
+                                    ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
+                                    : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
+                            mService.mActivityManager.moveTaskToDockedStack(
+                                    mTask.mTaskId, createMode, true /*toTop*/, true /* animate */,
+                                    null /* initialBounds */);
+                        }
+                    } catch(RemoteException e) {}
+
+                    // Post back to WM to handle clean-ups. We still need the input
+                    // event handler for the last finishInputEvent()!
+                    mService.mH.sendEmptyMessage(H.FINISH_TASK_POSITIONING);
+                }
+                handled = true;
+            } catch (Exception e) {
+                Slog.e(TAG, "Exception caught by drag handleMotion", e);
+            } finally {
+                finishInputEvent(event, handled);
+            }
+        }
+    }
+
+    TaskPositioner(WindowManagerService service) {
+        mService = service;
+    }
+
+    @VisibleForTesting
+    Rect getWindowDragBounds() {
+        return mWindowDragBounds;
+    }
+
+    /**
+     * @param display The Display that the window being dragged is on.
+     */
+    void register(Display display) {
+        if (DEBUG_TASK_POSITIONING) {
+            Slog.d(TAG, "Registering task positioner");
+        }
+
+        if (mClientChannel != null) {
+            Slog.e(TAG, "Task positioner already registered");
+            return;
+        }
+
+        mDisplay = display;
+        mDisplay.getMetrics(mDisplayMetrics);
+        final InputChannel[] channels = InputChannel.openInputChannelPair(TAG);
+        mServerChannel = channels[0];
+        mClientChannel = channels[1];
+        mService.mInputManager.registerInputChannel(mServerChannel, null);
+
+        mInputEventReceiver = new WindowPositionerEventReceiver(
+                mClientChannel, mService.mAnimationHandler.getLooper(),
+                mService.mAnimator.getChoreographer());
+
+        mDragApplicationHandle = new InputApplicationHandle(null);
+        mDragApplicationHandle.name = TAG;
+        mDragApplicationHandle.dispatchingTimeoutNanos =
+                WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+
+        mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null, null,
+                mDisplay.getDisplayId());
+        mDragWindowHandle.name = TAG;
+        mDragWindowHandle.inputChannel = mServerChannel;
+        mDragWindowHandle.layer = mService.getDragLayerLocked();
+        mDragWindowHandle.layoutParamsFlags = 0;
+        mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
+        mDragWindowHandle.dispatchingTimeoutNanos =
+                WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+        mDragWindowHandle.visible = true;
+        mDragWindowHandle.canReceiveKeys = false;
+        mDragWindowHandle.hasFocus = true;
+        mDragWindowHandle.hasWallpaper = false;
+        mDragWindowHandle.paused = false;
+        mDragWindowHandle.ownerPid = Process.myPid();
+        mDragWindowHandle.ownerUid = Process.myUid();
+        mDragWindowHandle.inputFeatures = 0;
+        mDragWindowHandle.scaleFactor = 1.0f;
+
+        // The drag window cannot receive new touches.
+        mDragWindowHandle.touchableRegion.setEmpty();
+
+        // The drag window covers the entire display
+        mDragWindowHandle.frameLeft = 0;
+        mDragWindowHandle.frameTop = 0;
+        final Point p = new Point();
+        mDisplay.getRealSize(p);
+        mDragWindowHandle.frameRight = p.x;
+        mDragWindowHandle.frameBottom = p.y;
+
+        // Pause rotations before a drag.
+        if (DEBUG_ORIENTATION) {
+            Slog.d(TAG, "Pausing rotation during re-position");
+        }
+        mService.pauseRotationLocked();
+
+        mDimLayer = new DimLayer(mService, this, mDisplay.getDisplayId(), TAG_LOCAL);
+        mSideMargin = dipToPixel(SIDE_MARGIN_DIP, mDisplayMetrics);
+        mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, mDisplayMetrics);
+        mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, mDisplayMetrics);
+        mDisplay.getRealSize(mMaxVisibleSize);
+
+        mDragEnded = false;
+    }
+
+    void unregister() {
+        if (DEBUG_TASK_POSITIONING) {
+            Slog.d(TAG, "Unregistering task positioner");
+        }
+
+        if (mClientChannel == null) {
+            Slog.e(TAG, "Task positioner not registered");
+            return;
+        }
+
+        mService.mInputManager.unregisterInputChannel(mServerChannel);
+
+        mInputEventReceiver.dispose();
+        mInputEventReceiver = null;
+        mClientChannel.dispose();
+        mServerChannel.dispose();
+        mClientChannel = null;
+        mServerChannel = null;
+
+        mDragWindowHandle = null;
+        mDragApplicationHandle = null;
+        mDisplay = null;
+
+        if (mDimLayer != null) {
+            mDimLayer.destroySurface();
+            mDimLayer = null;
+        }
+        mCurrentDimSide = CTRL_NONE;
+        mDragEnded = true;
+
+        // Resume rotations after a drag.
+        if (DEBUG_ORIENTATION) {
+            Slog.d(TAG, "Resuming rotation after re-position");
+        }
+        mService.resumeRotationLocked();
+    }
+
+    void startDrag(WindowState win, boolean resize, boolean preserveOrientation, float startX,
+                   float startY) {
+        if (DEBUG_TASK_POSITIONING) {
+            Slog.d(TAG, "startDrag: win=" + win + ", resize=" + resize
+                    + ", preserveOrientation=" + preserveOrientation + ", {" + startX + ", "
+                    + startY + "}");
+        }
+        mTask = win.getTask();
+        // Use the dim bounds, not the original task bounds. The cursor
+        // movement should be calculated relative to the visible bounds.
+        // Also, use the dim bounds of the task which accounts for
+        // multiple app windows. Don't use any bounds from win itself as it
+        // may not be the same size as the task.
+        mTask.getDimBounds(mTmpRect);
+        startDrag(resize, preserveOrientation, startX, startY, mTmpRect);
+    }
+
+    @VisibleForTesting
+    void startDrag(boolean resize, boolean preserveOrientation,
+                   float startX, float startY, Rect startBounds) {
+        mCtrlType = CTRL_NONE;
+        mStartDragX = startX;
+        mStartDragY = startY;
+        mPreserveOrientation = preserveOrientation;
+
+        if (resize) {
+            if (startX < startBounds.left) {
+                mCtrlType |= CTRL_LEFT;
+            }
+            if (startX > startBounds.right) {
+                mCtrlType |= CTRL_RIGHT;
+            }
+            if (startY < startBounds.top) {
+                mCtrlType |= CTRL_TOP;
+            }
+            if (startY > startBounds.bottom) {
+                mCtrlType |= CTRL_BOTTOM;
+            }
+            mResizing = mCtrlType != CTRL_NONE;
+        }
+
+        // In case of !isDockedInEffect we are using the union of all task bounds. These might be
+        // made up out of multiple windows which are only partially overlapping. When that happens,
+        // the orientation from the window of interest to the entire stack might diverge. However
+        // for now we treat them as the same.
+        mStartOrientationWasLandscape = startBounds.width() >= startBounds.height();
+        mWindowOriginalBounds.set(startBounds);
+
+        // Make sure we always have valid drag bounds even if the drag ends before any move events
+        // have been handled.
+        mWindowDragBounds.set(startBounds);
+    }
+
+    private void endDragLocked() {
+        mResizing = false;
+        mTask.setDragResizing(false, DRAG_RESIZE_MODE_FREEFORM);
+    }
+
+    /** Returns true if the move operation should be ended. */
+    private boolean notifyMoveLocked(float x, float y) {
+        if (DEBUG_TASK_POSITIONING) {
+            Slog.d(TAG, "notifyMoveLocked: {" + x + "," + y + "}");
+        }
+
+        if (mCtrlType != CTRL_NONE) {
+            resizeDrag(x, y);
+            mTask.setDragResizing(true, DRAG_RESIZE_MODE_FREEFORM);
+            return false;
+        }
+
+        // This is a moving or scrolling operation.
+        mTask.mStack.getDimBounds(mTmpRect);
+
+        int nX = (int) x;
+        int nY = (int) y;
+        if (!mTmpRect.contains(nX, nY)) {
+            // For a moving operation we allow the pointer to go out of the stack bounds, but
+            // use the clamped pointer position for the drag bounds computation.
+            nX = Math.min(Math.max(nX, mTmpRect.left), mTmpRect.right);
+            nY = Math.min(Math.max(nY, mTmpRect.top), mTmpRect.bottom);
+        }
+
+        updateWindowDragBounds(nX, nY, mTmpRect);
+        updateDimLayerVisibility(nX);
+        return false;
+    }
+
+    /**
+     * The user is drag - resizing the window.
+     *
+     * @param x The x coordinate of the current drag coordinate.
+     * @param y the y coordinate of the current drag coordinate.
+     */
+    @VisibleForTesting
+    void resizeDrag(float x, float y) {
+        // This is a resizing operation.
+        // We need to keep various constraints:
+        // 1. mMinVisible[Width/Height] <= [width/height] <= mMaxVisibleSize.[x/y]
+        // 2. The orientation is kept - if required.
+        final int deltaX = Math.round(x - mStartDragX);
+        final int deltaY = Math.round(y - mStartDragY);
+        int left = mWindowOriginalBounds.left;
+        int top = mWindowOriginalBounds.top;
+        int right = mWindowOriginalBounds.right;
+        int bottom = mWindowOriginalBounds.bottom;
+
+        // The aspect which we have to respect. Note that if the orientation does not need to be
+        // preserved the aspect will be calculated as 1.0 which neutralizes the following
+        // computations.
+        final float minAspect = !mPreserveOrientation
+                ? 1.0f
+                : (mStartOrientationWasLandscape ? MIN_ASPECT : (1.0f / MIN_ASPECT));
+        // Calculate the resulting width and height of the drag operation.
+        int width = right - left;
+        int height = bottom - top;
+        if ((mCtrlType & CTRL_LEFT) != 0) {
+            width = Math.max(mMinVisibleWidth, width - deltaX);
+        } else if ((mCtrlType & CTRL_RIGHT) != 0) {
+            width = Math.max(mMinVisibleWidth, width + deltaX);
+        }
+        if ((mCtrlType & CTRL_TOP) != 0) {
+            height = Math.max(mMinVisibleHeight, height - deltaY);
+        } else if ((mCtrlType & CTRL_BOTTOM) != 0) {
+            height = Math.max(mMinVisibleHeight, height + deltaY);
+        }
+
+        // If we have to preserve the orientation - check that we are doing so.
+        final float aspect = (float) width / (float) height;
+        if (mPreserveOrientation && ((mStartOrientationWasLandscape && aspect < MIN_ASPECT)
+                || (!mStartOrientationWasLandscape && aspect > (1.0 / MIN_ASPECT)))) {
+            // Calculate 2 rectangles fulfilling all requirements for either X or Y being the major
+            // drag axis. What ever is producing the bigger rectangle will be chosen.
+            int width1;
+            int width2;
+            int height1;
+            int height2;
+            if (mStartOrientationWasLandscape) {
+                // Assuming that the width is our target we calculate the height.
+                width1 = Math.max(mMinVisibleWidth, Math.min(mMaxVisibleSize.x, width));
+                height1 = Math.min(height, Math.round((float)width1 / MIN_ASPECT));
+                if (height1 < mMinVisibleHeight) {
+                    // If the resulting height is too small we adjust to the minimal size.
+                    height1 = mMinVisibleHeight;
+                    width1 = Math.max(mMinVisibleWidth,
+                            Math.min(mMaxVisibleSize.x, Math.round((float)height1 * MIN_ASPECT)));
+                }
+                // Assuming that the height is our target we calculate the width.
+                height2 = Math.max(mMinVisibleHeight, Math.min(mMaxVisibleSize.y, height));
+                width2 = Math.max(width, Math.round((float)height2 * MIN_ASPECT));
+                if (width2 < mMinVisibleWidth) {
+                    // If the resulting width is too small we adjust to the minimal size.
+                    width2 = mMinVisibleWidth;
+                    height2 = Math.max(mMinVisibleHeight,
+                            Math.min(mMaxVisibleSize.y, Math.round((float)width2 / MIN_ASPECT)));
+                }
+            } else {
+                // Assuming that the width is our target we calculate the height.
+                width1 = Math.max(mMinVisibleWidth, Math.min(mMaxVisibleSize.x, width));
+                height1 = Math.max(height, Math.round((float)width1 * MIN_ASPECT));
+                if (height1 < mMinVisibleHeight) {
+                    // If the resulting height is too small we adjust to the minimal size.
+                    height1 = mMinVisibleHeight;
+                    width1 = Math.max(mMinVisibleWidth,
+                            Math.min(mMaxVisibleSize.x, Math.round((float)height1 / MIN_ASPECT)));
+                }
+                // Assuming that the height is our target we calculate the width.
+                height2 = Math.max(mMinVisibleHeight, Math.min(mMaxVisibleSize.y, height));
+                width2 = Math.min(width, Math.round((float)height2 / MIN_ASPECT));
+                if (width2 < mMinVisibleWidth) {
+                    // If the resulting width is too small we adjust to the minimal size.
+                    width2 = mMinVisibleWidth;
+                    height2 = Math.max(mMinVisibleHeight,
+                            Math.min(mMaxVisibleSize.y, Math.round((float)width2 * MIN_ASPECT)));
+                }
+            }
+
+            // Use the bigger of the two rectangles if the major change was positive, otherwise
+            // do the opposite.
+            final boolean grows = width > (right - left) || height > (bottom - top);
+            if (grows == (width1 * height1 > width2 * height2)) {
+                width = width1;
+                height = height1;
+            } else {
+                width = width2;
+                height = height2;
+            }
+        }
+
+        // Update mWindowDragBounds to the new drag size.
+        updateDraggedBounds(left, top, right, bottom, width, height);
+    }
+
+    /**
+     * Given the old coordinates and the new width and height, update the mWindowDragBounds.
+     *
+     * @param left      The original left bound before the user started dragging.
+     * @param top       The original top bound before the user started dragging.
+     * @param right     The original right bound before the user started dragging.
+     * @param bottom    The original bottom bound before the user started dragging.
+     * @param newWidth  The new dragged width.
+     * @param newHeight The new dragged height.
+     */
+    void updateDraggedBounds(int left, int top, int right, int bottom, int newWidth,
+                             int newHeight) {
+        // Generate the final bounds by keeping the opposite drag edge constant.
+        if ((mCtrlType & CTRL_LEFT) != 0) {
+            left = right - newWidth;
+        } else { // Note: The right might have changed - if we pulled at the right or not.
+            right = left + newWidth;
+        }
+        if ((mCtrlType & CTRL_TOP) != 0) {
+            top = bottom - newHeight;
+        } else { // Note: The height might have changed - if we pulled at the bottom or not.
+            bottom = top + newHeight;
+        }
+
+        mWindowDragBounds.set(left, top, right, bottom);
+
+        checkBoundsForOrientationViolations(mWindowDragBounds);
+    }
+
+    /**
+     * Validate bounds against orientation violations (if DEBUG_ORIENTATION_VIOLATIONS is set).
+     *
+     * @param bounds The bounds to be checked.
+     */
+    private void checkBoundsForOrientationViolations(Rect bounds) {
+        // When using debug check that we are not violating the given constraints.
+        if (DEBUG_ORIENTATION_VIOLATIONS) {
+            if (mStartOrientationWasLandscape != (bounds.width() >= bounds.height())) {
+                Slog.e(TAG, "Orientation violation detected! should be "
+                        + (mStartOrientationWasLandscape ? "landscape" : "portrait")
+                        + " but is the other");
+            } else {
+                Slog.v(TAG, "new bounds size: " + bounds.width() + " x " + bounds.height());
+            }
+            if (mMinVisibleWidth > bounds.width() || mMinVisibleHeight > bounds.height()) {
+                Slog.v(TAG, "Minimum requirement violated: Width(min, is)=(" + mMinVisibleWidth
+                        + ", " + bounds.width() + ") Height(min,is)=("
+                        + mMinVisibleHeight + ", " + bounds.height() + ")");
+            }
+            if (mMaxVisibleSize.x < bounds.width() || mMaxVisibleSize.y < bounds.height()) {
+                Slog.v(TAG, "Maximum requirement violated: Width(min, is)=(" + mMaxVisibleSize.x
+                        + ", " + bounds.width() + ") Height(min,is)=("
+                        + mMaxVisibleSize.y + ", " + bounds.height() + ")");
+            }
+        }
+    }
+
+    private void updateWindowDragBounds(int x, int y, Rect stackBounds) {
+        final int offsetX = Math.round(x - mStartDragX);
+        final int offsetY = Math.round(y - mStartDragY);
+        mWindowDragBounds.set(mWindowOriginalBounds);
+        // Horizontally, at least mMinVisibleWidth pixels of the window should remain visible.
+        final int maxLeft = stackBounds.right - mMinVisibleWidth;
+        final int minLeft = stackBounds.left + mMinVisibleWidth - mWindowOriginalBounds.width();
+
+        // Vertically, the top mMinVisibleHeight of the window should remain visible.
+        // (This assumes that the window caption bar is at the top of the window).
+        final int minTop = stackBounds.top;
+        final int maxTop = stackBounds.bottom - mMinVisibleHeight;
+
+        mWindowDragBounds.offsetTo(
+                Math.min(Math.max(mWindowOriginalBounds.left + offsetX, minLeft), maxLeft),
+                Math.min(Math.max(mWindowOriginalBounds.top + offsetY, minTop), maxTop));
+
+        if (DEBUG_TASK_POSITIONING) Slog.d(TAG,
+                "updateWindowDragBounds: " + mWindowDragBounds);
+    }
+
+    private void updateDimLayerVisibility(int x) {
+        @CtrlType
+        int dimSide = getDimSide(x);
+        if (dimSide == mCurrentDimSide) {
+            return;
+        }
+
+        mCurrentDimSide = dimSide;
+
+        if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION updateDimLayerVisibility");
+        mService.openSurfaceTransaction();
+        if (mCurrentDimSide == CTRL_NONE) {
+            mDimLayer.hide();
+        } else {
+            showDimLayer();
+        }
+        mService.closeSurfaceTransaction();
+    }
+
+    /**
+     * Returns the side of the screen the dim layer should be shown.
+     * @param x horizontal coordinate used to determine if the dim layer should be shown
+     * @return Returns {@link #CTRL_LEFT} if the dim layer should be shown on the left half of the
+     * screen, {@link #CTRL_RIGHT} if on the right side, or {@link #CTRL_NONE} if the dim layer
+     * shouldn't be shown.
+     */
+    private int getDimSide(int x) {
+        if (mTask.mStack.mStackId != FREEFORM_WORKSPACE_STACK_ID
+                || !mTask.mStack.fillsParent()
+                || mTask.mStack.getConfiguration().orientation != ORIENTATION_LANDSCAPE) {
+            return CTRL_NONE;
+        }
+
+        mTask.mStack.getDimBounds(mTmpRect);
+        if (x - mSideMargin <= mTmpRect.left) {
+            return CTRL_LEFT;
+        }
+        if (x + mSideMargin >= mTmpRect.right) {
+            return CTRL_RIGHT;
+        }
+
+        return CTRL_NONE;
+    }
+
+    private void showDimLayer() {
+        mTask.mStack.getDimBounds(mTmpRect);
+        if (mCurrentDimSide == CTRL_LEFT) {
+            mTmpRect.right = mTmpRect.centerX();
+        } else if (mCurrentDimSide == CTRL_RIGHT) {
+            mTmpRect.left = mTmpRect.centerX();
+        }
+
+        mDimLayer.setBounds(mTmpRect);
+        mDimLayer.show(mService.getDragLayerLocked(), RESIZING_HINT_ALPHA,
+                RESIZING_HINT_DURATION_MS);
+    }
+
+    @Override /** {@link DimLayer.DimLayerUser} */
+    public boolean dimFullscreen() {
+        return isFullscreen();
+    }
+
+    boolean isFullscreen() {
+        return false;
+    }
+
+    @Override /** {@link DimLayer.DimLayerUser} */
+    public DisplayInfo getDisplayInfo() {
+        return mTask.mStack.getDisplayInfo();
+    }
+
+    @Override
+    public boolean isAttachedToDisplay() {
+        return mTask != null && mTask.getDisplayContent() != null;
+    }
+
+    @Override
+    public void getDimBounds(Rect out) {
+        // This dim layer user doesn't need this.
+    }
+
+    @Override
+    public String toShortString() {
+        return TAG;
+    }
+}
diff --git a/com/android/server/wm/TaskSnapshotCache.java b/com/android/server/wm/TaskSnapshotCache.java
new file mode 100644
index 0000000..7bf4edb
--- /dev/null
+++ b/com/android/server/wm/TaskSnapshotCache.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager.TaskSnapshot;
+import android.util.ArrayMap;
+import android.util.LruCache;
+
+import java.io.PrintWriter;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Caches snapshots. See {@link TaskSnapshotController}.
+ * <p>
+ * Access to this class should be guarded by the global window manager lock.
+ */
+class TaskSnapshotCache {
+
+    private final WindowManagerService mService;
+    private final TaskSnapshotLoader mLoader;
+    private final ArrayMap<AppWindowToken, Integer> mAppTaskMap = new ArrayMap<>();
+    private final ArrayMap<Integer, CacheEntry> mRunningCache = new ArrayMap<>();
+
+    TaskSnapshotCache(WindowManagerService service, TaskSnapshotLoader loader) {
+        mService = service;
+        mLoader = loader;
+    }
+
+    void putSnapshot(Task task, TaskSnapshot snapshot) {
+        final CacheEntry entry = mRunningCache.get(task.mTaskId);
+        if (entry != null) {
+            mAppTaskMap.remove(entry.topApp);
+        }
+        final AppWindowToken top = task.getTopChild();
+        mAppTaskMap.put(top, task.mTaskId);
+        mRunningCache.put(task.mTaskId, new CacheEntry(snapshot, task.getTopChild()));
+    }
+
+    /**
+     * If {@param restoreFromDisk} equals {@code true}, DO NOT HOLD THE WINDOW MANAGER LOCK!
+     */
+    @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
+            boolean reducedResolution) {
+
+        synchronized (mService.mWindowMap) {
+            // Try the running cache.
+            final CacheEntry entry = mRunningCache.get(taskId);
+            if (entry != null) {
+                return entry.snapshot;
+            }
+        }
+
+        // Try to restore from disk if asked.
+        if (!restoreFromDisk) {
+            return null;
+        }
+        return tryRestoreFromDisk(taskId, userId, reducedResolution);
+    }
+
+    /**
+     * DO NOT HOLD THE WINDOW MANAGER LOCK WHEN CALLING THIS METHOD!
+     */
+    private TaskSnapshot tryRestoreFromDisk(int taskId, int userId, boolean reducedResolution) {
+        final TaskSnapshot snapshot = mLoader.loadTask(taskId, userId, reducedResolution);
+        if (snapshot == null) {
+            return null;
+        }
+        return snapshot;
+    }
+
+    /**
+     * Called when an app token has been removed
+     */
+    void onAppRemoved(AppWindowToken wtoken) {
+        final Integer taskId = mAppTaskMap.get(wtoken);
+        if (taskId != null) {
+            removeRunningEntry(taskId);
+        }
+    }
+
+    /**
+     * Callend when an app window token's process died.
+     */
+    void onAppDied(AppWindowToken wtoken) {
+        final Integer taskId = mAppTaskMap.get(wtoken);
+        if (taskId != null) {
+            removeRunningEntry(taskId);
+        }
+    }
+
+    void onTaskRemoved(int taskId) {
+        removeRunningEntry(taskId);
+    }
+
+    private void removeRunningEntry(int taskId) {
+        final CacheEntry entry = mRunningCache.get(taskId);
+        if (entry != null) {
+            mAppTaskMap.remove(entry.topApp);
+            mRunningCache.remove(taskId);
+        }
+    }
+
+    void dump(PrintWriter pw, String prefix) {
+        final String doublePrefix = prefix + "  ";
+        final String triplePrefix = doublePrefix + "  ";
+        pw.println(prefix + "SnapshotCache");
+        for (int i = mRunningCache.size() - 1; i >= 0; i--) {
+            final CacheEntry entry = mRunningCache.valueAt(i);
+            pw.println(doublePrefix + "Entry taskId=" + mRunningCache.keyAt(i));
+            pw.println(triplePrefix + "topApp=" + entry.topApp);
+            pw.println(triplePrefix + "snapshot=" + entry.snapshot);
+        }
+    }
+
+    private static final class CacheEntry {
+
+        /** The snapshot. */
+        final TaskSnapshot snapshot;
+
+        /** The app token that was on top of the task when the snapshot was taken */
+        final AppWindowToken topApp;
+
+        CacheEntry(TaskSnapshot snapshot, AppWindowToken topApp) {
+            this.snapshot = snapshot;
+            this.topApp = topApp;
+        }
+    }
+}
diff --git a/com/android/server/wm/TaskSnapshotController.java b/com/android/server/wm/TaskSnapshotController.java
new file mode 100644
index 0000000..4632402
--- /dev/null
+++ b/com/android/server/wm/TaskSnapshotController.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.TaskSnapshotPersister.DISABLE_FULL_SIZED_BITMAPS;
+import static com.android.server.wm.TaskSnapshotPersister.REDUCED_SCALE;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityManager.StackId;
+import android.app.ActivityManager.TaskSnapshot;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.GraphicBuffer;
+import android.graphics.Rect;
+import android.os.Environment;
+import android.os.Handler;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+import android.view.ThreadedRenderer;
+import android.view.WindowManager.LayoutParams;
+import android.view.WindowManagerPolicy.ScreenOffListener;
+import android.view.WindowManagerPolicy.StartingSurface;
+
+import com.google.android.collect.Sets;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wm.TaskSnapshotSurface.SystemBarBackgroundPainter;
+
+import java.io.PrintWriter;
+
+/**
+ * When an app token becomes invisible, we take a snapshot (bitmap) of the corresponding task and
+ * put it into our cache. Internally we use gralloc buffers to be able to draw them wherever we
+ * like without any copying.
+ * <p>
+ * System applications may retrieve a snapshot to represent the current state of a task, and draw
+ * them in their own process.
+ * <p>
+ * When we task becomes visible again, we show a starting window with the snapshot as the content to
+ * make app transitions more responsive.
+ * <p>
+ * To access this class, acquire the global window manager lock.
+ */
+class TaskSnapshotController {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotController" : TAG_WM;
+
+    /**
+     * Return value for {@link #getSnapshotMode}: We are allowed to take a real screenshot to be
+     * used as the snapshot.
+     */
+    @VisibleForTesting
+    static final int SNAPSHOT_MODE_REAL = 0;
+
+    /**
+     * Return value for {@link #getSnapshotMode}: We are not allowed to take a real screenshot but
+     * we should try to use the app theme to create a dummy representation of the app.
+     */
+    @VisibleForTesting
+    static final int SNAPSHOT_MODE_APP_THEME = 1;
+
+    /**
+     * Return value for {@link #getSnapshotMode}: We aren't allowed to take any snapshot.
+     */
+    @VisibleForTesting
+    static final int SNAPSHOT_MODE_NONE = 2;
+
+    private final WindowManagerService mService;
+
+    private final TaskSnapshotCache mCache;
+    private final TaskSnapshotPersister mPersister = new TaskSnapshotPersister(
+            Environment::getDataSystemCeDirectory);
+    private final TaskSnapshotLoader mLoader = new TaskSnapshotLoader(mPersister);
+    private final ArraySet<Task> mTmpTasks = new ArraySet<>();
+    private final Handler mHandler = new Handler();
+
+    /**
+     * Flag indicating whether we are running on an Android TV device.
+     */
+    private final boolean mIsRunningOnTv;
+
+    /**
+     * Flag indicating whether we are running on an IoT device.
+     */
+    private final boolean mIsRunningOnIoT;
+
+    /**
+     * Flag indicating whether we are running on an Android Wear device.
+     */
+    private final boolean mIsRunningOnWear;
+
+    TaskSnapshotController(WindowManagerService service) {
+        mService = service;
+        mCache = new TaskSnapshotCache(mService, mLoader);
+        mIsRunningOnTv = mService.mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_LEANBACK);
+        mIsRunningOnIoT = mService.mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_EMBEDDED);
+        mIsRunningOnWear = mService.mContext.getPackageManager().hasSystemFeature(
+            PackageManager.FEATURE_WATCH);
+    }
+
+    void systemReady() {
+        mPersister.start();
+    }
+
+    void onTransitionStarting() {
+        handleClosingApps(mService.mClosingApps);
+    }
+
+    /**
+     * Called when the visibility of an app changes outside of the regular app transition flow.
+     */
+    void notifyAppVisibilityChanged(AppWindowToken appWindowToken, boolean visible) {
+        if (!visible) {
+            handleClosingApps(Sets.newArraySet(appWindowToken));
+        }
+    }
+
+    private void handleClosingApps(ArraySet<AppWindowToken> closingApps) {
+        if (shouldDisableSnapshots()) {
+            return;
+        }
+
+        // We need to take a snapshot of the task if and only if all activities of the task are
+        // either closing or hidden.
+        getClosingTasks(closingApps, mTmpTasks);
+        snapshotTasks(mTmpTasks);
+
+    }
+
+    private void snapshotTasks(ArraySet<Task> tasks) {
+        for (int i = tasks.size() - 1; i >= 0; i--) {
+            final Task task = tasks.valueAt(i);
+            final int mode = getSnapshotMode(task);
+            final TaskSnapshot snapshot;
+            switch (mode) {
+                case SNAPSHOT_MODE_NONE:
+                    continue;
+                case SNAPSHOT_MODE_APP_THEME:
+                    snapshot = drawAppThemeSnapshot(task);
+                    break;
+                case SNAPSHOT_MODE_REAL:
+                    snapshot = snapshotTask(task);
+                    break;
+                default:
+                    snapshot = null;
+                    break;
+            }
+            if (snapshot != null) {
+                final GraphicBuffer buffer = snapshot.getSnapshot();
+                if (buffer.getWidth() == 0 || buffer.getHeight() == 0) {
+                    buffer.destroy();
+                    Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x"
+                            + buffer.getHeight());
+                } else {
+                    mCache.putSnapshot(task, snapshot);
+                    mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
+                    if (task.getController() != null) {
+                        task.getController().reportSnapshotChanged(snapshot);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO HOLD THE WINDOW
+     * MANAGER LOCK WHEN CALLING THIS METHOD!
+     */
+    @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
+            boolean reducedResolution) {
+        return mCache.getSnapshot(taskId, userId, restoreFromDisk, reducedResolution
+                || DISABLE_FULL_SIZED_BITMAPS);
+    }
+
+    /**
+     * Creates a starting surface for {@param token} with {@param snapshot}. DO NOT HOLD THE WINDOW
+     * MANAGER LOCK WHEN CALLING THIS METHOD!
+     */
+    StartingSurface createStartingSurface(AppWindowToken token,
+            TaskSnapshot snapshot) {
+        return TaskSnapshotSurface.create(mService, token, snapshot);
+    }
+
+    private TaskSnapshot snapshotTask(Task task) {
+        final AppWindowToken top = task.getTopChild();
+        if (top == null) {
+            return null;
+        }
+        final WindowState mainWindow = top.findMainWindow();
+        if (mainWindow == null) {
+            return null;
+        }
+        final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
+        final float scaleFraction = isLowRamDevice ? REDUCED_SCALE : 1f;
+        final GraphicBuffer buffer = top.mDisplayContent.screenshotApplicationsToBuffer(top.token,
+                -1, -1, false, scaleFraction, false, true);
+        if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
+            return null;
+        }
+        return new TaskSnapshot(buffer, top.getConfiguration().orientation,
+                minRect(mainWindow.mContentInsets, mainWindow.mStableInsets),
+                isLowRamDevice /* reduced */, scaleFraction /* scale */);
+    }
+
+    private boolean shouldDisableSnapshots() {
+        return mIsRunningOnWear || mIsRunningOnTv || mIsRunningOnIoT;
+    }
+
+    private Rect minRect(Rect rect1, Rect rect2) {
+        return new Rect(Math.min(rect1.left, rect2.left),
+                Math.min(rect1.top, rect2.top),
+                Math.min(rect1.right, rect2.right),
+                Math.min(rect1.bottom, rect2.bottom));
+    }
+
+    /**
+     * Retrieves all closing tasks based on the list of closing apps during an app transition.
+     */
+    @VisibleForTesting
+    void getClosingTasks(ArraySet<AppWindowToken> closingApps, ArraySet<Task> outClosingTasks) {
+        outClosingTasks.clear();
+        for (int i = closingApps.size() - 1; i >= 0; i--) {
+            final AppWindowToken atoken = closingApps.valueAt(i);
+            final Task task = atoken.getTask();
+
+            // If the task of the app is not visible anymore, it means no other app in that task
+            // is opening. Thus, the task is closing.
+            if (task != null && !task.isVisible()) {
+                outClosingTasks.add(task);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    int getSnapshotMode(Task task) {
+        final AppWindowToken topChild = task.getTopChild();
+        if (StackId.isHomeOrRecentsStack(task.mStack.mStackId)) {
+            return SNAPSHOT_MODE_NONE;
+        } else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) {
+            return SNAPSHOT_MODE_APP_THEME;
+        } else {
+            return SNAPSHOT_MODE_REAL;
+        }
+    }
+
+    /**
+     * If we are not allowed to take a real screenshot, this attempts to represent the app as best
+     * as possible by using the theme's window background.
+     */
+    private TaskSnapshot drawAppThemeSnapshot(Task task) {
+        final AppWindowToken topChild = task.getTopChild();
+        if (topChild == null) {
+            return null;
+        }
+        final WindowState mainWindow = topChild.findMainWindow();
+        if (mainWindow == null) {
+            return null;
+        }
+        final int color = task.getTaskDescription().getBackgroundColor();
+        final int statusBarColor = task.getTaskDescription().getStatusBarColor();
+        final int navigationBarColor = task.getTaskDescription().getNavigationBarColor();
+        final LayoutParams attrs = mainWindow.getAttrs();
+        final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
+                attrs.privateFlags, attrs.systemUiVisibility, statusBarColor, navigationBarColor);
+        final int width = mainWindow.getFrameLw().width();
+        final int height = mainWindow.getFrameLw().height();
+
+        final RenderNode node = RenderNode.create("TaskSnapshotController", null);
+        node.setLeftTopRightBottom(0, 0, width, height);
+        node.setClipToBounds(false);
+        final DisplayListCanvas c = node.start(width, height);
+        c.drawColor(color);
+        decorPainter.setInsets(mainWindow.mContentInsets, mainWindow.mStableInsets);
+        decorPainter.drawDecors(c, null /* statusBarExcludeFrame */);
+        node.end(c);
+        final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
+
+        return new TaskSnapshot(hwBitmap.createGraphicBufferHandle(),
+                topChild.getConfiguration().orientation, mainWindow.mStableInsets,
+                false /* reduced */, 1.0f /* scale */);
+    }
+
+    /**
+     * Called when an {@link AppWindowToken} has been removed.
+     */
+    void onAppRemoved(AppWindowToken wtoken) {
+        mCache.onAppRemoved(wtoken);
+    }
+
+    /**
+     * Called when the process of an {@link AppWindowToken} has died.
+     */
+    void onAppDied(AppWindowToken wtoken) {
+        mCache.onAppDied(wtoken);
+    }
+
+    void notifyTaskRemovedFromRecents(int taskId, int userId) {
+        mCache.onTaskRemoved(taskId);
+        mPersister.onTaskRemovedFromRecents(taskId, userId);
+    }
+
+    /**
+     * See {@link TaskSnapshotPersister#removeObsoleteFiles}
+     */
+    void removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
+        mPersister.removeObsoleteFiles(persistentTaskIds, runningUserIds);
+    }
+
+    /**
+     * Temporarily pauses/unpauses persisting of task snapshots.
+     *
+     * @param paused Whether task snapshot persisting should be paused.
+     */
+    void setPersisterPaused(boolean paused) {
+        mPersister.setPaused(paused);
+    }
+
+    /**
+     * Called when screen is being turned off.
+     */
+    void screenTurningOff(ScreenOffListener listener) {
+        if (shouldDisableSnapshots()) {
+            listener.onScreenOff();
+            return;
+        }
+
+        // We can't take a snapshot when screen is off, so take a snapshot now!
+        mHandler.post(() -> {
+            try {
+                synchronized (mService.mWindowMap) {
+                    mTmpTasks.clear();
+                    mService.mRoot.forAllTasks(task -> {
+                        if (task.isVisible()) {
+                            mTmpTasks.add(task);
+                        }
+                    });
+                    snapshotTasks(mTmpTasks);
+                }
+            } finally {
+                listener.onScreenOff();
+            }
+        });
+    }
+
+    void dump(PrintWriter pw, String prefix) {
+        mCache.dump(pw, prefix);
+    }
+}
diff --git a/com/android/server/wm/TaskSnapshotLoader.java b/com/android/server/wm/TaskSnapshotLoader.java
new file mode 100644
index 0000000..537f317
--- /dev/null
+++ b/com/android/server/wm/TaskSnapshotLoader.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.TaskSnapshotPersister.*;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.app.ActivityManager.TaskSnapshot;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapFactory.Options;
+import android.graphics.GraphicBuffer;
+import android.graphics.Rect;
+import android.util.Slog;
+
+import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+
+/**
+ * Loads a persisted {@link TaskSnapshot} from disk.
+ * <p>
+ * Do not hold the window manager lock when accessing this class.
+ * <p>
+ * Test class: {@link TaskSnapshotPersisterLoaderTest}
+ */
+class TaskSnapshotLoader {
+
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotLoader" : TAG_WM;
+
+    private final TaskSnapshotPersister mPersister;
+
+    TaskSnapshotLoader(TaskSnapshotPersister persister) {
+        mPersister = persister;
+    }
+
+    /**
+     * Loads a task from the disk.
+     * <p>
+     * Do not hold the window manager lock when calling this method, as we directly read data from
+     * disk here, which might be slow.
+     *
+     * @param taskId The id of the task to load.
+     * @param userId The id of the user the task belonged to.
+     * @param reducedResolution Whether to load a reduced resolution version of the snapshot.
+     * @return The loaded {@link TaskSnapshot} or {@code null} if it couldn't be loaded.
+     */
+    TaskSnapshot loadTask(int taskId, int userId, boolean reducedResolution) {
+        final File protoFile = mPersister.getProtoFile(taskId, userId);
+        final File bitmapFile = reducedResolution
+                ? mPersister.getReducedResolutionBitmapFile(taskId, userId)
+                : mPersister.getBitmapFile(taskId, userId);
+        if (bitmapFile == null || !protoFile.exists() || !bitmapFile.exists()) {
+            return null;
+        }
+        try {
+            final byte[] bytes = Files.readAllBytes(protoFile.toPath());
+            final TaskSnapshotProto proto = TaskSnapshotProto.parseFrom(bytes);
+            final Options options = new Options();
+            options.inPreferredConfig = Config.HARDWARE;
+            final Bitmap bitmap = BitmapFactory.decodeFile(bitmapFile.getPath(), options);
+            if (bitmap == null) {
+                Slog.w(TAG, "Failed to load bitmap: " + bitmapFile.getPath());
+                return null;
+            }
+            final GraphicBuffer buffer = bitmap.createGraphicBufferHandle();
+            if (buffer == null) {
+                Slog.w(TAG, "Failed to retrieve gralloc buffer for bitmap: "
+                        + bitmapFile.getPath());
+                return null;
+            }
+            return new TaskSnapshot(buffer, proto.orientation,
+                    new Rect(proto.insetLeft, proto.insetTop, proto.insetRight, proto.insetBottom),
+                    reducedResolution, reducedResolution ? REDUCED_SCALE : 1f);
+        } catch (IOException e) {
+            Slog.w(TAG, "Unable to load task snapshot data for taskId=" + taskId);
+            return null;
+        }
+    }
+}
diff --git a/com/android/server/wm/TaskSnapshotPersister.java b/com/android/server/wm/TaskSnapshotPersister.java
new file mode 100644
index 0000000..7b047a8
--- /dev/null
+++ b/com/android/server/wm/TaskSnapshotPersister.java
@@ -0,0 +1,445 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static android.graphics.Bitmap.CompressFormat.*;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.annotation.TestApi;
+import android.app.ActivityManager;
+import android.app.ActivityManager.TaskSnapshot;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.Bitmap.Config;
+import android.graphics.GraphicBuffer;
+import android.os.Process;
+import android.os.SystemClock;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.AtomicFile;
+import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+
+/**
+ * Persists {@link TaskSnapshot}s to disk.
+ * <p>
+ * Test class: {@link TaskSnapshotPersisterLoaderTest}
+ */
+class TaskSnapshotPersister {
+
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotPersister" : TAG_WM;
+    private static final String SNAPSHOTS_DIRNAME = "snapshots";
+    private static final String REDUCED_POSTFIX = "_reduced";
+    static final float REDUCED_SCALE = ActivityManager.isLowRamDeviceStatic() ? 0.6f : 0.5f;
+    static final boolean DISABLE_FULL_SIZED_BITMAPS = ActivityManager.isLowRamDeviceStatic();
+    private static final long DELAY_MS = 100;
+    private static final int QUALITY = 95;
+    private static final String PROTO_EXTENSION = ".proto";
+    private static final String BITMAP_EXTENSION = ".jpg";
+    private static final int MAX_STORE_QUEUE_DEPTH = 2;
+
+    @GuardedBy("mLock")
+    private final ArrayDeque<WriteQueueItem> mWriteQueue = new ArrayDeque<>();
+    @GuardedBy("mLock")
+    private final ArrayDeque<StoreWriteQueueItem> mStoreQueueItems = new ArrayDeque<>();
+    @GuardedBy("mLock")
+    private boolean mQueueIdling;
+    @GuardedBy("mLock")
+    private boolean mPaused;
+    private boolean mStarted;
+    private final Object mLock = new Object();
+    private final DirectoryResolver mDirectoryResolver;
+
+    /**
+     * The list of ids of the tasks that have been persisted since {@link #removeObsoleteFiles} was
+     * called.
+     */
+    @GuardedBy("mLock")
+    private final ArraySet<Integer> mPersistedTaskIdsSinceLastRemoveObsolete = new ArraySet<>();
+
+    TaskSnapshotPersister(DirectoryResolver resolver) {
+        mDirectoryResolver = resolver;
+    }
+
+    /**
+     * Starts persisting.
+     */
+    void start() {
+        if (!mStarted) {
+            mStarted = true;
+            mPersister.start();
+        }
+    }
+
+    /**
+     * Persists a snapshot of a task to disk.
+     *
+     * @param taskId The id of the task that needs to be persisted.
+     * @param userId The id of the user this tasks belongs to.
+     * @param snapshot The snapshot to persist.
+     */
+    void persistSnapshot(int taskId, int userId, TaskSnapshot snapshot) {
+        synchronized (mLock) {
+            mPersistedTaskIdsSinceLastRemoveObsolete.add(taskId);
+            sendToQueueLocked(new StoreWriteQueueItem(taskId, userId, snapshot));
+        }
+    }
+
+    /**
+     * Callend when a task has been removed.
+     *
+     * @param taskId The id of task that has been removed.
+     * @param userId The id of the user the task belonged to.
+     */
+    void onTaskRemovedFromRecents(int taskId, int userId) {
+        synchronized (mLock) {
+            mPersistedTaskIdsSinceLastRemoveObsolete.remove(taskId);
+            sendToQueueLocked(new DeleteWriteQueueItem(taskId, userId));
+        }
+    }
+
+    /**
+     * In case a write/delete operation was lost because the system crashed, this makes sure to
+     * clean up the directory to remove obsolete files.
+     *
+     * @param persistentTaskIds A set of task ids that exist in our in-memory model.
+     * @param runningUserIds The ids of the list of users that have tasks loaded in our in-memory
+     *                       model.
+     */
+    void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
+        synchronized (mLock) {
+            mPersistedTaskIdsSinceLastRemoveObsolete.clear();
+            sendToQueueLocked(new RemoveObsoleteFilesQueueItem(persistentTaskIds, runningUserIds));
+        }
+    }
+
+    void setPaused(boolean paused) {
+        synchronized (mLock) {
+            mPaused = paused;
+            if (!paused) {
+                mLock.notifyAll();
+            }
+        }
+    }
+
+    @TestApi
+    void waitForQueueEmpty() {
+        while (true) {
+            synchronized (mLock) {
+                if (mWriteQueue.isEmpty() && mQueueIdling) {
+                    return;
+                }
+            }
+            SystemClock.sleep(100);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void sendToQueueLocked(WriteQueueItem item) {
+        mWriteQueue.offer(item);
+        item.onQueuedLocked();
+        ensureStoreQueueDepthLocked();
+        if (!mPaused) {
+            mLock.notifyAll();
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void ensureStoreQueueDepthLocked() {
+        while (mStoreQueueItems.size() > MAX_STORE_QUEUE_DEPTH) {
+            final StoreWriteQueueItem item = mStoreQueueItems.poll();
+            mWriteQueue.remove(item);
+            Slog.i(TAG, "Queue is too deep! Purged item with taskid=" + item.mTaskId);
+        }
+    }
+
+    private File getDirectory(int userId) {
+        return new File(mDirectoryResolver.getSystemDirectoryForUser(userId), SNAPSHOTS_DIRNAME);
+    }
+
+    File getProtoFile(int taskId, int userId) {
+        return new File(getDirectory(userId), taskId + PROTO_EXTENSION);
+    }
+
+    File getBitmapFile(int taskId, int userId) {
+        // Full sized bitmaps are disabled on low ram devices
+        if (DISABLE_FULL_SIZED_BITMAPS) {
+            Slog.wtf(TAG, "This device does not support full sized resolution bitmaps.");
+            return null;
+        }
+        return new File(getDirectory(userId), taskId + BITMAP_EXTENSION);
+    }
+
+    File getReducedResolutionBitmapFile(int taskId, int userId) {
+        return new File(getDirectory(userId), taskId + REDUCED_POSTFIX + BITMAP_EXTENSION);
+    }
+
+    private boolean createDirectory(int userId) {
+        final File dir = getDirectory(userId);
+        return dir.exists() || dir.mkdirs();
+    }
+
+    private void deleteSnapshot(int taskId, int userId) {
+        final File protoFile = getProtoFile(taskId, userId);
+        final File bitmapReducedFile = getReducedResolutionBitmapFile(taskId, userId);
+        protoFile.delete();
+        bitmapReducedFile.delete();
+
+        // Low ram devices do not have a full sized file to delete
+        if (!DISABLE_FULL_SIZED_BITMAPS) {
+            final File bitmapFile = getBitmapFile(taskId, userId);
+            bitmapFile.delete();
+        }
+    }
+
+    interface DirectoryResolver {
+        File getSystemDirectoryForUser(int userId);
+    }
+
+    private Thread mPersister = new Thread("TaskSnapshotPersister") {
+        public void run() {
+            android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+            while (true) {
+                WriteQueueItem next;
+                synchronized (mLock) {
+                    if (mPaused) {
+                        next = null;
+                    } else {
+                        next = mWriteQueue.poll();
+                        if (next != null) {
+                            next.onDequeuedLocked();
+                        }
+                    }
+                }
+                if (next != null) {
+                    next.write();
+                    SystemClock.sleep(DELAY_MS);
+                }
+                synchronized (mLock) {
+                    final boolean writeQueueEmpty = mWriteQueue.isEmpty();
+                    if (!writeQueueEmpty && !mPaused) {
+                        continue;
+                    }
+                    try {
+                        mQueueIdling = writeQueueEmpty;
+                        mLock.wait();
+                        mQueueIdling = false;
+                    } catch (InterruptedException e) {
+                    }
+                }
+            }
+        }
+    };
+
+    private abstract class WriteQueueItem {
+        abstract void write();
+
+        /**
+         * Called when this queue item has been put into the queue.
+         */
+        void onQueuedLocked() {
+        }
+
+        /**
+         * Called when this queue item has been taken out of the queue.
+         */
+        void onDequeuedLocked() {
+        }
+    }
+
+    private class StoreWriteQueueItem extends WriteQueueItem {
+        private final int mTaskId;
+        private final int mUserId;
+        private final TaskSnapshot mSnapshot;
+
+        StoreWriteQueueItem(int taskId, int userId, TaskSnapshot snapshot) {
+            mTaskId = taskId;
+            mUserId = userId;
+            mSnapshot = snapshot;
+        }
+
+        @Override
+        void onQueuedLocked() {
+            mStoreQueueItems.offer(this);
+        }
+
+        @Override
+        void onDequeuedLocked() {
+            mStoreQueueItems.remove(this);
+        }
+
+        @Override
+        void write() {
+            if (!createDirectory(mUserId)) {
+                Slog.e(TAG, "Unable to create snapshot directory for user dir="
+                        + getDirectory(mUserId));
+            }
+            boolean failed = false;
+            if (!writeProto()) {
+                failed = true;
+            }
+            if (!writeBuffer()) {
+                failed = true;
+            }
+            if (failed) {
+                deleteSnapshot(mTaskId, mUserId);
+            }
+        }
+
+        boolean writeProto() {
+            final TaskSnapshotProto proto = new TaskSnapshotProto();
+            proto.orientation = mSnapshot.getOrientation();
+            proto.insetLeft = mSnapshot.getContentInsets().left;
+            proto.insetTop = mSnapshot.getContentInsets().top;
+            proto.insetRight = mSnapshot.getContentInsets().right;
+            proto.insetBottom = mSnapshot.getContentInsets().bottom;
+            final byte[] bytes = TaskSnapshotProto.toByteArray(proto);
+            final File file = getProtoFile(mTaskId, mUserId);
+            final AtomicFile atomicFile = new AtomicFile(file);
+            FileOutputStream fos = null;
+            try {
+                fos = atomicFile.startWrite();
+                fos.write(bytes);
+                atomicFile.finishWrite(fos);
+            } catch (IOException e) {
+                atomicFile.failWrite(fos);
+                Slog.e(TAG, "Unable to open " + file + " for persisting. " + e);
+                return false;
+            }
+            return true;
+        }
+
+        boolean writeBuffer() {
+            final Bitmap bitmap = Bitmap.createHardwareBitmap(mSnapshot.getSnapshot());
+            if (bitmap == null) {
+                Slog.e(TAG, "Invalid task snapshot hw bitmap");
+                return false;
+            }
+
+            final Bitmap swBitmap = bitmap.copy(Config.ARGB_8888, false /* isMutable */);
+            final File reducedFile = getReducedResolutionBitmapFile(mTaskId, mUserId);
+            final Bitmap reduced = mSnapshot.isReducedResolution()
+                    ? swBitmap
+                    : Bitmap.createScaledBitmap(swBitmap,
+                            (int) (bitmap.getWidth() * REDUCED_SCALE),
+                            (int) (bitmap.getHeight() * REDUCED_SCALE), true /* filter */);
+            try {
+                FileOutputStream reducedFos = new FileOutputStream(reducedFile);
+                reduced.compress(JPEG, QUALITY, reducedFos);
+                reducedFos.close();
+            } catch (IOException e) {
+                Slog.e(TAG, "Unable to open " + reducedFile +" for persisting.", e);
+                return false;
+            }
+
+            // For snapshots with reduced resolution, do not create or save full sized bitmaps
+            if (mSnapshot.isReducedResolution()) {
+                return true;
+            }
+
+            final File file = getBitmapFile(mTaskId, mUserId);
+            try {
+                FileOutputStream fos = new FileOutputStream(file);
+                swBitmap.compress(JPEG, QUALITY, fos);
+                fos.close();
+            } catch (IOException e) {
+                Slog.e(TAG, "Unable to open " + file + " for persisting.", e);
+                return false;
+            }
+            return true;
+        }
+    }
+
+    private class DeleteWriteQueueItem extends WriteQueueItem {
+        private final int mTaskId;
+        private final int mUserId;
+
+        DeleteWriteQueueItem(int taskId, int userId) {
+            mTaskId = taskId;
+            mUserId = userId;
+        }
+
+        @Override
+        void write() {
+            deleteSnapshot(mTaskId, mUserId);
+        }
+    }
+
+    @VisibleForTesting
+    class RemoveObsoleteFilesQueueItem extends WriteQueueItem {
+        private final ArraySet<Integer> mPersistentTaskIds;
+        private final int[] mRunningUserIds;
+
+        @VisibleForTesting
+        RemoveObsoleteFilesQueueItem(ArraySet<Integer> persistentTaskIds,
+                int[] runningUserIds) {
+            mPersistentTaskIds = persistentTaskIds;
+            mRunningUserIds = runningUserIds;
+        }
+
+        @Override
+        void write() {
+            final ArraySet<Integer> newPersistedTaskIds;
+            synchronized (mLock) {
+                newPersistedTaskIds = new ArraySet<>(mPersistedTaskIdsSinceLastRemoveObsolete);
+            }
+            for (int userId : mRunningUserIds) {
+                final File dir = getDirectory(userId);
+                final String[] files = dir.list();
+                if (files == null) {
+                    continue;
+                }
+                for (String file : files) {
+                    final int taskId = getTaskId(file);
+                    if (!mPersistentTaskIds.contains(taskId)
+                            && !newPersistedTaskIds.contains(taskId)) {
+                        new File(dir, file).delete();
+                    }
+                }
+            }
+        }
+
+        @VisibleForTesting
+        int getTaskId(String fileName) {
+            if (!fileName.endsWith(PROTO_EXTENSION) && !fileName.endsWith(BITMAP_EXTENSION)) {
+                return -1;
+            }
+            final int end = fileName.lastIndexOf('.');
+            if (end == -1) {
+                return -1;
+            }
+            String name = fileName.substring(0, end);
+            if (name.endsWith(REDUCED_POSTFIX)) {
+                name = name.substring(0, name.length() - REDUCED_POSTFIX.length());
+            }
+            try {
+                return Integer.parseInt(name);
+            } catch (NumberFormatException e) {
+                return -1;
+            }
+        }
+    }
+}
diff --git a/com/android/server/wm/TaskSnapshotSurface.java b/com/android/server/wm/TaskSnapshotSurface.java
new file mode 100644
index 0000000..4698d72
--- /dev/null
+++ b/com/android/server/wm/TaskSnapshotSurface.java
@@ -0,0 +1,535 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static android.graphics.Color.WHITE;
+import static android.graphics.Color.alpha;
+import static android.view.SurfaceControl.HIDDEN;
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
+import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
+import static android.view.WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
+import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
+import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
+import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES;
+import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES;
+import static com.android.internal.policy.DecorView.getColorViewLeftInset;
+import static com.android.internal.policy.DecorView.getColorViewTopInset;
+import static com.android.internal.policy.DecorView.getNavigationBarRect;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager.TaskDescription;
+import android.app.ActivityManager.TaskSnapshot;
+import android.app.ActivityThread;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.GraphicBuffer;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.MergedConfiguration;
+import android.util.Slog;
+import android.view.IWindowSession;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.WindowManagerPolicy.StartingSurface;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.policy.DecorView;
+import com.android.internal.view.BaseIWindow;
+
+/**
+ * This class represents a starting window that shows a snapshot.
+ * <p>
+ * DO NOT HOLD THE WINDOW MANAGER LOCK WHEN CALLING METHODS OF THIS CLASS!
+ */
+class TaskSnapshotSurface implements StartingSurface {
+
+    private static final long SIZE_MISMATCH_MINIMUM_TIME_MS = 450;
+
+    /**
+     * When creating the starting window, we use the exact same layout flags such that we end up
+     * with a window with the exact same dimensions etc. However, these flags are not used in layout
+     * and might cause other side effects so we exclude them.
+     */
+    private static final int FLAG_INHERIT_EXCLUDES = FLAG_NOT_FOCUSABLE
+            | FLAG_NOT_TOUCHABLE
+            | FLAG_NOT_TOUCH_MODAL
+            | FLAG_ALT_FOCUSABLE_IM
+            | FLAG_NOT_FOCUSABLE
+            | FLAG_HARDWARE_ACCELERATED
+            | FLAG_IGNORE_CHEEK_PRESSES
+            | FLAG_LOCAL_FOCUS_MODE
+            | FLAG_SLIPPERY
+            | FLAG_WATCH_OUTSIDE_TOUCH
+            | FLAG_SPLIT_TOUCH
+            | FLAG_SCALED
+            | FLAG_SECURE;
+
+    private static final int PRIVATE_FLAG_INHERITS = PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND;
+
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "SnapshotStartingWindow" : TAG_WM;
+    private static final int MSG_REPORT_DRAW = 0;
+    private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=%s";
+    private final Window mWindow;
+    private final Surface mSurface;
+    private SurfaceControl mChildSurfaceControl;
+    private final IWindowSession mSession;
+    private final WindowManagerService mService;
+    private final Rect mTaskBounds;
+    private final Rect mStableInsets = new Rect();
+    private final Rect mContentInsets = new Rect();
+    private final Rect mFrame = new Rect();
+    private TaskSnapshot mSnapshot;
+    private final CharSequence mTitle;
+    private boolean mHasDrawn;
+    private long mShownTime;
+    private final Handler mHandler;
+    private boolean mSizeMismatch;
+    private final Paint mBackgroundPaint = new Paint();
+    private final int mStatusBarColor;
+    @VisibleForTesting final SystemBarBackgroundPainter mSystemBarBackgroundPainter;
+    private final int mOrientationOnCreation;
+
+    static TaskSnapshotSurface create(WindowManagerService service, AppWindowToken token,
+            TaskSnapshot snapshot) {
+
+        final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
+        final Window window = new Window();
+        final IWindowSession session = WindowManagerGlobal.getWindowSession();
+        window.setSession(session);
+        final Surface surface = new Surface();
+        final Rect tmpRect = new Rect();
+        final Rect tmpFrame = new Rect();
+        final Rect taskBounds;
+        final Rect tmpContentInsets = new Rect();
+        final Rect tmpStableInsets = new Rect();
+        final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration();
+        int backgroundColor = WHITE;
+        int statusBarColor = 0;
+        int navigationBarColor = 0;
+        final int sysUiVis;
+        final int windowFlags;
+        final int windowPrivateFlags;
+        final int currentOrientation;
+        synchronized (service.mWindowMap) {
+            final WindowState mainWindow = token.findMainWindow();
+            final Task task = token.getTask();
+            if (task == null) {
+                Slog.w(TAG, "TaskSnapshotSurface.create: Failed to find task for token="
+                        + token);
+                return null;
+            }
+            final AppWindowToken topFullscreenToken = token.getTask().getTopFullscreenAppToken();
+            if (topFullscreenToken == null) {
+                Slog.w(TAG, "TaskSnapshotSurface.create: Failed to find top fullscreen for task="
+                        + task);
+                return null;
+            }
+            final WindowState topFullscreenWindow = topFullscreenToken.getTopFullscreenWindow();
+            if (mainWindow == null || topFullscreenWindow == null) {
+                Slog.w(TAG, "TaskSnapshotSurface.create: Failed to find main window for token="
+                        + token);
+                return null;
+            }
+            sysUiVis = topFullscreenWindow.getSystemUiVisibility();
+            windowFlags = topFullscreenWindow.getAttrs().flags;
+            windowPrivateFlags = topFullscreenWindow.getAttrs().privateFlags;
+
+            layoutParams.dimAmount = mainWindow.getAttrs().dimAmount;
+            layoutParams.type = TYPE_APPLICATION_STARTING;
+            layoutParams.format = snapshot.getSnapshot().getFormat();
+            layoutParams.flags = (windowFlags & ~FLAG_INHERIT_EXCLUDES)
+                    | FLAG_NOT_FOCUSABLE
+                    | FLAG_NOT_TOUCHABLE;
+            layoutParams.privateFlags = windowPrivateFlags & PRIVATE_FLAG_INHERITS;
+            layoutParams.token = token.token;
+            layoutParams.width = LayoutParams.MATCH_PARENT;
+            layoutParams.height = LayoutParams.MATCH_PARENT;
+            layoutParams.systemUiVisibility = sysUiVis;
+            layoutParams.setTitle(String.format(TITLE_FORMAT, task.mTaskId));
+
+            final TaskDescription taskDescription = task.getTaskDescription();
+            if (taskDescription != null) {
+                backgroundColor = taskDescription.getBackgroundColor();
+                statusBarColor = taskDescription.getStatusBarColor();
+                navigationBarColor = taskDescription.getNavigationBarColor();
+            }
+            taskBounds = new Rect();
+            task.getBounds(taskBounds);
+            currentOrientation = topFullscreenWindow.getConfiguration().orientation;
+        }
+        try {
+            final int res = session.addToDisplay(window, window.mSeq, layoutParams,
+                    View.VISIBLE, token.getDisplayContent().getDisplayId(), tmpRect, tmpRect,
+                    tmpRect, null);
+            if (res < 0) {
+                Slog.w(TAG, "Failed to add snapshot starting window res=" + res);
+                return null;
+            }
+        } catch (RemoteException e) {
+            // Local call.
+        }
+        final TaskSnapshotSurface snapshotSurface = new TaskSnapshotSurface(service, window,
+                surface, snapshot, layoutParams.getTitle(), backgroundColor, statusBarColor,
+                navigationBarColor, sysUiVis, windowFlags, windowPrivateFlags, taskBounds,
+                currentOrientation);
+        window.setOuter(snapshotSurface);
+        try {
+            session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, tmpFrame,
+                    tmpRect, tmpContentInsets, tmpRect, tmpStableInsets, tmpRect, tmpRect,
+                    tmpMergedConfiguration, surface);
+        } catch (RemoteException e) {
+            // Local call.
+        }
+        snapshotSurface.setFrames(tmpFrame, tmpContentInsets, tmpStableInsets);
+        snapshotSurface.drawSnapshot();
+        return snapshotSurface;
+    }
+
+    @VisibleForTesting
+    TaskSnapshotSurface(WindowManagerService service, Window window, Surface surface,
+            TaskSnapshot snapshot, CharSequence title, int backgroundColor, int statusBarColor,
+            int navigationBarColor, int sysUiVis, int windowFlags, int windowPrivateFlags,
+            Rect taskBounds, int currentOrientation) {
+        mService = service;
+        mHandler = new Handler(mService.mH.getLooper());
+        mSession = WindowManagerGlobal.getWindowSession();
+        mWindow = window;
+        mSurface = surface;
+        mSnapshot = snapshot;
+        mTitle = title;
+        mBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE);
+        mTaskBounds = taskBounds;
+        mSystemBarBackgroundPainter = new SystemBarBackgroundPainter(windowFlags,
+                windowPrivateFlags, sysUiVis, statusBarColor, navigationBarColor);
+        mStatusBarColor = statusBarColor;
+        mOrientationOnCreation = currentOrientation;
+    }
+
+    @Override
+    public void remove() {
+        synchronized (mService.mWindowMap) {
+            final long now = SystemClock.uptimeMillis();
+            if (mSizeMismatch && now - mShownTime < SIZE_MISMATCH_MINIMUM_TIME_MS) {
+                mHandler.postAtTime(this::remove, mShownTime + SIZE_MISMATCH_MINIMUM_TIME_MS);
+                if (DEBUG_STARTING_WINDOW) {
+                    Slog.v(TAG, "Defer removing snapshot surface in "  + (now - mShownTime) + "ms");
+                }
+                return;
+            }
+        }
+        try {
+            if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Removing snapshot surface");
+            mSession.remove(mWindow);
+        } catch (RemoteException e) {
+            // Local call.
+        }
+    }
+
+    @VisibleForTesting
+    void setFrames(Rect frame, Rect contentInsets, Rect stableInsets) {
+        mFrame.set(frame);
+        mContentInsets.set(contentInsets);
+        mStableInsets.set(stableInsets);
+        mSizeMismatch = (mFrame.width() != mSnapshot.getSnapshot().getWidth()
+                || mFrame.height() != mSnapshot.getSnapshot().getHeight());
+        mSystemBarBackgroundPainter.setInsets(contentInsets, stableInsets);
+    }
+
+    private void drawSnapshot() {
+        final GraphicBuffer buffer = mSnapshot.getSnapshot();
+        if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Drawing snapshot surface sizeMismatch="
+                + mSizeMismatch);
+        if (mSizeMismatch) {
+            // The dimensions of the buffer and the window don't match, so attaching the buffer
+            // will fail. Better create a child window with the exact dimensions and fill the parent
+            // window with the background color!
+            drawSizeMismatchSnapshot(buffer);
+        } else {
+            drawSizeMatchSnapshot(buffer);
+        }
+        synchronized (mService.mWindowMap) {
+            mShownTime = SystemClock.uptimeMillis();
+            mHasDrawn = true;
+        }
+        reportDrawn();
+
+        // In case window manager leaks us, make sure we don't retain the snapshot.
+        mSnapshot = null;
+    }
+
+    private void drawSizeMatchSnapshot(GraphicBuffer buffer) {
+        mSurface.attachAndQueueBuffer(buffer);
+        mSurface.release();
+    }
+
+    private void drawSizeMismatchSnapshot(GraphicBuffer buffer) {
+        final SurfaceSession session = new SurfaceSession(mSurface);
+
+        // Keep a reference to it such that it doesn't get destroyed when finalized.
+        mChildSurfaceControl = new SurfaceControl(session,
+                mTitle + " - task-snapshot-surface",
+                buffer.getWidth(), buffer.getHeight(), buffer.getFormat(), HIDDEN);
+        Surface surface = new Surface();
+        surface.copyFrom(mChildSurfaceControl);
+
+        // Clip off ugly navigation bar.
+        final Rect crop = calculateSnapshotCrop();
+        final Rect frame = calculateSnapshotFrame(crop);
+        SurfaceControl.openTransaction();
+        try {
+            // We can just show the surface here as it will still be hidden as the parent is
+            // still hidden.
+            mChildSurfaceControl.show();
+            mChildSurfaceControl.setWindowCrop(crop);
+            mChildSurfaceControl.setPosition(frame.left, frame.top);
+
+            // Scale the mismatch dimensions to fill the task bounds
+            final float scale = 1 / mSnapshot.getScale();
+            mChildSurfaceControl.setMatrix(scale, 0, 0, scale);
+        } finally {
+            SurfaceControl.closeTransaction();
+        }
+        surface.attachAndQueueBuffer(buffer);
+        surface.release();
+
+        final Canvas c = mSurface.lockCanvas(null);
+        drawBackgroundAndBars(c, frame);
+        mSurface.unlockCanvasAndPost(c);
+        mSurface.release();
+    }
+
+    /**
+     * Calculates the snapshot crop in snapshot coordinate space.
+     *
+     * @return crop rect in snapshot coordinate space.
+     */
+    @VisibleForTesting
+    Rect calculateSnapshotCrop() {
+        final Rect rect = new Rect();
+        rect.set(0, 0, mSnapshot.getSnapshot().getWidth(), mSnapshot.getSnapshot().getHeight());
+        final Rect insets = mSnapshot.getContentInsets();
+
+        // Let's remove all system decorations except the status bar, but only if the task is at the
+        // very top of the screen.
+        rect.inset((int) (insets.left * mSnapshot.getScale()),
+                mTaskBounds.top != 0 ? (int) (insets.top * mSnapshot.getScale()) : 0,
+                (int) (insets.right * mSnapshot.getScale()),
+                (int) (insets.bottom * mSnapshot.getScale()));
+        return rect;
+    }
+
+    /**
+     * Calculates the snapshot frame in window coordinate space from crop.
+     *
+     * @param crop rect that is in snapshot coordinate space.
+     */
+    @VisibleForTesting
+    Rect calculateSnapshotFrame(Rect crop) {
+        final Rect frame = new Rect(crop);
+        final float scale = mSnapshot.getScale();
+
+        // Rescale the frame from snapshot to window coordinate space
+        frame.scale(1 / scale);
+
+        // By default, offset it to to top/left corner
+        frame.offsetTo((int) (-crop.left / scale), (int) (-crop.top / scale));
+
+        // However, we also need to make space for the navigation bar on the left side.
+        final int colorViewLeftInset = getColorViewLeftInset(mStableInsets.left,
+                mContentInsets.left);
+        frame.offset(colorViewLeftInset, 0);
+        return frame;
+    }
+
+    @VisibleForTesting
+    void drawBackgroundAndBars(Canvas c, Rect frame) {
+        final int statusBarHeight = mSystemBarBackgroundPainter.getStatusBarColorViewHeight();
+        final boolean fillHorizontally = c.getWidth() > frame.right;
+        final boolean fillVertically = c.getHeight() > frame.bottom;
+        if (fillHorizontally) {
+            c.drawRect(frame.right, alpha(mStatusBarColor) == 0xFF ? statusBarHeight : 0,
+                    c.getWidth(), fillVertically
+                            ? frame.bottom
+                            : c.getHeight(),
+                    mBackgroundPaint);
+        }
+        if (fillVertically) {
+            c.drawRect(0, frame.bottom, c.getWidth(), c.getHeight(), mBackgroundPaint);
+        }
+        mSystemBarBackgroundPainter.drawDecors(c, frame);
+    }
+
+    private void reportDrawn() {
+        try {
+            mSession.finishDrawing(mWindow);
+        } catch (RemoteException e) {
+            // Local call.
+        }
+    }
+
+    private static Handler sHandler = new Handler(Looper.getMainLooper()) {
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_REPORT_DRAW:
+                    final boolean hasDrawn;
+                    final TaskSnapshotSurface surface = (TaskSnapshotSurface) msg.obj;
+                    synchronized (surface.mService.mWindowMap) {
+                        hasDrawn = surface.mHasDrawn;
+                    }
+                    if (hasDrawn) {
+                        surface.reportDrawn();
+                    }
+                    break;
+            }
+        }
+    };
+
+    @VisibleForTesting
+    static class Window extends BaseIWindow {
+
+        private TaskSnapshotSurface mOuter;
+
+        public void setOuter(TaskSnapshotSurface outer) {
+            mOuter = outer;
+        }
+
+        @Override
+        public void resized(Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets,
+                Rect stableInsets, Rect outsets, boolean reportDraw,
+                MergedConfiguration mergedConfiguration, Rect backDropFrame, boolean forceLayout,
+                boolean alwaysConsumeNavBar, int displayId) {
+            if (mergedConfiguration != null && mOuter != null
+                    && mOuter.mOrientationOnCreation
+                            != mergedConfiguration.getMergedConfiguration().orientation) {
+
+                // The orientation of the screen is changing. We better remove the snapshot ASAP as
+                // we are going to wait on the new window in any case to unfreeze the screen, and
+                // the starting window is not needed anymore.
+                sHandler.post(mOuter::remove);
+            }
+            if (reportDraw) {
+                sHandler.obtainMessage(MSG_REPORT_DRAW, mOuter).sendToTarget();
+            }
+        }
+    }
+
+    /**
+     * Helper class to draw the background of the system bars in regions the task snapshot isn't
+     * filling the window.
+     */
+    static class SystemBarBackgroundPainter {
+
+        private final Rect mContentInsets = new Rect();
+        private final Rect mStableInsets = new Rect();
+        private final Paint mStatusBarPaint = new Paint();
+        private final Paint mNavigationBarPaint = new Paint();
+        private final int mStatusBarColor;
+        private final int mNavigationBarColor;
+        private final int mWindowFlags;
+        private final int mWindowPrivateFlags;
+        private final int mSysUiVis;
+
+        SystemBarBackgroundPainter( int windowFlags, int windowPrivateFlags, int sysUiVis,
+                int statusBarColor, int navigationBarColor) {
+            mWindowFlags = windowFlags;
+            mWindowPrivateFlags = windowPrivateFlags;
+            mSysUiVis = sysUiVis;
+            final Context context = ActivityThread.currentActivityThread().getSystemUiContext();
+            mStatusBarColor = DecorView.calculateStatusBarColor(windowFlags,
+                    context.getColor(R.color.system_bar_background_semi_transparent),
+                    statusBarColor);
+            mNavigationBarColor = navigationBarColor;
+            mStatusBarPaint.setColor(mStatusBarColor);
+            mNavigationBarPaint.setColor(navigationBarColor);
+        }
+
+        void setInsets(Rect contentInsets, Rect stableInsets) {
+            mContentInsets.set(contentInsets);
+            mStableInsets.set(stableInsets);
+        }
+
+        int getStatusBarColorViewHeight() {
+            final boolean forceStatusBarBackground =
+                    (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND) != 0;
+            if (STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
+                    mSysUiVis, mStatusBarColor, mWindowFlags, forceStatusBarBackground)) {
+                return getColorViewTopInset(mStableInsets.top, mContentInsets.top);
+            } else {
+                return 0;
+            }
+        }
+
+        private boolean isNavigationBarColorViewVisible() {
+            return NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
+                    mSysUiVis, mNavigationBarColor, mWindowFlags, false /* force */);
+        }
+
+        void drawDecors(Canvas c, @Nullable Rect alreadyDrawnFrame) {
+            drawStatusBarBackground(c, alreadyDrawnFrame, getStatusBarColorViewHeight());
+            drawNavigationBarBackground(c);
+        }
+
+        @VisibleForTesting
+        void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame,
+                int statusBarHeight) {
+            if (statusBarHeight > 0
+                    && (alreadyDrawnFrame == null || c.getWidth() > alreadyDrawnFrame.right)) {
+                final int rightInset = DecorView.getColorViewRightInset(mStableInsets.right,
+                        mContentInsets.right);
+                final int left = alreadyDrawnFrame != null ? alreadyDrawnFrame.right : 0;
+                c.drawRect(left, 0, c.getWidth() - rightInset, statusBarHeight, mStatusBarPaint);
+            }
+        }
+
+        @VisibleForTesting
+        void drawNavigationBarBackground(Canvas c) {
+            final Rect navigationBarRect = new Rect();
+            getNavigationBarRect(c.getWidth(), c.getHeight(), mStableInsets, mContentInsets,
+                    navigationBarRect);
+            final boolean visible = isNavigationBarColorViewVisible();
+            if (visible && !navigationBarRect.isEmpty()) {
+                c.drawRect(navigationBarRect, mNavigationBarPaint);
+            }
+        }
+    }
+}
diff --git a/com/android/server/wm/TaskStack.java b/com/android/server/wm/TaskStack.java
new file mode 100644
index 0000000..4664dcb
--- /dev/null
+++ b/com/android/server/wm/TaskStack.java
@@ -0,0 +1,1663 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.DOCKED_BOTTOM;
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
+import static android.view.WindowManager.DOCKED_TOP;
+import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;
+import static com.android.server.wm.proto.StackProto.ANIMATION_BACKGROUND_SURFACE_IS_DIMMING;
+import static com.android.server.wm.proto.StackProto.BOUNDS;
+import static com.android.server.wm.proto.StackProto.FILLS_PARENT;
+import static com.android.server.wm.proto.StackProto.ID;
+import static com.android.server.wm.proto.StackProto.TASKS;
+
+import android.app.ActivityManager.StackId;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.RemoteException;
+import android.util.EventLog;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
+import android.view.DisplayInfo;
+import android.view.Surface;
+
+import com.android.internal.policy.DividerSnapAlgorithm;
+import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
+import com.android.internal.policy.DockedDividerUtils;
+import com.android.server.EventLogTags;
+
+import java.io.PrintWriter;
+
+public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLayerUser,
+        BoundsAnimationTarget {
+    /** Minimum size of an adjusted stack bounds relative to original stack bounds. Used to
+     * restrict IME adjustment so that a min portion of top stack remains visible.*/
+    private static final float ADJUSTED_STACK_FRACTION_MIN = 0.3f;
+
+    /** Dimming amount for non-focused stack when stacks are IME-adjusted. */
+    private static final float IME_ADJUST_DIM_AMOUNT = 0.25f;
+
+    /** Unique identifier */
+    final int mStackId;
+
+    /** The service */
+    private final WindowManagerService mService;
+
+    /** The display this stack sits under. */
+    // TODO: Track parent marks like this in WindowContainer.
+    private DisplayContent mDisplayContent;
+
+    /** For comparison with DisplayContent bounds. */
+    private Rect mTmpRect = new Rect();
+    private Rect mTmpRect2 = new Rect();
+    private Rect mTmpRect3 = new Rect();
+
+    /** Content limits relative to the DisplayContent this sits in. */
+    private Rect mBounds = new Rect();
+
+    /** Stack bounds adjusted to screen content area (taking into account IM windows, etc.) */
+    private final Rect mAdjustedBounds = new Rect();
+
+    /**
+     * Fully adjusted IME bounds. These are different from {@link #mAdjustedBounds} because they
+     * represent the state when the animation has ended.
+     */
+    private final Rect mFullyAdjustedImeBounds = new Rect();
+
+    /** Whether mBounds is fullscreen */
+    private boolean mFillsParent = true;
+
+    // Device rotation as of the last time {@link #mBounds} was set.
+    private int mRotation;
+
+    /** Density as of last time {@link #mBounds} was set. */
+    private int mDensity;
+
+    /** Support for non-zero {@link android.view.animation.Animation#getBackgroundColor()} */
+    private DimLayer mAnimationBackgroundSurface;
+
+    /** The particular window with an Animation with non-zero background color. */
+    private WindowStateAnimator mAnimationBackgroundAnimator;
+
+    /** Application tokens that are exiting, but still on screen for animations. */
+    final AppTokenList mExitingAppTokens = new AppTokenList();
+    final AppTokenList mTmpAppTokens = new AppTokenList();
+
+    /** Detach this stack from its display when animation completes. */
+    // TODO: maybe tie this to WindowContainer#removeChild some how...
+    boolean mDeferRemoval;
+
+    private final Rect mTmpAdjustedBounds = new Rect();
+    private boolean mAdjustedForIme;
+    private boolean mImeGoingAway;
+    private WindowState mImeWin;
+    private float mMinimizeAmount;
+    private float mAdjustImeAmount;
+    private float mAdjustDividerAmount;
+    private final int mDockedStackMinimizeThickness;
+
+    // If this is true, we are in the bounds animating mode. The task will be down or upscaled to
+    // perfectly fit the region it would have been cropped to. We may also avoid certain logic we
+    // would otherwise apply while resizing, while resizing in the bounds animating mode.
+    private boolean mBoundsAnimating = false;
+    // Set when an animation has been requested but has not yet started from the UI thread. This is
+    // cleared when the animation actually starts.
+    private boolean mBoundsAnimatingRequested = false;
+    private boolean mBoundsAnimatingToFullscreen = false;
+    private boolean mCancelCurrentBoundsAnimation = false;
+    private Rect mBoundsAnimationTarget = new Rect();
+    private Rect mBoundsAnimationSourceHintBounds = new Rect();
+
+    // Temporary storage for the new bounds that should be used after the configuration change.
+    // Will be cleared once the client retrieves the new bounds via getBoundsForNewConfiguration().
+    private final Rect mBoundsAfterRotation = new Rect();
+
+    Rect mPreAnimationBounds = new Rect();
+
+    TaskStack(WindowManagerService service, int stackId) {
+        mService = service;
+        mStackId = stackId;
+        mDockedStackMinimizeThickness = service.mContext.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.docked_stack_minimize_thickness);
+        EventLog.writeEvent(EventLogTags.WM_STACK_CREATED, stackId);
+    }
+
+    DisplayContent getDisplayContent() {
+        return mDisplayContent;
+    }
+
+    Task findHomeTask() {
+        if (!isActivityTypeHome() || mChildren.isEmpty()) {
+            return null;
+        }
+        return mChildren.get(mChildren.size() - 1);
+    }
+
+    /**
+     * Set the bounds of the stack and its containing tasks.
+     * @param stackBounds New stack bounds. Passing in null sets the bounds to fullscreen.
+     * @param configs Configuration for individual tasks, keyed by task id.
+     * @param taskBounds Bounds for individual tasks, keyed by task id.
+     * @return True if the stack bounds was changed.
+     * */
+    boolean setBounds(
+            Rect stackBounds, SparseArray<Configuration> configs, SparseArray<Rect> taskBounds,
+            SparseArray<Rect> taskTempInsetBounds) {
+        setBounds(stackBounds);
+
+        // Update bounds of containing tasks.
+        for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; --taskNdx) {
+            final Task task = mChildren.get(taskNdx);
+            Configuration config = configs.get(task.mTaskId);
+            if (config != null) {
+                Rect bounds = taskBounds.get(task.mTaskId);
+                task.resizeLocked(bounds, config, false /* forced */);
+                task.setTempInsetBounds(taskTempInsetBounds != null ?
+                        taskTempInsetBounds.get(task.mTaskId) : null);
+            } else {
+                Slog.wtf(TAG_WM, "No config for task: " + task + ", is there a mismatch with AM?");
+            }
+        }
+        return true;
+    }
+
+    void prepareFreezingTaskBounds() {
+        for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; --taskNdx) {
+            final Task task = mChildren.get(taskNdx);
+            task.prepareFreezingBounds();
+        }
+    }
+
+    /**
+     * Overrides the adjusted bounds, i.e. sets temporary layout bounds which are different from
+     * the normal task bounds.
+     *
+     * @param bounds The adjusted bounds.
+     */
+    private void setAdjustedBounds(Rect bounds) {
+        if (mAdjustedBounds.equals(bounds) && !isAnimatingForIme()) {
+            return;
+        }
+
+        mAdjustedBounds.set(bounds);
+        final boolean adjusted = !mAdjustedBounds.isEmpty();
+        Rect insetBounds = null;
+        if (adjusted && isAdjustedForMinimizedDockedStack()) {
+            insetBounds = mBounds;
+        } else if (adjusted && mAdjustedForIme) {
+            if (mImeGoingAway) {
+                insetBounds = mBounds;
+            } else {
+                insetBounds = mFullyAdjustedImeBounds;
+            }
+        }
+        alignTasksToAdjustedBounds(adjusted ? mAdjustedBounds : mBounds, insetBounds);
+        mDisplayContent.setLayoutNeeded();
+    }
+
+    private void alignTasksToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds) {
+        if (mFillsParent) {
+            return;
+        }
+
+        final boolean alignBottom = mAdjustedForIme && getDockSide() == DOCKED_TOP;
+
+        // Update bounds of containing tasks.
+        for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; --taskNdx) {
+            final Task task = mChildren.get(taskNdx);
+            task.alignToAdjustedBounds(adjustedBounds, tempInsetBounds, alignBottom);
+        }
+    }
+
+    private boolean setBounds(Rect bounds) {
+        boolean oldFullscreen = mFillsParent;
+        int rotation = Surface.ROTATION_0;
+        int density = DENSITY_DPI_UNDEFINED;
+        if (mDisplayContent != null) {
+            mDisplayContent.getLogicalDisplayRect(mTmpRect);
+            rotation = mDisplayContent.getDisplayInfo().rotation;
+            density = mDisplayContent.getDisplayInfo().logicalDensityDpi;
+            mFillsParent = bounds == null;
+            if (mFillsParent) {
+                bounds = mTmpRect;
+            }
+        }
+
+        if (bounds == null) {
+            // Can't set to fullscreen if we don't have a display to get bounds from...
+            return false;
+        }
+        if (mBounds.equals(bounds) && oldFullscreen == mFillsParent && mRotation == rotation) {
+            return false;
+        }
+
+        if (mDisplayContent != null) {
+            mDisplayContent.mDimLayerController.updateDimLayer(this);
+            mAnimationBackgroundSurface.setBounds(bounds);
+        }
+
+        mBounds.set(bounds);
+        mRotation = rotation;
+        mDensity = density;
+
+        updateAdjustedBounds();
+
+        return true;
+    }
+
+    /** Bounds of the stack without adjusting for other factors in the system like visibility
+     * of docked stack.
+     * Most callers should be using {@link #getBounds} as it take into consideration other system
+     * factors. */
+    void getRawBounds(Rect out) {
+        out.set(mBounds);
+    }
+
+    /** Return true if the current bound can get outputted to the rest of the system as-is. */
+    private boolean useCurrentBounds() {
+        if (mFillsParent
+                || !inSplitScreenSecondaryWindowingMode()
+                || mDisplayContent == null
+                || mDisplayContent.getDockedStackLocked() != null) {
+            return true;
+        }
+        return false;
+    }
+
+    public void getBounds(Rect out) {
+        if (useCurrentBounds()) {
+            // If we're currently adjusting for IME or minimized docked stack, we use the adjusted
+            // bounds; otherwise, 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.
+            if (!mAdjustedBounds.isEmpty()) {
+                out.set(mAdjustedBounds);
+            } else {
+                out.set(mBounds);
+            }
+            return;
+        }
+
+        // The bounds has been adjusted to accommodate for a docked stack, but the docked stack
+        // is not currently visible. Go ahead a represent it as fullscreen to the rest of the
+        // system.
+        mDisplayContent.getLogicalDisplayRect(out);
+    }
+
+    /**
+     * Sets the bounds animation target bounds ahead of an animation.  This can't currently be done
+     * in onAnimationStart() since that is started on the UiThread.
+     */
+    void setAnimationFinalBounds(Rect sourceHintBounds, Rect destBounds, boolean toFullscreen) {
+        mBoundsAnimatingRequested = true;
+        mBoundsAnimatingToFullscreen = toFullscreen;
+        if (destBounds != null) {
+            mBoundsAnimationTarget.set(destBounds);
+        } else {
+            mBoundsAnimationTarget.setEmpty();
+        }
+        if (sourceHintBounds != null) {
+            mBoundsAnimationSourceHintBounds.set(sourceHintBounds);
+        } else {
+            mBoundsAnimationSourceHintBounds.setEmpty();
+        }
+
+        mPreAnimationBounds.set(mBounds);
+    }
+
+    /**
+     * @return the final bounds for the bounds animation.
+     */
+    void getFinalAnimationBounds(Rect outBounds) {
+        outBounds.set(mBoundsAnimationTarget);
+    }
+
+    /**
+     * @return the final source bounds for the bounds animation.
+     */
+    void getFinalAnimationSourceHintBounds(Rect outBounds) {
+        outBounds.set(mBoundsAnimationSourceHintBounds);
+    }
+
+    /**
+     * @return the final animation bounds if the task stack is currently being animated, or the
+     *         current stack bounds otherwise.
+     */
+    void getAnimationOrCurrentBounds(Rect outBounds) {
+        if ((mBoundsAnimatingRequested || mBoundsAnimating) && !mBoundsAnimationTarget.isEmpty()) {
+            getFinalAnimationBounds(outBounds);
+            return;
+        }
+        getBounds(outBounds);
+    }
+
+    /** Bounds of the stack with other system factors taken into consideration. */
+    @Override
+    public void getDimBounds(Rect out) {
+        getBounds(out);
+    }
+
+    void updateDisplayInfo(Rect bounds) {
+        if (mDisplayContent == null) {
+            return;
+        }
+
+        for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; --taskNdx) {
+            mChildren.get(taskNdx).updateDisplayInfo(mDisplayContent);
+        }
+        if (bounds != null) {
+            setBounds(bounds);
+            return;
+        } else if (mFillsParent) {
+            setBounds(null);
+            return;
+        }
+
+        mTmpRect2.set(mBounds);
+        final int newRotation = mDisplayContent.getDisplayInfo().rotation;
+        final int newDensity = mDisplayContent.getDisplayInfo().logicalDensityDpi;
+        if (mRotation == newRotation && mDensity == newDensity) {
+            setBounds(mTmpRect2);
+        }
+
+        // If the rotation or density didn't match, we'll update it in onConfigurationChanged.
+    }
+
+    /** @return true if bounds were updated to some non-empty value. */
+    boolean updateBoundsAfterConfigChange() {
+        if (mDisplayContent == null) {
+            // If the stack is already detached we're not updating anything,
+            // as it's going away soon anyway.
+            return false;
+        }
+
+        if (mStackId == PINNED_STACK_ID) {
+            getAnimationOrCurrentBounds(mTmpRect2);
+            boolean updated = mDisplayContent.mPinnedStackControllerLocked.onTaskStackBoundsChanged(
+                    mTmpRect2, mTmpRect3);
+            if (updated) {
+                mBoundsAfterRotation.set(mTmpRect3);
+
+                // Once we've set the bounds based on the rotation of the old bounds in the new
+                // orientation, clear the animation target bounds since they are obsolete, and
+                // cancel any currently running animations
+                mBoundsAnimationTarget.setEmpty();
+                mBoundsAnimationSourceHintBounds.setEmpty();
+                mCancelCurrentBoundsAnimation = true;
+                return true;
+            }
+        }
+
+        final int newRotation = getDisplayInfo().rotation;
+        final int newDensity = getDisplayInfo().logicalDensityDpi;
+
+        if (mRotation == newRotation && mDensity == newDensity) {
+            // Nothing to do here as we already update the state in updateDisplayInfo.
+            return false;
+        }
+
+        if (mFillsParent) {
+            // Update stack bounds again since rotation changed since updateDisplayInfo().
+            setBounds(null);
+            // Return false since we don't need the client to resize.
+            return false;
+        }
+
+        mTmpRect2.set(mBounds);
+        mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
+        switch (mStackId) {
+            case DOCKED_STACK_ID:
+                repositionDockedStackAfterRotation(mTmpRect2);
+                snapDockedStackAfterRotation(mTmpRect2);
+                final int newDockSide = getDockSide(mTmpRect2);
+
+                // Update the dock create mode and clear the dock create bounds, these
+                // might change after a rotation and the original values will be invalid.
+                mService.setDockedStackCreateStateLocked(
+                        (newDockSide == DOCKED_LEFT || newDockSide == DOCKED_TOP)
+                                ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
+                                : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT,
+                        null);
+                mDisplayContent.getDockedDividerController().notifyDockSideChanged(newDockSide);
+                break;
+        }
+
+        mBoundsAfterRotation.set(mTmpRect2);
+        return true;
+    }
+
+    void getBoundsForNewConfiguration(Rect outBounds) {
+        outBounds.set(mBoundsAfterRotation);
+        mBoundsAfterRotation.setEmpty();
+    }
+
+    /**
+     * Some dock sides are not allowed by the policy. This method queries the policy and moves
+     * the docked stack around if needed.
+     *
+     * @param inOutBounds the bounds of the docked stack to adjust
+     */
+    private void repositionDockedStackAfterRotation(Rect inOutBounds) {
+        int dockSide = getDockSide(inOutBounds);
+        if (mService.mPolicy.isDockSideAllowed(dockSide)) {
+            return;
+        }
+        mDisplayContent.getLogicalDisplayRect(mTmpRect);
+        dockSide = DockedDividerUtils.invertDockSide(dockSide);
+        switch (dockSide) {
+            case DOCKED_LEFT:
+                int movement = inOutBounds.left;
+                inOutBounds.left -= movement;
+                inOutBounds.right -= movement;
+                break;
+            case DOCKED_RIGHT:
+                movement = mTmpRect.right - inOutBounds.right;
+                inOutBounds.left += movement;
+                inOutBounds.right += movement;
+                break;
+            case DOCKED_TOP:
+                movement = inOutBounds.top;
+                inOutBounds.top -= movement;
+                inOutBounds.bottom -= movement;
+                break;
+            case DOCKED_BOTTOM:
+                movement = mTmpRect.bottom - inOutBounds.bottom;
+                inOutBounds.top += movement;
+                inOutBounds.bottom += movement;
+                break;
+        }
+    }
+
+    /**
+     * Snaps the bounds after rotation to the closest snap target for the docked stack.
+     */
+    private void snapDockedStackAfterRotation(Rect outBounds) {
+
+        // Calculate the current position.
+        final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
+        final int dividerSize = mDisplayContent.getDockedDividerController().getContentWidth();
+        final int dockSide = getDockSide(outBounds);
+        final int dividerPosition = DockedDividerUtils.calculatePositionForBounds(outBounds,
+                dockSide, dividerSize);
+        final int displayWidth = mDisplayContent.getDisplayInfo().logicalWidth;
+        final int displayHeight = mDisplayContent.getDisplayInfo().logicalHeight;
+
+        // Snap the position to a target.
+        final int rotation = displayInfo.rotation;
+        final int orientation = mDisplayContent.getConfiguration().orientation;
+        mService.mPolicy.getStableInsetsLw(rotation, displayWidth, displayHeight, outBounds);
+        final DividerSnapAlgorithm algorithm = new DividerSnapAlgorithm(
+                mService.mContext.getResources(), displayWidth, displayHeight,
+                dividerSize, orientation == Configuration.ORIENTATION_PORTRAIT, outBounds,
+                isMinimizedDockAndHomeStackResizable());
+        final SnapTarget target = algorithm.calculateNonDismissingSnapTarget(dividerPosition);
+
+        // Recalculate the bounds based on the position of the target.
+        DockedDividerUtils.calculateBoundsForPosition(target.position, dockSide,
+                outBounds, displayInfo.logicalWidth, displayInfo.logicalHeight,
+                dividerSize);
+    }
+
+    // TODO: Checkout the call points of this method and the ones below to see how they can fit in WC.
+    void addTask(Task task, int position) {
+        addTask(task, position, task.showForAllUsers(), true /* moveParents */);
+    }
+
+    /**
+     * Put a Task in this stack. Used for adding only.
+     * When task is added to top of the stack, the entire branch of the hierarchy (including stack
+     * and display) will be brought to top.
+     * @param task The task to add.
+     * @param position Target position to add the task to.
+     * @param showForAllUsers Whether to show the task regardless of the current user.
+     */
+    void addTask(Task task, int position, boolean showForAllUsers, boolean moveParents) {
+        final TaskStack currentStack = task.mStack;
+        // TODO: We pass stack to task's constructor, but we still need to call this method.
+        // This doesn't make sense, mStack will already be set equal to "this" at this point.
+        if (currentStack != null && currentStack.mStackId != mStackId) {
+            throw new IllegalStateException("Trying to add taskId=" + task.mTaskId
+                    + " to stackId=" + mStackId
+                    + ", but it is already attached to stackId=" + task.mStack.mStackId);
+        }
+
+        // Add child task.
+        task.mStack = this;
+        addChild(task, null);
+
+        // Move child to a proper position, as some restriction for position might apply.
+        positionChildAt(position, task, moveParents /* includingParents */, showForAllUsers);
+    }
+
+    @Override
+    void positionChildAt(int position, Task child, boolean includingParents) {
+        positionChildAt(position, child, includingParents, child.showForAllUsers());
+    }
+
+    /**
+     * Overridden version of {@link TaskStack#positionChildAt(int, Task, boolean)}. Used in
+     * {@link TaskStack#addTask(Task, int, boolean showForAllUsers, boolean)}, as it can receive
+     * showForAllUsers param from {@link AppWindowToken} instead of {@link Task#showForAllUsers()}.
+     */
+    private void positionChildAt(int position, Task child, boolean includingParents,
+            boolean showForAllUsers) {
+        final int targetPosition = findPositionForTask(child, position, showForAllUsers,
+                false /* addingNew */);
+        super.positionChildAt(targetPosition, child, includingParents);
+
+        // Log positioning.
+        if (DEBUG_TASK_MOVEMENT)
+            Slog.d(TAG_WM, "positionTask: task=" + this + " position=" + position);
+
+        final int toTop = targetPosition == mChildren.size() - 1 ? 1 : 0;
+        EventLog.writeEvent(EventLogTags.WM_TASK_MOVED, child.mTaskId, toTop, targetPosition);
+    }
+
+    // TODO: We should really have users as a window container in the hierarchy so that we don't
+    // have to do complicated things like we are doing in this method.
+    private int findPositionForTask(Task task, int targetPosition, boolean showForAllUsers,
+            boolean addingNew) {
+        final boolean canShowTask =
+                showForAllUsers || mService.isCurrentProfileLocked(task.mUserId);
+
+        final int stackSize = mChildren.size();
+        int minPosition = 0;
+        int maxPosition = addingNew ? stackSize : stackSize - 1;
+
+        if (canShowTask) {
+            minPosition = computeMinPosition(minPosition, stackSize);
+        } else {
+            maxPosition = computeMaxPosition(maxPosition);
+        }
+        // Reset position based on minimum/maximum possible positions.
+        return Math.min(Math.max(targetPosition, minPosition), maxPosition);
+    }
+
+    /** Calculate the minimum possible position for a task that can be shown to the user.
+     *  The minimum position will be above all other tasks that can't be shown.
+     *  @param minPosition The minimum position the caller is suggesting.
+     *                  We will start adjusting up from here.
+     *  @param size The size of the current task list.
+     */
+    private int computeMinPosition(int minPosition, int size) {
+        while (minPosition < size) {
+            final Task tmpTask = mChildren.get(minPosition);
+            final boolean canShowTmpTask =
+                    tmpTask.showForAllUsers()
+                            || mService.isCurrentProfileLocked(tmpTask.mUserId);
+            if (canShowTmpTask) {
+                break;
+            }
+            minPosition++;
+        }
+        return minPosition;
+    }
+
+    /** Calculate the maximum possible position for a task that can't be shown to the user.
+     *  The maximum position will be below all other tasks that can be shown.
+     *  @param maxPosition The maximum position the caller is suggesting.
+     *                  We will start adjusting down from here.
+     */
+    private int computeMaxPosition(int maxPosition) {
+        while (maxPosition > 0) {
+            final Task tmpTask = mChildren.get(maxPosition);
+            final boolean canShowTmpTask =
+                    tmpTask.showForAllUsers()
+                            || mService.isCurrentProfileLocked(tmpTask.mUserId);
+            if (!canShowTmpTask) {
+                break;
+            }
+            maxPosition--;
+        }
+        return maxPosition;
+    }
+
+    /**
+     * Delete a Task from this stack. If it is the last Task in the stack, move this stack to the
+     * back.
+     * @param task The Task to delete.
+     */
+    @Override
+    void removeChild(Task task) {
+        if (DEBUG_TASK_MOVEMENT) Slog.d(TAG_WM, "removeChild: task=" + task);
+
+        super.removeChild(task);
+        task.mStack = null;
+
+        if (mDisplayContent != null) {
+            if (mChildren.isEmpty()) {
+                getParent().positionChildAt(POSITION_BOTTOM, this, false /* includingParents */);
+            }
+            mDisplayContent.setLayoutNeeded();
+        }
+        for (int appNdx = mExitingAppTokens.size() - 1; appNdx >= 0; --appNdx) {
+            final AppWindowToken wtoken = mExitingAppTokens.get(appNdx);
+            if (wtoken.getTask() == task) {
+                wtoken.mIsExiting = false;
+                mExitingAppTokens.remove(appNdx);
+            }
+        }
+    }
+
+    void onDisplayChanged(DisplayContent dc) {
+        if (mDisplayContent != null) {
+            throw new IllegalStateException("onDisplayChanged: Already attached");
+        }
+
+        mDisplayContent = dc;
+        mAnimationBackgroundSurface = new DimLayer(mService, this, mDisplayContent.getDisplayId(),
+                "animation background stackId=" + mStackId);
+
+        Rect bounds = null;
+        final TaskStack dockedStack = dc.getDockedStackIgnoringVisibility();
+        if (mStackId == DOCKED_STACK_ID
+                || (dockedStack != null && inSplitScreenSecondaryWindowingMode()
+                        && !dockedStack.fillsParent())) {
+            // The existence of a docked stack affects the size of other static stack created since
+            // the docked stack occupies a dedicated region on screen, but only if the dock stack is
+            // not fullscreen. If it's fullscreen, it means that we are in the transition of
+            // dismissing it, so we must not resize this stack.
+            bounds = new Rect();
+            dc.getLogicalDisplayRect(mTmpRect);
+            mTmpRect2.setEmpty();
+            if (dockedStack != null) {
+                dockedStack.getRawBounds(mTmpRect2);
+            }
+            final boolean dockedOnTopOrLeft = mService.mDockedStackCreateMode
+                    == DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+            getStackDockedModeBounds(mTmpRect, bounds, mStackId, mTmpRect2,
+                    mDisplayContent.mDividerControllerLocked.getContentWidth(),
+                    dockedOnTopOrLeft);
+        } else if (mStackId == PINNED_STACK_ID) {
+            // Update the bounds based on any changes to the display info
+            getAnimationOrCurrentBounds(mTmpRect2);
+            boolean updated = mDisplayContent.mPinnedStackControllerLocked.onTaskStackBoundsChanged(
+                    mTmpRect2, mTmpRect3);
+            if (updated) {
+                bounds = new Rect(mTmpRect3);
+            }
+        }
+
+        updateDisplayInfo(bounds);
+        super.onDisplayChanged(dc);
+    }
+
+    /**
+     * Determines the stack and task bounds of the other stack when in docked mode. The current task
+     * bounds is passed in but depending on the stack, the task and stack must match. Only in
+     * minimized mode with resizable launcher, the other stack ignores calculating the stack bounds
+     * and uses the task bounds passed in as the stack and task bounds, otherwise the stack bounds
+     * is calculated and is also used for its task bounds.
+     * If any of the out bounds are empty, it represents default bounds
+     *
+     * @param currentTempTaskBounds the current task bounds of the other stack
+     * @param outStackBounds the calculated stack bounds of the other stack
+     * @param outTempTaskBounds the calculated task bounds of the other stack
+     * @param ignoreVisibility ignore visibility in getting the stack bounds
+     */
+    void getStackDockedModeBoundsLocked(Rect currentTempTaskBounds, Rect outStackBounds,
+            Rect outTempTaskBounds, boolean ignoreVisibility) {
+        outTempTaskBounds.setEmpty();
+
+        // When the home stack is resizable, should always have the same stack and task bounds
+        if (mStackId == HOME_STACK_ID) {
+            final Task homeTask = findHomeTask();
+            if (homeTask != null && homeTask.isResizeable()) {
+                // Calculate the home stack bounds when in docked mode and the home stack is
+                // resizeable.
+                getDisplayContent().mDividerControllerLocked
+                        .getHomeStackBoundsInDockedMode(outStackBounds);
+            } else {
+                // Home stack isn't resizeable, so don't specify stack bounds.
+                outStackBounds.setEmpty();
+            }
+
+            outTempTaskBounds.set(outStackBounds);
+            return;
+        }
+
+        // When minimized state, the stack bounds for all non-home and docked stack bounds should
+        // match the passed task bounds
+        if (isMinimizedDockAndHomeStackResizable() && currentTempTaskBounds != null) {
+            outStackBounds.set(currentTempTaskBounds);
+            return;
+        }
+
+        if (!inSplitScreenWindowingMode() || mDisplayContent == null) {
+            outStackBounds.set(mBounds);
+            return;
+        }
+
+        final TaskStack dockedStack = mDisplayContent.getDockedStackIgnoringVisibility();
+        if (dockedStack == null) {
+            // Not sure why you are calling this method when there is no docked stack...
+            throw new IllegalStateException(
+                    "Calling getStackDockedModeBoundsLocked() when there is no docked stack.");
+        }
+        if (!ignoreVisibility && !dockedStack.isVisible()) {
+            // The docked stack is being dismissed, but we caught before it finished being
+            // dismissed. In that case we want to treat it as if it is not occupying any space and
+            // let others occupy the whole display.
+            mDisplayContent.getLogicalDisplayRect(outStackBounds);
+            return;
+        }
+
+        final int dockedSide = dockedStack.getDockSide();
+        if (dockedSide == DOCKED_INVALID) {
+            // Not sure how you got here...Only thing we can do is return current bounds.
+            Slog.e(TAG_WM, "Failed to get valid docked side for docked stack=" + dockedStack);
+            outStackBounds.set(mBounds);
+            return;
+        }
+
+        mDisplayContent.getLogicalDisplayRect(mTmpRect);
+        dockedStack.getRawBounds(mTmpRect2);
+        final boolean dockedOnTopOrLeft = dockedSide == DOCKED_TOP || dockedSide == DOCKED_LEFT;
+        getStackDockedModeBounds(mTmpRect, outStackBounds, mStackId, mTmpRect2,
+                mDisplayContent.mDividerControllerLocked.getContentWidth(), dockedOnTopOrLeft);
+
+    }
+
+    /**
+     * Outputs the bounds a stack should be given the presence of a docked stack on the display.
+     * @param displayRect The bounds of the display the docked stack is on.
+     * @param outBounds Output bounds that should be used for the stack.
+     * @param stackId Id of stack we are calculating the bounds for.
+     * @param dockedBounds Bounds of the docked stack.
+     * @param dockDividerWidth We need to know the width of the divider make to the output bounds
+     *                         close to the side of the dock.
+     * @param dockOnTopOrLeft If the docked stack is on the top or left side of the screen.
+     */
+    private void getStackDockedModeBounds(
+            Rect displayRect, Rect outBounds, int stackId, Rect dockedBounds, int dockDividerWidth,
+            boolean dockOnTopOrLeft) {
+        final boolean dockedStack = stackId == DOCKED_STACK_ID;
+        final boolean splitHorizontally = displayRect.width() > displayRect.height();
+
+        outBounds.set(displayRect);
+        if (dockedStack) {
+            if (mService.mDockedStackCreateBounds != null) {
+                outBounds.set(mService.mDockedStackCreateBounds);
+                return;
+            }
+
+            // The initial bounds of the docked stack when it is created about half the screen space
+            // and its bounds can be adjusted after that. The bounds of all other stacks are
+            // adjusted to occupy whatever screen space the docked stack isn't occupying.
+            final DisplayInfo di = mDisplayContent.getDisplayInfo();
+            mService.mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
+                    mTmpRect2);
+            final int position = new DividerSnapAlgorithm(mService.mContext.getResources(),
+                    di.logicalWidth,
+                    di.logicalHeight,
+                    dockDividerWidth,
+                    mDisplayContent.getConfiguration().orientation == ORIENTATION_PORTRAIT,
+                    mTmpRect2).getMiddleTarget().position;
+
+            if (dockOnTopOrLeft) {
+                if (splitHorizontally) {
+                    outBounds.right = position;
+                } else {
+                    outBounds.bottom = position;
+                }
+            } else {
+                if (splitHorizontally) {
+                    outBounds.left = position + dockDividerWidth;
+                } else {
+                    outBounds.top = position + dockDividerWidth;
+                }
+            }
+            return;
+        }
+
+        // Other stacks occupy whatever space is left by the docked stack.
+        if (!dockOnTopOrLeft) {
+            if (splitHorizontally) {
+                outBounds.right = dockedBounds.left - dockDividerWidth;
+            } else {
+                outBounds.bottom = dockedBounds.top - dockDividerWidth;
+            }
+        } else {
+            if (splitHorizontally) {
+                outBounds.left = dockedBounds.right + dockDividerWidth;
+            } else {
+                outBounds.top = dockedBounds.bottom + dockDividerWidth;
+            }
+        }
+        DockedDividerUtils.sanitizeStackBounds(outBounds, !dockOnTopOrLeft);
+    }
+
+    void resetDockedStackToMiddle() {
+        if (mStackId != DOCKED_STACK_ID) {
+            throw new IllegalStateException("Not a docked stack=" + this);
+        }
+
+        mService.mDockedStackCreateBounds = null;
+
+        final Rect bounds = new Rect();
+        final Rect tempBounds = new Rect();
+        getStackDockedModeBoundsLocked(null /* currentTempTaskBounds */, bounds, tempBounds,
+                true /*ignoreVisibility*/);
+        getController().requestResize(bounds);
+    }
+
+    @Override
+    StackWindowController getController() {
+        return (StackWindowController) super.getController();
+    }
+
+    @Override
+    void removeIfPossible() {
+        if (isAnimating()) {
+            mDeferRemoval = true;
+            return;
+        }
+        removeImmediately();
+    }
+
+    @Override
+    void removeImmediately() {
+        super.removeImmediately();
+
+        onRemovedFromDisplay();
+    }
+
+    /**
+     * Removes the stack it from its current parent, so it can be either destroyed completely or
+     * re-parented.
+     */
+    void onRemovedFromDisplay() {
+        mDisplayContent.mDimLayerController.removeDimLayerUser(this);
+        EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId);
+
+        if (mAnimationBackgroundSurface != null) {
+            mAnimationBackgroundSurface.destroySurface();
+            mAnimationBackgroundSurface = null;
+        }
+
+        if (mStackId == DOCKED_STACK_ID) {
+            mDisplayContent.mDividerControllerLocked.notifyDockedStackExistsChanged(false);
+        }
+
+        mDisplayContent = null;
+        mService.mWindowPlacerLocked.requestTraversal();
+    }
+
+    void resetAnimationBackgroundAnimator() {
+        mAnimationBackgroundAnimator = null;
+        mAnimationBackgroundSurface.hide();
+    }
+
+    void setAnimationBackground(WindowStateAnimator winAnimator, int color) {
+        int animLayer = winAnimator.mAnimLayer;
+        if (mAnimationBackgroundAnimator == null
+                || animLayer < mAnimationBackgroundAnimator.mAnimLayer) {
+            mAnimationBackgroundAnimator = winAnimator;
+            animLayer = mDisplayContent.getLayerForAnimationBackground(winAnimator);
+            mAnimationBackgroundSurface.show(animLayer - LAYER_OFFSET_DIM,
+                    ((color >> 24) & 0xff) / 255f, 0);
+        }
+    }
+
+    // TODO: Should each user have there own stacks?
+    @Override
+    void switchUser() {
+        super.switchUser();
+        int top = mChildren.size();
+        for (int taskNdx = 0; taskNdx < top; ++taskNdx) {
+            Task task = mChildren.get(taskNdx);
+            if (mService.isCurrentProfileLocked(task.mUserId) || task.showForAllUsers()) {
+                mChildren.remove(taskNdx);
+                mChildren.add(task);
+                --top;
+            }
+        }
+    }
+
+    /**
+     * Adjusts the stack bounds if the IME is visible.
+     *
+     * @param imeWin The IME window.
+     */
+    void setAdjustedForIme(WindowState imeWin, boolean forceUpdate) {
+        mImeWin = imeWin;
+        mImeGoingAway = false;
+        if (!mAdjustedForIme || forceUpdate) {
+            mAdjustedForIme = true;
+            mAdjustImeAmount = 0f;
+            mAdjustDividerAmount = 0f;
+            updateAdjustForIme(0f, 0f, true /* force */);
+        }
+    }
+
+    boolean isAdjustedForIme() {
+        return mAdjustedForIme;
+    }
+
+    boolean isAnimatingForIme() {
+        return mImeWin != null && mImeWin.isAnimatingLw();
+    }
+
+    /**
+     * Update the stack's bounds (crop or position) according to the IME window's
+     * current position. When IME window is animated, the bottom stack is animated
+     * together to track the IME window's current position, and the top stack is
+     * cropped as necessary.
+     *
+     * @return true if a traversal should be performed after the adjustment.
+     */
+    boolean updateAdjustForIme(float adjustAmount, float adjustDividerAmount, boolean force) {
+        if (adjustAmount != mAdjustImeAmount
+                || adjustDividerAmount != mAdjustDividerAmount || force) {
+            mAdjustImeAmount = adjustAmount;
+            mAdjustDividerAmount = adjustDividerAmount;
+            updateAdjustedBounds();
+            return isVisible();
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Resets the adjustment after it got adjusted for the IME.
+     * @param adjustBoundsNow if true, reset and update the bounds immediately and forget about
+     *                        animations; otherwise, set flag and animates the window away together
+     *                        with IME window.
+     */
+    void resetAdjustedForIme(boolean adjustBoundsNow) {
+        if (adjustBoundsNow) {
+            mImeWin = null;
+            mAdjustedForIme = false;
+            mImeGoingAway = false;
+            mAdjustImeAmount = 0f;
+            mAdjustDividerAmount = 0f;
+            updateAdjustedBounds();
+            mService.setResizeDimLayer(false, mStackId, 1.0f);
+        } else {
+            mImeGoingAway |= mAdjustedForIme;
+        }
+    }
+
+    /**
+     * Sets the amount how much we currently minimize our stack.
+     *
+     * @param minimizeAmount The amount, between 0 and 1.
+     * @return Whether the amount has changed and a layout is needed.
+     */
+    boolean setAdjustedForMinimizedDock(float minimizeAmount) {
+        if (minimizeAmount != mMinimizeAmount) {
+            mMinimizeAmount = minimizeAmount;
+            updateAdjustedBounds();
+            return isVisible();
+        } else {
+            return false;
+        }
+    }
+
+    boolean shouldIgnoreInput() {
+        return isAdjustedForMinimizedDockedStack() || mStackId == DOCKED_STACK_ID &&
+                isMinimizedDockAndHomeStackResizable();
+    }
+
+    /**
+     * Puts all visible tasks that are adjusted for IME into resizing mode and adds the windows
+     * to the list of to be drawn windows the service is waiting for.
+     */
+    void beginImeAdjustAnimation() {
+        for (int j = mChildren.size() - 1; j >= 0; j--) {
+            final Task task = mChildren.get(j);
+            if (task.hasContentToDisplay()) {
+                task.setDragResizing(true, DRAG_RESIZE_MODE_DOCKED_DIVIDER);
+                task.setWaitingForDrawnIfResizingChanged();
+            }
+        }
+    }
+
+    /**
+     * Resets the resizing state of all windows.
+     */
+    void endImeAdjustAnimation() {
+        for (int j = mChildren.size() - 1; j >= 0; j--) {
+            mChildren.get(j).setDragResizing(false, DRAG_RESIZE_MODE_DOCKED_DIVIDER);
+        }
+    }
+
+    int getMinTopStackBottom(final Rect displayContentRect, int originalStackBottom) {
+        return displayContentRect.top + (int)
+                ((originalStackBottom - displayContentRect.top) * ADJUSTED_STACK_FRACTION_MIN);
+    }
+
+    private boolean adjustForIME(final WindowState imeWin) {
+        final int dockedSide = getDockSide();
+        final boolean dockedTopOrBottom = dockedSide == DOCKED_TOP || dockedSide == DOCKED_BOTTOM;
+        if (imeWin == null || !dockedTopOrBottom) {
+            return false;
+        }
+
+        final Rect displayContentRect = mTmpRect;
+        final Rect contentBounds = mTmpRect2;
+
+        // Calculate the content bounds excluding the area occupied by IME
+        getDisplayContent().getContentRect(displayContentRect);
+        contentBounds.set(displayContentRect);
+        int imeTop = Math.max(imeWin.getFrameLw().top, contentBounds.top);
+
+        imeTop += imeWin.getGivenContentInsetsLw().top;
+        if (contentBounds.bottom > imeTop) {
+            contentBounds.bottom = imeTop;
+        }
+
+        final int yOffset = displayContentRect.bottom - contentBounds.bottom;
+
+        final int dividerWidth =
+                getDisplayContent().mDividerControllerLocked.getContentWidth();
+        final int dividerWidthInactive =
+                getDisplayContent().mDividerControllerLocked.getContentWidthInactive();
+
+        if (dockedSide == DOCKED_TOP) {
+            // If this stack is docked on top, we make it smaller so the bottom stack is not
+            // occluded by IME. We shift its bottom up by the height of the IME, but
+            // leaves at least 30% of the top stack visible.
+            final int minTopStackBottom =
+                    getMinTopStackBottom(displayContentRect, mBounds.bottom);
+            final int bottom = Math.max(
+                    mBounds.bottom - yOffset + dividerWidth - dividerWidthInactive,
+                    minTopStackBottom);
+            mTmpAdjustedBounds.set(mBounds);
+            mTmpAdjustedBounds.bottom =
+                    (int) (mAdjustImeAmount * bottom + (1 - mAdjustImeAmount) * mBounds.bottom);
+            mFullyAdjustedImeBounds.set(mBounds);
+        } else {
+            // When the stack is on bottom and has no focus, it's only adjusted for divider width.
+            final int dividerWidthDelta = dividerWidthInactive - dividerWidth;
+
+            // When the stack is on bottom and has focus, it needs to be moved up so as to
+            // not occluded by IME, and at the same time adjusted for divider width.
+            // We try to move it up by the height of the IME window, but only to the extent
+            // that leaves at least 30% of the top stack visible.
+            // 'top' is where the top of bottom stack will move to in this case.
+            final int topBeforeImeAdjust = mBounds.top - dividerWidth + dividerWidthInactive;
+            final int minTopStackBottom =
+                    getMinTopStackBottom(displayContentRect, mBounds.top - dividerWidth);
+            final int top = Math.max(
+                    mBounds.top - yOffset, minTopStackBottom + dividerWidthInactive);
+
+            mTmpAdjustedBounds.set(mBounds);
+            // Account for the adjustment for IME and divider width separately.
+            // (top - topBeforeImeAdjust) is the amount of movement due to IME only,
+            // and dividerWidthDelta is due to divider width change only.
+            mTmpAdjustedBounds.top = mBounds.top +
+                    (int) (mAdjustImeAmount * (top - topBeforeImeAdjust) +
+                            mAdjustDividerAmount * dividerWidthDelta);
+            mFullyAdjustedImeBounds.set(mBounds);
+            mFullyAdjustedImeBounds.top = top;
+            mFullyAdjustedImeBounds.bottom = top + mBounds.height();
+        }
+        return true;
+    }
+
+    private boolean adjustForMinimizedDockedStack(float minimizeAmount) {
+        final int dockSide = getDockSide();
+        if (dockSide == DOCKED_INVALID && !mTmpAdjustedBounds.isEmpty()) {
+            return false;
+        }
+
+        if (dockSide == DOCKED_TOP) {
+            mService.getStableInsetsLocked(DEFAULT_DISPLAY, mTmpRect);
+            int topInset = mTmpRect.top;
+            mTmpAdjustedBounds.set(mBounds);
+            mTmpAdjustedBounds.bottom =
+                    (int) (minimizeAmount * topInset + (1 - minimizeAmount) * mBounds.bottom);
+        } else if (dockSide == DOCKED_LEFT) {
+            mTmpAdjustedBounds.set(mBounds);
+            final int width = mBounds.width();
+            mTmpAdjustedBounds.right =
+                    (int) (minimizeAmount * mDockedStackMinimizeThickness
+                            + (1 - minimizeAmount) * mBounds.right);
+            mTmpAdjustedBounds.left = mTmpAdjustedBounds.right - width;
+        } else if (dockSide == DOCKED_RIGHT) {
+            mTmpAdjustedBounds.set(mBounds);
+            mTmpAdjustedBounds.left =
+                    (int) (minimizeAmount * (mBounds.right - mDockedStackMinimizeThickness)
+                            + (1 - minimizeAmount) * mBounds.left);
+        }
+        return true;
+    }
+
+    private boolean isMinimizedDockAndHomeStackResizable() {
+        return mDisplayContent.mDividerControllerLocked.isMinimizedDock()
+                && mDisplayContent.mDividerControllerLocked.isHomeStackResizable();
+    }
+
+    /**
+     * @return the distance in pixels how much the stack gets minimized from it's original size
+     */
+    int getMinimizeDistance() {
+        final int dockSide = getDockSide();
+        if (dockSide == DOCKED_INVALID) {
+            return 0;
+        }
+
+        if (dockSide == DOCKED_TOP) {
+            mService.getStableInsetsLocked(DEFAULT_DISPLAY, mTmpRect);
+            int topInset = mTmpRect.top;
+            return mBounds.bottom - topInset;
+        } else if (dockSide == DOCKED_LEFT || dockSide == DOCKED_RIGHT) {
+            return mBounds.width() - mDockedStackMinimizeThickness;
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+     * Updates the adjustment depending on it's current state.
+     */
+    private void updateAdjustedBounds() {
+        boolean adjust = false;
+        if (mMinimizeAmount != 0f) {
+            adjust = adjustForMinimizedDockedStack(mMinimizeAmount);
+        } else if (mAdjustedForIme) {
+            adjust = adjustForIME(mImeWin);
+        }
+        if (!adjust) {
+            mTmpAdjustedBounds.setEmpty();
+        }
+        setAdjustedBounds(mTmpAdjustedBounds);
+
+        final boolean isImeTarget = (mService.getImeFocusStackLocked() == this);
+        if (mAdjustedForIme && adjust && !isImeTarget) {
+            final float alpha = Math.max(mAdjustImeAmount, mAdjustDividerAmount)
+                    * IME_ADJUST_DIM_AMOUNT;
+            mService.setResizeDimLayer(true, mStackId, alpha);
+        }
+    }
+
+    void applyAdjustForImeIfNeeded(Task task) {
+        if (mMinimizeAmount != 0f || !mAdjustedForIme || mAdjustedBounds.isEmpty()) {
+            return;
+        }
+
+        final Rect insetBounds = mImeGoingAway ? mBounds : mFullyAdjustedImeBounds;
+        task.alignToAdjustedBounds(mAdjustedBounds, insetBounds, getDockSide() == DOCKED_TOP);
+        mDisplayContent.setLayoutNeeded();
+    }
+
+    boolean isAdjustedForMinimizedDockedStack() {
+        return mMinimizeAmount != 0f;
+    }
+
+    void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(ID, mStackId);
+        for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) {
+            mChildren.get(taskNdx).writeToProto(proto, TASKS);
+        }
+        proto.write(FILLS_PARENT, mFillsParent);
+        mBounds.writeToProto(proto, BOUNDS);
+        proto.write(ANIMATION_BACKGROUND_SURFACE_IS_DIMMING, mAnimationBackgroundSurface.isDimming());
+        proto.end(token);
+    }
+
+    public void dump(String prefix, PrintWriter pw) {
+        pw.println(prefix + "mStackId=" + mStackId);
+        pw.println(prefix + "mDeferRemoval=" + mDeferRemoval);
+        pw.println(prefix + "mFillsParent=" + mFillsParent);
+        pw.println(prefix + "mBounds=" + mBounds.toShortString());
+        if (mMinimizeAmount != 0f) {
+            pw.println(prefix + "mMinimizeAmount=" + mMinimizeAmount);
+        }
+        if (mAdjustedForIme) {
+            pw.println(prefix + "mAdjustedForIme=true");
+            pw.println(prefix + "mAdjustImeAmount=" + mAdjustImeAmount);
+            pw.println(prefix + "mAdjustDividerAmount=" + mAdjustDividerAmount);
+        }
+        if (!mAdjustedBounds.isEmpty()) {
+            pw.println(prefix + "mAdjustedBounds=" + mAdjustedBounds.toShortString());
+        }
+        for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) {
+            mChildren.get(taskNdx).dump(prefix + "  ", pw);
+        }
+        if (mAnimationBackgroundSurface.isDimming()) {
+            pw.println(prefix + "mWindowAnimationBackgroundSurface:");
+            mAnimationBackgroundSurface.printTo(prefix + "  ", pw);
+        }
+        if (!mExitingAppTokens.isEmpty()) {
+            pw.println();
+            pw.println("  Exiting application tokens:");
+            for (int i = mExitingAppTokens.size() - 1; i >= 0; i--) {
+                WindowToken token = mExitingAppTokens.get(i);
+                pw.print("  Exiting App #"); pw.print(i);
+                pw.print(' '); pw.print(token);
+                pw.println(':');
+                token.dump(pw, "    ");
+            }
+        }
+    }
+
+    /** Fullscreen status of the stack without adjusting for other factors in the system like
+     * visibility of docked stack.
+     * Most callers should be using {@link #fillsParent} as it take into consideration other
+     * system factors. */
+    boolean getRawFullscreen() {
+        return mFillsParent;
+    }
+
+    @Override
+    public boolean dimFullscreen() {
+        return StackId.isHomeOrRecentsStack(mStackId) || fillsParent();
+    }
+
+    @Override
+    boolean fillsParent() {
+        if (useCurrentBounds()) {
+            return mFillsParent;
+        }
+        // The bounds has been adjusted to accommodate for a docked stack, but the docked stack
+        // is not currently visible. Go ahead a represent it as fullscreen to the rest of the
+        // system.
+        return true;
+    }
+
+    @Override
+    public DisplayInfo getDisplayInfo() {
+        return mDisplayContent.getDisplayInfo();
+    }
+
+    @Override
+    public boolean isAttachedToDisplay() {
+        return mDisplayContent != null;
+    }
+
+    @Override
+    public String toString() {
+        return "{stackId=" + mStackId + " tasks=" + mChildren + "}";
+    }
+
+    String getName() {
+        return toShortString();
+    }
+
+    @Override
+    public String toShortString() {
+        return "Stack=" + mStackId;
+    }
+
+    /**
+     * For docked workspace (or workspace that's side-by-side to the docked), provides
+     * information which side of the screen was the dock anchored.
+     */
+    int getDockSide() {
+        return getDockSide(mBounds);
+    }
+
+    private int getDockSide(Rect bounds) {
+        if (!inSplitScreenWindowingMode()) {
+            return DOCKED_INVALID;
+        }
+        if (mDisplayContent == null) {
+            return DOCKED_INVALID;
+        }
+        mDisplayContent.getLogicalDisplayRect(mTmpRect);
+        final int orientation = mDisplayContent.getConfiguration().orientation;
+        return getDockSideUnchecked(bounds, mTmpRect, orientation);
+    }
+
+    static int getDockSideUnchecked(Rect bounds, Rect displayRect, int orientation) {
+        if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+            // Portrait mode, docked either at the top or the bottom.
+            if (bounds.top - displayRect.top <= displayRect.bottom - bounds.bottom) {
+                return DOCKED_TOP;
+            } else {
+                return DOCKED_BOTTOM;
+            }
+        } else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+            // Landscape mode, docked either on the left or on the right.
+            if (bounds.left - displayRect.left <= displayRect.right - bounds.right) {
+                return DOCKED_LEFT;
+            } else {
+                return DOCKED_RIGHT;
+            }
+        } else {
+            return DOCKED_INVALID;
+        }
+    }
+
+    boolean hasTaskForUser(int userId) {
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final Task task = mChildren.get(i);
+            if (task.mUserId == userId) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    int taskIdFromPoint(int x, int y) {
+        getBounds(mTmpRect);
+        if (!mTmpRect.contains(x, y) || isAdjustedForMinimizedDockedStack()) {
+            return -1;
+        }
+
+        for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; --taskNdx) {
+            final Task task = mChildren.get(taskNdx);
+            final WindowState win = task.getTopVisibleAppMainWindow();
+            if (win == null) {
+                continue;
+            }
+            // We need to use the task's dim bounds (which is derived from the visible bounds of its
+            // apps windows) for any touch-related tests. Can't use the task's original bounds
+            // because it might be adjusted to fit the content frame. For example, the presence of
+            // the IME adjusting the windows frames when the app window is the IME target.
+            task.getDimBounds(mTmpRect);
+            if (mTmpRect.contains(x, y)) {
+                return task.mTaskId;
+            }
+        }
+
+        return -1;
+    }
+
+    void findTaskForResizePoint(int x, int y, int delta,
+            DisplayContent.TaskForResizePointSearchResult results) {
+        if (!getWindowConfiguration().canResizeTask()) {
+            results.searchDone = true;
+            return;
+        }
+
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final Task task = mChildren.get(i);
+            if (task.isFullscreen()) {
+                results.searchDone = true;
+                return;
+            }
+
+            // We need to use the task's dim bounds (which is derived from the visible bounds of
+            // its apps windows) for any touch-related tests. Can't use the task's original
+            // bounds because it might be adjusted to fit the content frame. One example is when
+            // the task is put to top-left quadrant, the actual visible area would not start at
+            // (0,0) after it's adjusted for the status bar.
+            task.getDimBounds(mTmpRect);
+            mTmpRect.inset(-delta, -delta);
+            if (mTmpRect.contains(x, y)) {
+                mTmpRect.inset(delta, delta);
+
+                results.searchDone = true;
+
+                if (!mTmpRect.contains(x, y)) {
+                    results.taskForResize = task;
+                    return;
+                }
+                // User touched inside the task. No need to look further,
+                // focus transfer will be handled in ACTION_UP.
+                return;
+            }
+        }
+    }
+
+    void setTouchExcludeRegion(Task focusedTask, int delta, Region touchExcludeRegion,
+            Rect contentRect, Rect postExclude) {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final Task task = mChildren.get(i);
+            AppWindowToken token = task.getTopVisibleAppToken();
+            if (token == null || !token.hasContentToDisplay()) {
+                continue;
+            }
+
+            /**
+             * Exclusion region is the region that TapDetector doesn't care about.
+             * Here we want to remove all non-focused tasks from the exclusion region.
+             * We also remove the outside touch area for resizing for all freeform
+             * tasks (including the focused).
+             *
+             * We save the focused task region once we find it, and add it back at the end.
+             *
+             * If the task is home stack and it is resizable in the minimized state, we want to
+             * exclude the docked stack from touch so we need the entire screen area and not just a
+             * small portion which the home stack currently is resized to.
+             */
+
+            if (task.isActivityTypeHome() && isMinimizedDockAndHomeStackResizable()) {
+                mDisplayContent.getLogicalDisplayRect(mTmpRect);
+            } else {
+                task.getDimBounds(mTmpRect);
+            }
+
+            if (task == focusedTask) {
+                // Add the focused task rect back into the exclude region once we are done
+                // processing stacks.
+                postExclude.set(mTmpRect);
+            }
+
+            final boolean isFreeformed = task.inFreeformWorkspace();
+            if (task != focusedTask || isFreeformed) {
+                if (isFreeformed) {
+                    // If the task is freeformed, enlarge the area to account for outside
+                    // touch area for resize.
+                    mTmpRect.inset(-delta, -delta);
+                    // Intersect with display content rect. If we have system decor (status bar/
+                    // navigation bar), we want to exclude that from the tap detection.
+                    // Otherwise, if the app is partially placed under some system button (eg.
+                    // Recents, Home), pressing that button would cause a full series of
+                    // unwanted transfer focus/resume/pause, before we could go home.
+                    mTmpRect.intersect(contentRect);
+                }
+                touchExcludeRegion.op(mTmpRect, Region.Op.DIFFERENCE);
+            }
+        }
+    }
+
+    public boolean setPinnedStackSize(Rect stackBounds, Rect tempTaskBounds) {
+        // Hold the lock since this is called from the BoundsAnimator running on the UiThread
+        synchronized (mService.mWindowMap) {
+            if (mCancelCurrentBoundsAnimation) {
+                return false;
+            }
+        }
+
+        try {
+            mService.mActivityManager.resizePinnedStack(stackBounds, tempTaskBounds);
+        } catch (RemoteException e) {
+            // I don't believe you.
+        }
+        return true;
+    }
+
+    void onAllWindowsDrawn() {
+        if (!mBoundsAnimating && !mBoundsAnimatingRequested) {
+            return;
+        }
+
+        mService.mBoundsAnimationController.onAllWindowsDrawn();
+    }
+
+    @Override  // AnimatesBounds
+    public void onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate) {
+        // Hold the lock since this is called from the BoundsAnimator running on the UiThread
+        synchronized (mService.mWindowMap) {
+            mBoundsAnimatingRequested = false;
+            mBoundsAnimating = true;
+            mCancelCurrentBoundsAnimation = false;
+
+            // If we are changing UI mode, as in the PiP to fullscreen
+            // transition, then we need to wait for the window to draw.
+            if (schedulePipModeChangedCallback) {
+                forAllWindows((w) -> { w.mWinAnimator.resetDrawState(); },
+                        false /* traverseTopToBottom */);
+            }
+        }
+
+        if (mStackId == PINNED_STACK_ID) {
+            try {
+                mService.mActivityManager.notifyPinnedStackAnimationStarted();
+            } catch (RemoteException e) {
+                // I don't believe you...
+            }
+
+            final PinnedStackWindowController controller =
+                    (PinnedStackWindowController) getController();
+            if (schedulePipModeChangedCallback && controller != null) {
+                // We need to schedule the PiP mode change before the animation up. It is possible
+                // in this case for the animation down to not have been completed, so always
+                // force-schedule and update to the client to ensure that it is notified that it
+                // is no longer in picture-in-picture mode
+                controller.updatePictureInPictureModeForPinnedStackAnimation(null, forceUpdate);
+            }
+        }
+    }
+
+    @Override  // AnimatesBounds
+    public void onAnimationEnd(boolean schedulePipModeChangedCallback, Rect finalStackSize,
+            boolean moveToFullscreen) {
+        // Hold the lock since this is called from the BoundsAnimator running on the UiThread
+        synchronized (mService.mWindowMap) {
+            mBoundsAnimating = false;
+            for (int i = 0; i < mChildren.size(); i++) {
+                final Task t = mChildren.get(i);
+                t.clearPreserveNonFloatingState();
+            }
+            mService.requestTraversal();
+        }
+
+        if (mStackId == PINNED_STACK_ID) {
+            // Update to the final bounds if requested. This is done here instead of in the bounds
+            // animator to allow us to coordinate this after we notify the PiP mode changed
+
+            final PinnedStackWindowController controller =
+                    (PinnedStackWindowController) getController();
+            if (schedulePipModeChangedCallback && controller != null) {
+                // We need to schedule the PiP mode change after the animation down, so use the
+                // final bounds
+                controller.updatePictureInPictureModeForPinnedStackAnimation(
+                        mBoundsAnimationTarget, false /* forceUpdate */);
+            }
+
+            if (finalStackSize != null) {
+                setPinnedStackSize(finalStackSize, null);
+            }
+
+            try {
+                mService.mActivityManager.notifyPinnedStackAnimationEnded();
+                if (moveToFullscreen) {
+                    mService.mActivityManager.moveTasksToFullscreenStack(mStackId,
+                            true /* onTop */);
+                }
+            } catch (RemoteException e) {
+                // I don't believe you...
+            }
+        }
+    }
+
+    /**
+     * @return True if we are currently animating the pinned stack from fullscreen to non-fullscreen
+     *         bounds and we have a deferred PiP mode changed callback set with the animation.
+     */
+    public boolean deferScheduleMultiWindowModeChanged() {
+        if (mStackId == PINNED_STACK_ID) {
+            return (mBoundsAnimatingRequested || mBoundsAnimating);
+        }
+        return false;
+    }
+
+    public boolean isForceScaled() {
+        return mBoundsAnimating;
+    }
+
+    public boolean isAnimatingBounds() {
+        return mBoundsAnimating;
+    }
+
+    public boolean lastAnimatingBoundsWasToFullscreen() {
+        return mBoundsAnimatingToFullscreen;
+    }
+
+    public boolean isAnimatingBoundsToFullscreen() {
+        return isAnimatingBounds() && lastAnimatingBoundsWasToFullscreen();
+    }
+
+    public boolean pinnedStackResizeDisallowed() {
+        if (mBoundsAnimating && mCancelCurrentBoundsAnimation) {
+            return true;
+        }
+        return false;
+    }
+
+    /** Returns true if a removal action is still being deferred. */
+    boolean checkCompleteDeferredRemoval() {
+        if (isAnimating()) {
+            return true;
+        }
+        if (mDeferRemoval) {
+            removeImmediately();
+        }
+
+        return super.checkCompleteDeferredRemoval();
+    }
+
+    void stepAppWindowsAnimation(long currentTime) {
+        super.stepAppWindowsAnimation(currentTime);
+
+        // TODO: Why aren't we just using the loop above for this? mAppAnimator.animating isn't set
+        // below but is set in the loop above. See if it really matters...
+
+        // Clear before using.
+        mTmpAppTokens.clear();
+        // We copy the list as things can be removed from the exiting token list while we are
+        // processing.
+        mTmpAppTokens.addAll(mExitingAppTokens);
+        for (int i = 0; i < mTmpAppTokens.size(); i++) {
+            final AppWindowAnimator appAnimator = mTmpAppTokens.get(i).mAppAnimator;
+            appAnimator.wasAnimating = appAnimator.animating;
+            if (appAnimator.stepAnimationLocked(currentTime)) {
+                mService.mAnimator.setAnimating(true);
+                mService.mAnimator.mAppWindowAnimating = true;
+            } else if (appAnimator.wasAnimating) {
+                // stopped animating, do one more pass through the layout
+                appAnimator.mAppToken.setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER,
+                        "exiting appToken " + appAnimator.mAppToken + " done");
+                if (DEBUG_ANIM) Slog.v(TAG_WM,
+                        "updateWindowsApps...: done animating exiting " + appAnimator.mAppToken);
+            }
+        }
+        // Clear to avoid holding reference to tokens.
+        mTmpAppTokens.clear();
+    }
+
+    @Override
+    int getOrientation() {
+        return (StackId.canSpecifyOrientation(mStackId))
+                ? super.getOrientation() : SCREEN_ORIENTATION_UNSET;
+    }
+}
diff --git a/com/android/server/wm/TaskTapPointerEventListener.java b/com/android/server/wm/TaskTapPointerEventListener.java
new file mode 100644
index 0000000..42a2d9d
--- /dev/null
+++ b/com/android/server/wm/TaskTapPointerEventListener.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.hardware.input.InputManager;
+import android.view.MotionEvent;
+import android.view.WindowManagerPolicy.PointerEventListener;
+
+import com.android.server.wm.WindowManagerService.H;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.PointerIcon.TYPE_NOT_SPECIFIED;
+import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
+import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
+import static android.view.PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;
+import static android.view.PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
+
+public class TaskTapPointerEventListener implements PointerEventListener {
+
+    final private Region mTouchExcludeRegion = new Region();
+    private final WindowManagerService mService;
+    private final DisplayContent mDisplayContent;
+    private final Rect mTmpRect = new Rect();
+    private int mPointerIconType = TYPE_NOT_SPECIFIED;
+
+    public TaskTapPointerEventListener(WindowManagerService service,
+            DisplayContent displayContent) {
+        mService = service;
+        mDisplayContent = displayContent;
+    }
+
+    @Override
+    public void onPointerEvent(MotionEvent motionEvent, int displayId) {
+        if (displayId == getDisplayId()) {
+            onPointerEvent(motionEvent);
+        }
+    }
+
+    @Override
+    public void onPointerEvent(MotionEvent motionEvent) {
+        final int action = motionEvent.getAction();
+        switch (action & MotionEvent.ACTION_MASK) {
+            case MotionEvent.ACTION_DOWN: {
+                final int x = (int) motionEvent.getX();
+                final int y = (int) motionEvent.getY();
+
+                synchronized (this) {
+                    if (!mTouchExcludeRegion.contains(x, y)) {
+                        mService.mH.obtainMessage(H.TAP_OUTSIDE_TASK,
+                                x, y, mDisplayContent).sendToTarget();
+                    }
+                }
+            }
+            break;
+
+            case MotionEvent.ACTION_HOVER_MOVE: {
+                final int x = (int) motionEvent.getX();
+                final int y = (int) motionEvent.getY();
+                final Task task = mDisplayContent.findTaskForResizePoint(x, y);
+                int iconType = TYPE_NOT_SPECIFIED;
+                if (task != null) {
+                    task.getDimBounds(mTmpRect);
+                    if (!mTmpRect.isEmpty() && !mTmpRect.contains(x, y)) {
+                        if (x < mTmpRect.left) {
+                            iconType =
+                                (y < mTmpRect.top) ? TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW :
+                                (y > mTmpRect.bottom) ? TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW :
+                                TYPE_HORIZONTAL_DOUBLE_ARROW;
+                        } else if (x > mTmpRect.right) {
+                            iconType =
+                                (y < mTmpRect.top) ? TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW :
+                                (y > mTmpRect.bottom) ? TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW :
+                                TYPE_HORIZONTAL_DOUBLE_ARROW;
+                        } else if (y < mTmpRect.top || y > mTmpRect.bottom) {
+                            iconType = TYPE_VERTICAL_DOUBLE_ARROW;
+                        }
+                    }
+                }
+                if (mPointerIconType != iconType) {
+                    mPointerIconType = iconType;
+                    if (mPointerIconType == TYPE_NOT_SPECIFIED) {
+                        // Find the underlying window and ask it restore the pointer icon.
+                        mService.mH.obtainMessage(H.RESTORE_POINTER_ICON,
+                                x, y, mDisplayContent).sendToTarget();
+                    } else {
+                        InputManager.getInstance().setPointerIconType(mPointerIconType);
+                    }
+                }
+            }
+            break;
+        }
+    }
+
+    void setTouchExcludeRegion(Region newRegion) {
+        synchronized (this) {
+           mTouchExcludeRegion.set(newRegion);
+        }
+    }
+
+    private int getDisplayId() {
+        return mDisplayContent.getDisplayId();
+    }
+}
diff --git a/com/android/server/wm/TaskWindowContainerController.java b/com/android/server/wm/TaskWindowContainerController.java
new file mode 100644
index 0000000..65f8cdf
--- /dev/null
+++ b/com/android/server/wm/TaskWindowContainerController.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import android.app.ActivityManager.TaskDescription;
+import android.app.ActivityManager.TaskSnapshot;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.EventLog;
+import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.ref.WeakReference;
+
+import static com.android.server.EventLogTags.WM_TASK_CREATED;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
+import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+/**
+ * Controller for the task container. This is created by activity manager to link task records to
+ * the task container they use in window manager.
+ *
+ * Test class: {@link TaskWindowContainerControllerTests}
+ */
+public class TaskWindowContainerController
+        extends WindowContainerController<Task, TaskWindowContainerListener> {
+
+    private final int mTaskId;
+    private final H mHandler;
+
+    public TaskWindowContainerController(int taskId, TaskWindowContainerListener listener,
+            StackWindowController stackController, int userId, Rect bounds, int resizeMode,
+            boolean supportsPictureInPicture, boolean toTop, boolean showForAllUsers,
+            TaskDescription taskDescription) {
+        this(taskId, listener, stackController, userId, bounds, resizeMode,
+                supportsPictureInPicture, toTop, showForAllUsers, taskDescription,
+                WindowManagerService.getInstance());
+    }
+
+    public TaskWindowContainerController(int taskId, TaskWindowContainerListener listener,
+            StackWindowController stackController, int userId, Rect bounds, int resizeMode,
+            boolean supportsPictureInPicture, boolean toTop, boolean showForAllUsers,
+            TaskDescription taskDescription, WindowManagerService service) {
+        super(listener, service);
+        mTaskId = taskId;
+        mHandler = new H(new WeakReference<>(this), service.mH.getLooper());
+
+        synchronized(mWindowMap) {
+            if (DEBUG_STACK) Slog.i(TAG_WM, "TaskWindowContainerController: taskId=" + taskId
+                    + " stack=" + stackController + " bounds=" + bounds);
+
+            final TaskStack stack = stackController.mContainer;
+            if (stack == null) {
+                throw new IllegalArgumentException("TaskWindowContainerController: invalid stack="
+                        + stackController);
+            }
+            EventLog.writeEvent(WM_TASK_CREATED, taskId, stack.mStackId);
+            final Task task = createTask(taskId, stack, userId, bounds, resizeMode,
+                    supportsPictureInPicture, taskDescription);
+            final int position = toTop ? POSITION_TOP : POSITION_BOTTOM;
+            // We only want to move the parents to the parents if we are creating this task at the
+            // top of its stack.
+            stack.addTask(task, position, showForAllUsers, toTop /* moveParents */);
+        }
+    }
+
+    @VisibleForTesting
+    Task createTask(int taskId, TaskStack stack, int userId, Rect bounds, int resizeMode,
+            boolean supportsPictureInPicture, TaskDescription taskDescription) {
+        return new Task(taskId, stack, userId, mService, bounds, resizeMode,
+                supportsPictureInPicture, taskDescription, this);
+    }
+
+    @Override
+    public void removeContainer() {
+        synchronized(mWindowMap) {
+            if (mContainer == null) {
+                if (DEBUG_STACK) Slog.i(TAG_WM, "removeTask: could not find taskId=" + mTaskId);
+                return;
+            }
+            mContainer.removeIfPossible();
+            super.removeContainer();
+        }
+    }
+
+    public void positionChildAt(AppWindowContainerController childController, int position) {
+        synchronized(mService.mWindowMap) {
+            final AppWindowToken aToken = childController.mContainer;
+            if (aToken == null) {
+                Slog.w(TAG_WM,
+                        "Attempted to position of non-existing app : " + childController);
+                return;
+            }
+
+            final Task task = mContainer;
+            if (task == null) {
+                throw new IllegalArgumentException("positionChildAt: invalid task=" + this);
+            }
+            task.positionChildAt(position, aToken, false /* includeParents */);
+        }
+    }
+
+    public void reparent(StackWindowController stackController, int position, boolean moveParents) {
+        synchronized (mWindowMap) {
+            if (DEBUG_STACK) Slog.i(TAG_WM, "reparent: moving taskId=" + mTaskId
+                    + " to stack=" + stackController + " at " + position);
+            if (mContainer == null) {
+                if (DEBUG_STACK) Slog.i(TAG_WM,
+                        "reparent: could not find taskId=" + mTaskId);
+                return;
+            }
+            final TaskStack stack = stackController.mContainer;
+            if (stack == null) {
+                throw new IllegalArgumentException("reparent: could not find stack="
+                        + stackController);
+            }
+            mContainer.reparent(stack, position, moveParents);
+            mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
+        }
+    }
+
+    public void setResizeable(int resizeMode) {
+        synchronized (mWindowMap) {
+            if (mContainer != null) {
+                mContainer.setResizeable(resizeMode);
+            }
+        }
+    }
+
+    public void resize(Rect bounds, Configuration overrideConfig, boolean relayout,
+            boolean forced) {
+        synchronized (mWindowMap) {
+            if (mContainer == null) {
+                throw new IllegalArgumentException("resizeTask: taskId " + mTaskId + " not found.");
+            }
+
+            if (mContainer.resizeLocked(bounds, overrideConfig, forced) && relayout) {
+                mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
+            }
+        }
+    }
+
+    public void getBounds(Rect bounds) {
+        synchronized (mWindowMap) {
+            if (mContainer != null) {
+                mContainer.getBounds(bounds);
+                return;
+            }
+            bounds.setEmpty();
+        }
+    }
+
+    /**
+     * Puts this task into docked drag resizing mode. See {@link DragResizeMode}.
+     *
+     * @param resizing Whether to put the task into drag resize mode.
+     */
+    public void setTaskDockedResizing(boolean resizing) {
+        synchronized (mWindowMap) {
+            if (mContainer == null) {
+                Slog.w(TAG_WM, "setTaskDockedResizing: taskId " + mTaskId + " not found.");
+                return;
+            }
+            mContainer.setDragResizing(resizing, DRAG_RESIZE_MODE_DOCKED_DIVIDER);
+        }
+    }
+
+    public void cancelWindowTransition() {
+        synchronized (mWindowMap) {
+            if (mContainer == null) {
+                Slog.w(TAG_WM, "cancelWindowTransition: taskId " + mTaskId + " not found.");
+                return;
+            }
+            mContainer.cancelTaskWindowTransition();
+        }
+    }
+
+    public void cancelThumbnailTransition() {
+        synchronized (mWindowMap) {
+            if (mContainer == null) {
+                Slog.w(TAG_WM, "cancelThumbnailTransition: taskId " + mTaskId + " not found.");
+                return;
+            }
+            mContainer.cancelTaskThumbnailTransition();
+        }
+    }
+
+    public void setTaskDescription(TaskDescription taskDescription) {
+        synchronized (mWindowMap) {
+            if (mContainer == null) {
+                Slog.w(TAG_WM, "setTaskDescription: taskId " + mTaskId + " not found.");
+                return;
+            }
+            mContainer.setTaskDescription(taskDescription);
+        }
+    }
+
+    void reportSnapshotChanged(TaskSnapshot snapshot) {
+        mHandler.obtainMessage(H.REPORT_SNAPSHOT_CHANGED, snapshot).sendToTarget();
+    }
+
+    void requestResize(Rect bounds, int resizeMode) {
+        mHandler.obtainMessage(H.REQUEST_RESIZE, resizeMode, 0, bounds).sendToTarget();
+    }
+
+    @Override
+    public String toString() {
+        return "{TaskWindowContainerController taskId=" + mTaskId + "}";
+    }
+
+    private static final class H extends Handler {
+
+        static final int REPORT_SNAPSHOT_CHANGED = 0;
+        static final int REQUEST_RESIZE = 1;
+
+        private final WeakReference<TaskWindowContainerController> mController;
+
+        H(WeakReference<TaskWindowContainerController> controller, Looper looper) {
+            super(looper);
+            mController = controller;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            final TaskWindowContainerController controller = mController.get();
+            final TaskWindowContainerListener listener = (controller != null)
+                    ? controller.mListener : null;
+            if (listener == null) {
+                return;
+            }
+            switch (msg.what) {
+                case REPORT_SNAPSHOT_CHANGED:
+                    listener.onSnapshotChanged((TaskSnapshot) msg.obj);
+                    break;
+                case REQUEST_RESIZE:
+                    listener.requestResize((Rect) msg.obj, msg.arg1);
+                    break;
+            }
+        }
+    }
+}
diff --git a/com/android/server/wm/TaskWindowContainerListener.java b/com/android/server/wm/TaskWindowContainerListener.java
new file mode 100644
index 0000000..af67de3
--- /dev/null
+++ b/com/android/server/wm/TaskWindowContainerListener.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import android.app.ActivityManager.TaskSnapshot;
+import android.graphics.Rect;
+
+/**
+ * Interface used by the creator of {@link TaskWindowContainerController} to listen to changes with
+ * the task container.
+ */
+public interface TaskWindowContainerListener extends WindowContainerListener {
+
+    /** Called when the snapshot of this task has changed. */
+    void onSnapshotChanged(TaskSnapshot snapshot);
+
+    /** Called when the task container would like its controller to resize. */
+    void requestResize(Rect bounds, int resizeMode);
+}
diff --git a/com/android/server/wm/UnknownAppVisibilityController.java b/com/android/server/wm/UnknownAppVisibilityController.java
new file mode 100644
index 0000000..eb751fa
--- /dev/null
+++ b/com/android/server/wm/UnknownAppVisibilityController.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_UNKNOWN_APP_VISIBILITY;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.annotation.NonNull;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.server.wm.WindowManagerService.H;
+
+import java.io.PrintWriter;
+
+/**
+ * Manages the set of {@link AppWindowToken}s for which we don't know yet whether it's visible or
+ * not. This happens when starting an activity while the lockscreen is showing. In that case, the
+ * keyguard flags an app might set influence it's visibility, so we wait until this is resolved to
+ * start the transition to avoid flickers.
+ */
+class UnknownAppVisibilityController {
+
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "UnknownAppVisibility" : TAG_WM;
+
+    /**
+     * We are currently waiting until the app is done resuming.
+     */
+    private static final int UNKNOWN_STATE_WAITING_RESUME = 1;
+
+    /**
+     * The activity has finished resuming, and we are waiting on the next relayout.
+     */
+    private static final int UNKNOWN_STATE_WAITING_RELAYOUT = 2;
+
+    /**
+     * The client called {@link Session#relayout} with the appropriate Keyguard flags and we are
+     * waiting until activity manager has updated the visibilities of all the apps.
+     */
+    private static final int UNKNOWN_STATE_WAITING_VISIBILITY_UPDATE = 3;
+
+    // Set of apps for which we don't know yet whether it's visible or not, depending on what kind
+    // of lockscreen flags the app might set during its first relayout.
+    private final ArrayMap<AppWindowToken, Integer> mUnknownApps = new ArrayMap<>();
+
+    private final WindowManagerService mService;
+
+    UnknownAppVisibilityController(WindowManagerService service) {
+        mService = service;
+    }
+
+    boolean allResolved() {
+        return mUnknownApps.isEmpty();
+    }
+
+    void clear() {
+        mUnknownApps.clear();
+    }
+
+    String getDebugMessage() {
+        final StringBuilder builder = new StringBuilder();
+        for (int i = mUnknownApps.size() - 1; i >= 0; i--) {
+            builder.append("app=").append(mUnknownApps.keyAt(i))
+                    .append(" state=").append(mUnknownApps.valueAt(i));
+            if (i != 0) {
+                builder.append(' ');
+            }
+        }
+        return builder.toString();
+    }
+
+    void appRemovedOrHidden(@NonNull AppWindowToken appWindow) {
+        if (DEBUG_UNKNOWN_APP_VISIBILITY) {
+            Slog.d(TAG, "App removed or hidden appWindow=" + appWindow);
+        }
+        mUnknownApps.remove(appWindow);
+    }
+
+    /**
+     * Notifies that {@param appWindow} has been launched behind Keyguard, and we need to wait until
+     * it is resumed and relaid out to resolve the visibility.
+     */
+    void notifyLaunched(@NonNull AppWindowToken appWindow) {
+        if (DEBUG_UNKNOWN_APP_VISIBILITY) {
+            Slog.d(TAG, "App launched appWindow=" + appWindow);
+        }
+        mUnknownApps.put(appWindow, UNKNOWN_STATE_WAITING_RESUME);
+    }
+
+    /**
+     * Notifies that {@param appWindow} has finished resuming.
+     */
+    void notifyAppResumedFinished(@NonNull AppWindowToken appWindow) {
+        if (mUnknownApps.containsKey(appWindow)
+                && mUnknownApps.get(appWindow) == UNKNOWN_STATE_WAITING_RESUME) {
+            if (DEBUG_UNKNOWN_APP_VISIBILITY) {
+                Slog.d(TAG, "App resume finished appWindow=" + appWindow);
+            }
+            mUnknownApps.put(appWindow, UNKNOWN_STATE_WAITING_RELAYOUT);
+        }
+    }
+
+    /**
+     * Notifies that {@param appWindow} has relaid out.
+     */
+    void notifyRelayouted(@NonNull AppWindowToken appWindow) {
+        if (!mUnknownApps.containsKey(appWindow)) {
+            return;
+        }
+        if (DEBUG_UNKNOWN_APP_VISIBILITY) {
+            Slog.d(TAG, "App relayouted appWindow=" + appWindow);
+        }
+        int state = mUnknownApps.get(appWindow);
+        if (state == UNKNOWN_STATE_WAITING_RELAYOUT) {
+            mUnknownApps.put(appWindow, UNKNOWN_STATE_WAITING_VISIBILITY_UPDATE);
+            mService.notifyKeyguardFlagsChanged(this::notifyVisibilitiesUpdated);
+        }
+    }
+
+    private void notifyVisibilitiesUpdated() {
+        if (DEBUG_UNKNOWN_APP_VISIBILITY) {
+            Slog.d(TAG, "Visibility updated DONE");
+        }
+        boolean changed = false;
+        for (int i = mUnknownApps.size() - 1; i >= 0; i--) {
+            if (mUnknownApps.valueAt(i) == UNKNOWN_STATE_WAITING_VISIBILITY_UPDATE) {
+                mUnknownApps.removeAt(i);
+                changed = true;
+            }
+        }
+        if (changed) {
+            mService.mWindowPlacerLocked.performSurfacePlacement();
+        }
+    }
+
+    void dump(PrintWriter pw, String prefix) {
+        if (mUnknownApps.isEmpty()) {
+            return;
+        }
+        pw.println(prefix + "Unknown visibilities:");
+        for (int i = mUnknownApps.size() - 1; i >= 0; i--) {
+            pw.println(prefix + "  app=" + mUnknownApps.keyAt(i)
+                    + " state=" + mUnknownApps.valueAt(i));
+        }
+    }
+}
diff --git a/com/android/server/wm/ViewServer.java b/com/android/server/wm/ViewServer.java
new file mode 100644
index 0000000..ecf5652
--- /dev/null
+++ b/com/android/server/wm/ViewServer.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.util.Slog;
+
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.InetAddress;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.io.IOException;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.BufferedWriter;
+import java.io.OutputStreamWriter;
+
+/**
+ * The ViewServer is local socket server that can be used to communicate with the
+ * views of the opened windows. Communication with the views is ensured by the
+ * {@link com.android.server.wm.WindowManagerService} and is a cross-process operation.
+ *
+ * {@hide}
+ */
+class ViewServer implements Runnable {
+    /**
+     * The default port used to start view servers.
+     */
+    public static final int VIEW_SERVER_DEFAULT_PORT = 4939;
+
+    private static final int VIEW_SERVER_MAX_CONNECTIONS = 10;
+
+    // Debug facility
+    private static final String LOG_TAG = TAG_WITH_CLASS_NAME ? "ViewServer" : TAG_WM;
+
+    private static final String VALUE_PROTOCOL_VERSION = "4";
+    private static final String VALUE_SERVER_VERSION = "4";
+
+    // Protocol commands
+    // Returns the protocol version
+    private static final String COMMAND_PROTOCOL_VERSION = "PROTOCOL";
+    // Returns the server version
+    private static final String COMMAND_SERVER_VERSION = "SERVER";
+    // Lists all of the available windows in the system
+    private static final String COMMAND_WINDOW_MANAGER_LIST = "LIST";
+    // Keeps a connection open and notifies when the list of windows changes
+    private static final String COMMAND_WINDOW_MANAGER_AUTOLIST = "AUTOLIST";
+    // Returns the focused window
+    private static final String COMMAND_WINDOW_MANAGER_GET_FOCUS = "GET_FOCUS";
+
+    private ServerSocket mServer;
+    private Thread mThread;
+
+    private final WindowManagerService mWindowManager;
+    private final int mPort;
+
+    private ExecutorService mThreadPool;
+
+    /**
+     * Creates a new ViewServer associated with the specified window manager on the
+     * specified local port. The server is not started by default.
+     *
+     * @param windowManager The window manager used to communicate with the views.
+     * @param port The port for the server to listen to.
+     *
+     * @see #start()
+     */
+    ViewServer(WindowManagerService windowManager, int port) {
+        mWindowManager = windowManager;
+        mPort = port;
+    }
+
+    /**
+     * Starts the server.
+     *
+     * @return True if the server was successfully created, or false if it already exists.
+     * @throws IOException If the server cannot be created.
+     *
+     * @see #stop()
+     * @see #isRunning()
+     * @see WindowManagerService#startViewServer(int)
+     */
+    boolean start() throws IOException {
+        if (mThread != null) {
+            return false;
+        }
+
+        mServer = new ServerSocket(mPort, VIEW_SERVER_MAX_CONNECTIONS, InetAddress.getLocalHost());
+        mThread = new Thread(this, "Remote View Server [port=" + mPort + "]");
+        mThreadPool = Executors.newFixedThreadPool(VIEW_SERVER_MAX_CONNECTIONS);
+        mThread.start();
+
+        return true;
+    }
+
+    /**
+     * Stops the server.
+     *
+     * @return True if the server was stopped, false if an error occured or if the
+     *         server wasn't started.
+     *
+     * @see #start()
+     * @see #isRunning()
+     * @see WindowManagerService#stopViewServer()
+     */
+    boolean stop() {
+        if (mThread != null) {
+
+            mThread.interrupt();
+            if (mThreadPool != null) {
+                try {
+                    mThreadPool.shutdownNow();
+                } catch (SecurityException e) {
+                    Slog.w(LOG_TAG, "Could not stop all view server threads");
+                }
+            }
+            mThreadPool = null;
+            mThread = null;
+            try {
+                mServer.close();
+                mServer = null;
+                return true;
+            } catch (IOException e) {
+                Slog.w(LOG_TAG, "Could not close the view server");
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Indicates whether the server is currently running.
+     *
+     * @return True if the server is running, false otherwise.
+     *
+     * @see #start()
+     * @see #stop()
+     * @see WindowManagerService#isViewServerRunning()
+     */
+    boolean isRunning() {
+        return mThread != null && mThread.isAlive();
+    }
+
+    /**
+     * Main server loop.
+     */
+    public void run() {
+        while (Thread.currentThread() == mThread) {
+            // Any uncaught exception will crash the system process
+            try {
+                Socket client = mServer.accept();
+                if (mThreadPool != null) {
+                    mThreadPool.submit(new ViewServerWorker(client));
+                } else {
+                    try {
+                        client.close();
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                }
+            } catch (Exception e) {
+                Slog.w(LOG_TAG, "Connection error: ", e);
+            }
+        }
+    }
+
+    private static boolean writeValue(Socket client, String value) {
+        boolean result;
+        BufferedWriter out = null;
+        try {
+            OutputStream clientStream = client.getOutputStream();
+            out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024);
+            out.write(value);
+            out.write("\n");
+            out.flush();
+            result = true;
+        } catch (Exception e) {
+            result = false;
+        } finally {
+            if (out != null) {
+                try {
+                    out.close();
+                } catch (IOException e) {
+                    result = false;
+                }
+            }
+        }
+        return result;
+    }
+
+    class ViewServerWorker implements Runnable, WindowManagerService.WindowChangeListener {
+        private Socket mClient;
+        private boolean mNeedWindowListUpdate;
+        private boolean mNeedFocusedWindowUpdate;
+
+        public ViewServerWorker(Socket client) {
+            mClient = client;
+            mNeedWindowListUpdate = false;
+            mNeedFocusedWindowUpdate = false;
+        }
+
+        public void run() {
+
+            BufferedReader in = null;
+            try {
+                in = new BufferedReader(new InputStreamReader(mClient.getInputStream()), 1024);
+
+                final String request = in.readLine();
+
+                String command;
+                String parameters;
+
+                int index = request.indexOf(' ');
+                if (index == -1) {
+                    command = request;
+                    parameters = "";
+                } else {
+                    command = request.substring(0, index);
+                    parameters = request.substring(index + 1);
+                }
+
+                boolean result;
+                if (COMMAND_PROTOCOL_VERSION.equalsIgnoreCase(command)) {
+                    result = writeValue(mClient, VALUE_PROTOCOL_VERSION);
+                } else if (COMMAND_SERVER_VERSION.equalsIgnoreCase(command)) {
+                    result = writeValue(mClient, VALUE_SERVER_VERSION);
+                } else if (COMMAND_WINDOW_MANAGER_LIST.equalsIgnoreCase(command)) {
+                    result = mWindowManager.viewServerListWindows(mClient);
+                } else if (COMMAND_WINDOW_MANAGER_GET_FOCUS.equalsIgnoreCase(command)) {
+                    result = mWindowManager.viewServerGetFocusedWindow(mClient);
+                } else if (COMMAND_WINDOW_MANAGER_AUTOLIST.equalsIgnoreCase(command)) {
+                    result = windowManagerAutolistLoop();
+                } else {
+                    result = mWindowManager.viewServerWindowCommand(mClient,
+                            command, parameters);
+                }
+
+                if (!result) {
+                    Slog.w(LOG_TAG, "An error occurred with the command: " + command);
+                }
+            } catch(IOException e) {
+                Slog.w(LOG_TAG, "Connection error: ", e);
+            } finally {
+                if (in != null) {
+                    try {
+                        in.close();
+
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                }
+                if (mClient != null) {
+                    try {
+                        mClient.close();
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+
+        public void windowsChanged() {
+            synchronized(this) {
+                mNeedWindowListUpdate = true;
+                notifyAll();
+            }
+        }
+
+        public void focusChanged() {
+            synchronized(this) {
+                mNeedFocusedWindowUpdate = true;
+                notifyAll();
+            }
+        }
+
+        private boolean windowManagerAutolistLoop() {
+            mWindowManager.addWindowChangeListener(this);
+            BufferedWriter out = null;
+            try {
+                out = new BufferedWriter(new OutputStreamWriter(mClient.getOutputStream()));
+                while (!Thread.interrupted()) {
+                    boolean needWindowListUpdate = false;
+                    boolean needFocusedWindowUpdate = false;
+                    synchronized (this) {
+                        while (!mNeedWindowListUpdate && !mNeedFocusedWindowUpdate) {
+                            wait();
+                        }
+                        if (mNeedWindowListUpdate) {
+                            mNeedWindowListUpdate = false;
+                            needWindowListUpdate = true;
+                        }
+                        if (mNeedFocusedWindowUpdate) {
+                            mNeedFocusedWindowUpdate = false;
+                            needFocusedWindowUpdate = true;
+                        }
+                    }
+                    if (needWindowListUpdate) {
+                        out.write("LIST UPDATE\n");
+                        out.flush();
+                    }
+                    if (needFocusedWindowUpdate) {
+                        out.write("ACTION_FOCUS UPDATE\n");
+                        out.flush();
+                    }
+                }
+            } catch (Exception e) {
+                // Ignore
+            } finally {
+                if (out != null) {
+                    try {
+                        out.close();
+                    } catch (IOException e) {
+                        // Ignore
+                    }
+                }
+                mWindowManager.removeWindowChangeListener(this);
+            }
+            return true;
+        }
+    }
+}
diff --git a/com/android/server/wm/WallpaperController.java b/com/android/server/wm/WallpaperController.java
new file mode 100644
index 0000000..7213c95
--- /dev/null
+++ b/com/android/server/wm/WallpaperController.java
@@ -0,0 +1,716 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import com.android.internal.util.ToBooleanFunction;
+
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+
+import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.H.WALLPAPER_DRAW_PENDING_TIMEOUT;
+
+import android.os.Bundle;
+import android.os.Debug;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.view.DisplayInfo;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Controls wallpaper windows visibility, ordering, and so on.
+ * NOTE: All methods in this class must be called with the window manager service lock held.
+ */
+class WallpaperController {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "WallpaperController" : TAG_WM;
+    private WindowManagerService mService;
+
+    private final ArrayList<WallpaperWindowToken> mWallpaperTokens = new ArrayList<>();
+
+    // If non-null, this is the currently visible window that is associated
+    // with the wallpaper.
+    private WindowState mWallpaperTarget = null;
+    // If non-null, we are in the middle of animating from one wallpaper target
+    // to another, and this is the previous wallpaper target.
+    private WindowState mPrevWallpaperTarget = null;
+
+    private int mWallpaperAnimLayerAdjustment;
+
+    private float mLastWallpaperX = -1;
+    private float mLastWallpaperY = -1;
+    private float mLastWallpaperXStep = -1;
+    private float mLastWallpaperYStep = -1;
+    private int mLastWallpaperDisplayOffsetX = Integer.MIN_VALUE;
+    private int mLastWallpaperDisplayOffsetY = Integer.MIN_VALUE;
+
+    // This is set when we are waiting for a wallpaper to tell us it is done
+    // changing its scroll position.
+    private WindowState mWaitingOnWallpaper;
+
+    // The last time we had a timeout when waiting for a wallpaper.
+    private long mLastWallpaperTimeoutTime;
+    // We give a wallpaper up to 150ms to finish scrolling.
+    private static final long WALLPAPER_TIMEOUT = 150;
+    // Time we wait after a timeout before trying to wait again.
+    private static final long WALLPAPER_TIMEOUT_RECOVERY = 10000;
+
+    // Set to the wallpaper window we would like to hide once the transition animations are done.
+    // This is useful in cases where we don't want the wallpaper to be hidden when the close app
+    // is a wallpaper target and is done animating out, but the opening app isn't a wallpaper
+    // target and isn't done animating in.
+    WindowState mDeferredHideWallpaper = null;
+
+    // We give a wallpaper up to 500ms to finish drawing before playing app transitions.
+    private static final long WALLPAPER_DRAW_PENDING_TIMEOUT_DURATION = 500;
+    private static final int WALLPAPER_DRAW_NORMAL = 0;
+    private static final int WALLPAPER_DRAW_PENDING = 1;
+    private static final int WALLPAPER_DRAW_TIMEOUT = 2;
+    private int mWallpaperDrawState = WALLPAPER_DRAW_NORMAL;
+
+    private final FindWallpaperTargetResult mFindResults = new FindWallpaperTargetResult();
+
+    private final ToBooleanFunction<WindowState> mFindWallpaperTargetFunction = w -> {
+        final WindowAnimator winAnimator = mService.mAnimator;
+        if ((w.mAttrs.type == TYPE_WALLPAPER)) {
+            if (mFindResults.topWallpaper == null || mFindResults.resetTopWallpaper) {
+                mFindResults.setTopWallpaper(w);
+                mFindResults.resetTopWallpaper = false;
+            }
+            return false;
+        }
+
+        mFindResults.resetTopWallpaper = true;
+        if (w != winAnimator.mWindowDetachedWallpaper && w.mAppToken != null) {
+            // If this window's app token is hidden and not animating, it is of no interest to us.
+            if (w.mAppToken.hidden && w.mAppToken.mAppAnimator.animation == null) {
+                if (DEBUG_WALLPAPER) Slog.v(TAG,
+                        "Skipping hidden and not animating token: " + w);
+                return false;
+            }
+        }
+        if (DEBUG_WALLPAPER) Slog.v(TAG, "Win " + w + ": isOnScreen=" + w.isOnScreen()
+                + " mDrawState=" + w.mWinAnimator.mDrawState);
+
+        if (w.mWillReplaceWindow && mWallpaperTarget == null
+                && !mFindResults.useTopWallpaperAsTarget) {
+            // When we are replacing a window and there was wallpaper before replacement, we want to
+            // keep the window until the new windows fully appear and can determine the visibility,
+            // to avoid flickering.
+            mFindResults.setUseTopWallpaperAsTarget(true);
+        }
+
+        final boolean keyguardGoingAwayWithWallpaper = (w.mAppToken != null
+                && AppTransition.isKeyguardGoingAwayTransit(
+                w.mAppToken.mAppAnimator.getTransit())
+                && (w.mAppToken.mAppAnimator.getTransitFlags()
+                & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0);
+
+        boolean needsShowWhenLockedWallpaper = false;
+        if ((w.mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0
+                && mService.mPolicy.isKeyguardLocked()
+                && mService.mPolicy.isKeyguardOccluded()) {
+            // The lowest show when locked window decides whether we need to put the wallpaper
+            // behind.
+            needsShowWhenLockedWallpaper = !isFullscreen(w.mAttrs)
+                    || (w.mAppToken != null && !w.mAppToken.fillsParent());
+        }
+
+        if (keyguardGoingAwayWithWallpaper || needsShowWhenLockedWallpaper) {
+            // Keep the wallpaper during Keyguard exit but also when it's needed for a
+            // non-fullscreen show when locked activity.
+            mFindResults.setUseTopWallpaperAsTarget(true);
+        }
+
+        final boolean hasWallpaper = (w.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0;
+        if (hasWallpaper && w.isOnScreen() && (mWallpaperTarget == w || w.isDrawFinishedLw())) {
+            if (DEBUG_WALLPAPER) Slog.v(TAG, "Found wallpaper target: " + w);
+            mFindResults.setWallpaperTarget(w);
+            if (w == mWallpaperTarget && w.mWinAnimator.isAnimationSet()) {
+                // The current wallpaper target is animating, so we'll look behind it for
+                // another possible target and figure out what is going on later.
+                if (DEBUG_WALLPAPER) Slog.v(TAG,
+                        "Win " + w + ": token animating, looking behind.");
+            }
+            // Found a target! End search.
+            return true;
+        } else if (w == winAnimator.mWindowDetachedWallpaper) {
+            if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
+                    "Found animating detached wallpaper target win: " + w);
+            mFindResults.setUseTopWallpaperAsTarget(true);
+        }
+        return false;
+    };
+
+    public WallpaperController(WindowManagerService service) {
+        mService = service;
+    }
+
+    WindowState getWallpaperTarget() {
+        return mWallpaperTarget;
+    }
+
+    boolean isWallpaperTarget(WindowState win) {
+        return win == mWallpaperTarget;
+    }
+
+    boolean isBelowWallpaperTarget(WindowState win) {
+        return mWallpaperTarget != null && mWallpaperTarget.mLayer >= win.mBaseLayer;
+    }
+
+    boolean isWallpaperVisible() {
+        return isWallpaperVisible(mWallpaperTarget);
+    }
+
+    /**
+     * Starts {@param a} on all wallpaper windows.
+     */
+    void startWallpaperAnimation(Animation a) {
+        for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
+            final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
+            token.startAnimation(a);
+        }
+    }
+
+    private boolean isWallpaperVisible(WindowState wallpaperTarget) {
+        if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper vis: target " + wallpaperTarget + ", obscured="
+                + (wallpaperTarget != null ? Boolean.toString(wallpaperTarget.mObscured) : "??")
+                + " anim=" + ((wallpaperTarget != null && wallpaperTarget.mAppToken != null)
+                ? wallpaperTarget.mAppToken.mAppAnimator.animation : null)
+                + " prev=" + mPrevWallpaperTarget);
+        return (wallpaperTarget != null
+                && (!wallpaperTarget.mObscured || (wallpaperTarget.mAppToken != null
+                && wallpaperTarget.mAppToken.mAppAnimator.animation != null)))
+                || mPrevWallpaperTarget != null;
+    }
+
+    boolean isWallpaperTargetAnimating() {
+        return mWallpaperTarget != null && mWallpaperTarget.mWinAnimator.isAnimationSet()
+                && !mWallpaperTarget.mWinAnimator.isDummyAnimation();
+    }
+
+    void updateWallpaperVisibility() {
+        final boolean visible = isWallpaperVisible(mWallpaperTarget);
+
+        for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
+            final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
+            token.updateWallpaperVisibility(visible);
+        }
+    }
+
+    void hideDeferredWallpapersIfNeeded() {
+        if (mDeferredHideWallpaper != null) {
+            hideWallpapers(mDeferredHideWallpaper);
+            mDeferredHideWallpaper = null;
+        }
+    }
+
+    void hideWallpapers(final WindowState winGoingAway) {
+        if (mWallpaperTarget != null
+                && (mWallpaperTarget != winGoingAway || mPrevWallpaperTarget != null)) {
+            return;
+        }
+        if (mService.mAppTransition.isRunning()) {
+            // Defer hiding the wallpaper when app transition is running until the animations
+            // are done.
+            mDeferredHideWallpaper = winGoingAway;
+            return;
+        }
+
+        final boolean wasDeferred = (mDeferredHideWallpaper == winGoingAway);
+        for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
+            final WallpaperWindowToken token = mWallpaperTokens.get(i);
+            token.hideWallpaperToken(wasDeferred, "hideWallpapers");
+            if (DEBUG_WALLPAPER_LIGHT && !token.hidden) Slog.d(TAG, "Hiding wallpaper " + token
+                    + " from " + winGoingAway + " target=" + mWallpaperTarget + " prev="
+                    + mPrevWallpaperTarget + "\n" + Debug.getCallers(5, "  "));
+        }
+    }
+
+    boolean updateWallpaperOffset(WindowState wallpaperWin, int dw, int dh, boolean sync) {
+        boolean rawChanged = false;
+        // Set the default wallpaper x-offset to either edge of the screen (depending on RTL), to
+        // match the behavior of most Launchers
+        float defaultWallpaperX = wallpaperWin.isRtl() ? 1f : 0f;
+        float wpx = mLastWallpaperX >= 0 ? mLastWallpaperX : defaultWallpaperX;
+        float wpxs = mLastWallpaperXStep >= 0 ? mLastWallpaperXStep : -1.0f;
+        int availw = wallpaperWin.mFrame.right - wallpaperWin.mFrame.left - dw;
+        int offset = availw > 0 ? -(int)(availw * wpx + .5f) : 0;
+        if (mLastWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
+            offset += mLastWallpaperDisplayOffsetX;
+        }
+        boolean changed = wallpaperWin.mXOffset != offset;
+        if (changed) {
+            if (DEBUG_WALLPAPER) Slog.v(TAG, "Update wallpaper " + wallpaperWin + " x: " + offset);
+            wallpaperWin.mXOffset = offset;
+        }
+        if (wallpaperWin.mWallpaperX != wpx || wallpaperWin.mWallpaperXStep != wpxs) {
+            wallpaperWin.mWallpaperX = wpx;
+            wallpaperWin.mWallpaperXStep = wpxs;
+            rawChanged = true;
+        }
+
+        float wpy = mLastWallpaperY >= 0 ? mLastWallpaperY : 0.5f;
+        float wpys = mLastWallpaperYStep >= 0 ? mLastWallpaperYStep : -1.0f;
+        int availh = wallpaperWin.mFrame.bottom - wallpaperWin.mFrame.top - dh;
+        offset = availh > 0 ? -(int)(availh * wpy + .5f) : 0;
+        if (mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
+            offset += mLastWallpaperDisplayOffsetY;
+        }
+        if (wallpaperWin.mYOffset != offset) {
+            if (DEBUG_WALLPAPER) Slog.v(TAG, "Update wallpaper " + wallpaperWin + " y: " + offset);
+            changed = true;
+            wallpaperWin.mYOffset = offset;
+        }
+        if (wallpaperWin.mWallpaperY != wpy || wallpaperWin.mWallpaperYStep != wpys) {
+            wallpaperWin.mWallpaperY = wpy;
+            wallpaperWin.mWallpaperYStep = wpys;
+            rawChanged = true;
+        }
+
+        if (rawChanged && (wallpaperWin.mAttrs.privateFlags &
+                WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS) != 0) {
+            try {
+                if (DEBUG_WALLPAPER) Slog.v(TAG, "Report new wp offset "
+                        + wallpaperWin + " x=" + wallpaperWin.mWallpaperX
+                        + " y=" + wallpaperWin.mWallpaperY);
+                if (sync) {
+                    mWaitingOnWallpaper = wallpaperWin;
+                }
+                wallpaperWin.mClient.dispatchWallpaperOffsets(
+                        wallpaperWin.mWallpaperX, wallpaperWin.mWallpaperY,
+                        wallpaperWin.mWallpaperXStep, wallpaperWin.mWallpaperYStep, sync);
+                if (sync) {
+                    if (mWaitingOnWallpaper != null) {
+                        long start = SystemClock.uptimeMillis();
+                        if ((mLastWallpaperTimeoutTime + WALLPAPER_TIMEOUT_RECOVERY)
+                                < start) {
+                            try {
+                                if (DEBUG_WALLPAPER) Slog.v(TAG,
+                                        "Waiting for offset complete...");
+                                mService.mWindowMap.wait(WALLPAPER_TIMEOUT);
+                            } catch (InterruptedException e) {
+                            }
+                            if (DEBUG_WALLPAPER) Slog.v(TAG, "Offset complete!");
+                            if ((start + WALLPAPER_TIMEOUT) < SystemClock.uptimeMillis()) {
+                                Slog.i(TAG, "Timeout waiting for wallpaper to offset: "
+                                        + wallpaperWin);
+                                mLastWallpaperTimeoutTime = start;
+                            }
+                        }
+                        mWaitingOnWallpaper = null;
+                    }
+                }
+            } catch (RemoteException e) {
+            }
+        }
+
+        return changed;
+    }
+
+    void setWindowWallpaperPosition(
+            WindowState window, float x, float y, float xStep, float yStep) {
+        if (window.mWallpaperX != x || window.mWallpaperY != y)  {
+            window.mWallpaperX = x;
+            window.mWallpaperY = y;
+            window.mWallpaperXStep = xStep;
+            window.mWallpaperYStep = yStep;
+            updateWallpaperOffsetLocked(window, true);
+        }
+    }
+
+    void setWindowWallpaperDisplayOffset(WindowState window, int x, int y) {
+        if (window.mWallpaperDisplayOffsetX != x || window.mWallpaperDisplayOffsetY != y)  {
+            window.mWallpaperDisplayOffsetX = x;
+            window.mWallpaperDisplayOffsetY = y;
+            updateWallpaperOffsetLocked(window, true);
+        }
+    }
+
+    Bundle sendWindowWallpaperCommand(
+            WindowState window, String action, int x, int y, int z, Bundle extras, boolean sync) {
+        if (window == mWallpaperTarget || window == mPrevWallpaperTarget) {
+            boolean doWait = sync;
+            for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
+                final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
+                token.sendWindowWallpaperCommand(action, x, y, z, extras, sync);
+            }
+
+            if (doWait) {
+                // TODO: Need to wait for result.
+            }
+        }
+
+        return null;
+    }
+
+    private void updateWallpaperOffsetLocked(WindowState changingTarget, boolean sync) {
+        final DisplayContent displayContent = changingTarget.getDisplayContent();
+        if (displayContent == null) {
+            return;
+        }
+        final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+        final int dw = displayInfo.logicalWidth;
+        final int dh = displayInfo.logicalHeight;
+
+        WindowState target = mWallpaperTarget;
+        if (target != null) {
+            if (target.mWallpaperX >= 0) {
+                mLastWallpaperX = target.mWallpaperX;
+            } else if (changingTarget.mWallpaperX >= 0) {
+                mLastWallpaperX = changingTarget.mWallpaperX;
+            }
+            if (target.mWallpaperY >= 0) {
+                mLastWallpaperY = target.mWallpaperY;
+            } else if (changingTarget.mWallpaperY >= 0) {
+                mLastWallpaperY = changingTarget.mWallpaperY;
+            }
+            if (target.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
+                mLastWallpaperDisplayOffsetX = target.mWallpaperDisplayOffsetX;
+            } else if (changingTarget.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
+                mLastWallpaperDisplayOffsetX = changingTarget.mWallpaperDisplayOffsetX;
+            }
+            if (target.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
+                mLastWallpaperDisplayOffsetY = target.mWallpaperDisplayOffsetY;
+            } else if (changingTarget.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
+                mLastWallpaperDisplayOffsetY = changingTarget.mWallpaperDisplayOffsetY;
+            }
+            if (target.mWallpaperXStep >= 0) {
+                mLastWallpaperXStep = target.mWallpaperXStep;
+            } else if (changingTarget.mWallpaperXStep >= 0) {
+                mLastWallpaperXStep = changingTarget.mWallpaperXStep;
+            }
+            if (target.mWallpaperYStep >= 0) {
+                mLastWallpaperYStep = target.mWallpaperYStep;
+            } else if (changingTarget.mWallpaperYStep >= 0) {
+                mLastWallpaperYStep = changingTarget.mWallpaperYStep;
+            }
+        }
+
+        for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
+            mWallpaperTokens.get(curTokenNdx).updateWallpaperOffset(dw, dh, sync);
+        }
+    }
+
+    void clearLastWallpaperTimeoutTime() {
+        mLastWallpaperTimeoutTime = 0;
+    }
+
+    void wallpaperCommandComplete(IBinder window) {
+        if (mWaitingOnWallpaper != null &&
+                mWaitingOnWallpaper.mClient.asBinder() == window) {
+            mWaitingOnWallpaper = null;
+            mService.mWindowMap.notifyAll();
+        }
+    }
+
+    void wallpaperOffsetsComplete(IBinder window) {
+        if (mWaitingOnWallpaper != null &&
+                mWaitingOnWallpaper.mClient.asBinder() == window) {
+            mWaitingOnWallpaper = null;
+            mService.mWindowMap.notifyAll();
+        }
+    }
+
+    int getAnimLayerAdjustment() {
+        return mWallpaperAnimLayerAdjustment;
+    }
+
+    private void findWallpaperTarget(DisplayContent dc) {
+        mFindResults.reset();
+        if (dc.isStackVisible(FREEFORM_WORKSPACE_STACK_ID)) {
+            // In freeform mode we set the wallpaper as its own target, so we don't need an
+            // additional window to make it visible.
+            mFindResults.setUseTopWallpaperAsTarget(true);
+        }
+
+        dc.forAllWindows(mFindWallpaperTargetFunction, true /* traverseTopToBottom */);
+
+        if (mFindResults.wallpaperTarget == null && mFindResults.useTopWallpaperAsTarget) {
+            mFindResults.setWallpaperTarget(mFindResults.topWallpaper);
+        }
+    }
+
+    private boolean isFullscreen(WindowManager.LayoutParams attrs) {
+        return attrs.x == 0 && attrs.y == 0
+                && attrs.width == MATCH_PARENT && attrs.height == MATCH_PARENT;
+    }
+
+    /** Updates the target wallpaper if needed and returns true if an update happened. */
+    private void updateWallpaperWindowsTarget(DisplayContent dc,
+            FindWallpaperTargetResult result) {
+
+        WindowState wallpaperTarget = result.wallpaperTarget;
+
+        if (mWallpaperTarget == wallpaperTarget
+                || (mPrevWallpaperTarget != null && mPrevWallpaperTarget == wallpaperTarget)) {
+
+            if (mPrevWallpaperTarget == null) {
+                return;
+            }
+
+            // Is it time to stop animating?
+            if (!mPrevWallpaperTarget.isAnimatingLw()) {
+                if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "No longer animating wallpaper targets!");
+                mPrevWallpaperTarget = null;
+                mWallpaperTarget = wallpaperTarget;
+            }
+            return;
+        }
+
+        if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
+                "New wallpaper target: " + wallpaperTarget + " prevTarget: " + mWallpaperTarget);
+
+        mPrevWallpaperTarget = null;
+
+        final WindowState prevWallpaperTarget = mWallpaperTarget;
+        mWallpaperTarget = wallpaperTarget;
+
+        if (wallpaperTarget == null || prevWallpaperTarget == null) {
+            return;
+        }
+
+        // Now what is happening...  if the current and new targets are animating,
+        // then we are in our super special mode!
+        boolean oldAnim = prevWallpaperTarget.isAnimatingLw();
+        boolean foundAnim = wallpaperTarget.isAnimatingLw();
+        if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
+                "New animation: " + foundAnim + " old animation: " + oldAnim);
+
+        if (!foundAnim || !oldAnim) {
+            return;
+        }
+
+        if (dc.getWindow(w -> w == prevWallpaperTarget) == null) {
+            return;
+        }
+
+        final boolean newTargetHidden = wallpaperTarget.mAppToken != null
+                && wallpaperTarget.mAppToken.hiddenRequested;
+        final boolean oldTargetHidden = prevWallpaperTarget.mAppToken != null
+                && prevWallpaperTarget.mAppToken.hiddenRequested;
+
+        if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Animating wallpapers:" + " old: "
+                + prevWallpaperTarget + " hidden=" + oldTargetHidden + " new: " + wallpaperTarget
+                + " hidden=" + newTargetHidden);
+
+        mPrevWallpaperTarget = prevWallpaperTarget;
+
+        if (newTargetHidden && !oldTargetHidden) {
+            if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Old wallpaper still the target.");
+            // Use the old target if new target is hidden but old target
+            // is not. If they're both hidden, still use the new target.
+            mWallpaperTarget = prevWallpaperTarget;
+        } else if (newTargetHidden == oldTargetHidden
+                && !mService.mOpeningApps.contains(wallpaperTarget.mAppToken)
+                && (mService.mOpeningApps.contains(prevWallpaperTarget.mAppToken)
+                || mService.mClosingApps.contains(prevWallpaperTarget.mAppToken))) {
+            // If they're both hidden (or both not hidden), prefer the one that's currently in
+            // opening or closing app list, this allows transition selection logic to better
+            // determine the wallpaper status of opening/closing apps.
+            mWallpaperTarget = prevWallpaperTarget;
+        }
+
+        result.setWallpaperTarget(wallpaperTarget);
+    }
+
+    private void updateWallpaperTokens(boolean visible) {
+        for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
+            final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
+            token.updateWallpaperWindows(visible, mWallpaperAnimLayerAdjustment);
+            token.getDisplayContent().assignWindowLayers(false);
+        }
+    }
+
+    void adjustWallpaperWindows(DisplayContent dc) {
+        mService.mRoot.mWallpaperMayChange = false;
+
+        // First find top-most window that has asked to be on top of the wallpaper;
+        // all wallpapers go behind it.
+        findWallpaperTarget(dc);
+        updateWallpaperWindowsTarget(dc, mFindResults);
+
+        // The window is visible to the compositor...but is it visible to the user?
+        // That is what the wallpaper cares about.
+        final boolean visible = mWallpaperTarget != null && isWallpaperVisible(mWallpaperTarget);
+        if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper visibility: " + visible);
+
+        if (visible) {
+            // If the wallpaper target is animating, we may need to copy its layer adjustment.
+            // Only do this if we are not transferring between two wallpaper targets.
+            mWallpaperAnimLayerAdjustment =
+                    (mPrevWallpaperTarget == null && mWallpaperTarget.mAppToken != null)
+                            ? mWallpaperTarget.mAppToken.getAnimLayerAdjustment() : 0;
+
+            if (mWallpaperTarget.mWallpaperX >= 0) {
+                mLastWallpaperX = mWallpaperTarget.mWallpaperX;
+                mLastWallpaperXStep = mWallpaperTarget.mWallpaperXStep;
+            }
+            if (mWallpaperTarget.mWallpaperY >= 0) {
+                mLastWallpaperY = mWallpaperTarget.mWallpaperY;
+                mLastWallpaperYStep = mWallpaperTarget.mWallpaperYStep;
+            }
+            if (mWallpaperTarget.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
+                mLastWallpaperDisplayOffsetX = mWallpaperTarget.mWallpaperDisplayOffsetX;
+            }
+            if (mWallpaperTarget.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
+                mLastWallpaperDisplayOffsetY = mWallpaperTarget.mWallpaperDisplayOffsetY;
+            }
+        }
+
+        updateWallpaperTokens(visible);
+
+        if (DEBUG_WALLPAPER_LIGHT)  Slog.d(TAG, "New wallpaper: target=" + mWallpaperTarget
+                + " prev=" + mPrevWallpaperTarget);
+    }
+
+    boolean processWallpaperDrawPendingTimeout() {
+        if (mWallpaperDrawState == WALLPAPER_DRAW_PENDING) {
+            mWallpaperDrawState = WALLPAPER_DRAW_TIMEOUT;
+            if (DEBUG_APP_TRANSITIONS || DEBUG_WALLPAPER) Slog.v(TAG,
+                    "*** WALLPAPER DRAW TIMEOUT");
+            return true;
+        }
+        return false;
+    }
+
+    boolean wallpaperTransitionReady() {
+        boolean transitionReady = true;
+        boolean wallpaperReady = true;
+        for (int curTokenIndex = mWallpaperTokens.size() - 1;
+                curTokenIndex >= 0 && wallpaperReady; curTokenIndex--) {
+            final WallpaperWindowToken token = mWallpaperTokens.get(curTokenIndex);
+            if (token.hasVisibleNotDrawnWallpaper()) {
+                // We've told this wallpaper to be visible, but it is not drawn yet
+                wallpaperReady = false;
+                if (mWallpaperDrawState != WALLPAPER_DRAW_TIMEOUT) {
+                    // wait for this wallpaper until it is drawn or timeout
+                    transitionReady = false;
+                }
+                if (mWallpaperDrawState == WALLPAPER_DRAW_NORMAL) {
+                    mWallpaperDrawState = WALLPAPER_DRAW_PENDING;
+                    mService.mH.removeMessages(WALLPAPER_DRAW_PENDING_TIMEOUT);
+                    mService.mH.sendEmptyMessageDelayed(WALLPAPER_DRAW_PENDING_TIMEOUT,
+                            WALLPAPER_DRAW_PENDING_TIMEOUT_DURATION);
+                }
+                if (DEBUG_APP_TRANSITIONS || DEBUG_WALLPAPER) Slog.v(TAG,
+                        "Wallpaper should be visible but has not been drawn yet. " +
+                                "mWallpaperDrawState=" + mWallpaperDrawState);
+                break;
+            }
+        }
+        if (wallpaperReady) {
+            mWallpaperDrawState = WALLPAPER_DRAW_NORMAL;
+            mService.mH.removeMessages(WALLPAPER_DRAW_PENDING_TIMEOUT);
+        }
+
+        return transitionReady;
+    }
+
+    /**
+     * Adjusts the wallpaper windows if the input display has a pending wallpaper layout or one of
+     * the opening apps should be a wallpaper target.
+     */
+    void adjustWallpaperWindowsForAppTransitionIfNeeded(DisplayContent dc,
+            ArraySet<AppWindowToken> openingApps) {
+        boolean adjust = false;
+        if ((dc.pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
+            adjust = true;
+        } else {
+            for (int i = openingApps.size() - 1; i >= 0; --i) {
+                final AppWindowToken token = openingApps.valueAt(i);
+                if (token.windowsCanBeWallpaperTarget()) {
+                    adjust = true;
+                    break;
+                }
+            }
+        }
+
+        if (adjust) {
+            adjustWallpaperWindows(dc);
+        }
+    }
+
+    void addWallpaperToken(WallpaperWindowToken token) {
+        mWallpaperTokens.add(token);
+    }
+
+    void removeWallpaperToken(WallpaperWindowToken token) {
+        mWallpaperTokens.remove(token);
+    }
+
+    void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix); pw.print("mWallpaperTarget="); pw.println(mWallpaperTarget);
+        if (mPrevWallpaperTarget != null) {
+            pw.print(prefix); pw.print("mPrevWallpaperTarget="); pw.println(mPrevWallpaperTarget);
+        }
+        pw.print(prefix); pw.print("mLastWallpaperX="); pw.print(mLastWallpaperX);
+        pw.print(" mLastWallpaperY="); pw.println(mLastWallpaperY);
+        if (mLastWallpaperDisplayOffsetX != Integer.MIN_VALUE
+                || mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
+            pw.print(prefix);
+            pw.print("mLastWallpaperDisplayOffsetX="); pw.print(mLastWallpaperDisplayOffsetX);
+            pw.print(" mLastWallpaperDisplayOffsetY="); pw.println(mLastWallpaperDisplayOffsetY);
+        }
+
+        if (mWallpaperAnimLayerAdjustment != 0) {
+            pw.println(prefix + "mWallpaperAnimLayerAdjustment=" + mWallpaperAnimLayerAdjustment);
+        }
+    }
+
+    /** Helper class for storing the results of a wallpaper target find operation. */
+    final private static class FindWallpaperTargetResult {
+        WindowState topWallpaper = null;
+        boolean useTopWallpaperAsTarget = false;
+        WindowState wallpaperTarget = null;
+        boolean resetTopWallpaper = false;
+
+        void setTopWallpaper(WindowState win) {
+            topWallpaper = win;
+        }
+
+        void setWallpaperTarget(WindowState win) {
+            wallpaperTarget = win;
+        }
+
+        void setUseTopWallpaperAsTarget(boolean topWallpaperAsTarget) {
+            useTopWallpaperAsTarget = topWallpaperAsTarget;
+        }
+
+        void reset() {
+            topWallpaper = null;
+            wallpaperTarget = null;
+            useTopWallpaperAsTarget = false;
+            resetTopWallpaper = false;
+        }
+    }
+}
diff --git a/com/android/server/wm/WallpaperVisibilityListeners.java b/com/android/server/wm/WallpaperVisibilityListeners.java
new file mode 100644
index 0000000..2c06851
--- /dev/null
+++ b/com/android/server/wm/WallpaperVisibilityListeners.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.SparseArray;
+import android.view.IWallpaperVisibilityListener;
+
+/**
+ * Manages and trigger wallpaper visibility listeners.
+ */
+class WallpaperVisibilityListeners {
+
+    /**
+     * A map of displayIds and its listeners.
+     */
+    private final SparseArray<RemoteCallbackList<IWallpaperVisibilityListener>> mDisplayListeners =
+            new SparseArray<>();
+
+    void registerWallpaperVisibilityListener(IWallpaperVisibilityListener listener,
+            int displayId) {
+        RemoteCallbackList<IWallpaperVisibilityListener> listeners =
+                mDisplayListeners.get(displayId);
+        if (listeners == null) {
+            listeners = new RemoteCallbackList<>();
+            mDisplayListeners.append(displayId, listeners);
+        }
+        listeners.register(listener);
+    }
+
+    void unregisterWallpaperVisibilityListener(IWallpaperVisibilityListener listener,
+            int displayId) {
+        RemoteCallbackList<IWallpaperVisibilityListener> listeners =
+                mDisplayListeners.get(displayId);
+        if (listeners == null) {
+            return;
+        }
+        listeners.unregister(listener);
+    }
+
+    void notifyWallpaperVisibilityChanged(DisplayContent displayContent) {
+        final int displayId = displayContent.getDisplayId();
+        final boolean visible = displayContent.mWallpaperController.isWallpaperVisible();
+        RemoteCallbackList<IWallpaperVisibilityListener> displayListeners =
+                mDisplayListeners.get(displayId);
+
+        // No listeners for this display.
+        if (displayListeners == null) {
+            return;
+        }
+
+        int i = displayListeners.beginBroadcast();
+        while (i > 0) {
+            i--;
+            IWallpaperVisibilityListener listener = displayListeners.getBroadcastItem(i);
+            try {
+                listener.onWallpaperVisibilityChanged(visible, displayId);
+            } catch (RemoteException e) {
+                // Nothing to do in here, RemoteCallbackListener will clean it up.
+            }
+        }
+        displayListeners.finishBroadcast();
+    }
+}
diff --git a/com/android/server/wm/WallpaperWindowToken.java b/com/android/server/wm/WallpaperWindowToken.java
new file mode 100644
index 0000000..a12c0e5
--- /dev/null
+++ b/com/android/server/wm/WallpaperWindowToken.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEMENT;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.view.DisplayInfo;
+import android.view.animation.Animation;
+
+/**
+ * A token that represents a set of wallpaper windows.
+ */
+class WallpaperWindowToken extends WindowToken {
+
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "WallpaperWindowToken" : TAG_WM;
+
+    WallpaperWindowToken(WindowManagerService service, IBinder token, boolean explicit,
+            DisplayContent dc, boolean ownerCanManageAppTokens) {
+        super(service, token, TYPE_WALLPAPER, explicit, dc, ownerCanManageAppTokens);
+        dc.mWallpaperController.addWallpaperToken(this);
+    }
+
+    @Override
+    void setExiting() {
+        super.setExiting();
+        mDisplayContent.mWallpaperController.removeWallpaperToken(this);
+    }
+
+    void hideWallpaperToken(boolean wasDeferred, String reason) {
+        for (int j = mChildren.size() - 1; j >= 0; j--) {
+            final WindowState wallpaper = mChildren.get(j);
+            wallpaper.hideWallpaperWindow(wasDeferred, reason);
+        }
+        hidden = true;
+    }
+
+    void sendWindowWallpaperCommand(
+            String action, int x, int y, int z, Bundle extras, boolean sync) {
+        for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
+            final WindowState wallpaper = mChildren.get(wallpaperNdx);
+            try {
+                wallpaper.mClient.dispatchWallpaperCommand(action, x, y, z, extras, sync);
+                // We only want to be synchronous with one wallpaper.
+                sync = false;
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    void updateWallpaperOffset(int dw, int dh, boolean sync) {
+        final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
+        for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
+            final WindowState wallpaper = mChildren.get(wallpaperNdx);
+            if (wallpaperController.updateWallpaperOffset(wallpaper, dw, dh, sync)) {
+                final WindowStateAnimator winAnimator = wallpaper.mWinAnimator;
+                winAnimator.computeShownFrameLocked();
+                // No need to lay out the windows - we can just set the wallpaper position directly.
+                winAnimator.setWallpaperOffset(wallpaper.mShownPosition);
+                // We only want to be synchronous with one wallpaper.
+                sync = false;
+            }
+        }
+    }
+
+    void updateWallpaperVisibility(boolean visible) {
+        final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
+        final int dw = displayInfo.logicalWidth;
+        final int dh = displayInfo.logicalHeight;
+
+        if (hidden == visible) {
+            hidden = !visible;
+            // Need to do a layout to ensure the wallpaper now has the correct size.
+            mDisplayContent.setLayoutNeeded();
+        }
+
+        final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
+        for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
+            final WindowState wallpaper = mChildren.get(wallpaperNdx);
+            if (visible) {
+                wallpaperController.updateWallpaperOffset(wallpaper, dw, dh, false);
+            }
+
+            wallpaper.dispatchWallpaperVisibility(visible);
+        }
+    }
+
+    /**
+     * Starts {@param anim} on all children.
+     */
+    void startAnimation(Animation anim) {
+        for (int ndx = mChildren.size() - 1; ndx >= 0; ndx--) {
+            final WindowState windowState = mChildren.get(ndx);
+            windowState.mWinAnimator.setAnimation(anim);
+        }
+    }
+
+    void updateWallpaperWindows(boolean visible, int animLayerAdj) {
+
+        if (hidden == visible) {
+            if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG,
+                    "Wallpaper token " + token + " hidden=" + !visible);
+            hidden = !visible;
+            // Need to do a layout to ensure the wallpaper now has the correct size.
+            mDisplayContent.setLayoutNeeded();
+        }
+
+        final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
+        final int dw = displayInfo.logicalWidth;
+        final int dh = displayInfo.logicalHeight;
+        final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
+        for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
+            final WindowState wallpaper = mChildren.get(wallpaperNdx);
+
+            if (visible) {
+                wallpaperController.updateWallpaperOffset(wallpaper, dw, dh, false);
+            }
+
+            // First, make sure the client has the current visibility state.
+            wallpaper.dispatchWallpaperVisibility(visible);
+
+            if (DEBUG_LAYERS || DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "adjustWallpaper win "
+                    + wallpaper + " anim layer: " + wallpaper.mWinAnimator.mAnimLayer);
+        }
+    }
+
+    boolean hasVisibleNotDrawnWallpaper() {
+        for (int j = mChildren.size() - 1; j >= 0; --j) {
+            final WindowState wallpaper = mChildren.get(j);
+            if (wallpaper.hasVisibleNotDrawnWallpaper()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        if (stringName == null) {
+            StringBuilder sb = new StringBuilder();
+            sb.append("WallpaperWindowToken{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(" token="); sb.append(token); sb.append('}');
+            stringName = sb.toString();
+        }
+        return stringName;
+    }
+}
diff --git a/com/android/server/wm/Watermark.java b/com/android/server/wm/Watermark.java
new file mode 100644
index 0000000..171e575
--- /dev/null
+++ b/com/android/server/wm/Watermark.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.graphics.Paint.FontMetricsInt;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Display;
+import android.view.Surface.OutOfResourcesException;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+
+/**
+ * Displays a watermark on top of the window manager's windows.
+ */
+class Watermark {
+    private final Display mDisplay;
+    private final String[] mTokens;
+    private final String mText;
+    private final Paint mTextPaint;
+    private final int mTextWidth;
+    private final int mTextHeight;
+    private final int mDeltaX;
+    private final int mDeltaY;
+
+    private final SurfaceControl mSurfaceControl;
+    private final Surface mSurface = new Surface();
+    private int mLastDW;
+    private int mLastDH;
+    private boolean mDrawNeeded;
+
+    Watermark(Display display, DisplayMetrics dm, SurfaceSession session, String[] tokens) {
+        if (false) {
+            Log.i(TAG_WM, "*********************** WATERMARK");
+            for (int i=0; i<tokens.length; i++) {
+                Log.i(TAG_WM, "  TOKEN #" + i + ": " + tokens[i]);
+            }
+        }
+
+        mDisplay = display;
+        mTokens = tokens;
+
+        StringBuilder builder = new StringBuilder(32);
+        int len = mTokens[0].length();
+        len = len & ~1;
+        for (int i=0; i<len; i+=2) {
+            int c1 = mTokens[0].charAt(i);
+            int c2 = mTokens[0].charAt(i+1);
+            if (c1 >= 'a' && c1 <= 'f') c1 = c1 - 'a' + 10;
+            else if (c1 >= 'A' && c1 <= 'F') c1 = c1 - 'A' + 10;
+            else c1 -= '0';
+            if (c2 >= 'a' && c2 <= 'f') c2 = c2 - 'a' + 10;
+            else if (c2 >= 'A' && c2 <= 'F') c2 = c2 - 'A' + 10;
+            else c2 -= '0';
+            builder.append((char)(255-((c1*16)+c2)));
+        }
+        mText = builder.toString();
+        if (false) {
+            Log.i(TAG_WM, "Final text: " + mText);
+        }
+
+        int fontSize = WindowManagerService.getPropertyInt(tokens, 1,
+                TypedValue.COMPLEX_UNIT_DIP, 20, dm);
+
+        mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        mTextPaint.setTextSize(fontSize);
+        mTextPaint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD));
+
+        FontMetricsInt fm = mTextPaint.getFontMetricsInt();
+        mTextWidth = (int)mTextPaint.measureText(mText);
+        mTextHeight = fm.descent - fm.ascent;
+
+        mDeltaX = WindowManagerService.getPropertyInt(tokens, 2,
+                TypedValue.COMPLEX_UNIT_PX, mTextWidth*2, dm);
+        mDeltaY = WindowManagerService.getPropertyInt(tokens, 3,
+                TypedValue.COMPLEX_UNIT_PX, mTextHeight*3, dm);
+        int shadowColor = WindowManagerService.getPropertyInt(tokens, 4,
+                TypedValue.COMPLEX_UNIT_PX, 0xb0000000, dm);
+        int color = WindowManagerService.getPropertyInt(tokens, 5,
+                TypedValue.COMPLEX_UNIT_PX, 0x60ffffff, dm);
+        int shadowRadius = WindowManagerService.getPropertyInt(tokens, 6,
+                TypedValue.COMPLEX_UNIT_PX, 7, dm);
+        int shadowDx = WindowManagerService.getPropertyInt(tokens, 8,
+                TypedValue.COMPLEX_UNIT_PX, 0, dm);
+        int shadowDy = WindowManagerService.getPropertyInt(tokens, 9,
+                TypedValue.COMPLEX_UNIT_PX, 0, dm);
+
+        mTextPaint.setColor(color);
+        mTextPaint.setShadowLayer(shadowRadius, shadowDx, shadowDy, shadowColor);
+
+        SurfaceControl ctrl = null;
+        try {
+            ctrl = new SurfaceControl(session, "WatermarkSurface",
+                    1, 1, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+            ctrl.setLayerStack(mDisplay.getLayerStack());
+            ctrl.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER*100);
+            ctrl.setPosition(0, 0);
+            ctrl.show();
+            mSurface.copyFrom(ctrl);
+        } catch (OutOfResourcesException e) {
+        }
+        mSurfaceControl = ctrl;
+    }
+
+    void positionSurface(int dw, int dh) {
+        if (mLastDW != dw || mLastDH != dh) {
+            mLastDW = dw;
+            mLastDH = dh;
+            mSurfaceControl.setSize(dw, dh);
+            mDrawNeeded = true;
+        }
+    }
+
+    void drawIfNeeded() {
+        if (mDrawNeeded) {
+            final int dw = mLastDW;
+            final int dh = mLastDH;
+
+            mDrawNeeded = false;
+            Rect dirty = new Rect(0, 0, dw, dh);
+            Canvas c = null;
+            try {
+                c = mSurface.lockCanvas(dirty);
+            } catch (IllegalArgumentException e) {
+            } catch (Surface.OutOfResourcesException e) {
+            }
+            if (c != null) {
+                c.drawColor(0, PorterDuff.Mode.CLEAR);
+
+                int deltaX = mDeltaX;
+                int deltaY = mDeltaY;
+
+                // deltaX shouldn't be close to a round fraction of our
+                // x step, or else things will line up too much.
+                int div = (dw+mTextWidth)/deltaX;
+                int rem = (dw+mTextWidth) - (div*deltaX);
+                int qdelta = deltaX/4;
+                if (rem < qdelta || rem > (deltaX-qdelta)) {
+                    deltaX += deltaX/3;
+                }
+
+                int y = -mTextHeight;
+                int x = -mTextWidth;
+                while (y < (dh+mTextHeight)) {
+                    c.drawText(mText, x, y, mTextPaint);
+                    x += deltaX;
+                    if (x >= dw) {
+                        x -= (dw+mTextWidth);
+                        y += deltaY;
+                    }
+                }
+                mSurface.unlockCanvasAndPost(c);
+            }
+        }
+    }
+}
diff --git a/com/android/server/wm/WindowAnimator.java b/com/android/server/wm/WindowAnimator.java
new file mode 100644
index 0000000..c01ee31
--- /dev/null
+++ b/com/android/server/wm/WindowAnimator.java
@@ -0,0 +1,440 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowSurfacePlacer.SET_ORIENTATION_CHANGE_COMPLETE;
+import static com.android.server.wm.WindowSurfacePlacer.SET_UPDATE_ROTATION;
+
+import android.content.Context;
+import android.os.Trace;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+import android.view.Choreographer;
+import android.view.SurfaceControl;
+import android.view.WindowManagerPolicy;
+
+import com.android.server.AnimationThread;
+
+import java.io.PrintWriter;
+
+/**
+ * Singleton class that carries out the animations and Surface operations in a separate task
+ * on behalf of WindowManagerService.
+ */
+public class WindowAnimator {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowAnimator" : TAG_WM;
+
+    final WindowManagerService mService;
+    final Context mContext;
+    final WindowManagerPolicy mPolicy;
+    private final WindowSurfacePlacer mWindowPlacerLocked;
+
+    /** Is any window animating? */
+    private boolean mAnimating;
+    private boolean mLastAnimating;
+
+    /** Is any app window animating? */
+    boolean mAppWindowAnimating;
+
+    final Choreographer.FrameCallback mAnimationFrameCallback;
+
+    /** Time of current animation step. Reset on each iteration */
+    long mCurrentTime;
+
+    /** Skip repeated AppWindowTokens initialization. Note that AppWindowsToken's version of this
+     * is a long initialized to Long.MIN_VALUE so that it doesn't match this value on startup. */
+    int mAnimTransactionSequence;
+
+    /** Window currently running an animation that has requested it be detached
+     * from the wallpaper.  This means we need to ensure the wallpaper is
+     * visible behind it in case it animates in a way that would allow it to be
+     * seen. If multiple windows satisfy this, use the lowest window. */
+    WindowState mWindowDetachedWallpaper = null;
+
+    int mBulkUpdateParams = 0;
+    Object mLastWindowFreezeSource;
+
+    SparseArray<DisplayContentsAnimator> mDisplayContentsAnimators = new SparseArray<>(2);
+
+    boolean mInitialized = false;
+
+    // When set to true the animator will go over all windows after an animation frame is posted and
+    // check if some got replaced and can be removed.
+    private boolean mRemoveReplacedWindows = false;
+
+    private Choreographer mChoreographer;
+
+    /**
+     * Indicates whether we have an animation frame callback scheduled, which will happen at
+     * vsync-app and then schedule the animation tick at the right time (vsync-sf).
+     */
+    private boolean mAnimationFrameCallbackScheduled;
+
+    WindowAnimator(final WindowManagerService service) {
+        mService = service;
+        mContext = service.mContext;
+        mPolicy = service.mPolicy;
+        mWindowPlacerLocked = service.mWindowPlacerLocked;
+        AnimationThread.getHandler().runWithScissors(
+                () -> mChoreographer = Choreographer.getSfInstance(), 0 /* timeout */);
+
+        mAnimationFrameCallback = frameTimeNs -> {
+            synchronized (mService.mWindowMap) {
+                mAnimationFrameCallbackScheduled = false;
+            }
+            animate(frameTimeNs);
+        };
+    }
+
+    void addDisplayLocked(final int displayId) {
+        // Create the DisplayContentsAnimator object by retrieving it if the associated
+        // {@link DisplayContent} exists.
+        getDisplayContentsAnimatorLocked(displayId);
+        if (displayId == DEFAULT_DISPLAY) {
+            mInitialized = true;
+        }
+    }
+
+    void removeDisplayLocked(final int displayId) {
+        final DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.get(displayId);
+        if (displayAnimator != null) {
+            if (displayAnimator.mScreenRotationAnimation != null) {
+                displayAnimator.mScreenRotationAnimation.kill();
+                displayAnimator.mScreenRotationAnimation = null;
+            }
+        }
+
+        mDisplayContentsAnimators.delete(displayId);
+    }
+
+    /**
+     * DO NOT HOLD THE WINDOW MANAGER LOCK WHILE CALLING THIS METHOD. Reason: the method closes
+     * an animation transaction, that might be blocking until the next sf-vsync, so we want to make
+     * sure other threads can make progress if this happens.
+     */
+    private void animate(long frameTimeNs) {
+
+        synchronized (mService.mWindowMap) {
+            if (!mInitialized) {
+                return;
+            }
+
+            // Schedule next frame already such that back-pressure happens continuously
+            scheduleAnimation();
+        }
+
+        // Simulate back-pressure by opening and closing an empty animation transaction. This makes
+        // sure that an animation frame is at least presented once on the screen. We do this outside
+        // of the regular transaction such that we can avoid holding the window manager lock in case
+        // we receive back-pressure from SurfaceFlinger. Since closing an animation transaction
+        // without the window manager locks leads to ordering issues (as the transaction will be
+        // processed only at the beginning of the next frame which may result in another transaction
+        // that was executed later in WM side gets executed first on SF side), we don't update any
+        // Surface properties here such that reordering doesn't cause issues.
+        mService.executeEmptyAnimationTransaction();
+
+        synchronized (mService.mWindowMap) {
+            mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS;
+            mBulkUpdateParams = SET_ORIENTATION_CHANGE_COMPLETE;
+            mAnimating = false;
+            mAppWindowAnimating = false;
+            if (DEBUG_WINDOW_TRACE) {
+                Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime);
+            }
+
+            if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION animate");
+            mService.openSurfaceTransaction();
+            try {
+                final AccessibilityController accessibilityController =
+                        mService.mAccessibilityController;
+                final int numDisplays = mDisplayContentsAnimators.size();
+                for (int i = 0; i < numDisplays; i++) {
+                    final int displayId = mDisplayContentsAnimators.keyAt(i);
+                    final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId);
+                    dc.stepAppWindowsAnimation(mCurrentTime);
+                    DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i);
+
+                    final ScreenRotationAnimation screenRotationAnimation =
+                            displayAnimator.mScreenRotationAnimation;
+                    if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
+                        if (screenRotationAnimation.stepAnimationLocked(mCurrentTime)) {
+                            setAnimating(true);
+                        } else {
+                            mBulkUpdateParams |= SET_UPDATE_ROTATION;
+                            screenRotationAnimation.kill();
+                            displayAnimator.mScreenRotationAnimation = null;
+
+                            //TODO (multidisplay): Accessibility supported only for the default
+                            // display.
+                            if (accessibilityController != null && dc.isDefaultDisplay) {
+                                // We just finished rotation animation which means we did not
+                                // announce the rotation and waited for it to end, announce now.
+                                accessibilityController.onRotationChangedLocked(
+                                        mService.getDefaultDisplayContentLocked());
+                            }
+                        }
+                    }
+
+                    // Update animations of all applications, including those
+                    // associated with exiting/removed apps
+                    ++mAnimTransactionSequence;
+                    dc.updateWindowsForAnimator(this);
+                    dc.updateWallpaperForAnimator(this);
+                    dc.prepareWindowSurfaces();
+                }
+
+                for (int i = 0; i < numDisplays; i++) {
+                    final int displayId = mDisplayContentsAnimators.keyAt(i);
+                    final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId);
+
+                    dc.checkAppWindowsReadyToShow();
+
+                    final ScreenRotationAnimation screenRotationAnimation =
+                            mDisplayContentsAnimators.valueAt(i).mScreenRotationAnimation;
+                    if (screenRotationAnimation != null) {
+                        screenRotationAnimation.updateSurfacesInTransaction();
+                    }
+
+                    orAnimating(dc.animateDimLayers());
+                    orAnimating(dc.getDockedDividerController().animate(mCurrentTime));
+                    //TODO (multidisplay): Magnification is supported only for the default display.
+                    if (accessibilityController != null && dc.isDefaultDisplay) {
+                        accessibilityController.drawMagnifiedRegionBorderIfNeededLocked();
+                    }
+                }
+
+                if (mService.mDragState != null) {
+                    mAnimating |= mService.mDragState.stepAnimationLocked(mCurrentTime);
+                }
+
+                if (!mAnimating) {
+                    cancelAnimation();
+                }
+
+                if (mService.mWatermark != null) {
+                    mService.mWatermark.drawIfNeeded();
+                }
+            } catch (RuntimeException e) {
+                Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
+            } finally {
+                mService.closeSurfaceTransaction();
+                if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION animate");
+            }
+
+            boolean hasPendingLayoutChanges = mService.mRoot.hasPendingLayoutChanges(this);
+            boolean doRequest = false;
+            if (mBulkUpdateParams != 0) {
+                doRequest = mService.mRoot.copyAnimToLayoutParams();
+            }
+
+            if (hasPendingLayoutChanges || doRequest) {
+                mWindowPlacerLocked.requestTraversal();
+            }
+
+            if (mAnimating && !mLastAnimating) {
+
+                // Usually app transitions but quite a load onto the system already (with all the
+                // things happening in app), so pause task snapshot persisting to not increase the
+                // load.
+                mService.mTaskSnapshotController.setPersisterPaused(true);
+                Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0);
+            }
+            if (!mAnimating && mLastAnimating) {
+                mWindowPlacerLocked.requestTraversal();
+                mService.mTaskSnapshotController.setPersisterPaused(false);
+                Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0);
+            }
+
+            mLastAnimating = mAnimating;
+
+            if (mRemoveReplacedWindows) {
+                mService.mRoot.removeReplacedWindows();
+                mRemoveReplacedWindows = false;
+            }
+
+            mService.destroyPreservedSurfaceLocked();
+            mService.mWindowPlacerLocked.destroyPendingSurfaces();
+
+            if (DEBUG_WINDOW_TRACE) {
+                Slog.i(TAG, "!!! animate: exit mAnimating=" + mAnimating
+                        + " mBulkUpdateParams=" + Integer.toHexString(mBulkUpdateParams)
+                        + " mPendingLayoutChanges(DEFAULT_DISPLAY)="
+                        + Integer.toHexString(getPendingLayoutChanges(DEFAULT_DISPLAY)));
+            }
+        }
+    }
+
+    private static String bulkUpdateParamsToString(int bulkUpdateParams) {
+        StringBuilder builder = new StringBuilder(128);
+        if ((bulkUpdateParams & WindowSurfacePlacer.SET_UPDATE_ROTATION) != 0) {
+            builder.append(" UPDATE_ROTATION");
+        }
+        if ((bulkUpdateParams & WindowSurfacePlacer.SET_WALLPAPER_MAY_CHANGE) != 0) {
+            builder.append(" WALLPAPER_MAY_CHANGE");
+        }
+        if ((bulkUpdateParams & WindowSurfacePlacer.SET_FORCE_HIDING_CHANGED) != 0) {
+            builder.append(" FORCE_HIDING_CHANGED");
+        }
+        if ((bulkUpdateParams & WindowSurfacePlacer.SET_ORIENTATION_CHANGE_COMPLETE) != 0) {
+            builder.append(" ORIENTATION_CHANGE_COMPLETE");
+        }
+        if ((bulkUpdateParams & WindowSurfacePlacer.SET_TURN_ON_SCREEN) != 0) {
+            builder.append(" TURN_ON_SCREEN");
+        }
+        return builder.toString();
+    }
+
+    public void dumpLocked(PrintWriter pw, String prefix, boolean dumpAll) {
+        final String subPrefix = "  " + prefix;
+        final String subSubPrefix = "  " + subPrefix;
+
+        for (int i = 0; i < mDisplayContentsAnimators.size(); i++) {
+            pw.print(prefix); pw.print("DisplayContentsAnimator #");
+                    pw.print(mDisplayContentsAnimators.keyAt(i));
+                    pw.println(":");
+            final DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i);
+            final DisplayContent dc =
+                    mService.mRoot.getDisplayContentOrCreate(mDisplayContentsAnimators.keyAt(i));
+            dc.dumpWindowAnimators(pw, subPrefix);
+            if (displayAnimator.mScreenRotationAnimation != null) {
+                pw.print(subPrefix); pw.println("mScreenRotationAnimation:");
+                displayAnimator.mScreenRotationAnimation.printTo(subSubPrefix, pw);
+            } else if (dumpAll) {
+                pw.print(subPrefix); pw.println("no ScreenRotationAnimation ");
+            }
+            pw.println();
+        }
+
+        pw.println();
+
+        if (dumpAll) {
+            pw.print(prefix); pw.print("mAnimTransactionSequence=");
+                    pw.print(mAnimTransactionSequence);
+            pw.print(prefix); pw.print("mCurrentTime=");
+                    pw.println(TimeUtils.formatUptime(mCurrentTime));
+        }
+        if (mBulkUpdateParams != 0) {
+            pw.print(prefix); pw.print("mBulkUpdateParams=0x");
+                    pw.print(Integer.toHexString(mBulkUpdateParams));
+                    pw.println(bulkUpdateParamsToString(mBulkUpdateParams));
+        }
+        if (mWindowDetachedWallpaper != null) {
+            pw.print(prefix); pw.print("mWindowDetachedWallpaper=");
+                pw.println(mWindowDetachedWallpaper);
+        }
+    }
+
+    int getPendingLayoutChanges(final int displayId) {
+        if (displayId < 0) {
+            return 0;
+        }
+        final DisplayContent displayContent = mService.mRoot.getDisplayContentOrCreate(displayId);
+        return (displayContent != null) ? displayContent.pendingLayoutChanges : 0;
+    }
+
+    void setPendingLayoutChanges(final int displayId, final int changes) {
+        if (displayId < 0) {
+            return;
+        }
+        final DisplayContent displayContent = mService.mRoot.getDisplayContentOrCreate(displayId);
+        if (displayContent != null) {
+            displayContent.pendingLayoutChanges |= changes;
+        }
+    }
+
+    private DisplayContentsAnimator getDisplayContentsAnimatorLocked(int displayId) {
+        if (displayId < 0) {
+            return null;
+        }
+
+        DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.get(displayId);
+
+        // It is possible that this underlying {@link DisplayContent} has been removed. In this
+        // case, we do not want to create an animator associated with it as {link #animate} will
+        // fail.
+        if (displayAnimator == null && mService.mRoot.getDisplayContent(displayId) != null) {
+            displayAnimator = new DisplayContentsAnimator();
+            mDisplayContentsAnimators.put(displayId, displayAnimator);
+        }
+        return displayAnimator;
+    }
+
+    void setScreenRotationAnimationLocked(int displayId, ScreenRotationAnimation animation) {
+        final DisplayContentsAnimator animator = getDisplayContentsAnimatorLocked(displayId);
+
+        if (animator != null) {
+            animator.mScreenRotationAnimation = animation;
+        }
+    }
+
+    ScreenRotationAnimation getScreenRotationAnimationLocked(int displayId) {
+        if (displayId < 0) {
+            return null;
+        }
+
+        DisplayContentsAnimator animator = getDisplayContentsAnimatorLocked(displayId);
+        return animator != null? animator.mScreenRotationAnimation : null;
+    }
+
+    void requestRemovalOfReplacedWindows(WindowState win) {
+        mRemoveReplacedWindows = true;
+    }
+
+    void scheduleAnimation() {
+        if (!mAnimationFrameCallbackScheduled) {
+            mAnimationFrameCallbackScheduled = true;
+            mChoreographer.postFrameCallback(mAnimationFrameCallback);
+        }
+    }
+
+    private void cancelAnimation() {
+        if (mAnimationFrameCallbackScheduled) {
+            mAnimationFrameCallbackScheduled = false;
+            mChoreographer.removeFrameCallback(mAnimationFrameCallback);
+        }
+    }
+
+    private class DisplayContentsAnimator {
+        ScreenRotationAnimation mScreenRotationAnimation = null;
+    }
+
+    boolean isAnimating() {
+        return mAnimating;
+    }
+
+    boolean isAnimationScheduled() {
+        return mAnimationFrameCallbackScheduled;
+    }
+
+    Choreographer getChoreographer() {
+        return mChoreographer;
+    }
+
+    void setAnimating(boolean animating) {
+        mAnimating = animating;
+    }
+
+    void orAnimating(boolean animating) {
+        mAnimating |= animating;
+    }
+}
diff --git a/com/android/server/wm/WindowContainer.java b/com/android/server/wm/WindowContainer.java
new file mode 100644
index 0000000..926719d
--- /dev/null
+++ b/com/android/server/wm/WindowContainer.java
@@ -0,0 +1,720 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.res.Configuration.EMPTY;
+
+import android.annotation.CallSuper;
+import android.content.res.Configuration;
+import android.util.Pools;
+
+import com.android.internal.util.ToBooleanFunction;
+
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+/**
+ * Defines common functionality for classes that can hold windows directly or through their
+ * children in a hierarchy form.
+ * The test class is {@link WindowContainerTests} which must be kept up-to-date and ran anytime
+ * changes are made to this class.
+ */
+class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<E>
+        implements Comparable<WindowContainer> {
+
+    static final int POSITION_TOP = Integer.MAX_VALUE;
+    static final int POSITION_BOTTOM = Integer.MIN_VALUE;
+
+    /**
+     * The parent of this window container.
+     * For removing or setting new parent {@link #setParent} should be used, because it also
+     * performs configuration updates based on new parent's settings.
+     */
+    private WindowContainer mParent = null;
+
+    // List of children for this window container. List is in z-order as the children appear on
+    // screen with the top-most window container at the tail of the list.
+    protected final WindowList<E> mChildren = new WindowList<E>();
+
+    // The specified orientation for this window container.
+    protected int mOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+
+    private final Pools.SynchronizedPool<ForAllWindowsConsumerWrapper> mConsumerWrapperPool =
+            new Pools.SynchronizedPool<>(3);
+
+    // The owner/creator for this container. No controller if null.
+    private WindowContainerController mController;
+
+    @Override
+    final protected WindowContainer getParent() {
+        return mParent;
+    }
+
+
+    @Override
+    final protected int getChildCount() {
+        return mChildren.size();
+    }
+
+    @Override
+    final protected E getChildAt(int index) {
+        return mChildren.get(index);
+    }
+
+    final protected void setParent(WindowContainer parent) {
+        mParent = parent;
+        // Removing parent usually means that we've detached this entity to destroy it or to attach
+        // to another parent. In both cases we don't need to update the configuration now.
+        if (mParent != null) {
+            // Update full configuration of this container and all its children.
+            onConfigurationChanged(mParent.getConfiguration());
+            // Update merged override configuration of this container and all its children.
+            onMergedOverrideConfigurationChanged();
+        }
+
+        onParentSet();
+    }
+
+    /**
+     * Callback that is triggered when @link WindowContainer#setParent(WindowContainer)} was called.
+     * Supposed to be overridden and contain actions that should be executed after parent was set.
+     */
+    void onParentSet() {
+        // Do nothing by default.
+    }
+
+    // Temp. holders for a chain of containers we are currently processing.
+    private final LinkedList<WindowContainer> mTmpChain1 = new LinkedList();
+    private final LinkedList<WindowContainer> mTmpChain2 = new LinkedList();
+
+    /**
+     * Adds the input window container has a child of this container in order based on the input
+     * comparator.
+     * @param child The window container to add as a child of this window container.
+     * @param comparator Comparator to use in determining the position the child should be added to.
+     *                   If null, the child will be added to the top.
+     */
+    @CallSuper
+    protected void addChild(E child, Comparator<E> comparator) {
+        if (child.getParent() != null) {
+            throw new IllegalArgumentException("addChild: container=" + child.getName()
+                    + " is already a child of container=" + child.getParent().getName()
+                    + " can't add to container=" + getName());
+        }
+
+        int positionToAdd = -1;
+        if (comparator != null) {
+            final int count = mChildren.size();
+            for (int i = 0; i < count; i++) {
+                if (comparator.compare(child, mChildren.get(i)) < 0) {
+                    positionToAdd = i;
+                    break;
+                }
+            }
+        }
+
+        if (positionToAdd == -1) {
+            mChildren.add(child);
+        } else {
+            mChildren.add(positionToAdd, child);
+        }
+        // Set the parent after we've actually added a child in case a subclass depends on this.
+        child.setParent(this);
+    }
+
+    /** Adds the input window container has a child of this container at the input index. */
+    @CallSuper
+    void addChild(E child, int index) {
+        if (child.getParent() != null) {
+            throw new IllegalArgumentException("addChild: container=" + child.getName()
+                    + " is already a child of container=" + child.getParent().getName()
+                    + " can't add to container=" + getName());
+        }
+        mChildren.add(index, child);
+        // Set the parent after we've actually added a child in case a subclass depends on this.
+        child.setParent(this);
+    }
+
+    /**
+     * Removes the input child container from this container which is its parent.
+     *
+     * @return True if the container did contain the input child and it was detached.
+     */
+    @CallSuper
+    void removeChild(E child) {
+        if (mChildren.remove(child)) {
+            child.setParent(null);
+        } else {
+            throw new IllegalArgumentException("removeChild: container=" + child.getName()
+                    + " is not a child of container=" + getName());
+        }
+    }
+
+    /**
+     * Removes this window container and its children with no regard for what else might be going on
+     * in the system. For example, the container will be removed during animation if this method is
+     * called which isn't desirable. For most cases you want to call {@link #removeIfPossible()}
+     * which allows the system to defer removal until a suitable time.
+     */
+    @CallSuper
+    void removeImmediately() {
+        while (!mChildren.isEmpty()) {
+            final WindowContainer child = mChildren.peekLast();
+            child.removeImmediately();
+            // Need to do this after calling remove on the child because the child might try to
+            // remove/detach itself from its parent which will cause an exception if we remove
+            // it before calling remove on the child.
+            mChildren.remove(child);
+        }
+
+        if (mParent != null) {
+            mParent.removeChild(this);
+        }
+
+        if (mController != null) {
+            setController(null);
+        }
+    }
+
+    /**
+     * Removes this window container and its children taking care not to remove them during a
+     * critical stage in the system. For example, some containers will not be removed during
+     * animation if this method is called.
+     */
+    // TODO: figure-out implementation that works best for this.
+    // E.g. when do we remove from parent list? maybe not...
+    void removeIfPossible() {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowContainer wc = mChildren.get(i);
+            wc.removeIfPossible();
+        }
+    }
+
+    /** Returns true if this window container has the input child. */
+    boolean hasChild(WindowContainer child) {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowContainer current = mChildren.get(i);
+            if (current == child || current.hasChild(child)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Move a child from it's current place in siblings list to the specified position,
+     * with an option to move all its parents to top.
+     * @param position Target position to move the child to.
+     * @param child Child to move to selected position.
+     * @param includingParents Flag indicating whether we need to move the entire branch of the
+     *                         hierarchy when we're moving a child to {@link #POSITION_TOP} or
+     *                         {@link #POSITION_BOTTOM}. When moving to other intermediate positions
+     *                         this flag will do nothing.
+     */
+    @CallSuper
+    void positionChildAt(int position, E child, boolean includingParents) {
+
+        if (child.getParent() != this) {
+            throw new IllegalArgumentException("removeChild: container=" + child.getName()
+                    + " is not a child of container=" + getName()
+                    + " current parent=" + child.getParent());
+        }
+
+        if ((position < 0 && position != POSITION_BOTTOM)
+                || (position > mChildren.size() && position != POSITION_TOP)) {
+            throw new IllegalArgumentException("positionAt: invalid position=" + position
+                    + ", children number=" + mChildren.size());
+        }
+
+        if (position >= mChildren.size() - 1) {
+            position = POSITION_TOP;
+        } else if (position == 0) {
+            position = POSITION_BOTTOM;
+        }
+
+        switch (position) {
+            case POSITION_TOP:
+                if (mChildren.peekLast() != child) {
+                    mChildren.remove(child);
+                    mChildren.add(child);
+                }
+                if (includingParents && getParent() != null) {
+                    getParent().positionChildAt(POSITION_TOP, this /* child */,
+                            true /* includingParents */);
+                }
+                break;
+            case POSITION_BOTTOM:
+                if (mChildren.peekFirst() != child) {
+                    mChildren.remove(child);
+                    mChildren.addFirst(child);
+                }
+                if (includingParents && getParent() != null) {
+                    getParent().positionChildAt(POSITION_BOTTOM, this /* child */,
+                            true /* includingParents */);
+                }
+                break;
+            default:
+                mChildren.remove(child);
+                mChildren.add(position, child);
+        }
+    }
+
+    /**
+     * Update override configuration and recalculate full config.
+     * @see #mOverrideConfiguration
+     * @see #mFullConfiguration
+     */
+    @Override
+    final public void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
+        super.onOverrideConfigurationChanged(overrideConfiguration);
+        if (mParent != null) {
+            mParent.onDescendantOverrideConfigurationChanged();
+        }
+    }
+
+    /**
+     * Notify that a descendant's overrideConfiguration has changed.
+     */
+    void onDescendantOverrideConfigurationChanged() {
+        if (mParent != null) {
+            mParent.onDescendantOverrideConfigurationChanged();
+        }
+    }
+
+    /**
+     * Notify that the display this container is on has changed.
+     * @param dc The new display this container is on.
+     */
+    void onDisplayChanged(DisplayContent dc) {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowContainer child = mChildren.get(i);
+            child.onDisplayChanged(dc);
+        }
+    }
+
+    void setWaitingForDrawnIfResizingChanged() {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowContainer wc = mChildren.get(i);
+            wc.setWaitingForDrawnIfResizingChanged();
+        }
+    }
+
+    void onResize() {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowContainer wc = mChildren.get(i);
+            wc.onResize();
+        }
+    }
+
+    void onMovedByResize() {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowContainer wc = mChildren.get(i);
+            wc.onMovedByResize();
+        }
+    }
+
+    void resetDragResizingChangeReported() {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowContainer wc = mChildren.get(i);
+            wc.resetDragResizingChangeReported();
+        }
+    }
+
+    void forceWindowsScaleableInTransaction(boolean force) {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowContainer wc = mChildren.get(i);
+            wc.forceWindowsScaleableInTransaction(force);
+        }
+    }
+
+    boolean isAnimating() {
+        for (int j = mChildren.size() - 1; j >= 0; j--) {
+            final WindowContainer wc = mChildren.get(j);
+            if (wc.isAnimating()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    void sendAppVisibilityToClients() {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowContainer wc = mChildren.get(i);
+            wc.sendAppVisibilityToClients();
+        }
+    }
+
+    /**
+     * Returns true if the container or one of its children as some content it can display or wants
+     * to display (e.g. app views or saved surface).
+     *
+     * NOTE: While this method will return true if the there is some content to display, it doesn't
+     * mean the container is visible. Use {@link #isVisible()} to determine if the container is
+     * visible.
+     */
+    boolean hasContentToDisplay() {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowContainer wc = mChildren.get(i);
+            if (wc.hasContentToDisplay()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if the container or one of its children is considered visible from the
+     * WindowManager perspective which usually means valid surface and some other internal state
+     * are true.
+     *
+     * NOTE: While this method will return true if the surface is visible, it doesn't mean the
+     * client has actually displayed any content. Use {@link #hasContentToDisplay()} to determine if
+     * the container has any content to display.
+     */
+    boolean isVisible() {
+        // TODO: Will this be more correct if it checks the visibility of its parents?
+        // It depends...For example, Tasks and Stacks are only visible if there children are visible
+        // but, WindowState are not visible if there parent are not visible. Maybe have the
+        // container specify which direction to traverse for visibility?
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowContainer wc = mChildren.get(i);
+            if (wc.isVisible()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+a     * Returns whether this child is on top of the window hierarchy.
+     */
+    boolean isOnTop() {
+        return getParent().getTopChild() == this && getParent().isOnTop();
+    }
+
+    /** Returns the top child container. */
+    E getTopChild() {
+        return mChildren.peekLast();
+    }
+
+    /** Returns true if there is still a removal being deferred */
+    boolean checkCompleteDeferredRemoval() {
+        boolean stillDeferringRemoval = false;
+
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowContainer wc = mChildren.get(i);
+            stillDeferringRemoval |= wc.checkCompleteDeferredRemoval();
+        }
+
+        return stillDeferringRemoval;
+    }
+
+    /** Checks if all windows in an app are all drawn and shows them if needed. */
+    void checkAppWindowsReadyToShow() {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowContainer wc = mChildren.get(i);
+            wc.checkAppWindowsReadyToShow();
+        }
+    }
+
+    /** Step currently ongoing animation for App window containers. */
+    void stepAppWindowsAnimation(long currentTime) {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowContainer wc = mChildren.get(i);
+            wc.stepAppWindowsAnimation(currentTime);
+        }
+    }
+
+    void onAppTransitionDone() {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowContainer wc = mChildren.get(i);
+            wc.onAppTransitionDone();
+        }
+    }
+
+    void setOrientation(int orientation) {
+        mOrientation = orientation;
+    }
+
+    int getOrientation() {
+        return getOrientation(mOrientation);
+    }
+
+    /**
+     * Returns the specified orientation for this window container or one of its children is there
+     * is one set, or {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSET} if no
+     * specification is set.
+     * NOTE: {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED} is a
+     * specification...
+     *
+     * @param candidate The current orientation candidate that will be returned if we don't find a
+     *                  better match.
+     * @return The orientation as specified by this branch or the window hierarchy.
+     */
+    int getOrientation(int candidate) {
+        if (!fillsParent()) {
+            // Ignore containers that don't completely fill their parents.
+            return SCREEN_ORIENTATION_UNSET;
+        }
+
+        // The container fills its parent so we can use it orientation if it has one
+        // specified; otherwise we prefer to use the orientation of its topmost child that has one
+        // specified and fall back on this container's unset or unspecified value as a candidate
+        // if none of the children have a better candidate for the orientation.
+        if (mOrientation != SCREEN_ORIENTATION_UNSET
+                && mOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
+            return mOrientation;
+        }
+
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowContainer wc = mChildren.get(i);
+
+            // TODO: Maybe mOrientation should default to SCREEN_ORIENTATION_UNSET vs.
+            // SCREEN_ORIENTATION_UNSPECIFIED?
+            final int orientation = wc.getOrientation(candidate == SCREEN_ORIENTATION_BEHIND
+                    ? SCREEN_ORIENTATION_BEHIND : SCREEN_ORIENTATION_UNSET);
+            if (orientation == SCREEN_ORIENTATION_BEHIND) {
+                // container wants us to use the orientation of the container behind it. See if we
+                // can find one. Else return SCREEN_ORIENTATION_BEHIND so the caller can choose to
+                // look behind this container.
+                candidate = orientation;
+                continue;
+            }
+
+            if (orientation == SCREEN_ORIENTATION_UNSET) {
+                continue;
+            }
+
+            if (wc.fillsParent() || orientation != SCREEN_ORIENTATION_UNSPECIFIED) {
+                // Use the orientation if the container fills its parent or requested an explicit
+                // orientation that isn't SCREEN_ORIENTATION_UNSPECIFIED.
+                return orientation;
+            }
+        }
+
+        return candidate;
+    }
+
+    /**
+     * Returns true if this container is opaque and fills all the space made available by its parent
+     * container.
+     *
+     * NOTE: It is possible for this container to occupy more space than the parent has (or less),
+     * this is just a signal from the client to window manager stating its intent, but not what it
+     * actually does.
+     */
+    boolean fillsParent() {
+        return false;
+    }
+
+    // TODO: Users would have their own window containers under the display container?
+    void switchUser() {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            mChildren.get(i).switchUser();
+        }
+    }
+
+    /**
+     * For all windows at or below this container call the callback.
+     * @param   callback Calls the {@link ToBooleanFunction#apply} method for each window found and
+     *                   stops the search if {@link ToBooleanFunction#apply} returns true.
+     * @param   traverseTopToBottom If true traverses the hierarchy from top-to-bottom in terms of
+     *                              z-order, else from bottom-to-top.
+     * @return  True if the search ended before we reached the end of the hierarchy due to
+     *          {@link ToBooleanFunction#apply} returning true.
+     */
+    boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
+        if (traverseTopToBottom) {
+            for (int i = mChildren.size() - 1; i >= 0; --i) {
+                if (mChildren.get(i).forAllWindows(callback, traverseTopToBottom)) {
+                    return true;
+                }
+            }
+        } else {
+            final int count = mChildren.size();
+            for (int i = 0; i < count; i++) {
+                if (mChildren.get(i).forAllWindows(callback, traverseTopToBottom)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    void forAllWindows(Consumer<WindowState> callback, boolean traverseTopToBottom) {
+        ForAllWindowsConsumerWrapper wrapper = obtainConsumerWrapper(callback);
+        forAllWindows(wrapper, traverseTopToBottom);
+        wrapper.release();
+    }
+
+    /**
+     * For all tasks at or below this container call the callback.
+     *
+     * @param callback Callback to be called for every task.
+     */
+    void forAllTasks(Consumer<Task> callback) {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            mChildren.get(i).forAllTasks(callback);
+        }
+    }
+
+    WindowState getWindow(Predicate<WindowState> callback) {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowState w = mChildren.get(i).getWindow(callback);
+            if (w != null) {
+                return w;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns 1, 0, or -1 depending on if this container is greater than, equal to, or lesser than
+     * the input container in terms of z-order.
+     */
+    @Override
+    public int compareTo(WindowContainer other) {
+        if (this == other) {
+            return 0;
+        }
+
+        if (mParent != null && mParent == other.mParent) {
+            final WindowList<WindowContainer> list = mParent.mChildren;
+            return list.indexOf(this) > list.indexOf(other) ? 1 : -1;
+        }
+
+        final LinkedList<WindowContainer> thisParentChain = mTmpChain1;
+        final LinkedList<WindowContainer> otherParentChain = mTmpChain2;
+        try {
+            getParents(thisParentChain);
+            other.getParents(otherParentChain);
+
+            // Find the common ancestor of both containers.
+            WindowContainer commonAncestor = null;
+            WindowContainer thisTop = thisParentChain.peekLast();
+            WindowContainer otherTop = otherParentChain.peekLast();
+            while (thisTop != null && otherTop != null && thisTop == otherTop) {
+                commonAncestor = thisParentChain.removeLast();
+                otherParentChain.removeLast();
+                thisTop = thisParentChain.peekLast();
+                otherTop = otherParentChain.peekLast();
+            }
+
+            // Containers don't belong to the same hierarchy???
+            if (commonAncestor == null) {
+                throw new IllegalArgumentException("No in the same hierarchy this="
+                        + thisParentChain + " other=" + otherParentChain);
+            }
+
+            // Children are always considered greater than their parents, so if one of the containers
+            // we are comparing it the parent of the other then whichever is the child is greater.
+            if (commonAncestor == this) {
+                return -1;
+            } else if (commonAncestor == other) {
+                return 1;
+            }
+
+            // The position of the first non-common ancestor in the common ancestor list determines
+            // which is greater the which.
+            final WindowList<WindowContainer> list = commonAncestor.mChildren;
+            return list.indexOf(thisParentChain.peekLast()) > list.indexOf(otherParentChain.peekLast())
+                    ? 1 : -1;
+        } finally {
+            mTmpChain1.clear();
+            mTmpChain2.clear();
+        }
+    }
+
+    private void getParents(LinkedList<WindowContainer> parents) {
+        parents.clear();
+        WindowContainer current = this;
+        do {
+            parents.addLast(current);
+            current = current.mParent;
+        } while (current != null);
+    }
+
+    WindowContainerController getController() {
+        return mController;
+    }
+
+    void setController(WindowContainerController controller) {
+        if (mController != null && controller != null) {
+            throw new IllegalArgumentException("Can't set controller=" + mController
+                    + " for container=" + this + " Already set to=" + mController);
+        }
+        if (controller != null) {
+            controller.setContainer(this);
+        } else if (mController != null) {
+            mController.setContainer(null);
+        }
+        mController = controller;
+    }
+
+    /**
+     * Dumps the names of this container children in the input print writer indenting each
+     * level with the input prefix.
+     */
+    void dumpChildrenNames(StringBuilder out, String prefix) {
+        final String childPrefix = prefix + " ";
+        out.append(getName() + "\n");
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowContainer wc = mChildren.get(i);
+            out.append(childPrefix + "#" + i + " ");
+            wc.dumpChildrenNames(out, childPrefix);
+        }
+    }
+
+    String getName() {
+        return toString();
+    }
+
+    private ForAllWindowsConsumerWrapper obtainConsumerWrapper(Consumer<WindowState> consumer) {
+        ForAllWindowsConsumerWrapper wrapper = mConsumerWrapperPool.acquire();
+        if (wrapper == null) {
+            wrapper = new ForAllWindowsConsumerWrapper();
+        }
+        wrapper.setConsumer(consumer);
+        return wrapper;
+    }
+
+    private final class ForAllWindowsConsumerWrapper implements ToBooleanFunction<WindowState> {
+
+        private Consumer<WindowState> mConsumer;
+
+        void setConsumer(Consumer<WindowState> consumer) {
+            mConsumer = consumer;
+        }
+
+        @Override
+        public boolean apply(WindowState w) {
+            mConsumer.accept(w);
+            return false;
+        }
+
+        void release() {
+            mConsumer = null;
+            mConsumerWrapperPool.release(this);
+        }
+    }
+}
diff --git a/com/android/server/wm/WindowContainerController.java b/com/android/server/wm/WindowContainerController.java
new file mode 100644
index 0000000..eb23faf
--- /dev/null
+++ b/com/android/server/wm/WindowContainerController.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import android.content.res.Configuration;
+
+/**
+ * Class that allows the owner/creator of a {@link WindowContainer} to communicate directly with the
+ * container and make changes.
+ * Note that public calls (mostly in sub-classes) into this class are assumed to be originating from
+ * outside the window manager so the window manager lock is held and appropriate permissions are
+ * checked before calls are allowed to proceed.
+ *
+ * Test class: {@link WindowContainerControllerTests}
+ */
+class WindowContainerController<E extends WindowContainer, I extends WindowContainerListener>
+        implements ConfigurationContainerListener {
+
+    final WindowManagerService mService;
+    final RootWindowContainer mRoot;
+    final WindowHashMap mWindowMap;
+
+    // The window container this controller owns.
+    E mContainer;
+    // Interface for communicating changes back to the owner.
+    final I mListener;
+
+    WindowContainerController(I listener, WindowManagerService service) {
+        mListener = listener;
+        mService = service;
+        mRoot = mService != null ? mService.mRoot : null;
+        mWindowMap = mService != null ? mService.mWindowMap : null;
+    }
+
+    void setContainer(E container) {
+        if (mContainer != null && container != null) {
+            throw new IllegalArgumentException("Can't set container=" + container
+                    + " for controller=" + this + " Already set to=" + mContainer);
+        }
+        mContainer = container;
+        if (mContainer != null && mListener != null) {
+            mListener.registerConfigurationChangeListener(this);
+        }
+    }
+
+    void removeContainer() {
+        // TODO: See if most uses cases should support removeIfPossible here.
+        //mContainer.removeIfPossible();
+        if (mContainer == null) {
+            return;
+        }
+
+        mContainer.setController(null);
+        mContainer = null;
+        if (mListener != null) {
+            mListener.unregisterConfigurationChangeListener(this);
+        }
+    }
+
+    @Override
+    public void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
+        synchronized (mWindowMap) {
+            if (mContainer == null) {
+                return;
+            }
+            mContainer.onOverrideConfigurationChanged(overrideConfiguration);
+        }
+    }
+}
diff --git a/com/android/server/wm/WindowContainerListener.java b/com/android/server/wm/WindowContainerListener.java
new file mode 100644
index 0000000..4b3cd36
--- /dev/null
+++ b/com/android/server/wm/WindowContainerListener.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+/**
+ * Interface used by the owner/creator of the container to listen to changes with the container.
+ * @see WindowContainerController
+ */
+public interface WindowContainerListener {
+    void registerConfigurationChangeListener(ConfigurationContainerListener listener);
+    void unregisterConfigurationChangeListener(ConfigurationContainerListener listener);
+}
diff --git a/com/android/server/wm/WindowHashMap.java b/com/android/server/wm/WindowHashMap.java
new file mode 100644
index 0000000..49bba41
--- /dev/null
+++ b/com/android/server/wm/WindowHashMap.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import android.os.IBinder;
+
+import java.util.HashMap;
+
+/**
+ * Subclass of HashMap such that we can instruct the compiler to boost our thread priority when
+ * locking this class. See makefile.
+ */
+class WindowHashMap extends HashMap<IBinder, WindowState> {
+}
diff --git a/com/android/server/wm/WindowLayersController.java b/com/android/server/wm/WindowLayersController.java
new file mode 100644
index 0000000..857b13d
--- /dev/null
+++ b/com/android/server/wm/WindowLayersController.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.util.Slog;
+
+import java.util.ArrayDeque;
+import java.util.function.Consumer;
+
+import static android.app.ActivityManager.StackId;
+import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.TYPE_LAYER_OFFSET;
+import static com.android.server.wm.WindowManagerService.WINDOW_LAYER_MULTIPLIER;
+
+/**
+ * Controller for assigning layers to windows on the display.
+ *
+ * This class encapsulates general algorithm for assigning layers and special rules that we need to
+ * apply on top. The general algorithm goes through windows from bottom to the top and the higher
+ * the window is, the higher layer is assigned. The final layer is equal to base layer +
+ * adjustment from the order. This means that the window list is assumed to be ordered roughly by
+ * the base layer (there are exceptions, e.g. due to keyguard and wallpaper and they need to be
+ * handled with care, because they break the algorithm).
+ *
+ * On top of the general algorithm we add special rules, that govern such amazing things as:
+ * <li>IME (which has higher base layer, but will be positioned above application windows)</li>
+ * <li>docked/pinned windows (that need to be lifted above other application windows, including
+ * animations)
+ * <li>dock divider (which needs to live above applications, but below IME)</li>
+ * <li>replaced windows, which need to live above their normal level, because they anticipate
+ * an animation</li>.
+ */
+class WindowLayersController {
+    private final WindowManagerService mService;
+
+    WindowLayersController(WindowManagerService service) {
+        mService = service;
+    }
+
+    private ArrayDeque<WindowState> mPinnedWindows = new ArrayDeque<>();
+    private ArrayDeque<WindowState> mDockedWindows = new ArrayDeque<>();
+    private ArrayDeque<WindowState> mAssistantWindows = new ArrayDeque<>();
+    private ArrayDeque<WindowState> mInputMethodWindows = new ArrayDeque<>();
+    private WindowState mDockDivider = null;
+    private ArrayDeque<WindowState> mReplacingWindows = new ArrayDeque<>();
+    private int mCurBaseLayer;
+    private int mCurLayer;
+    private boolean mAnyLayerChanged;
+    private int mHighestApplicationLayer;
+    private int mHighestDockedAffectedLayer;
+    private int mHighestLayerInImeTargetBaseLayer;
+    private WindowState mImeTarget;
+    private boolean mAboveImeTarget;
+    private ArrayDeque<WindowState> mAboveImeTargetAppWindows = new ArrayDeque();
+
+    private final Consumer<WindowState> mAssignWindowLayersConsumer = w -> {
+        boolean layerChanged = false;
+
+        int oldLayer = w.mLayer;
+        if (w.mBaseLayer == mCurBaseLayer) {
+            mCurLayer += WINDOW_LAYER_MULTIPLIER;
+        } else {
+            mCurBaseLayer = mCurLayer = w.mBaseLayer;
+        }
+        assignAnimLayer(w, mCurLayer);
+
+        // TODO: Preserved old behavior of code here but not sure comparing oldLayer to
+        // mAnimLayer and mLayer makes sense...though the worst case would be unintentional
+        // layer reassignment.
+        if (w.mLayer != oldLayer || w.mWinAnimator.mAnimLayer != oldLayer) {
+            layerChanged = true;
+            mAnyLayerChanged = true;
+        }
+
+        if (w.mAppToken != null) {
+            mHighestApplicationLayer = Math.max(mHighestApplicationLayer,
+                    w.mWinAnimator.mAnimLayer);
+        }
+        if (mImeTarget != null && w.mBaseLayer == mImeTarget.mBaseLayer) {
+            mHighestLayerInImeTargetBaseLayer = Math.max(mHighestLayerInImeTargetBaseLayer,
+                    w.mWinAnimator.mAnimLayer);
+        }
+        if (w.getAppToken() != null && w.inSplitScreenSecondaryWindowingMode()) {
+            mHighestDockedAffectedLayer = Math.max(mHighestDockedAffectedLayer,
+                    w.mWinAnimator.mAnimLayer);
+        }
+
+        collectSpecialWindows(w);
+
+        if (layerChanged) {
+            w.scheduleAnimationIfDimming();
+        }
+    };
+
+    final void assignWindowLayers(DisplayContent dc) {
+        if (DEBUG_LAYERS) Slog.v(TAG_WM, "Assigning layers based",
+                new RuntimeException("here").fillInStackTrace());
+
+        reset();
+        dc.forAllWindows(mAssignWindowLayersConsumer, false /* traverseTopToBottom */);
+
+        adjustSpecialWindows();
+
+        //TODO (multidisplay): Magnification is supported only for the default display.
+        if (mService.mAccessibilityController != null && mAnyLayerChanged
+                && dc.getDisplayId() == DEFAULT_DISPLAY) {
+            mService.mAccessibilityController.onWindowLayersChangedLocked();
+        }
+
+        if (DEBUG_LAYERS) logDebugLayers(dc);
+    }
+
+    private void logDebugLayers(DisplayContent dc) {
+        dc.forAllWindows((w) -> {
+            final WindowStateAnimator winAnimator = w.mWinAnimator;
+            Slog.v(TAG_WM, "Assign layer " + w + ": " + "mBase=" + w.mBaseLayer
+                    + " mLayer=" + w.mLayer + (w.mAppToken == null
+                    ? "" : " mAppLayer=" + w.mAppToken.getAnimLayerAdjustment())
+                    + " =mAnimLayer=" + winAnimator.mAnimLayer);
+        }, false /* traverseTopToBottom */);
+    }
+
+    private void reset() {
+        mPinnedWindows.clear();
+        mInputMethodWindows.clear();
+        mDockedWindows.clear();
+        mAssistantWindows.clear();
+        mReplacingWindows.clear();
+        mDockDivider = null;
+
+        mCurBaseLayer = 0;
+        mCurLayer = 0;
+        mAnyLayerChanged = false;
+
+        mHighestApplicationLayer = 0;
+        mHighestDockedAffectedLayer = 0;
+        mHighestLayerInImeTargetBaseLayer = (mImeTarget != null) ? mImeTarget.mBaseLayer : 0;
+        mImeTarget = mService.mInputMethodTarget;
+        mAboveImeTarget = false;
+        mAboveImeTargetAppWindows.clear();
+    }
+
+    private void collectSpecialWindows(WindowState w) {
+        if (w.mAttrs.type == TYPE_DOCK_DIVIDER) {
+            mDockDivider = w;
+            return;
+        }
+        if (w.mWillReplaceWindow) {
+            mReplacingWindows.add(w);
+        }
+        if (w.mIsImWindow) {
+            mInputMethodWindows.add(w);
+            return;
+        }
+        if (mImeTarget != null) {
+            if (w.getParentWindow() == mImeTarget && w.mSubLayer > 0) {
+                // Child windows of the ime target with a positive sub-layer should be placed above
+                // the IME.
+                mAboveImeTargetAppWindows.add(w);
+            } else if (mAboveImeTarget && w.mAppToken != null) {
+                // windows of apps above the IME target should be placed above the IME.
+                mAboveImeTargetAppWindows.add(w);
+            }
+            if (w == mImeTarget) {
+                mAboveImeTarget = true;
+            }
+        }
+
+        final int stackId = w.getAppToken() != null ? w.getStackId() : INVALID_STACK_ID;
+        if (stackId == PINNED_STACK_ID) {
+            mPinnedWindows.add(w);
+        } else if (stackId == DOCKED_STACK_ID) {
+            mDockedWindows.add(w);
+        } else if (stackId == ASSISTANT_STACK_ID) {
+            mAssistantWindows.add(w);
+        }
+    }
+
+    private void adjustSpecialWindows() {
+        // The following adjustments are beyond the highest docked-affected layer
+        int layer = mHighestDockedAffectedLayer +  TYPE_LAYER_OFFSET;
+
+        // Adjust the docked stack windows and dock divider above only the windows that are affected
+        // by the docked stack. When this happens, also boost the assistant window layers, otherwise
+        // the docked stack windows & divider would be promoted above the assistant.
+        if (!mDockedWindows.isEmpty() && mHighestDockedAffectedLayer > 0) {
+            while (!mDockedWindows.isEmpty()) {
+                final WindowState window = mDockedWindows.remove();
+                layer = assignAndIncreaseLayerIfNeeded(window, layer);
+            }
+
+            layer = assignAndIncreaseLayerIfNeeded(mDockDivider, layer);
+
+            while (!mAssistantWindows.isEmpty()) {
+                final WindowState window = mAssistantWindows.remove();
+                if (window.mLayer > mHighestDockedAffectedLayer) {
+                    layer = assignAndIncreaseLayerIfNeeded(window, layer);
+                }
+            }
+        }
+
+        // The following adjustments are beyond the highest app layer or boosted layer
+        layer = Math.max(layer, mHighestApplicationLayer + WINDOW_LAYER_MULTIPLIER);
+
+        // We know that we will be animating a relaunching window in the near future, which will
+        // receive a z-order increase. We want the replaced window to immediately receive the same
+        // treatment, e.g. to be above the dock divider.
+        while (!mReplacingWindows.isEmpty()) {
+            layer = assignAndIncreaseLayerIfNeeded(mReplacingWindows.remove(), layer);
+        }
+
+        while (!mPinnedWindows.isEmpty()) {
+            layer = assignAndIncreaseLayerIfNeeded(mPinnedWindows.remove(), layer);
+        }
+
+        // Make sure IME is the highest window in the base layer of it's target.
+        if (mImeTarget != null) {
+            if (mImeTarget.mAppToken == null) {
+                // For non-app ime targets adjust the layer we start from to match what we found
+                // when assigning layers. Otherwise, just use the highest app layer we have some far.
+                layer = mHighestLayerInImeTargetBaseLayer + WINDOW_LAYER_MULTIPLIER;
+            }
+
+            while (!mInputMethodWindows.isEmpty()) {
+                layer = assignAndIncreaseLayerIfNeeded(mInputMethodWindows.remove(), layer);
+            }
+
+            // Adjust app windows the should be displayed above the IME since they are above the IME
+            // target.
+            while (!mAboveImeTargetAppWindows.isEmpty()) {
+                layer = assignAndIncreaseLayerIfNeeded(mAboveImeTargetAppWindows.remove(), layer);
+            }
+        }
+
+    }
+
+    private int assignAndIncreaseLayerIfNeeded(WindowState win, int layer) {
+        if (win != null) {
+            assignAnimLayer(win, layer);
+            // Make sure we leave space in-between normal windows for dims and such.
+            layer += WINDOW_LAYER_MULTIPLIER;
+        }
+        return layer;
+    }
+
+    private void assignAnimLayer(WindowState w, int layer) {
+        w.mLayer = layer;
+        w.mWinAnimator.mAnimLayer = w.getAnimLayerAdjustment()
+                + w.getSpecialWindowAnimLayerAdjustment();
+        if (w.mAppToken != null && w.mAppToken.mAppAnimator.thumbnailForceAboveLayer > 0) {
+            if (w.mWinAnimator.mAnimLayer > w.mAppToken.mAppAnimator.thumbnailForceAboveLayer) {
+                w.mAppToken.mAppAnimator.thumbnailForceAboveLayer = w.mWinAnimator.mAnimLayer;
+            }
+            // TODO(b/62029108): the entire contents of the if statement should call the refactored
+            // function to set the thumbnail layer for w.AppToken
+            int highestLayer = w.mAppToken.getHighestAnimLayer();
+            if (highestLayer > 0) {
+                if (w.mAppToken.mAppAnimator.thumbnail != null
+                        && w.mAppToken.mAppAnimator.thumbnailForceAboveLayer != highestLayer) {
+                    w.mAppToken.mAppAnimator.thumbnailForceAboveLayer = highestLayer;
+                    w.mAppToken.mAppAnimator.thumbnail.setLayer(highestLayer + 1);
+                }
+            }
+        }
+    }
+}
diff --git a/com/android/server/wm/WindowList.java b/com/android/server/wm/WindowList.java
new file mode 100644
index 0000000..dfeba40
--- /dev/null
+++ b/com/android/server/wm/WindowList.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import java.util.ArrayList;
+
+/**
+ * An {@link ArrayList} with extended functionality to be used as the children data structure in
+ * {@link WindowContainer}.
+ */
+class WindowList<E> extends ArrayList<E> {
+
+    void addFirst(E e) {
+        add(0, e);
+    }
+
+    E peekLast() {
+        return size() > 0 ? get(size() - 1) : null;
+    }
+
+    E peekFirst() {
+        return size() > 0 ? get(0) : null;
+    }
+}
diff --git a/com/android/server/wm/WindowManagerDebugConfig.java b/com/android/server/wm/WindowManagerDebugConfig.java
new file mode 100644
index 0000000..6d5673e
--- /dev/null
+++ b/com/android/server/wm/WindowManagerDebugConfig.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.server.wm;
+
+/**
+ * Common class for the various debug {@link android.util.Log} output configuration in the window
+ * manager package.
+ */
+public class WindowManagerDebugConfig {
+    // All output logs in the window manager package use the {@link #TAG_WM} string for tagging
+    // their log output. This makes it easy to identify the origin of the log message when sifting
+    // through a large amount of log output from multiple sources. However, it also makes trying
+    // to figure-out the origin of a log message while debugging the window manager a little
+    // painful. By setting this constant to true, log messages from the window manager package
+    // will be tagged with their class names instead fot the generic tag.
+    static final boolean TAG_WITH_CLASS_NAME = false;
+
+    // Default log tag for the window manager package.
+    static final String TAG_WM = "WindowManager";
+
+    static final boolean DEBUG_RESIZE = false;
+    static final boolean DEBUG = false;
+    static final boolean DEBUG_ADD_REMOVE = false;
+    static final boolean DEBUG_FOCUS = false;
+    static final boolean DEBUG_FOCUS_LIGHT = DEBUG_FOCUS || false;
+    static final boolean DEBUG_ANIM = false;
+    static final boolean DEBUG_KEYGUARD = false;
+    static final boolean DEBUG_LAYOUT = false;
+    static final boolean DEBUG_LAYERS = false;
+    static final boolean DEBUG_INPUT = false;
+    static final boolean DEBUG_INPUT_METHOD = false;
+    static final boolean DEBUG_VISIBILITY = false;
+    static final boolean DEBUG_WINDOW_MOVEMENT = false;
+    static final boolean DEBUG_TOKEN_MOVEMENT = false;
+    static final boolean DEBUG_ORIENTATION = false;
+    static final boolean DEBUG_APP_ORIENTATION = false;
+    static final boolean DEBUG_CONFIGURATION = false;
+    static final boolean DEBUG_APP_TRANSITIONS = false;
+    static final boolean DEBUG_STARTING_WINDOW_VERBOSE = false;
+    static final boolean DEBUG_STARTING_WINDOW = DEBUG_STARTING_WINDOW_VERBOSE || false;
+    static final boolean DEBUG_WALLPAPER = false;
+    static final boolean DEBUG_WALLPAPER_LIGHT = false || DEBUG_WALLPAPER;
+    static final boolean DEBUG_DRAG = false;
+    static final boolean DEBUG_SCREEN_ON = false;
+    static final boolean DEBUG_SCREENSHOT = false;
+    static final boolean DEBUG_BOOT = false;
+    static final boolean DEBUG_LAYOUT_REPEATS = false;
+    static final boolean DEBUG_SURFACE_TRACE = false;
+    static final boolean DEBUG_WINDOW_TRACE = false;
+    static final boolean DEBUG_TASK_MOVEMENT = false;
+    static final boolean DEBUG_TASK_POSITIONING = false;
+    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_VERBOSE_TRANSACTIONS = false && SHOW_TRANSACTIONS;
+    static final boolean SHOW_LIGHT_TRANSACTIONS = false || SHOW_TRANSACTIONS;
+    static final boolean SHOW_STACK_CRAWLS = false;
+    static final boolean DEBUG_WINDOW_CROP = false;
+    static final boolean DEBUG_UNKNOWN_APP_VISIBILITY = false;
+
+    static final String TAG_KEEP_SCREEN_ON = "DebugKeepScreenOn";
+    static final boolean DEBUG_KEEP_SCREEN_ON = false;
+}
diff --git a/com/android/server/wm/WindowManagerService.java b/com/android/server/wm/WindowManagerService.java
new file mode 100644
index 0000000..32ee51c
--- /dev/null
+++ b/com/android/server/wm/WindowManagerService.java
@@ -0,0 +1,7710 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.Manifest.permission.MANAGE_APP_TOKENS;
+import static android.Manifest.permission.READ_FRAME_BUFFER;
+import static android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS;
+import static android.Manifest.permission.RESTRICTED_VR_ACCESS;
+import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
+import static android.app.StatusBarManager.DISABLE_MASK;
+import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
+import static android.content.Intent.ACTION_USER_REMOVED;
+import static android.content.Intent.EXTRA_USER_HANDLE;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Process.ROOT_UID;
+import static android.os.Process.SHELL_UID;
+import static android.os.Process.SYSTEM_UID;
+import static android.os.Process.myPid;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.os.UserHandle.USER_NULL;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
+import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
+import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import static android.view.WindowManager.LayoutParams.TYPE_DRAG;
+import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+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;
+import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
+import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
+import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.LockGuard.INDEX_WINDOW;
+import static com.android.server.LockGuard.installLock;
+import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
+import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_END;
+import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START;
+import static com.android.server.wm.KeyguardDisableHandler.KEYGUARD_POLICY_CHANGED;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_KEEP_SCREEN_ON;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREEN_ON;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEMENT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_STACK_CRAWLS;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_VERBOSE_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_KEEP_SCREEN_ON;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.proto.WindowManagerServiceProto.APP_TRANSITION;
+import static com.android.server.wm.proto.WindowManagerServiceProto.DISPLAY_FROZEN;
+import static com.android.server.wm.proto.WindowManagerServiceProto.FOCUSED_APP;
+import static com.android.server.wm.proto.WindowManagerServiceProto.FOCUSED_WINDOW;
+import static com.android.server.wm.proto.WindowManagerServiceProto.INPUT_METHOD_WINDOW;
+import static com.android.server.wm.proto.WindowManagerServiceProto.LAST_ORIENTATION;
+import static com.android.server.wm.proto.WindowManagerServiceProto.POLICY;
+import static com.android.server.wm.proto.WindowManagerServiceProto.ROTATION;
+
+import android.Manifest;
+import android.Manifest.permission;
+import android.animation.AnimationHandler;
+import android.animation.ValueAnimator;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityManager.TaskSnapshot;
+import android.app.ActivityManagerInternal;
+import android.app.ActivityThread;
+import android.app.AppOpsManager;
+import android.app.IActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.database.ContentObserver;
+import android.graphics.Bitmap;
+import android.graphics.GraphicBuffer;
+import android.graphics.Matrix;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.hardware.configstore.V1_0.ISurfaceFlingerConfigs;
+import android.hardware.configstore.V1_0.OptionalBool;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.input.InputManager;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+import android.os.PowerManagerInternal;
+import android.os.PowerSaveState;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.StrictMode;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.SystemService;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.provider.Settings;
+import android.text.format.DateUtils;
+import android.util.ArraySet;
+import android.util.DisplayMetrics;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.MergedConfiguration;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.util.TimeUtils;
+import android.util.TypedValue;
+import android.util.proto.ProtoOutputStream;
+import android.view.AppTransitionAnimationSpec;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.Gravity;
+import android.view.IAppTransitionAnimationSpecsFuture;
+import android.view.IDockedStackListener;
+import android.view.IInputFilter;
+import android.view.IOnKeyguardExitResult;
+import android.view.IPinnedStackListener;
+import android.view.IRotationWatcher;
+import android.view.IWallpaperVisibilityListener;
+import android.view.IWindow;
+import android.view.IWindowId;
+import android.view.IWindowManager;
+import android.view.IWindowSession;
+import android.view.IWindowSessionCallback;
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.KeyEvent;
+import android.view.MagnificationSpec;
+import android.view.MotionEvent;
+import android.view.PointerIcon;
+import android.view.Surface;
+import android.view.Surface.OutOfResourcesException;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.View;
+import android.view.WindowContentFrameStats;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.view.WindowManagerGlobal;
+import android.view.WindowManagerInternal;
+import android.view.WindowManagerPolicy;
+import android.view.WindowManagerPolicy.PointerEventListener;
+import android.view.WindowManagerPolicy.ScreenOffListener;
+import android.view.animation.Animation;
+import android.view.inputmethod.InputMethodManagerInternal;
+
+import com.android.internal.R;
+import com.android.internal.app.IAssistScreenshotReceiver;
+import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.internal.os.IResultReceiver;
+import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.internal.policy.IShortcutService;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FastPrintWriter;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethodClient;
+import com.android.internal.view.IInputMethodManager;
+import com.android.internal.view.WindowManagerPolicyThread;
+import com.android.server.AnimationThread;
+import com.android.server.DisplayThread;
+import com.android.server.EventLogTags;
+import com.android.server.FgThread;
+import com.android.server.LocalServices;
+import com.android.server.UiThread;
+import com.android.server.Watchdog;
+import com.android.server.input.InputManagerService;
+import com.android.server.power.BatterySaverPolicy.ServiceType;
+import com.android.server.power.ShutdownThread;
+
+import java.io.BufferedWriter;
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.Socket;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+/** {@hide} */
+public class WindowManagerService extends IWindowManager.Stub
+        implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowManagerService" : TAG_WM;
+
+    static final int LAYOUT_REPEAT_THRESHOLD = 4;
+
+    static final boolean PROFILE_ORIENTATION = false;
+    static final boolean localLOGV = DEBUG;
+
+    /** How much to multiply the policy's type layer, to reserve room
+     * for multiple windows of the same type and Z-ordering adjustment
+     * with TYPE_LAYER_OFFSET. */
+    static final int TYPE_LAYER_MULTIPLIER = 10000;
+
+    /** Offset from TYPE_LAYER_MULTIPLIER for moving a group of windows above
+     * or below others in the same layer. */
+    static final int TYPE_LAYER_OFFSET = 1000;
+
+    /** How much to increment the layer for each window, to reserve room
+     * for effect surfaces between them.
+     */
+    static final int WINDOW_LAYER_MULTIPLIER = 5;
+
+    /**
+     * Dim surface layer is immediately below target window.
+     */
+    static final int LAYER_OFFSET_DIM = 1;
+
+    /**
+     * Animation thumbnail is as far as possible below the window above
+     * the thumbnail (or in other words as far as possible above the window
+     * below it).
+     */
+    static final int LAYER_OFFSET_THUMBNAIL = WINDOW_LAYER_MULTIPLIER - 1;
+
+    /** The maximum length we will accept for a loaded animation duration:
+     * this is 10 seconds.
+     */
+    static final int MAX_ANIMATION_DURATION = 10 * 1000;
+
+    /** Amount of time (in milliseconds) to delay before declaring a window freeze timeout. */
+    static final int WINDOW_FREEZE_TIMEOUT_DURATION = 2000;
+
+    /** Amount of time (in milliseconds) to delay before declaring a seamless rotation timeout. */
+    static final int SEAMLESS_ROTATION_TIMEOUT_DURATION = 2000;
+
+    /** Amount of time (in milliseconds) to delay before declaring a window replacement timeout. */
+    static final int WINDOW_REPLACEMENT_TIMEOUT_DURATION = 2000;
+
+    /** Amount of time to allow a last ANR message to exist before freeing the memory. */
+    static final int LAST_ANR_LIFETIME_DURATION_MSECS = 2 * 60 * 60 * 1000; // Two hours
+    /**
+     * If true, the window manager will do its own custom freezing and general
+     * management of the screen during rotation.
+     */
+    static final boolean CUSTOM_SCREEN_ROTATION = true;
+
+    // Maximum number of milliseconds to wait for input devices to be enumerated before
+    // proceding with safe mode detection.
+    private static final int INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS = 1000;
+
+    // Default input dispatching timeout in nanoseconds.
+    static final long DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS = 5000 * 1000000L;
+
+    // Poll interval in milliseconds for watching boot animation finished.
+    private static final int BOOT_ANIMATION_POLL_INTERVAL = 200;
+
+    // The name of the boot animation service in init.rc.
+    private static final String BOOT_ANIMATION_SERVICE = "bootanim";
+
+    static final int UPDATE_FOCUS_NORMAL = 0;
+    static final int UPDATE_FOCUS_WILL_ASSIGN_LAYERS = 1;
+    static final int UPDATE_FOCUS_PLACING_SURFACES = 2;
+    static final int UPDATE_FOCUS_WILL_PLACE_SURFACES = 3;
+
+    private static final String SYSTEM_SECURE = "ro.secure";
+    private static final String SYSTEM_DEBUGGABLE = "ro.debuggable";
+
+    private static final String DENSITY_OVERRIDE = "ro.config.density_override";
+    private static final String SIZE_OVERRIDE = "ro.config.size_override";
+
+    private static final int MAX_SCREENSHOT_RETRIES = 3;
+
+    private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.emulator.circular";
+
+    // Used to indicate that if there is already a transition set, it should be preserved when
+    // trying to apply a new one.
+    private static final boolean ALWAYS_KEEP_CURRENT = true;
+
+    private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f;
+
+    // Enums for animation scale update types.
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({WINDOW_ANIMATION_SCALE, TRANSITION_ANIMATION_SCALE, ANIMATION_DURATION_SCALE})
+    private @interface UpdateAnimationScaleMode {};
+    private static final int WINDOW_ANIMATION_SCALE = 0;
+    private static final int TRANSITION_ANIMATION_SCALE = 1;
+    private static final int ANIMATION_DURATION_SCALE = 2;
+
+    final private KeyguardDisableHandler mKeyguardDisableHandler;
+    boolean mKeyguardGoingAway;
+    // VR Vr2d Display Id.
+    int mVr2dDisplayId = INVALID_DISPLAY;
+
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            switch (intent.getAction()) {
+                case ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED:
+                    mKeyguardDisableHandler.sendEmptyMessage(KEYGUARD_POLICY_CHANGED);
+                    break;
+                case ACTION_USER_REMOVED:
+                    final int userId = intent.getIntExtra(EXTRA_USER_HANDLE, USER_NULL);
+                    if (userId != USER_NULL) {
+                        synchronized (mWindowMap) {
+                            mScreenCaptureDisabled.remove(userId);
+                        }
+                    }
+                    break;
+            }
+        }
+    };
+    final WindowSurfacePlacer mWindowPlacerLocked;
+
+    /**
+     * Current user when multi-user is enabled. Don't show windows of
+     * non-current user. Also see mCurrentProfileIds.
+     */
+    int mCurrentUserId;
+    /**
+     * Users that are profiles of the current user. These are also allowed to show windows
+     * on the current user.
+     */
+    int[] mCurrentProfileIds = new int[] {};
+
+    final Context mContext;
+
+    final boolean mHaveInputMethods;
+
+    final boolean mHasPermanentDpad;
+    final long mDrawLockTimeoutMillis;
+    final boolean mAllowAnimationsInLowPowerMode;
+
+    final boolean mAllowBootMessages;
+
+    final boolean mLimitedAlphaCompositing;
+    final int mMaxUiWidth;
+
+    final WindowManagerPolicy mPolicy;
+
+    final IActivityManager mActivityManager;
+    final ActivityManagerInternal mAmInternal;
+
+    final AppOpsManager mAppOps;
+
+    final DisplaySettings mDisplaySettings;
+
+    /** If the system should display notifications for apps displaying an alert window. */
+    boolean mShowAlertWindowNotifications = true;
+
+    /**
+     * All currently active sessions with clients.
+     */
+    final ArraySet<Session> mSessions = new ArraySet<>();
+
+    /**
+     * Mapping from an IWindow IBinder to the server's Window object.
+     * This is also used as the lock for all of our state.
+     * NOTE: Never call into methods that lock ActivityManagerService while holding this object.
+     */
+    final WindowHashMap mWindowMap = new WindowHashMap();
+
+    /**
+     * List of window tokens that have finished starting their application,
+     * and now need to have the policy remove their windows.
+     */
+    final ArrayList<AppWindowToken> mFinishedStarting = new ArrayList<>();
+
+    /**
+     * List of app window tokens that are waiting for replacing windows. If the
+     * replacement doesn't come in time the stale windows needs to be disposed of.
+     */
+    final ArrayList<AppWindowToken> mWindowReplacementTimeouts = new ArrayList<>();
+
+    /**
+     * Windows that are being resized.  Used so we can tell the client about
+     * the resize after closing the transaction in which we resized the
+     * underlying surface.
+     */
+    final ArrayList<WindowState> mResizingWindows = new ArrayList<>();
+
+    /**
+     * Windows whose animations have ended and now must be removed.
+     */
+    final ArrayList<WindowState> mPendingRemove = new ArrayList<>();
+
+    /**
+     * Used when processing mPendingRemove to avoid working on the original array.
+     */
+    WindowState[] mPendingRemoveTmp = new WindowState[20];
+
+    /**
+     * Windows whose surface should be destroyed.
+     */
+    final ArrayList<WindowState> mDestroySurface = new ArrayList<>();
+
+    /**
+     * Windows with a preserved surface waiting to be destroyed. These windows
+     * are going through a surface change. We keep the old surface around until
+     * the first frame on the new surface finishes drawing.
+     */
+    final ArrayList<WindowState> mDestroyPreservedSurface = new ArrayList<>();
+
+    /**
+     * Windows that have lost input focus and are waiting for the new
+     * focus window to be displayed before they are told about this.
+     */
+    ArrayList<WindowState> mLosingFocus = new ArrayList<>();
+
+    /**
+     * This is set when we have run out of memory, and will either be an empty
+     * list or contain windows that need to be force removed.
+     */
+    final ArrayList<WindowState> mForceRemoves = new ArrayList<>();
+
+    /**
+     * Windows that clients are waiting to have drawn.
+     */
+    ArrayList<WindowState> mWaitingForDrawn = new ArrayList<>();
+    /**
+     * And the callback to make when they've all been drawn.
+     */
+    Runnable mWaitingForDrawnCallback;
+
+    /** List of window currently causing non-system overlay windows to be hidden. */
+    private ArrayList<WindowState> mHidingNonSystemOverlayWindows = new ArrayList<>();
+
+    /**
+     * Stores for each user whether screencapture is disabled
+     * This array is essentially a cache for all userId for
+     * {@link android.app.admin.DevicePolicyManager#getScreenCaptureDisabled}
+     */
+    private SparseArray<Boolean> mScreenCaptureDisabled = new SparseArray<>();
+
+    IInputMethodManager mInputMethodManager;
+
+    AccessibilityController mAccessibilityController;
+
+    final SurfaceSession mFxSession;
+    Watermark mWatermark;
+    StrictModeFlash mStrictModeFlash;
+    CircularDisplayMask mCircularDisplayMask;
+    EmulatorDisplayOverlay mEmulatorDisplayOverlay;
+
+    final float[] mTmpFloats = new float[9];
+    final Rect mTmpRect = new Rect();
+    final Rect mTmpRect2 = new Rect();
+    final Rect mTmpRect3 = new Rect();
+    final RectF mTmpRectF = new RectF();
+
+    final Matrix mTmpTransform = new Matrix();
+
+    boolean mDisplayReady;
+    boolean mSafeMode;
+    boolean mDisplayEnabled = false;
+    boolean mSystemBooted = false;
+    boolean mForceDisplayEnabled = false;
+    boolean mShowingBootMessages = false;
+    boolean mBootAnimationStopped = false;
+
+    // Following variables are for debugging screen wakelock only.
+    WindowState mLastWakeLockHoldingWindow = null;
+    WindowState mLastWakeLockObscuringWindow = null;
+
+    /** Dump of the windows and app tokens at the time of the last ANR. Cleared after
+     * LAST_ANR_LIFETIME_DURATION_MSECS */
+    String mLastANRState;
+
+    // The root of the device window hierarchy.
+    RootWindowContainer mRoot;
+
+    int mDockedStackCreateMode = DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+    Rect mDockedStackCreateBounds;
+
+    private final SparseIntArray mTmpTaskIds = new SparseIntArray();
+
+    boolean mForceResizableTasks = false;
+    boolean mSupportsPictureInPicture = false;
+
+    int getDragLayerLocked() {
+        return mPolicy.getWindowLayerFromTypeLw(TYPE_DRAG) * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
+    }
+
+    class RotationWatcher {
+        final IRotationWatcher mWatcher;
+        final IBinder.DeathRecipient mDeathRecipient;
+        final int mDisplayId;
+        RotationWatcher(IRotationWatcher watcher, IBinder.DeathRecipient deathRecipient,
+                int displayId) {
+            mWatcher = watcher;
+            mDeathRecipient = deathRecipient;
+            mDisplayId = displayId;
+        }
+    }
+
+    ArrayList<RotationWatcher> mRotationWatchers = new ArrayList<>();
+    int mDeferredRotationPauseCount;
+    final WallpaperVisibilityListeners mWallpaperVisibilityListeners =
+            new WallpaperVisibilityListeners();
+
+    int mSystemDecorLayer = 0;
+    final Rect mScreenRect = new Rect();
+
+    boolean mDisplayFrozen = false;
+    long mDisplayFreezeTime = 0;
+    int mLastDisplayFreezeDuration = 0;
+    Object mLastFinishedFreezeSource = null;
+    boolean mWaitingForConfig = false;
+    boolean mSwitchingUser = false;
+
+    final static int WINDOWS_FREEZING_SCREENS_NONE = 0;
+    final static int WINDOWS_FREEZING_SCREENS_ACTIVE = 1;
+    final static int WINDOWS_FREEZING_SCREENS_TIMEOUT = 2;
+    int mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
+
+    boolean mClientFreezingScreen = false;
+    int mAppsFreezingScreen = 0;
+
+    int mLayoutSeq = 0;
+
+    // Last systemUiVisibility we received from status bar.
+    int mLastStatusBarVisibility = 0;
+    // Last systemUiVisibility we dispatched to windows.
+    int mLastDispatchedSystemUiVisibility = 0;
+
+    // State while inside of layoutAndPlaceSurfacesLocked().
+    boolean mFocusMayChange;
+
+    // This is held as long as we have the screen frozen, to give us time to
+    // perform a rotation animation when turning off shows the lock screen which
+    // changes the orientation.
+    private final PowerManager.WakeLock mScreenFrozenLock;
+
+    final AppTransition mAppTransition;
+    boolean mSkipAppTransitionAnimation = false;
+
+    final ArraySet<AppWindowToken> mOpeningApps = new ArraySet<>();
+    final ArraySet<AppWindowToken> mClosingApps = new ArraySet<>();
+
+    final UnknownAppVisibilityController mUnknownAppVisibilityController =
+            new UnknownAppVisibilityController(this);
+    final TaskSnapshotController mTaskSnapshotController;
+
+    boolean mIsTouchDevice;
+
+    final H mH = new H();
+
+    /**
+     * Handler for things to run that have direct impact on an animation, i.e. animation tick,
+     * layout, starting window creation, whereas {@link H} runs things that are still important, but
+     * not as critical.
+     */
+    final Handler mAnimationHandler = new Handler(AnimationThread.getHandler().getLooper());
+
+    WindowState mCurrentFocus = null;
+    WindowState mLastFocus = null;
+
+    /** Windows added since {@link #mCurrentFocus} was set to null. Used for ANR blaming. */
+    private final ArrayList<WindowState> mWinAddedSinceNullFocus = new ArrayList<>();
+    /** Windows removed since {@link #mCurrentFocus} was set to null. Used for ANR blaming. */
+    private final ArrayList<WindowState> mWinRemovedSinceNullFocus = new ArrayList<>();
+
+    /** This just indicates the window the input method is on top of, not
+     * necessarily the window its input is going to. */
+    WindowState mInputMethodTarget = null;
+
+    /** If true hold off on modifying the animation layer of mInputMethodTarget */
+    boolean mInputMethodTargetWaitingAnim;
+
+    WindowState mInputMethodWindow = null;
+
+    boolean mHardKeyboardAvailable;
+    WindowManagerInternal.OnHardKeyboardStatusChangeListener mHardKeyboardStatusChangeListener;
+    SettingsObserver mSettingsObserver;
+
+    // A count of the windows which are 'seamlessly rotated', e.g. a surface
+    // at an old orientation is being transformed. We freeze orientation updates
+    // while any windows are seamlessly rotated, so we need to track when this
+    // hits zero so we can apply deferred orientation updates.
+    int mSeamlessRotationCount = 0;
+
+    private final class SettingsObserver extends ContentObserver {
+        private final Uri mDisplayInversionEnabledUri =
+                Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
+        private final Uri mWindowAnimationScaleUri =
+                Settings.Global.getUriFor(Settings.Global.WINDOW_ANIMATION_SCALE);
+        private final Uri mTransitionAnimationScaleUri =
+                Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE);
+        private final Uri mAnimationDurationScaleUri =
+                Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE);
+
+        public SettingsObserver() {
+            super(new Handler());
+            ContentResolver resolver = mContext.getContentResolver();
+            resolver.registerContentObserver(mDisplayInversionEnabledUri, false, this,
+                    UserHandle.USER_ALL);
+            resolver.registerContentObserver(mWindowAnimationScaleUri, false, this,
+                    UserHandle.USER_ALL);
+            resolver.registerContentObserver(mTransitionAnimationScaleUri, false, this,
+                    UserHandle.USER_ALL);
+            resolver.registerContentObserver(mAnimationDurationScaleUri, false, this,
+                    UserHandle.USER_ALL);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            if (uri == null) {
+                return;
+            }
+
+            if (mDisplayInversionEnabledUri.equals(uri)) {
+                updateCircularDisplayMaskIfNeeded();
+            } else {
+                @UpdateAnimationScaleMode
+                final int mode;
+                if (mWindowAnimationScaleUri.equals(uri)) {
+                    mode = WINDOW_ANIMATION_SCALE;
+                } else if (mTransitionAnimationScaleUri.equals(uri)) {
+                    mode = TRANSITION_ANIMATION_SCALE;
+                } else if (mAnimationDurationScaleUri.equals(uri)) {
+                    mode = ANIMATION_DURATION_SCALE;
+                } else {
+                    // Ignoring unrecognized content changes
+                    return;
+                }
+                Message m = mH.obtainMessage(H.UPDATE_ANIMATION_SCALE, mode, 0);
+                mH.sendMessage(m);
+            }
+        }
+    }
+
+    boolean mAnimateWallpaperWithTarget;
+
+    // TODO: Move to RootWindowContainer
+    AppWindowToken mFocusedApp = null;
+
+    PowerManager mPowerManager;
+    PowerManagerInternal mPowerManagerInternal;
+
+    private float mWindowAnimationScaleSetting = 1.0f;
+    private float mTransitionAnimationScaleSetting = 1.0f;
+    private float mAnimatorDurationScaleSetting = 1.0f;
+    private boolean mAnimationsDisabled = false;
+
+    final InputManagerService mInputManager;
+    final DisplayManagerInternal mDisplayManagerInternal;
+    final DisplayManager mDisplayManager;
+    private final Display[] mDisplays;
+
+    // Indicates whether this device supports wide color gamut rendering
+    private boolean mHasWideColorGamutSupport;
+
+    // Who is holding the screen on.
+    private Session mHoldingScreenOn;
+    private PowerManager.WakeLock mHoldingScreenWakeLock;
+
+    boolean mTurnOnScreen;
+
+    // Whether or not a layout can cause a wake up when theater mode is enabled.
+    boolean mAllowTheaterModeWakeFromLayout;
+
+    TaskPositioner mTaskPositioner;
+    DragState mDragState = null;
+
+    // For frozen screen animations.
+    private int mExitAnimId, mEnterAnimId;
+
+    // The display that the rotation animation is applying to.
+    private int mFrozenDisplayId;
+
+    /** Skip repeated AppWindowTokens initialization. Note that AppWindowsToken's version of this
+     * is a long initialized to Long.MIN_VALUE so that it doesn't match this value on startup. */
+    int mTransactionSequence;
+
+    final WindowAnimator mAnimator;
+
+    final BoundsAnimationController mBoundsAnimationController;
+
+    private final PointerEventDispatcher mPointerEventDispatcher;
+
+    private WindowContentFrameStats mTempWindowRenderStats;
+
+    final class DragInputEventReceiver extends InputEventReceiver {
+        // Set, if stylus button was down at the start of the drag.
+        private boolean mStylusButtonDownAtStart;
+        // Indicates the first event to check for button state.
+        private boolean mIsStartEvent = true;
+        // Set to true to ignore input events after the drag gesture is complete but the drag events
+        // are still being dispatched.
+        private boolean mMuteInput = false;
+
+        public DragInputEventReceiver(InputChannel inputChannel, Looper looper) {
+            super(inputChannel, looper);
+        }
+
+        @Override
+        public void onInputEvent(InputEvent event, int displayId) {
+            boolean handled = false;
+            try {
+                if (mDragState == null) {
+                    // The drag has ended but the clean-up message has not been processed by
+                    // window manager. Drop events that occur after this until window manager
+                    // has a chance to clean-up the input handle.
+                    handled = true;
+                    return;
+                }
+                if (event instanceof MotionEvent
+                        && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0
+                        && !mMuteInput) {
+                    final MotionEvent motionEvent = (MotionEvent)event;
+                    boolean endDrag = false;
+                    final float newX = motionEvent.getRawX();
+                    final float newY = motionEvent.getRawY();
+                    final boolean isStylusButtonDown =
+                            (motionEvent.getButtonState() & MotionEvent.BUTTON_STYLUS_PRIMARY) != 0;
+
+                    if (mIsStartEvent) {
+                        if (isStylusButtonDown) {
+                            // First event and the button was down, check for the button being
+                            // lifted in the future, if that happens we'll drop the item.
+                            mStylusButtonDownAtStart = true;
+                        }
+                        mIsStartEvent = false;
+                    }
+
+                    switch (motionEvent.getAction()) {
+                    case MotionEvent.ACTION_DOWN: {
+                        if (DEBUG_DRAG) {
+                            Slog.w(TAG_WM, "Unexpected ACTION_DOWN in drag layer");
+                        }
+                    } break;
+
+                    case MotionEvent.ACTION_MOVE: {
+                        if (mStylusButtonDownAtStart && !isStylusButtonDown) {
+                            if (DEBUG_DRAG) Slog.d(TAG_WM, "Button no longer pressed; dropping at "
+                                    + newX + "," + newY);
+                            mMuteInput = true;
+                            synchronized (mWindowMap) {
+                                endDrag = mDragState.notifyDropLw(newX, newY);
+                            }
+                        } else {
+                            synchronized (mWindowMap) {
+                                // move the surface and tell the involved window(s) where we are
+                                mDragState.notifyMoveLw(newX, newY);
+                            }
+                        }
+                    } break;
+
+                    case MotionEvent.ACTION_UP: {
+                        if (DEBUG_DRAG) Slog.d(TAG_WM, "Got UP on move channel; dropping at "
+                                + newX + "," + newY);
+                        mMuteInput = true;
+                        synchronized (mWindowMap) {
+                            endDrag = mDragState.notifyDropLw(newX, newY);
+                        }
+                    } break;
+
+                    case MotionEvent.ACTION_CANCEL: {
+                        if (DEBUG_DRAG) Slog.d(TAG_WM, "Drag cancelled!");
+                        mMuteInput = true;
+                        endDrag = true;
+                    } break;
+                    }
+
+                    if (endDrag) {
+                        if (DEBUG_DRAG) Slog.d(TAG_WM, "Drag ended; tearing down state");
+                        // tell all the windows that the drag has ended
+                        synchronized (mWindowMap) {
+                            // endDragLw will post back to looper to dispose the receiver
+                            // since we still need the receiver for the last finishInputEvent.
+                            mDragState.endDragLw();
+                        }
+                        mStylusButtonDownAtStart = false;
+                        mIsStartEvent = true;
+                    }
+
+                    handled = true;
+                }
+            } catch (Exception e) {
+                Slog.e(TAG_WM, "Exception caught by drag handleMotion", e);
+            } finally {
+                finishInputEvent(event, handled);
+            }
+        }
+    }
+
+    /**
+     * Whether the UI is currently running in touch mode (not showing
+     * navigational focus because the user is directly pressing the screen).
+     */
+    boolean mInTouchMode;
+
+    private ViewServer mViewServer;
+    final ArrayList<WindowChangeListener> mWindowChangeListeners = new ArrayList<>();
+    boolean mWindowsChanged = false;
+
+    public interface WindowChangeListener {
+        public void windowsChanged();
+        public void focusChanged();
+    }
+
+    final Configuration mTempConfiguration = new Configuration();
+
+    // If true, only the core apps and services are being launched because the device
+    // is in a special boot mode, such as being encrypted or waiting for a decryption password.
+    // For example, when this flag is true, there will be no wallpaper service.
+    final boolean mOnlyCore;
+
+    // List of clients without a transtiton animation that we notify once we are done transitioning
+    // since they won't be notified through the app window animator.
+    final List<IBinder> mNoAnimationNotifyOnTransitionFinished = new ArrayList<>();
+
+    static WindowManagerThreadPriorityBooster sThreadPriorityBooster =
+            new WindowManagerThreadPriorityBooster();
+
+    static void boostPriorityForLockedSection() {
+        sThreadPriorityBooster.boost();
+    }
+
+    static void resetPriorityAfterLockedSection() {
+        sThreadPriorityBooster.reset();
+    }
+
+    void openSurfaceTransaction() {
+        try {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "openSurfaceTransaction");
+            synchronized (mWindowMap) {
+                if (mRoot.mSurfaceTraceEnabled) {
+                    mRoot.mRemoteEventTrace.openSurfaceTransaction();
+                }
+                SurfaceControl.openTransaction();
+            }
+        } finally {
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
+    /**
+     * Closes a surface transaction.
+     */
+    void closeSurfaceTransaction() {
+        try {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "closeSurfaceTransaction");
+            synchronized (mWindowMap) {
+                if (mRoot.mSurfaceTraceEnabled) {
+                    mRoot.mRemoteEventTrace.closeSurfaceTransaction();
+                }
+                SurfaceControl.closeTransaction();
+            }
+        } finally {
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
+    /**
+     * Executes an empty animation transaction without holding the WM lock to simulate
+     * back-pressure. See {@link WindowAnimator#animate} why this is needed.
+     */
+    void executeEmptyAnimationTransaction() {
+        try {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "openSurfaceTransaction");
+            synchronized (mWindowMap) {
+                if (mRoot.mSurfaceTraceEnabled) {
+                    mRoot.mRemoteEventTrace.openSurfaceTransaction();
+                }
+                SurfaceControl.openTransaction();
+                SurfaceControl.setAnimationTransaction();
+                if (mRoot.mSurfaceTraceEnabled) {
+                    mRoot.mRemoteEventTrace.closeSurfaceTransaction();
+                }
+            }
+        } finally {
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+        }
+        try {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "closeSurfaceTransaction");
+            SurfaceControl.closeTransaction();
+        } finally {
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
+    /** Listener to notify activity manager about app transitions. */
+    final WindowManagerInternal.AppTransitionListener mActivityManagerAppTransitionNotifier
+            = new WindowManagerInternal.AppTransitionListener() {
+
+        @Override
+        public void onAppTransitionCancelledLocked(int transit) {
+            mH.sendEmptyMessage(H.NOTIFY_APP_TRANSITION_CANCELLED);
+        }
+
+        @Override
+        public void onAppTransitionFinishedLocked(IBinder token) {
+            mH.sendEmptyMessage(H.NOTIFY_APP_TRANSITION_FINISHED);
+            final AppWindowToken atoken = mRoot.getAppWindowToken(token);
+            if (atoken == null) {
+                return;
+            }
+            if (atoken.mLaunchTaskBehind) {
+                try {
+                    mActivityManager.notifyLaunchTaskBehindComplete(atoken.token);
+                } catch (RemoteException e) {
+                }
+                atoken.mLaunchTaskBehind = false;
+            } else {
+                atoken.updateReportedVisibilityLocked();
+                if (atoken.mEnteringAnimation) {
+                    atoken.mEnteringAnimation = false;
+                    try {
+                        mActivityManager.notifyEnterAnimationComplete(atoken.token);
+                    } catch (RemoteException e) {
+                    }
+                }
+            }
+        }
+    };
+
+    final ArrayList<AppFreezeListener> mAppFreezeListeners = new ArrayList<>();
+
+    interface AppFreezeListener {
+        void onAppFreezeTimeout();
+    }
+
+    private static WindowManagerService sInstance;
+    static WindowManagerService getInstance() {
+        return sInstance;
+    }
+
+    public static WindowManagerService main(final Context context, final InputManagerService im,
+            final boolean haveInputMethods, final boolean showBootMsgs, final boolean onlyCore,
+            WindowManagerPolicy policy) {
+        DisplayThread.getHandler().runWithScissors(() ->
+                sInstance = new WindowManagerService(context, im, haveInputMethods, showBootMsgs,
+                        onlyCore, policy), 0);
+        return sInstance;
+    }
+
+    private void initPolicy() {
+        UiThread.getHandler().runWithScissors(new Runnable() {
+            @Override
+            public void run() {
+                WindowManagerPolicyThread.set(Thread.currentThread(), Looper.myLooper());
+
+                mPolicy.init(mContext, WindowManagerService.this, WindowManagerService.this);
+            }
+        }, 0);
+    }
+
+    private WindowManagerService(Context context, InputManagerService inputManager,
+            boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore,
+            WindowManagerPolicy policy) {
+        installLock(this, INDEX_WINDOW);
+        mRoot = new RootWindowContainer(this);
+        mContext = context;
+        mHaveInputMethods = haveInputMethods;
+        mAllowBootMessages = showBootMsgs;
+        mOnlyCore = onlyCore;
+        mLimitedAlphaCompositing = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_sf_limitedAlpha);
+        mHasPermanentDpad = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_hasPermanentDpad);
+        mInTouchMode = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_defaultInTouchMode);
+        mDrawLockTimeoutMillis = context.getResources().getInteger(
+                com.android.internal.R.integer.config_drawLockTimeoutMillis);
+        mAllowAnimationsInLowPowerMode = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_allowAnimationsInLowPowerMode);
+        mMaxUiWidth = context.getResources().getInteger(
+                com.android.internal.R.integer.config_maxUiWidth);
+        mInputManager = inputManager; // Must be before createDisplayContentLocked.
+        mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
+        mDisplaySettings = new DisplaySettings();
+        mDisplaySettings.readSettingsLocked();
+
+        mWindowPlacerLocked = new WindowSurfacePlacer(this);
+        mPolicy = policy;
+        mTaskSnapshotController = new TaskSnapshotController(this);
+
+        LocalServices.addService(WindowManagerPolicy.class, mPolicy);
+
+        if(mInputManager != null) {
+            final InputChannel inputChannel = mInputManager.monitorInput(TAG_WM);
+            mPointerEventDispatcher = inputChannel != null
+                    ? new PointerEventDispatcher(inputChannel) : null;
+        } else {
+            mPointerEventDispatcher = null;
+        }
+
+        mFxSession = new SurfaceSession();
+        mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
+        mDisplays = mDisplayManager.getDisplays();
+        for (Display display : mDisplays) {
+            createDisplayContentLocked(display);
+        }
+
+        mKeyguardDisableHandler = new KeyguardDisableHandler(mContext, mPolicy);
+
+        mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+        mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
+
+        if (mPowerManagerInternal != null) {
+            mPowerManagerInternal.registerLowPowerModeObserver(
+                    new PowerManagerInternal.LowPowerModeListener() {
+                @Override
+                public int getServiceType() {
+                    return ServiceType.ANIMATION;
+                }
+
+                @Override
+                public void onLowPowerModeChanged(PowerSaveState result) {
+                    synchronized (mWindowMap) {
+                        final boolean enabled = result.batterySaverEnabled;
+                        if (mAnimationsDisabled != enabled && !mAllowAnimationsInLowPowerMode) {
+                            mAnimationsDisabled = enabled;
+                            dispatchNewAnimatorScaleLocked(null);
+                        }
+                    }
+                }
+            });
+            mAnimationsDisabled = mPowerManagerInternal
+                    .getLowPowerState(ServiceType.ANIMATION).batterySaverEnabled;
+        }
+        mScreenFrozenLock = mPowerManager.newWakeLock(
+                PowerManager.PARTIAL_WAKE_LOCK, "SCREEN_FROZEN");
+        mScreenFrozenLock.setReferenceCounted(false);
+
+        mAppTransition = new AppTransition(context, this);
+        mAppTransition.registerListenerLocked(mActivityManagerAppTransitionNotifier);
+
+        final AnimationHandler animationHandler = new AnimationHandler();
+        animationHandler.setProvider(new SfVsyncFrameCallbackProvider());
+        mBoundsAnimationController = new BoundsAnimationController(context, mAppTransition,
+                AnimationThread.getHandler(), animationHandler);
+
+        mActivityManager = ActivityManager.getService();
+        mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
+        mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
+        AppOpsManager.OnOpChangedInternalListener opListener =
+                new AppOpsManager.OnOpChangedInternalListener() {
+                    @Override public void onOpChanged(int op, String packageName) {
+                        updateAppOpsState();
+                    }
+                };
+        mAppOps.startWatchingMode(OP_SYSTEM_ALERT_WINDOW, null, opListener);
+        mAppOps.startWatchingMode(AppOpsManager.OP_TOAST_WINDOW, null, opListener);
+
+        // Get persisted window scale setting
+        mWindowAnimationScaleSetting = Settings.Global.getFloat(context.getContentResolver(),
+                Settings.Global.WINDOW_ANIMATION_SCALE, mWindowAnimationScaleSetting);
+        mTransitionAnimationScaleSetting = Settings.Global.getFloat(context.getContentResolver(),
+                Settings.Global.TRANSITION_ANIMATION_SCALE,
+                context.getResources().getFloat(
+                        R.dimen.config_appTransitionAnimationDurationScaleDefault));
+
+        setAnimatorDurationScale(Settings.Global.getFloat(context.getContentResolver(),
+                Settings.Global.ANIMATOR_DURATION_SCALE, mAnimatorDurationScaleSetting));
+
+        IntentFilter filter = new IntentFilter();
+        // Track changes to DevicePolicyManager state so we can enable/disable keyguard.
+        filter.addAction(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
+        // Listen to user removal broadcasts so that we can remove the user-specific data.
+        filter.addAction(Intent.ACTION_USER_REMOVED);
+        mContext.registerReceiver(mBroadcastReceiver, filter);
+
+        mSettingsObserver = new SettingsObserver();
+
+        mHoldingScreenWakeLock = mPowerManager.newWakeLock(
+                PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, TAG_WM);
+        mHoldingScreenWakeLock.setReferenceCounted(false);
+
+        mAnimator = new WindowAnimator(this);
+
+        mAllowTheaterModeWakeFromLayout = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_allowTheaterModeWakeFromWindowLayout);
+
+
+        LocalServices.addService(WindowManagerInternal.class, new LocalService());
+        initPolicy();
+
+        // Add ourself to the Watchdog monitors.
+        Watchdog.getInstance().addMonitor(this);
+
+        openSurfaceTransaction();
+        try {
+            createWatermarkInTransaction();
+        } finally {
+            closeSurfaceTransaction();
+        }
+
+        showEmulatorDisplayOverlayIfNeeded();
+    }
+
+    public InputMonitor getInputMonitor() {
+        return mInputMonitor;
+    }
+
+    @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        try {
+            return super.onTransact(code, data, reply, flags);
+        } catch (RuntimeException e) {
+            // The window manager only throws security exceptions, so let's
+            // log all others.
+            if (!(e instanceof SecurityException)) {
+                Slog.wtf(TAG_WM, "Window Manager Crash", e);
+            }
+            throw e;
+        }
+    }
+
+    static boolean excludeWindowTypeFromTapOutTask(int windowType) {
+        switch (windowType) {
+            case TYPE_STATUS_BAR:
+            case TYPE_NAVIGATION_BAR:
+            case TYPE_INPUT_METHOD_DIALOG:
+                return true;
+        }
+        return false;
+    }
+
+    public int addWindow(Session session, IWindow client, int seq,
+            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
+            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
+            InputChannel outInputChannel) {
+        int[] appOp = new int[1];
+        int res = mPolicy.checkAddPermission(attrs, appOp);
+        if (res != WindowManagerGlobal.ADD_OKAY) {
+            return res;
+        }
+
+        boolean reportNewConfig = false;
+        WindowState parentWindow = null;
+        long origId;
+        final int callingUid = Binder.getCallingUid();
+        final int type = attrs.type;
+
+        synchronized(mWindowMap) {
+            if (!mDisplayReady) {
+                throw new IllegalStateException("Display has not been initialialized");
+            }
+
+            final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+            if (displayContent == null) {
+                Slog.w(TAG_WM, "Attempted to add window to a display that does not exist: "
+                        + displayId + ".  Aborting.");
+                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
+            }
+            if (!displayContent.hasAccess(session.mUid)
+                    && !mDisplayManagerInternal.isUidPresentOnDisplay(session.mUid, displayId)) {
+                Slog.w(TAG_WM, "Attempted to add window to a display for which the application "
+                        + "does not have access: " + displayId + ".  Aborting.");
+                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
+            }
+
+            if (mWindowMap.containsKey(client.asBinder())) {
+                Slog.w(TAG_WM, "Window " + client + " is already added");
+                return WindowManagerGlobal.ADD_DUPLICATE_ADD;
+            }
+
+            if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
+                parentWindow = windowForClientLocked(null, attrs.token, false);
+                if (parentWindow == null) {
+                    Slog.w(TAG_WM, "Attempted to add window with token that is not a window: "
+                          + attrs.token + ".  Aborting.");
+                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
+                }
+                if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
+                        && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
+                    Slog.w(TAG_WM, "Attempted to add window with token that is a sub-window: "
+                            + attrs.token + ".  Aborting.");
+                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
+                }
+            }
+
+            if (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) {
+                Slog.w(TAG_WM, "Attempted to add private presentation window to a non-private display.  Aborting.");
+                return WindowManagerGlobal.ADD_PERMISSION_DENIED;
+            }
+
+            AppWindowToken atoken = null;
+            final boolean hasParent = parentWindow != null;
+            // Use existing parent window token for child windows since they go in the same token
+            // as there parent window so we can apply the same policy on them.
+            WindowToken token = displayContent.getWindowToken(
+                    hasParent ? parentWindow.mAttrs.token : attrs.token);
+            // If this is a child window, we want to apply the same type checking rules as the
+            // parent window type.
+            final int rootType = hasParent ? parentWindow.mAttrs.type : type;
+
+            boolean addToastWindowRequiresToken = false;
+
+            if (token == null) {
+                if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
+                    Slog.w(TAG_WM, "Attempted to add application window with unknown token "
+                          + attrs.token + ".  Aborting.");
+                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
+                }
+                if (rootType == TYPE_INPUT_METHOD) {
+                    Slog.w(TAG_WM, "Attempted to add input method window with unknown token "
+                          + attrs.token + ".  Aborting.");
+                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
+                }
+                if (rootType == TYPE_VOICE_INTERACTION) {
+                    Slog.w(TAG_WM, "Attempted to add voice interaction window with unknown token "
+                          + attrs.token + ".  Aborting.");
+                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
+                }
+                if (rootType == TYPE_WALLPAPER) {
+                    Slog.w(TAG_WM, "Attempted to add wallpaper window with unknown token "
+                          + attrs.token + ".  Aborting.");
+                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
+                }
+                if (rootType == TYPE_DREAM) {
+                    Slog.w(TAG_WM, "Attempted to add Dream window with unknown token "
+                          + attrs.token + ".  Aborting.");
+                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
+                }
+                if (rootType == TYPE_QS_DIALOG) {
+                    Slog.w(TAG_WM, "Attempted to add QS dialog window with unknown token "
+                          + attrs.token + ".  Aborting.");
+                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
+                }
+                if (rootType == TYPE_ACCESSIBILITY_OVERLAY) {
+                    Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with unknown token "
+                            + 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,
+                            parentWindow)) {
+                        Slog.w(TAG_WM, "Attempted to add a toast window with unknown token "
+                                + attrs.token + ".  Aborting.");
+                        return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
+                    }
+                }
+                final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
+                token = new WindowToken(this, binder, type, false, displayContent,
+                        session.mCanAddInternalSystemWindow);
+            } else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
+                atoken = token.asAppWindowToken();
+                if (atoken == null) {
+                    Slog.w(TAG_WM, "Attempted to add window with non-application token "
+                          + token + ".  Aborting.");
+                    return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
+                } else if (atoken.removed) {
+                    Slog.w(TAG_WM, "Attempted to add window with exiting application token "
+                          + token + ".  Aborting.");
+                    return WindowManagerGlobal.ADD_APP_EXITING;
+                }
+            } else if (rootType == TYPE_INPUT_METHOD) {
+                if (token.windowType != TYPE_INPUT_METHOD) {
+                    Slog.w(TAG_WM, "Attempted to add input method window with bad token "
+                            + attrs.token + ".  Aborting.");
+                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
+                }
+            } else if (rootType == TYPE_VOICE_INTERACTION) {
+                if (token.windowType != TYPE_VOICE_INTERACTION) {
+                    Slog.w(TAG_WM, "Attempted to add voice interaction window with bad token "
+                            + attrs.token + ".  Aborting.");
+                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
+                }
+            } else if (rootType == TYPE_WALLPAPER) {
+                if (token.windowType != TYPE_WALLPAPER) {
+                    Slog.w(TAG_WM, "Attempted to add wallpaper window with bad token "
+                            + attrs.token + ".  Aborting.");
+                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
+                }
+            } else if (rootType == TYPE_DREAM) {
+                if (token.windowType != TYPE_DREAM) {
+                    Slog.w(TAG_WM, "Attempted to add Dream window with bad token "
+                            + attrs.token + ".  Aborting.");
+                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
+                }
+            } else if (rootType == TYPE_ACCESSIBILITY_OVERLAY) {
+                if (token.windowType != TYPE_ACCESSIBILITY_OVERLAY) {
+                    Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with bad token "
+                            + 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, parentWindow);
+                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 "
+                            + attrs.token + ".  Aborting.");
+                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
+                }
+            } else if (token.asAppWindowToken() != null) {
+                Slog.w(TAG_WM, "Non-null appWindowToken for system window of rootType=" + rootType);
+                // It is not valid to use an app token with other system types; we will
+                // instead make a new token for it (as if null had been passed in for the token).
+                attrs.token = null;
+                token = new WindowToken(this, client.asBinder(), type, false, displayContent,
+                        session.mCanAddInternalSystemWindow);
+            }
+
+            final WindowState win = new WindowState(this, session, client, token, parentWindow,
+                    appOp[0], seq, attrs, viewVisibility, session.mUid,
+                    session.mCanAddInternalSystemWindow);
+            if (win.mDeathRecipient == null) {
+                // Client has apparently died, so there is no reason to
+                // continue.
+                Slog.w(TAG_WM, "Adding window client " + client.asBinder()
+                        + " that is dead, aborting.");
+                return WindowManagerGlobal.ADD_APP_EXITING;
+            }
+
+            if (win.getDisplayContent() == null) {
+                Slog.w(TAG_WM, "Adding window to Display that has been removed.");
+                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
+            }
+
+            mPolicy.adjustWindowParamsLw(win.mAttrs);
+            win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));
+
+            res = mPolicy.prepareAddWindowLw(win, attrs);
+            if (res != WindowManagerGlobal.ADD_OKAY) {
+                return res;
+            }
+
+            final boolean openInputChannels = (outInputChannel != null
+                    && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
+            if  (openInputChannels) {
+                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 (!getDefaultDisplayContentLocked().canAddToastWindowForUid(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;
+            if (mCurrentFocus == null) {
+                mWinAddedSinceNullFocus.add(win);
+            }
+
+            if (excludeWindowTypeFromTapOutTask(type)) {
+                displayContent.mTapExcludedWindows.add(win);
+            }
+
+            origId = Binder.clearCallingIdentity();
+
+            win.attach();
+            mWindowMap.put(client.asBinder(), win);
+            if (win.mAppOp != AppOpsManager.OP_NONE) {
+                int startOpResult = mAppOps.startOpNoThrow(win.mAppOp, win.getOwningUid(),
+                        win.getOwningPackage());
+                if ((startOpResult != AppOpsManager.MODE_ALLOWED) &&
+                        (startOpResult != AppOpsManager.MODE_DEFAULT)) {
+                    win.setAppOpVisibilityLw(false);
+                }
+            }
+
+            final boolean hideSystemAlertWindows = !mHidingNonSystemOverlayWindows.isEmpty();
+            win.setForceHideNonSystemOverlayWindowIfNeeded(hideSystemAlertWindows);
+
+            final AppWindowToken aToken = token.asAppWindowToken();
+            if (type == TYPE_APPLICATION_STARTING && aToken != null) {
+                aToken.startingWindow = win;
+                if (DEBUG_STARTING_WINDOW) Slog.v (TAG_WM, "addWindow: " + aToken
+                        + " startingWindow=" + win);
+            }
+
+            boolean imMayMove = true;
+
+            win.mToken.addWindow(win);
+            if (type == TYPE_INPUT_METHOD) {
+                win.mGivenInsetsPending = true;
+                setInputMethodWindowLocked(win);
+                imMayMove = false;
+            } else if (type == TYPE_INPUT_METHOD_DIALOG) {
+                displayContent.computeImeTarget(true /* updateImeTarget */);
+                imMayMove = false;
+            } else {
+                if (type == TYPE_WALLPAPER) {
+                    displayContent.mWallpaperController.clearLastWallpaperTimeoutTime();
+                    displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+                } else if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0) {
+                    displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+                } else if (displayContent.mWallpaperController.isBelowWallpaperTarget(win)) {
+                    // If there is currently a wallpaper being shown, and
+                    // the base layer of the new window is below the current
+                    // layer of the target window, then adjust the wallpaper.
+                    // This is to avoid a new window being placed between the
+                    // wallpaper and its target.
+                    displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+                }
+            }
+
+            // If the window is being added to a stack that's currently adjusted for IME,
+            // make sure to apply the same adjust to this new window.
+            win.applyAdjustForImeIfNeeded();
+
+            if (type == TYPE_DOCK_DIVIDER) {
+                mRoot.getDisplayContent(displayId).getDockedDividerController().setWindow(win);
+            }
+
+            final WindowStateAnimator winAnimator = win.mWinAnimator;
+            winAnimator.mEnterAnimationPending = true;
+            winAnimator.mEnteringAnimation = true;
+            // Check if we need to prepare a transition for replacing window first.
+            if (atoken != null && atoken.isVisible()
+                    && !prepareWindowReplacementTransition(atoken)) {
+                // If not, check if need to set up a dummy transition during display freeze
+                // so that the unfreeze wait for the apps to draw. This might be needed if
+                // the app is relaunching.
+                prepareNoneTransitionForRelaunching(atoken);
+            }
+
+            if (displayContent.isDefaultDisplay) {
+                final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+                final Rect taskBounds;
+                if (atoken != null && atoken.getTask() != null) {
+                    taskBounds = mTmpRect;
+                    atoken.getTask().getBounds(mTmpRect);
+                } else {
+                    taskBounds = null;
+                }
+                if (mPolicy.getInsetHintLw(win.mAttrs, taskBounds, displayInfo.rotation,
+                        displayInfo.logicalWidth, displayInfo.logicalHeight, outContentInsets,
+                        outStableInsets, outOutsets)) {
+                    res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR;
+                }
+            } else {
+                outContentInsets.setEmpty();
+                outStableInsets.setEmpty();
+            }
+
+            if (mInTouchMode) {
+                res |= WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE;
+            }
+            if (win.mAppToken == null || !win.mAppToken.isClientHidden()) {
+                res |= WindowManagerGlobal.ADD_FLAG_APP_VISIBLE;
+            }
+
+            mInputMonitor.setUpdateInputWindowsNeededLw();
+
+            boolean focusChanged = false;
+            if (win.canReceiveKeys()) {
+                focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,
+                        false /*updateInputWindows*/);
+                if (focusChanged) {
+                    imMayMove = false;
+                }
+            }
+
+            if (imMayMove) {
+                displayContent.computeImeTarget(true /* updateImeTarget */);
+            }
+
+            // Don't do layout here, the window must call
+            // relayout to be displayed, so we'll do it there.
+            displayContent.assignWindowLayers(false /* setLayoutNeeded */);
+
+            if (focusChanged) {
+                mInputMonitor.setInputFocusLw(mCurrentFocus, false /*updateInputWindows*/);
+            }
+            mInputMonitor.updateInputWindowsLw(false /*force*/);
+
+            if (localLOGV || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addWindow: New client "
+                    + client.asBinder() + ": window=" + win + " Callers=" + Debug.getCallers(5));
+
+            if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked(false, displayId)) {
+                reportNewConfig = true;
+            }
+        }
+
+        if (reportNewConfig) {
+            sendNewConfiguration(displayId);
+        }
+
+        Binder.restoreCallingIdentity(origId);
+
+        return res;
+    }
+
+    private boolean doesAddToastWindowRequireToken(String packageName, int callingUid,
+            WindowState attachedWindow) {
+        // Try using the target SDK of the root window
+        if (attachedWindow != null) {
+            return attachedWindow.mAppToken != null
+                    && attachedWindow.mAppToken.mTargetSdk >= Build.VERSION_CODES.O;
+        } 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.O) {
+                    return true;
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                /* ignore */
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if we're done setting up any transitions.
+     */
+    private boolean prepareWindowReplacementTransition(AppWindowToken atoken) {
+        atoken.clearAllDrawn();
+        final WindowState replacedWindow = atoken.getReplacingWindow();
+        if (replacedWindow == null) {
+            // We expect to already receive a request to remove the old window. If it did not
+            // happen, let's just simply add a window.
+            return false;
+        }
+        // We use the visible frame, because we want the animation to morph the window from what
+        // was visible to the user to the final destination of the new window.
+        Rect frame = replacedWindow.mVisibleFrame;
+        // We treat this as if this activity was opening, so we can trigger the app transition
+        // animation and piggy-back on existing transition animation infrastructure.
+        mOpeningApps.add(atoken);
+        prepareAppTransition(AppTransition.TRANSIT_ACTIVITY_RELAUNCH, ALWAYS_KEEP_CURRENT);
+        mAppTransition.overridePendingAppTransitionClipReveal(frame.left, frame.top,
+                frame.width(), frame.height());
+        executeAppTransition();
+        return true;
+    }
+
+    private void prepareNoneTransitionForRelaunching(AppWindowToken atoken) {
+        // Set up a none-transition and add the app to opening apps, so that the display
+        // unfreeze wait for the apps to be drawn.
+        // Note that if the display unfroze already because app unfreeze timed out,
+        // we don't set up the transition anymore and just let it go.
+        if (mDisplayFrozen && !mOpeningApps.contains(atoken) && atoken.isRelaunching()) {
+            mOpeningApps.add(atoken);
+            prepareAppTransition(AppTransition.TRANSIT_NONE, !ALWAYS_KEEP_CURRENT);
+            executeAppTransition();
+        }
+    }
+
+    /**
+     * Returns whether screen capture is disabled for all windows of a specific user.
+     */
+    boolean isScreenCaptureDisabledLocked(int userId) {
+        Boolean disabled = mScreenCaptureDisabled.get(userId);
+        if (disabled == null) {
+            return false;
+        }
+        return disabled;
+    }
+
+    boolean isSecureLocked(WindowState w) {
+        if ((w.mAttrs.flags&WindowManager.LayoutParams.FLAG_SECURE) != 0) {
+            return true;
+        }
+        if (isScreenCaptureDisabledLocked(UserHandle.getUserId(w.mOwnerUid))) {
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void enableSurfaceTrace(ParcelFileDescriptor pfd) {
+        final int callingUid = Binder.getCallingUid();
+        if (callingUid != SHELL_UID && callingUid != ROOT_UID) {
+            throw new SecurityException("Only shell can call enableSurfaceTrace");
+        }
+
+        synchronized (mWindowMap) {
+            mRoot.enableSurfaceTrace(pfd);
+        }
+    }
+
+    @Override
+    public void disableSurfaceTrace() {
+        final int callingUid = Binder.getCallingUid();
+        if (callingUid != SHELL_UID && callingUid != ROOT_UID &&
+            callingUid != SYSTEM_UID) {
+            throw new SecurityException("Only shell can call disableSurfaceTrace");
+        }
+        synchronized (mWindowMap) {
+            mRoot.disableSurfaceTrace();
+        }
+    }
+
+    /**
+     * Set mScreenCaptureDisabled for specific user
+     */
+    @Override
+    public void setScreenCaptureDisabled(int userId, boolean disabled) {
+        int callingUid = Binder.getCallingUid();
+        if (callingUid != SYSTEM_UID) {
+            throw new SecurityException("Only system can call setScreenCaptureDisabled.");
+        }
+
+        synchronized(mWindowMap) {
+            mScreenCaptureDisabled.put(userId, disabled);
+            // Update secure surface for all windows belonging to this user.
+            mRoot.setSecureSurfaceState(userId, disabled);
+        }
+    }
+
+    void removeWindow(Session session, IWindow client) {
+        synchronized(mWindowMap) {
+            WindowState win = windowForClientLocked(session, client, false);
+            if (win == null) {
+                return;
+            }
+            win.removeIfPossible();
+        }
+    }
+
+    /**
+     * Performs some centralized bookkeeping clean-up on the window that is being removed.
+     * NOTE: Should only be called from {@link WindowState#removeImmediately()}
+     * TODO: Maybe better handled with a method {@link WindowContainer#removeChild} if we can
+     * figure-out a good way to have all parents of a WindowState doing the same thing without
+     * forgetting to add the wiring when a new parent of WindowState is added.
+     */
+    void postWindowRemoveCleanupLocked(WindowState win) {
+        if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "postWindowRemoveCleanupLocked: " + win);
+        mWindowMap.remove(win.mClient.asBinder());
+        if (win.mAppOp != AppOpsManager.OP_NONE) {
+            mAppOps.finishOp(win.mAppOp, win.getOwningUid(), win.getOwningPackage());
+        }
+
+        if (mCurrentFocus == null) {
+            mWinRemovedSinceNullFocus.add(win);
+        }
+        mPendingRemove.remove(win);
+        mResizingWindows.remove(win);
+        updateNonSystemOverlayWindowsVisibilityIfNeeded(win, false /* surfaceShown */);
+        mWindowsChanged = true;
+        if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Final remove of window: " + win);
+
+        if (mInputMethodWindow == win) {
+            setInputMethodWindowLocked(null);
+        }
+
+        final WindowToken token = win.mToken;
+        final AppWindowToken atoken = win.mAppToken;
+        if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Removing " + win + " from " + token);
+        // Window will already be removed from token before this post clean-up method is called.
+        if (token.isEmpty()) {
+            if (!token.mPersistOnEmpty) {
+                token.removeImmediately();
+            } else if (atoken != null) {
+                // TODO: Should this be moved into AppWindowToken.removeWindow? Might go away after
+                // re-factor.
+                atoken.firstWindowDrawn = false;
+                atoken.clearAllDrawn();
+                final TaskStack stack = atoken.getStack();
+                if (stack != null) {
+                    stack.mExitingAppTokens.remove(atoken);
+                }
+            }
+        }
+
+        if (atoken != null) {
+            atoken.postWindowRemoveStartingWindowCleanup(win);
+        }
+
+        final DisplayContent dc = win.getDisplayContent();
+        if (win.mAttrs.type == TYPE_WALLPAPER) {
+            dc.mWallpaperController.clearLastWallpaperTimeoutTime();
+            dc.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+        } else if ((win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
+            dc.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+        }
+
+        if (dc != null && !mWindowPlacerLocked.isInLayout()) {
+            dc.assignWindowLayers(true /* setLayoutNeeded */);
+            mWindowPlacerLocked.performSurfacePlacement();
+            if (win.mAppToken != null) {
+                win.mAppToken.updateReportedVisibilityLocked();
+            }
+        }
+
+        mInputMonitor.updateInputWindowsLw(true /*force*/);
+    }
+
+    void setInputMethodWindowLocked(WindowState win) {
+        mInputMethodWindow = win;
+        final DisplayContent dc = win != null
+                ? win.getDisplayContent() : getDefaultDisplayContentLocked();
+        dc.computeImeTarget(true /* updateImeTarget */);
+    }
+
+    private void updateAppOpsState() {
+        synchronized(mWindowMap) {
+            mRoot.updateAppOpsState();
+        }
+    }
+
+    static void logSurface(WindowState w, String msg, boolean withStackTrace) {
+        String str = "  SURFACE " + msg + ": " + w;
+        if (withStackTrace) {
+            logWithStack(TAG, str);
+        } else {
+            Slog.i(TAG_WM, str);
+        }
+    }
+
+    static void logSurface(SurfaceControl s, String title, String msg) {
+        String str = "  SURFACE " + s + ": " + msg + " / " + title;
+        Slog.i(TAG_WM, str);
+    }
+
+    static void logWithStack(String tag, String s) {
+        RuntimeException e = null;
+        if (SHOW_STACK_CRAWLS) {
+            e = new RuntimeException();
+            e.fillInStackTrace();
+        }
+        Slog.i(tag, s, e);
+    }
+
+    void setTransparentRegionWindow(Session session, IWindow client, Region region) {
+        long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mWindowMap) {
+                WindowState w = windowForClientLocked(session, client, false);
+                if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(w,
+                        "transparentRegionHint=" + region, false);
+
+                if ((w != null) && w.mHasSurface) {
+                    w.mWinAnimator.setTransparentRegionHintLocked(region);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    void setInsetsWindow(Session session, IWindow client, int touchableInsets, Rect contentInsets,
+            Rect visibleInsets, Region touchableRegion) {
+        long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mWindowMap) {
+                WindowState w = windowForClientLocked(session, client, false);
+                if (DEBUG_LAYOUT) Slog.d(TAG, "setInsetsWindow " + w
+                        + ", contentInsets=" + w.mGivenContentInsets + " -> " + contentInsets
+                        + ", visibleInsets=" + w.mGivenVisibleInsets + " -> " + visibleInsets
+                        + ", touchableRegion=" + w.mGivenTouchableRegion + " -> " + touchableRegion
+                        + ", touchableInsets " + w.mTouchableInsets + " -> " + touchableInsets);
+                if (w != null) {
+                    w.mGivenInsetsPending = false;
+                    w.mGivenContentInsets.set(contentInsets);
+                    w.mGivenVisibleInsets.set(visibleInsets);
+                    w.mGivenTouchableRegion.set(touchableRegion);
+                    w.mTouchableInsets = touchableInsets;
+                    if (w.mGlobalScale != 1) {
+                        w.mGivenContentInsets.scale(w.mGlobalScale);
+                        w.mGivenVisibleInsets.scale(w.mGlobalScale);
+                        w.mGivenTouchableRegion.scale(w.mGlobalScale);
+                    }
+                    w.setDisplayLayoutNeeded();
+                    mWindowPlacerLocked.performSurfacePlacement();
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    public void getWindowDisplayFrame(Session session, IWindow client,
+            Rect outDisplayFrame) {
+        synchronized(mWindowMap) {
+            WindowState win = windowForClientLocked(session, client, false);
+            if (win == null) {
+                outDisplayFrame.setEmpty();
+                return;
+            }
+            outDisplayFrame.set(win.mDisplayFrame);
+        }
+    }
+
+    public void onRectangleOnScreenRequested(IBinder token, Rect rectangle) {
+        synchronized (mWindowMap) {
+            if (mAccessibilityController != null) {
+                WindowState window = mWindowMap.get(token);
+                //TODO (multidisplay): Magnification is supported only for the default display.
+                if (window != null && window.getDisplayId() == DEFAULT_DISPLAY) {
+                    mAccessibilityController.onRectangleOnScreenRequestedLocked(rectangle);
+                }
+            }
+        }
+    }
+
+    public IWindowId getWindowId(IBinder token) {
+        synchronized (mWindowMap) {
+            WindowState window = mWindowMap.get(token);
+            return window != null ? window.mWindowId : null;
+        }
+    }
+
+    public void pokeDrawLock(Session session, IBinder token) {
+        synchronized (mWindowMap) {
+            WindowState window = windowForClientLocked(session, token, false);
+            if (window != null) {
+                window.pokeDrawLockLw(mDrawLockTimeoutMillis);
+            }
+        }
+    }
+
+    public int relayoutWindow(Session session, IWindow client, int seq,
+            WindowManager.LayoutParams attrs, int requestedWidth,
+            int requestedHeight, int viewVisibility, int flags,
+            Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
+            Rect outVisibleInsets, Rect outStableInsets, Rect outOutsets, Rect outBackdropFrame,
+            MergedConfiguration mergedConfiguration, Surface outSurface) {
+        int result = 0;
+        boolean configChanged;
+        boolean hasStatusBarPermission =
+                mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)
+                        == PackageManager.PERMISSION_GRANTED;
+
+        long origId = Binder.clearCallingIdentity();
+        final int displayId;
+        synchronized(mWindowMap) {
+            WindowState win = windowForClientLocked(session, client, false);
+            if (win == null) {
+                return 0;
+            }
+            displayId = win.getDisplayId();
+
+            WindowStateAnimator winAnimator = win.mWinAnimator;
+            if (viewVisibility != View.GONE) {
+                win.setRequestedSize(requestedWidth, requestedHeight);
+            }
+
+            int attrChanges = 0;
+            int flagChanges = 0;
+            if (attrs != null) {
+                mPolicy.adjustWindowParamsLw(attrs);
+                // if they don't have the permission, mask out the status bar bits
+                if (seq == win.mSeq) {
+                    int systemUiVisibility = attrs.systemUiVisibility
+                            | attrs.subtreeSystemUiVisibility;
+                    if ((systemUiVisibility & DISABLE_MASK) != 0) {
+                        if (!hasStatusBarPermission) {
+                            systemUiVisibility &= ~DISABLE_MASK;
+                        }
+                    }
+                    win.mSystemUiVisibility = systemUiVisibility;
+                }
+                if (win.mAttrs.type != attrs.type) {
+                    throw new IllegalArgumentException(
+                            "Window type can not be changed after the window is added.");
+                }
+
+                // Odd choice but less odd than embedding in copyFrom()
+                if ((attrs.privateFlags & WindowManager.LayoutParams.PRIVATE_FLAG_PRESERVE_GEOMETRY)
+                        != 0) {
+                    attrs.x = win.mAttrs.x;
+                    attrs.y = win.mAttrs.y;
+                    attrs.width = win.mAttrs.width;
+                    attrs.height = win.mAttrs.height;
+                }
+
+                flagChanges = win.mAttrs.flags ^= attrs.flags;
+                attrChanges = win.mAttrs.copyFrom(attrs);
+                if ((attrChanges & (WindowManager.LayoutParams.LAYOUT_CHANGED
+                        | WindowManager.LayoutParams.SYSTEM_UI_VISIBILITY_CHANGED)) != 0) {
+                    win.mLayoutNeeded = true;
+                }
+                if (win.mAppToken != null && ((flagChanges & FLAG_SHOW_WHEN_LOCKED) != 0
+                        || (flagChanges & FLAG_DISMISS_KEYGUARD) != 0)) {
+                    win.mAppToken.checkKeyguardFlagsChanged();
+                }
+                if (((attrChanges & LayoutParams.ACCESSIBILITY_TITLE_CHANGED) != 0)
+                        && (mAccessibilityController != null)
+                        && (win.getDisplayId() == DEFAULT_DISPLAY)) {
+                    // No move or resize, but the controller checks for title changes as well
+                    mAccessibilityController.onSomeWindowResizedOrMovedLocked();
+                }
+            }
+
+            if (DEBUG_LAYOUT) Slog.v(TAG_WM, "Relayout " + win + ": viewVisibility=" + viewVisibility
+                    + " req=" + requestedWidth + "x" + requestedHeight + " " + win.mAttrs);
+            winAnimator.mSurfaceDestroyDeferred = (flags & RELAYOUT_DEFER_SURFACE_DESTROY) != 0;
+            win.mEnforceSizeCompat =
+                    (win.mAttrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0;
+            if ((attrChanges & WindowManager.LayoutParams.ALPHA_CHANGED) != 0) {
+                winAnimator.mAlpha = attrs.alpha;
+            }
+            win.setWindowScale(win.mRequestedWidth, win.mRequestedHeight);
+
+            if (win.mAttrs.surfaceInsets.left != 0
+                    || win.mAttrs.surfaceInsets.top != 0
+                    || win.mAttrs.surfaceInsets.right != 0
+                    || win.mAttrs.surfaceInsets.bottom != 0) {
+                winAnimator.setOpaqueLocked(false);
+            }
+
+            boolean imMayMove = (flagChanges & (FLAG_ALT_FOCUSABLE_IM | FLAG_NOT_FOCUSABLE)) != 0;
+            final boolean isDefaultDisplay = win.isDefaultDisplay();
+            boolean focusMayChange = isDefaultDisplay && (win.mViewVisibility != viewVisibility
+                    || ((flagChanges & FLAG_NOT_FOCUSABLE) != 0)
+                    || (!win.mRelayoutCalled));
+
+            boolean wallpaperMayMove = win.mViewVisibility != viewVisibility
+                    && (win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0;
+            wallpaperMayMove |= (flagChanges & FLAG_SHOW_WALLPAPER) != 0;
+            if ((flagChanges & FLAG_SECURE) != 0 && winAnimator.mSurfaceController != null) {
+                winAnimator.mSurfaceController.setSecure(isSecureLocked(win));
+            }
+
+            win.mRelayoutCalled = true;
+            win.mInRelayout = true;
+
+            final int oldVisibility = win.mViewVisibility;
+            win.mViewVisibility = viewVisibility;
+            if (DEBUG_SCREEN_ON) {
+                RuntimeException stack = new RuntimeException();
+                stack.fillInStackTrace();
+                Slog.i(TAG_WM, "Relayout " + win + ": oldVis=" + oldVisibility
+                        + " newVis=" + viewVisibility, stack);
+            }
+            if (viewVisibility == View.VISIBLE &&
+                    (win.mAppToken == null || win.mAttrs.type == TYPE_APPLICATION_STARTING
+                            || !win.mAppToken.isClientHidden())) {
+
+                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: viewVisibility_1");
+
+                // We are about to create a surface, but we didn't run a layout yet. So better run
+                // a layout now that we already know the right size, as a resize call will make the
+                // surface transaction blocking until next vsync and slow us down.
+                // TODO: Ideally we'd create the surface after running layout a bit further down,
+                // but moving this seems to be too risky at this point in the release.
+                if (win.mLayoutSeq == -1) {
+                    win.setDisplayLayoutNeeded();
+                    mWindowPlacerLocked.performSurfacePlacement(true);
+                }
+                result = win.relayoutVisibleWindow(result, attrChanges, oldVisibility);
+
+                try {
+                    result = createSurfaceControl(outSurface, result, win, winAnimator);
+                } catch (Exception e) {
+                    mInputMonitor.updateInputWindowsLw(true /*force*/);
+
+                    Slog.w(TAG_WM, "Exception thrown when creating surface for client "
+                             + client + " (" + win.mAttrs.getTitle() + ")",
+                             e);
+                    Binder.restoreCallingIdentity(origId);
+                    return 0;
+                }
+                if ((result & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
+                    focusMayChange = isDefaultDisplay;
+                }
+                if (win.mAttrs.type == TYPE_INPUT_METHOD && mInputMethodWindow == null) {
+                    setInputMethodWindowLocked(win);
+                    imMayMove = true;
+                }
+                win.adjustStartingWindowFlags();
+                Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+            } else {
+                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: viewVisibility_2");
+
+                winAnimator.mEnterAnimationPending = false;
+                winAnimator.mEnteringAnimation = false;
+
+                if (winAnimator.hasSurface() && !win.mAnimatingExit) {
+                    if (DEBUG_VISIBILITY) Slog.i(TAG_WM, "Relayout invis " + win
+                            + ": mAnimatingExit=" + win.mAnimatingExit);
+                    // If we are not currently running the exit animation, we
+                    // need to see about starting one.
+                    // We don't want to animate visibility of windows which are pending
+                    // replacement. In the case of activity relaunch child windows
+                    // could request visibility changes as they are detached from the main
+                    // application window during the tear down process. If we satisfied
+                    // these visibility changes though, we would cause a visual glitch
+                    // hiding the window before it's replacement was available.
+                    // So we just do nothing on our side.
+                    if (!win.mWillReplaceWindow) {
+                        focusMayChange = tryStartExitingAnimation(
+                                win, winAnimator, isDefaultDisplay, focusMayChange);
+                    }
+                    result |= RELAYOUT_RES_SURFACE_CHANGED;
+                }
+                if (viewVisibility == View.VISIBLE && winAnimator.hasSurface()) {
+                    // We already told the client to go invisible, but the message may not be
+                    // handled yet, or it might want to draw a last frame. If we already have a
+                    // surface, let the client use that, but don't create new surface at this point.
+                    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: getSurface");
+                    winAnimator.mSurfaceController.getSurface(outSurface);
+                    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+                } else {
+                    if (DEBUG_VISIBILITY) Slog.i(TAG_WM, "Releasing surface in: " + win);
+
+                    try {
+                        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmReleaseOutSurface_"
+                                + win.mAttrs.getTitle());
+                        outSurface.release();
+                    } finally {
+                        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+                    }
+                }
+
+                Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+            }
+
+            if (focusMayChange) {
+                //System.out.println("Focus may change: " + win.mAttrs.getTitle());
+                if (updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
+                        false /*updateInputWindows*/)) {
+                    imMayMove = false;
+                }
+                //System.out.println("Relayout " + win + ": focus=" + mCurrentFocus);
+            }
+
+            // updateFocusedWindowLocked() already assigned layers so we only need to
+            // reassign them at this point if the IM window state gets shuffled
+            boolean toBeDisplayed = (result & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0;
+            final DisplayContent dc = win.getDisplayContent();
+            if (imMayMove) {
+                dc.computeImeTarget(true /* updateImeTarget */);
+                if (toBeDisplayed) {
+                    // Little hack here -- we -should- be able to rely on the function to return
+                    // true if the IME has moved and needs its layer recomputed. However, if the IME
+                    // was hidden and isn't actually moved in the list, its layer may be out of data
+                    // so we make sure to recompute it.
+                    dc.assignWindowLayers(false /* setLayoutNeeded */);
+                }
+            }
+
+            if (wallpaperMayMove) {
+                win.getDisplayContent().pendingLayoutChanges |=
+                        WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+            }
+
+            if (win.mAppToken != null) {
+                mUnknownAppVisibilityController.notifyRelayouted(win.mAppToken);
+            }
+
+            win.setDisplayLayoutNeeded();
+            win.mGivenInsetsPending = (flags & WindowManagerGlobal.RELAYOUT_INSETS_PENDING) != 0;
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
+                    "relayoutWindow: updateOrientationFromAppTokens");
+            configChanged = updateOrientationFromAppTokensLocked(false, displayId);
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+
+            // We may be deferring layout passes at the moment, but since the client is interested
+            // in the new out values right now we need to force a layout.
+            mWindowPlacerLocked.performSurfacePlacement(true /* force */);
+            if (toBeDisplayed && win.mIsWallpaper) {
+                DisplayInfo displayInfo = win.getDisplayContent().getDisplayInfo();
+                dc.mWallpaperController.updateWallpaperOffset(
+                        win, displayInfo.logicalWidth, displayInfo.logicalHeight, false);
+            }
+            if (win.mAppToken != null) {
+                win.mAppToken.updateReportedVisibilityLocked();
+            }
+            if (winAnimator.mReportSurfaceResized) {
+                winAnimator.mReportSurfaceResized = false;
+                result |= WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED;
+            }
+            if (mPolicy.isNavBarForcedShownLw(win)) {
+                result |= WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_NAV_BAR;
+            }
+            if (!win.isGoneForLayoutLw()) {
+                win.mResizedWhileGone = false;
+            }
+
+            // We must always send the latest {@link MergedConfiguration}, regardless of whether we
+            // have already reported it. The client might not have processed the previous value yet
+            // and needs process it before handling the corresponding window frame. the variable
+            // {@code mergedConfiguration} is an out parameter that will be passed back to the
+            // client over IPC and checked there.
+            win.getMergedConfiguration(mergedConfiguration);
+            win.setReportedConfiguration(mergedConfiguration);
+
+            outFrame.set(win.mCompatFrame);
+            outOverscanInsets.set(win.mOverscanInsets);
+            outContentInsets.set(win.mContentInsets);
+            win.mLastRelayoutContentInsets.set(win.mContentInsets);
+            outVisibleInsets.set(win.mVisibleInsets);
+            outStableInsets.set(win.mStableInsets);
+            outOutsets.set(win.mOutsets);
+            outBackdropFrame.set(win.getBackdropFrame(win.mFrame));
+            if (localLOGV) Slog.v(
+                TAG_WM, "Relayout given client " + client.asBinder()
+                + ", requestedWidth=" + requestedWidth
+                + ", requestedHeight=" + requestedHeight
+                + ", viewVisibility=" + viewVisibility
+                + "\nRelayout returning frame=" + outFrame
+                + ", surface=" + outSurface);
+
+            if (localLOGV || DEBUG_FOCUS) Slog.v(
+                TAG_WM, "Relayout of " + win + ": focusMayChange=" + focusMayChange);
+
+            result |= mInTouchMode ? WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE : 0;
+
+            mInputMonitor.updateInputWindowsLw(true /*force*/);
+
+            if (DEBUG_LAYOUT) {
+                Slog.v(TAG_WM, "Relayout complete " + win + ": outFrame=" + outFrame.toShortString());
+            }
+            win.mInRelayout = false;
+        }
+
+        if (configChanged) {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: sendNewConfiguration");
+            sendNewConfiguration(displayId);
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+        }
+        Binder.restoreCallingIdentity(origId);
+        return result;
+    }
+
+    private boolean tryStartExitingAnimation(WindowState win, WindowStateAnimator winAnimator,
+            boolean isDefaultDisplay, boolean focusMayChange) {
+        // Try starting an animation; if there isn't one, we
+        // can destroy the surface right away.
+        int transit = WindowManagerPolicy.TRANSIT_EXIT;
+        if (win.mAttrs.type == TYPE_APPLICATION_STARTING) {
+            transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
+        }
+        if (win.isWinVisibleLw() && winAnimator.applyAnimationLocked(transit, false)) {
+            focusMayChange = isDefaultDisplay;
+            win.mAnimatingExit = true;
+            win.mWinAnimator.mAnimating = true;
+        } else if (win.mWinAnimator.isAnimationSet()) {
+            // Currently in a hide animation... turn this into
+            // an exit.
+            win.mAnimatingExit = true;
+            win.mWinAnimator.mAnimating = true;
+        } else if (win.getDisplayContent().mWallpaperController.isWallpaperTarget(win)) {
+            // If the wallpaper is currently behind this
+            // window, we need to change both of them inside
+            // of a transaction to avoid artifacts.
+            win.mAnimatingExit = true;
+            win.mWinAnimator.mAnimating = true;
+        } else {
+            if (mInputMethodWindow == win) {
+                setInputMethodWindowLocked(null);
+            }
+            boolean stopped = win.mAppToken != null ? win.mAppToken.mAppStopped : false;
+            // We set mDestroying=true so AppWindowToken#notifyAppStopped in-to destroy surfaces
+            // will later actually destroy the surface if we do not do so here. Normally we leave
+            // this to the exit animation.
+            win.mDestroying = true;
+            win.destroySurface(false, stopped);
+        }
+        // TODO(multidisplay): Magnification is supported only for the default display.
+        if (mAccessibilityController != null && win.getDisplayId() == DEFAULT_DISPLAY) {
+            mAccessibilityController.onWindowTransitionLocked(win, transit);
+        }
+
+        // When we start the exit animation we take the Surface from the client
+        // so it will stop perturbing it. We need to likewise takeaway the SurfaceFlinger
+        // side child surfaces, so they will remain preserved in their current state
+        // (rather than be cleaned up immediately by the app code).
+        SurfaceControl.openTransaction();
+        winAnimator.detachChildren();
+        SurfaceControl.closeTransaction();
+
+        return focusMayChange;
+    }
+
+    private int createSurfaceControl(Surface outSurface, int result, WindowState win,
+            WindowStateAnimator winAnimator) {
+        if (!win.mHasSurface) {
+            result |= RELAYOUT_RES_SURFACE_CHANGED;
+        }
+
+        WindowSurfaceController surfaceController;
+        try {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createSurfaceControl");
+            surfaceController = winAnimator.createSurfaceLocked(win.mAttrs.type, win.mOwnerUid);
+        } finally {
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+        }
+        if (surfaceController != null) {
+            surfaceController.getSurface(outSurface);
+            if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, "  OUT SURFACE " + outSurface + ": copied");
+        } else {
+            // For some reason there isn't a surface.  Clear the
+            // caller's object so they see the same state.
+            Slog.w(TAG_WM, "Failed to create surface control for " + win);
+            outSurface.release();
+        }
+
+        return result;
+    }
+
+    public boolean outOfMemoryWindow(Session session, IWindow client) {
+        final long origId = Binder.clearCallingIdentity();
+
+        try {
+            synchronized (mWindowMap) {
+                WindowState win = windowForClientLocked(session, client, false);
+                if (win == null) {
+                    return false;
+                }
+                return mRoot.reclaimSomeSurfaceMemory(win.mWinAnimator, "from-client", false);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    void finishDrawingWindow(Session session, IWindow client) {
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mWindowMap) {
+                WindowState win = windowForClientLocked(session, client, false);
+                if (DEBUG_ADD_REMOVE) Slog.d(TAG_WM, "finishDrawingWindow: " + win + " mDrawState="
+                        + (win != null ? win.mWinAnimator.drawStateToString() : "null"));
+                if (win != null && win.mWinAnimator.finishDrawingLocked()) {
+                    if ((win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
+                        win.getDisplayContent().pendingLayoutChanges |=
+                                WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+                    }
+                    win.setDisplayLayoutNeeded();
+                    mWindowPlacerLocked.requestTraversal();
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    boolean applyAnimationLocked(AppWindowToken atoken, WindowManager.LayoutParams lp,
+            int transit, boolean enter, boolean isVoiceInteraction) {
+        // Only apply an animation if the display isn't frozen.  If it is
+        // frozen, there is no reason to animate and it can cause strange
+        // artifacts when we unfreeze the display if some different animation
+        // is running.
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WM#applyAnimationLocked");
+        if (atoken.okToAnimate()) {
+            final DisplayContent displayContent = atoken.getTask().getDisplayContent();
+            final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+            final int width = displayInfo.appWidth;
+            final int height = displayInfo.appHeight;
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG_WM,
+                    "applyAnimation: atoken=" + atoken);
+
+            // Determine the visible rect to calculate the thumbnail clip
+            final WindowState win = atoken.findMainWindow();
+            final Rect frame = new Rect(0, 0, width, height);
+            final Rect displayFrame = new Rect(0, 0,
+                    displayInfo.logicalWidth, displayInfo.logicalHeight);
+            final Rect insets = new Rect();
+            final Rect stableInsets = new Rect();
+            Rect surfaceInsets = null;
+            final boolean freeform = win != null && win.inFreeformWorkspace();
+            if (win != null) {
+                // Containing frame will usually cover the whole screen, including dialog windows.
+                // For freeform workspace windows it will not cover the whole screen and it also
+                // won't exactly match the final freeform window frame (e.g. when overlapping with
+                // the status bar). In that case we need to use the final frame.
+                if (freeform) {
+                    frame.set(win.mFrame);
+                } else {
+                    frame.set(win.mContainingFrame);
+                }
+                surfaceInsets = win.getAttrs().surfaceInsets;
+                insets.set(win.mContentInsets);
+                stableInsets.set(win.mStableInsets);
+            }
+
+            if (atoken.mLaunchTaskBehind) {
+                // Differentiate the two animations. This one which is briefly on the screen
+                // gets the !enter animation, and the other activity which remains on the
+                // screen gets the enter animation. Both appear in the mOpeningApps set.
+                enter = false;
+            }
+            if (DEBUG_APP_TRANSITIONS) Slog.d(TAG_WM, "Loading animation for app transition."
+                    + " transit=" + AppTransition.appTransitionToString(transit) + " enter=" + enter
+                    + " frame=" + frame + " insets=" + insets + " surfaceInsets=" + surfaceInsets);
+            final Configuration displayConfig = displayContent.getConfiguration();
+            Animation a = mAppTransition.loadAnimation(lp, transit, enter, displayConfig.uiMode,
+                    displayConfig.orientation, frame, displayFrame, insets, surfaceInsets,
+                    stableInsets, isVoiceInteraction, freeform, atoken.getTask().mTaskId);
+            if (a != null) {
+                if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + atoken);
+                final int containingWidth = frame.width();
+                final int containingHeight = frame.height();
+                atoken.mAppAnimator.setAnimation(a, containingWidth, containingHeight, width,
+                        height, mAppTransition.canSkipFirstFrame(),
+                        mAppTransition.getAppStackClipMode(),
+                        transit, mAppTransition.getTransitFlags());
+            }
+        } else {
+            atoken.mAppAnimator.clearAnimation();
+        }
+        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+
+        return atoken.mAppAnimator.animation != null;
+    }
+
+    boolean checkCallingPermission(String permission, String func) {
+        // Quick check: if the calling permission is me, it's all okay.
+        if (Binder.getCallingPid() == myPid()) {
+            return true;
+        }
+
+        if (mContext.checkCallingPermission(permission)
+                == PackageManager.PERMISSION_GRANTED) {
+            return true;
+        }
+        final String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid()
+                + ", uid=" + Binder.getCallingUid() + " requires " + permission;
+        Slog.w(TAG_WM, msg);
+        return false;
+    }
+
+    @Override
+    public void addWindowToken(IBinder binder, int type, int displayId) {
+        if (!checkCallingPermission(MANAGE_APP_TOKENS, "addWindowToken()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        }
+
+        synchronized(mWindowMap) {
+            final DisplayContent dc = mRoot.getDisplayContentOrCreate(displayId);
+            WindowToken token = dc.getWindowToken(binder);
+            if (token != null) {
+                Slog.w(TAG_WM, "addWindowToken: Attempted to add binder token: " + binder
+                        + " for already created window token: " + token
+                        + " displayId=" + displayId);
+                return;
+            }
+            if (type == TYPE_WALLPAPER) {
+                new WallpaperWindowToken(this, binder, true, dc,
+                        true /* ownerCanManageAppTokens */);
+            } else {
+                new WindowToken(this, binder, type, true, dc, true /* ownerCanManageAppTokens */);
+            }
+        }
+    }
+
+    @Override
+    public void removeWindowToken(IBinder binder, int displayId) {
+        if (!checkCallingPermission(MANAGE_APP_TOKENS, "removeWindowToken()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        }
+
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mWindowMap) {
+                final DisplayContent dc = mRoot.getDisplayContent(displayId);
+                if (dc == null) {
+                    Slog.w(TAG_WM, "removeWindowToken: Attempted to remove token: " + binder
+                            + " for non-exiting displayId=" + displayId);
+                    return;
+                }
+
+                final WindowToken token = dc.removeWindowToken(binder);
+                if (token == null) {
+                    Slog.w(TAG_WM,
+                            "removeWindowToken: Attempted to remove non-existing token: " + binder);
+                    return;
+                }
+
+                mInputMonitor.updateInputWindowsLw(true /*force*/);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    @Override
+    public Configuration updateOrientationFromAppTokens(Configuration currentConfig,
+            IBinder freezeThisOneIfNeeded, int displayId) {
+        if (!checkCallingPermission(MANAGE_APP_TOKENS, "updateOrientationFromAppTokens()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        }
+
+        final Configuration config;
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized(mWindowMap) {
+                config = updateOrientationFromAppTokensLocked(currentConfig, freezeThisOneIfNeeded,
+                        displayId);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+
+        return config;
+    }
+
+    private Configuration updateOrientationFromAppTokensLocked(Configuration currentConfig,
+            IBinder freezeThisOneIfNeeded, int displayId) {
+        if (!mDisplayReady) {
+            return null;
+        }
+        Configuration config = null;
+
+        if (updateOrientationFromAppTokensLocked(false, displayId)) {
+            // If we changed the orientation but mOrientationChangeComplete is already true,
+            // we used seamless rotation, and we don't need to freeze the screen.
+            if (freezeThisOneIfNeeded != null && !mRoot.mOrientationChangeComplete) {
+                final AppWindowToken atoken = mRoot.getAppWindowToken(freezeThisOneIfNeeded);
+                if (atoken != null) {
+                    atoken.startFreezingScreen();
+                }
+            }
+            config = computeNewConfigurationLocked(displayId);
+
+        } else if (currentConfig != null) {
+            // No obvious action we need to take, but if our current state mismatches the activity
+            // manager's, update it, disregarding font scale, which should remain set to the value
+            // of the previous configuration.
+            // Here we're calling Configuration#unset() instead of setToDefaults() because we need
+            // to keep override configs clear of non-empty values (e.g. fontSize).
+            mTempConfiguration.unset();
+            mTempConfiguration.updateFrom(currentConfig);
+            final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+            displayContent.computeScreenConfiguration(mTempConfiguration);
+            if (currentConfig.diff(mTempConfiguration) != 0) {
+                mWaitingForConfig = true;
+                displayContent.setLayoutNeeded();
+                int anim[] = new int[2];
+                if (displayContent.isDimming()) {
+                    anim[0] = anim[1] = 0;
+                } else {
+                    mPolicy.selectRotationAnimationLw(anim);
+                }
+                startFreezingDisplayLocked(false, anim[0], anim[1], displayContent);
+                config = new Configuration(mTempConfiguration);
+            }
+        }
+
+        return config;
+    }
+
+    /**
+     * Determine the new desired orientation of the display, returning a non-null new Configuration
+     * if it has changed from the current orientation.  IF TRUE IS RETURNED SOMEONE MUST CALL
+     * {@link #setNewDisplayOverrideConfiguration(Configuration, int)} TO TELL THE WINDOW MANAGER IT
+     * CAN UNFREEZE THE SCREEN.  This will typically be done for you if you call
+     * {@link #sendNewConfiguration(int)}.
+     *
+     * The orientation is computed from non-application windows first. If none of the
+     * non-application windows specify orientation, the orientation is computed from application
+     * tokens.
+     * @see android.view.IWindowManager#updateOrientationFromAppTokens(Configuration, IBinder, int)
+     */
+    boolean updateOrientationFromAppTokensLocked(boolean inTransaction, int displayId) {
+        long ident = Binder.clearCallingIdentity();
+        try {
+            final DisplayContent dc = mRoot.getDisplayContent(displayId);
+            final int req = dc.getOrientation();
+            if (req != dc.getLastOrientation()) {
+                dc.setLastOrientation(req);
+                //send a message to Policy indicating orientation change to take
+                //action like disabling/enabling sensors etc.,
+                // TODO(multi-display): Implement policy for secondary displays.
+                if (dc.isDefaultDisplay) {
+                    mPolicy.setCurrentOrientationLw(req);
+                }
+                if (dc.updateRotationUnchecked(inTransaction)) {
+                    // changed
+                    return true;
+                }
+            }
+
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    // If this is true we have updated our desired orientation, but not yet
+    // changed the real orientation our applied our screen rotation animation.
+    // For example, because a previous screen rotation was in progress.
+    boolean rotationNeedsUpdateLocked() {
+        // TODO(multi-display): Check for updates on all displays. Need to have per-display policy
+        // to implement WindowManagerPolicy#rotationForOrientationLw() correctly.
+        final DisplayContent defaultDisplayContent = getDefaultDisplayContentLocked();
+        final int lastOrientation = defaultDisplayContent.getLastOrientation();
+        final int oldRotation = defaultDisplayContent.getRotation();
+        final boolean oldAltOrientation = defaultDisplayContent.getAltOrientation();
+
+        final int rotation = mPolicy.rotationForOrientationLw(lastOrientation,
+                oldRotation);
+        boolean altOrientation = !mPolicy.rotationHasCompatibleMetricsLw(
+                lastOrientation, rotation);
+        if (oldRotation == rotation && oldAltOrientation == altOrientation) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int[] setNewDisplayOverrideConfiguration(Configuration overrideConfig, int displayId) {
+        if (!checkCallingPermission(MANAGE_APP_TOKENS, "setNewDisplayOverrideConfiguration()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        }
+
+        synchronized(mWindowMap) {
+            if (mWaitingForConfig) {
+                mWaitingForConfig = false;
+                mLastFinishedFreezeSource = "new-config";
+            }
+            return mRoot.setDisplayOverrideConfigurationIfNeeded(overrideConfig, displayId);
+        }
+    }
+
+    void setFocusTaskRegionLocked(AppWindowToken previousFocus) {
+        final Task focusedTask = mFocusedApp != null ? mFocusedApp.getTask() : null;
+        final Task previousTask = previousFocus != null ? previousFocus.getTask() : null;
+        final DisplayContent focusedDisplayContent =
+                focusedTask != null ? focusedTask.getDisplayContent() : null;
+        final DisplayContent previousDisplayContent =
+                previousTask != null ? previousTask.getDisplayContent() : null;
+        if (previousDisplayContent != null && previousDisplayContent != focusedDisplayContent) {
+            previousDisplayContent.setTouchExcludeRegion(null);
+        }
+        if (focusedDisplayContent != null) {
+            focusedDisplayContent.setTouchExcludeRegion(focusedTask);
+        }
+    }
+
+    @Override
+    public void setFocusedApp(IBinder token, boolean moveFocusNow) {
+        if (!checkCallingPermission(MANAGE_APP_TOKENS, "setFocusedApp()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        }
+
+        synchronized(mWindowMap) {
+            final AppWindowToken newFocus;
+            if (token == null) {
+                if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "Clearing focused app, was " + mFocusedApp);
+                newFocus = null;
+            } else {
+                newFocus = mRoot.getAppWindowToken(token);
+                if (newFocus == null) {
+                    Slog.w(TAG_WM, "Attempted to set focus to non-existing app token: " + token);
+                }
+                if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "Set focused app to: " + newFocus
+                        + " old focus=" + mFocusedApp + " moveFocusNow=" + moveFocusNow);
+            }
+
+            final boolean changed = mFocusedApp != newFocus;
+            if (changed) {
+                AppWindowToken prev = mFocusedApp;
+                mFocusedApp = newFocus;
+                mInputMonitor.setFocusedAppLw(newFocus);
+                setFocusTaskRegionLocked(prev);
+            }
+
+            if (moveFocusNow && changed) {
+                final long origId = Binder.clearCallingIdentity();
+                updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
+                Binder.restoreCallingIdentity(origId);
+            }
+        }
+    }
+
+    @Override
+    public void prepareAppTransition(int transit, boolean alwaysKeepCurrent) {
+        prepareAppTransition(transit, alwaysKeepCurrent, 0 /* flags */, false /* forceOverride */);
+    }
+
+    /**
+     * @param transit What kind of transition is happening. Use one of the constants
+     *                AppTransition.TRANSIT_*.
+     * @param alwaysKeepCurrent If true and a transition is already set, new transition will NOT
+     *                          be set.
+     * @param flags Additional flags for the app transition, Use a combination of the constants
+     *              AppTransition.TRANSIT_FLAG_*.
+     * @param forceOverride Always override the transit, not matter what was set previously.
+     */
+    public void prepareAppTransition(int transit, boolean alwaysKeepCurrent, int flags,
+            boolean forceOverride) {
+        if (!checkCallingPermission(MANAGE_APP_TOKENS, "prepareAppTransition()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        }
+        synchronized(mWindowMap) {
+            boolean prepared = mAppTransition.prepareAppTransitionLocked(transit, alwaysKeepCurrent,
+                    flags, forceOverride);
+            // TODO (multidisplay): associate app transitions with displays
+            final DisplayContent dc = mRoot.getDisplayContent(DEFAULT_DISPLAY);
+            if (prepared && dc != null && dc.okToAnimate()) {
+                mSkipAppTransitionAnimation = false;
+            }
+        }
+    }
+
+    @Override
+    public int getPendingAppTransition() {
+        return mAppTransition.getAppTransition();
+    }
+
+    @Override
+    public void overridePendingAppTransition(String packageName,
+            int enterAnim, int exitAnim, IRemoteCallback startedCallback) {
+        synchronized(mWindowMap) {
+            mAppTransition.overridePendingAppTransition(packageName, enterAnim, exitAnim,
+                    startedCallback);
+        }
+    }
+
+    @Override
+    public void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth,
+            int startHeight) {
+        synchronized(mWindowMap) {
+            mAppTransition.overridePendingAppTransitionScaleUp(startX, startY, startWidth,
+                    startHeight);
+        }
+    }
+
+    @Override
+    public void overridePendingAppTransitionClipReveal(int startX, int startY,
+            int startWidth, int startHeight) {
+        synchronized(mWindowMap) {
+            mAppTransition.overridePendingAppTransitionClipReveal(startX, startY, startWidth,
+                    startHeight);
+        }
+    }
+
+    @Override
+    public void overridePendingAppTransitionThumb(GraphicBuffer srcThumb, int startX,
+            int startY, IRemoteCallback startedCallback, boolean scaleUp) {
+        synchronized(mWindowMap) {
+            mAppTransition.overridePendingAppTransitionThumb(srcThumb, startX, startY,
+                    startedCallback, scaleUp);
+        }
+    }
+
+    @Override
+    public void overridePendingAppTransitionAspectScaledThumb(GraphicBuffer srcThumb, int startX,
+            int startY, int targetWidth, int targetHeight, IRemoteCallback startedCallback,
+            boolean scaleUp) {
+        synchronized(mWindowMap) {
+            mAppTransition.overridePendingAppTransitionAspectScaledThumb(srcThumb, startX, startY,
+                    targetWidth, targetHeight, startedCallback, scaleUp);
+        }
+    }
+
+    @Override
+    public void overridePendingAppTransitionMultiThumb(AppTransitionAnimationSpec[] specs,
+            IRemoteCallback onAnimationStartedCallback, IRemoteCallback onAnimationFinishedCallback,
+            boolean scaleUp) {
+        synchronized (mWindowMap) {
+            mAppTransition.overridePendingAppTransitionMultiThumb(specs, onAnimationStartedCallback,
+                    onAnimationFinishedCallback, scaleUp);
+            prolongAnimationsFromSpecs(specs, scaleUp);
+
+        }
+    }
+
+    void prolongAnimationsFromSpecs(@NonNull AppTransitionAnimationSpec[] specs, boolean scaleUp) {
+        // This is used by freeform <-> recents windows transition. We need to synchronize
+        // the animation with the appearance of the content of recents, so we will make
+        // animation stay on the first or last frame a little longer.
+        mTmpTaskIds.clear();
+        for (int i = specs.length - 1; i >= 0; i--) {
+            mTmpTaskIds.put(specs[i].taskId, 0);
+        }
+        for (final WindowState win : mWindowMap.values()) {
+            final Task task = win.getTask();
+            if (task != null && mTmpTaskIds.get(task.mTaskId, -1) != -1
+                    && task.inFreeformWorkspace()) {
+                final AppWindowToken appToken = win.mAppToken;
+                if (appToken != null && appToken.mAppAnimator != null) {
+                    appToken.mAppAnimator.startProlongAnimation(scaleUp ?
+                            PROLONG_ANIMATION_AT_START : PROLONG_ANIMATION_AT_END);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void overridePendingAppTransitionInPlace(String packageName, int anim) {
+        synchronized(mWindowMap) {
+            mAppTransition.overrideInPlaceAppTransition(packageName, anim);
+        }
+    }
+
+    @Override
+    public void overridePendingAppTransitionMultiThumbFuture(
+            IAppTransitionAnimationSpecsFuture specsFuture, IRemoteCallback callback,
+            boolean scaleUp) {
+        synchronized(mWindowMap) {
+            mAppTransition.overridePendingAppTransitionMultiThumbFuture(specsFuture, callback,
+                    scaleUp);
+        }
+    }
+
+    @Override
+    public void endProlongedAnimations() {
+        synchronized (mWindowMap) {
+            for (final WindowState win : mWindowMap.values()) {
+                final AppWindowToken appToken = win.mAppToken;
+                if (appToken != null && appToken.mAppAnimator != null) {
+                    appToken.mAppAnimator.endProlongedAnimation();
+                }
+            }
+            mAppTransition.notifyProlongedAnimationsEnded();
+        }
+    }
+
+    @Override
+    public void executeAppTransition() {
+        if (!checkCallingPermission(MANAGE_APP_TOKENS, "executeAppTransition()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        }
+
+        synchronized(mWindowMap) {
+            if (DEBUG_APP_TRANSITIONS) Slog.w(TAG_WM, "Execute app transition: " + mAppTransition
+                    + " Callers=" + Debug.getCallers(5));
+            if (mAppTransition.isTransitionSet()) {
+                mAppTransition.setReady();
+                final long origId = Binder.clearCallingIdentity();
+                try {
+                    mWindowPlacerLocked.performSurfacePlacement();
+                } finally {
+                    Binder.restoreCallingIdentity(origId);
+                }
+            }
+        }
+    }
+
+    public void setAppFullscreen(IBinder token, boolean toOpaque) {
+        synchronized (mWindowMap) {
+            final AppWindowToken atoken = mRoot.getAppWindowToken(token);
+            if (atoken != null) {
+                atoken.setFillsParent(toOpaque);
+                setWindowOpaqueLocked(token, toOpaque);
+                mWindowPlacerLocked.requestTraversal();
+            }
+        }
+    }
+
+    public void setWindowOpaque(IBinder token, boolean isOpaque) {
+        synchronized (mWindowMap) {
+            setWindowOpaqueLocked(token, isOpaque);
+        }
+    }
+
+    private void setWindowOpaqueLocked(IBinder token, boolean isOpaque) {
+        final AppWindowToken wtoken = mRoot.getAppWindowToken(token);
+        if (wtoken != null) {
+            final WindowState win = wtoken.findMainWindow();
+            if (win != null) {
+                win.mWinAnimator.setOpaqueLocked(isOpaque);
+            }
+        }
+    }
+
+    void updateTokenInPlaceLocked(AppWindowToken wtoken, int transit) {
+        if (transit != TRANSIT_UNSET) {
+            if (wtoken.mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) {
+                wtoken.mAppAnimator.setNullAnimation();
+            }
+            applyAnimationLocked(wtoken, null, transit, false, false);
+        }
+    }
+
+    public void setDockedStackCreateState(int mode, Rect bounds) {
+        synchronized (mWindowMap) {
+            setDockedStackCreateStateLocked(mode, bounds);
+        }
+    }
+
+    void setDockedStackCreateStateLocked(int mode, Rect bounds) {
+        mDockedStackCreateMode = mode;
+        mDockedStackCreateBounds = bounds;
+    }
+
+    public boolean isValidPictureInPictureAspectRatio(int displayId, float aspectRatio) {
+        final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+        return displayContent.getPinnedStackController().isValidPictureInPictureAspectRatio(
+                aspectRatio);
+    }
+
+    @Override
+    public void getStackBounds(int stackId, Rect bounds) {
+        synchronized (mWindowMap) {
+            final TaskStack stack = mRoot.getStackById(stackId);
+            if (stack != null) {
+                stack.getBounds(bounds);
+                return;
+            }
+            bounds.setEmpty();
+        }
+    }
+
+    @Override
+    public void notifyShowingDreamChanged() {
+        notifyKeyguardFlagsChanged(null /* callback */);
+    }
+
+    @Override
+    public WindowManagerPolicy.WindowState getInputMethodWindowLw() {
+        return mInputMethodWindow;
+    }
+
+    @Override
+    public void notifyKeyguardTrustedChanged() {
+        mH.sendEmptyMessage(H.NOTIFY_KEYGUARD_TRUSTED_CHANGED);
+    }
+
+    @Override
+    public void screenTurningOff(ScreenOffListener listener) {
+        mTaskSnapshotController.screenTurningOff(listener);
+    }
+
+    /**
+     * Starts deferring layout passes. Useful when doing multiple changes but to optimize
+     * performance, only one layout pass should be done. This can be called multiple times, and
+     * layouting will be resumed once the last caller has called {@link #continueSurfaceLayout}
+     */
+    public void deferSurfaceLayout() {
+        synchronized (mWindowMap) {
+            mWindowPlacerLocked.deferLayout();
+        }
+    }
+
+    /**
+     * Resumes layout passes after deferring them. See {@link #deferSurfaceLayout()}
+     */
+    public void continueSurfaceLayout() {
+        synchronized (mWindowMap) {
+            mWindowPlacerLocked.continueLayout();
+        }
+    }
+
+    /**
+     * @return true if the activity contains windows that have
+     *         {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} set
+     */
+    public boolean containsShowWhenLockedWindow(IBinder token) {
+        synchronized (mWindowMap) {
+            final AppWindowToken wtoken = mRoot.getAppWindowToken(token);
+            return wtoken != null && wtoken.containsShowWhenLockedWindow();
+        }
+    }
+
+    /**
+     * @return true if the activity contains windows that have
+     *         {@link LayoutParams#FLAG_DISMISS_KEYGUARD} set
+     */
+    public boolean containsDismissKeyguardWindow(IBinder token) {
+        synchronized (mWindowMap) {
+            final AppWindowToken wtoken = mRoot.getAppWindowToken(token);
+            return wtoken != null && wtoken.containsDismissKeyguardWindow();
+        }
+    }
+
+    /**
+     * Notifies activity manager that some Keyguard flags have changed and that it needs to
+     * reevaluate the visibilities of the activities.
+     * @param callback Runnable to be called when activity manager is done reevaluating visibilities
+     */
+    void notifyKeyguardFlagsChanged(@Nullable Runnable callback) {
+        final Runnable wrappedCallback = callback != null
+                ? () -> { synchronized (mWindowMap) { callback.run(); } }
+                : null;
+        mH.obtainMessage(H.NOTIFY_KEYGUARD_FLAGS_CHANGED, wrappedCallback).sendToTarget();
+    }
+
+    public boolean isKeyguardTrusted() {
+        synchronized (mWindowMap) {
+            return mPolicy.isKeyguardTrustedLw();
+        }
+    }
+
+    public void setKeyguardGoingAway(boolean keyguardGoingAway) {
+// TODO: Use of this can be removed. Revert ag/I8369723d6a77f2c602f1ef080371fa7cd9ee094e
+//        synchronized (mWindowMap) {
+//            mKeyguardGoingAway = keyguardGoingAway;
+//        }
+    }
+
+    // -------------------------------------------------------------
+    // Misc IWindowSession methods
+    // -------------------------------------------------------------
+
+    @Override
+    public void startFreezingScreen(int exitAnim, int enterAnim) {
+        if (!checkCallingPermission(android.Manifest.permission.FREEZE_SCREEN,
+                "startFreezingScreen()")) {
+            throw new SecurityException("Requires FREEZE_SCREEN permission");
+        }
+
+        synchronized(mWindowMap) {
+            if (!mClientFreezingScreen) {
+                mClientFreezingScreen = true;
+                final long origId = Binder.clearCallingIdentity();
+                try {
+                    startFreezingDisplayLocked(false, exitAnim, enterAnim);
+                    mH.removeMessages(H.CLIENT_FREEZE_TIMEOUT);
+                    mH.sendEmptyMessageDelayed(H.CLIENT_FREEZE_TIMEOUT, 5000);
+                } finally {
+                    Binder.restoreCallingIdentity(origId);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void stopFreezingScreen() {
+        if (!checkCallingPermission(android.Manifest.permission.FREEZE_SCREEN,
+                "stopFreezingScreen()")) {
+            throw new SecurityException("Requires FREEZE_SCREEN permission");
+        }
+
+        synchronized(mWindowMap) {
+            if (mClientFreezingScreen) {
+                mClientFreezingScreen = false;
+                mLastFinishedFreezeSource = "client";
+                final long origId = Binder.clearCallingIdentity();
+                try {
+                    stopFreezingDisplayLocked();
+                } finally {
+                    Binder.restoreCallingIdentity(origId);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void disableKeyguard(IBinder token, String tag) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD)
+            != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires DISABLE_KEYGUARD permission");
+        }
+        // If this isn't coming from the system then don't allow disabling the lockscreen
+        // to bypass security.
+        if (Binder.getCallingUid() != SYSTEM_UID && isKeyguardSecure()) {
+            Log.d(TAG_WM, "current mode is SecurityMode, ignore disableKeyguard");
+            return;
+        }
+
+        // If this isn't coming from the current profiles, ignore it.
+        if (!isCurrentProfileLocked(UserHandle.getCallingUserId())) {
+            Log.d(TAG_WM, "non-current profiles, ignore disableKeyguard");
+            return;
+        }
+
+        if (token == null) {
+            throw new IllegalArgumentException("token == null");
+        }
+
+        mKeyguardDisableHandler.sendMessage(mKeyguardDisableHandler.obtainMessage(
+                KeyguardDisableHandler.KEYGUARD_DISABLE, new Pair<IBinder, String>(token, tag)));
+    }
+
+    @Override
+    public void reenableKeyguard(IBinder token) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD)
+            != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires DISABLE_KEYGUARD permission");
+        }
+
+        if (token == null) {
+            throw new IllegalArgumentException("token == null");
+        }
+
+        mKeyguardDisableHandler.sendMessage(mKeyguardDisableHandler.obtainMessage(
+                KeyguardDisableHandler.KEYGUARD_REENABLE, token));
+    }
+
+    /**
+     * @see android.app.KeyguardManager#exitKeyguardSecurely
+     */
+    @Override
+    public void exitKeyguardSecurely(final IOnKeyguardExitResult callback) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD)
+            != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires DISABLE_KEYGUARD permission");
+        }
+
+        if (callback == null) {
+            throw new IllegalArgumentException("callback == null");
+        }
+
+        mPolicy.exitKeyguardSecurely(new WindowManagerPolicy.OnKeyguardExitResult() {
+            @Override
+            public void onKeyguardExitResult(boolean success) {
+                try {
+                    callback.onKeyguardExitResult(success);
+                } catch (RemoteException e) {
+                    // Client has died, we don't care.
+                }
+            }
+        });
+    }
+
+    @Override
+    public boolean inKeyguardRestrictedInputMode() {
+        return mPolicy.inKeyguardRestrictedKeyInputMode();
+    }
+
+    @Override
+    public boolean isKeyguardLocked() {
+        return mPolicy.isKeyguardLocked();
+    }
+
+    public boolean isKeyguardShowingAndNotOccluded() {
+        return mPolicy.isKeyguardShowingAndNotOccluded();
+    }
+
+    @Override
+    public boolean isKeyguardSecure() {
+        int userId = UserHandle.getCallingUserId();
+        long origId = Binder.clearCallingIdentity();
+        try {
+            return mPolicy.isKeyguardSecure(userId);
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    public boolean isShowingDream() {
+        synchronized (mWindowMap) {
+            return mPolicy.isShowingDreamLw();
+        }
+    }
+
+    @Override
+    public void dismissKeyguard(IKeyguardDismissCallback callback) {
+        checkCallingPermission(permission.CONTROL_KEYGUARD, "dismissKeyguard");
+        synchronized(mWindowMap) {
+            mPolicy.dismissKeyguardLw(callback);
+        }
+    }
+
+    public void onKeyguardOccludedChanged(boolean occluded) {
+        synchronized (mWindowMap) {
+            mPolicy.onKeyguardOccludedChangedLw(occluded);
+        }
+    }
+
+    @Override
+    public void setSwitchingUser(boolean switching) {
+        if (!checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                "setSwitchingUser()")) {
+            throw new SecurityException("Requires INTERACT_ACROSS_USERS_FULL permission");
+        }
+        mPolicy.setSwitchingUser(switching);
+        synchronized (mWindowMap) {
+            mSwitchingUser = switching;
+        }
+    }
+
+    void showGlobalActions() {
+        mPolicy.showGlobalActions();
+    }
+
+    @Override
+    public void closeSystemDialogs(String reason) {
+        synchronized(mWindowMap) {
+            mRoot.closeSystemDialogs(reason);
+        }
+    }
+
+    static float fixScale(float scale) {
+        if (scale < 0) scale = 0;
+        else if (scale > 20) scale = 20;
+        return Math.abs(scale);
+    }
+
+    @Override
+    public void setAnimationScale(int which, float scale) {
+        if (!checkCallingPermission(android.Manifest.permission.SET_ANIMATION_SCALE,
+                "setAnimationScale()")) {
+            throw new SecurityException("Requires SET_ANIMATION_SCALE permission");
+        }
+
+        scale = fixScale(scale);
+        switch (which) {
+            case 0: mWindowAnimationScaleSetting = scale; break;
+            case 1: mTransitionAnimationScaleSetting = scale; break;
+            case 2: mAnimatorDurationScaleSetting = scale; break;
+        }
+
+        // Persist setting
+        mH.sendEmptyMessage(H.PERSIST_ANIMATION_SCALE);
+    }
+
+    @Override
+    public void setAnimationScales(float[] scales) {
+        if (!checkCallingPermission(android.Manifest.permission.SET_ANIMATION_SCALE,
+                "setAnimationScale()")) {
+            throw new SecurityException("Requires SET_ANIMATION_SCALE permission");
+        }
+
+        if (scales != null) {
+            if (scales.length >= 1) {
+                mWindowAnimationScaleSetting = fixScale(scales[0]);
+            }
+            if (scales.length >= 2) {
+                mTransitionAnimationScaleSetting = fixScale(scales[1]);
+            }
+            if (scales.length >= 3) {
+                mAnimatorDurationScaleSetting = fixScale(scales[2]);
+                dispatchNewAnimatorScaleLocked(null);
+            }
+        }
+
+        // Persist setting
+        mH.sendEmptyMessage(H.PERSIST_ANIMATION_SCALE);
+    }
+
+    private void setAnimatorDurationScale(float scale) {
+        mAnimatorDurationScaleSetting = scale;
+        ValueAnimator.setDurationScale(scale);
+    }
+
+    public float getWindowAnimationScaleLocked() {
+        return mAnimationsDisabled ? 0 : mWindowAnimationScaleSetting;
+    }
+
+    public float getTransitionAnimationScaleLocked() {
+        return mAnimationsDisabled ? 0 : mTransitionAnimationScaleSetting;
+    }
+
+    @Override
+    public float getAnimationScale(int which) {
+        switch (which) {
+            case 0: return mWindowAnimationScaleSetting;
+            case 1: return mTransitionAnimationScaleSetting;
+            case 2: return mAnimatorDurationScaleSetting;
+        }
+        return 0;
+    }
+
+    @Override
+    public float[] getAnimationScales() {
+        return new float[] { mWindowAnimationScaleSetting, mTransitionAnimationScaleSetting,
+                mAnimatorDurationScaleSetting };
+    }
+
+    @Override
+    public float getCurrentAnimatorScale() {
+        synchronized(mWindowMap) {
+            return mAnimationsDisabled ? 0 : mAnimatorDurationScaleSetting;
+        }
+    }
+
+    void dispatchNewAnimatorScaleLocked(Session session) {
+        mH.obtainMessage(H.NEW_ANIMATOR_SCALE, session).sendToTarget();
+    }
+
+    @Override
+    public void registerPointerEventListener(PointerEventListener listener) {
+        mPointerEventDispatcher.registerInputEventListener(listener);
+    }
+
+    @Override
+    public void unregisterPointerEventListener(PointerEventListener listener) {
+        mPointerEventDispatcher.unregisterInputEventListener(listener);
+    }
+
+    /** Check if the service is set to dispatch pointer events. */
+    boolean canDispatchPointerEvents() {
+        return mPointerEventDispatcher != null;
+    }
+
+    // Called by window manager policy. Not exposed externally.
+    @Override
+    public int getLidState() {
+        int sw = mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY,
+                InputManagerService.SW_LID);
+        if (sw > 0) {
+            // Switch state: AKEY_STATE_DOWN or AKEY_STATE_VIRTUAL.
+            return LID_CLOSED;
+        } else if (sw == 0) {
+            // Switch state: AKEY_STATE_UP.
+            return LID_OPEN;
+        } else {
+            // Switch state: AKEY_STATE_UNKNOWN.
+            return LID_ABSENT;
+        }
+    }
+
+    // Called by window manager policy. Not exposed externally.
+    @Override
+    public void lockDeviceNow() {
+        lockNow(null);
+    }
+
+    // Called by window manager policy. Not exposed externally.
+    @Override
+    public int getCameraLensCoverState() {
+        int sw = mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY,
+                InputManagerService.SW_CAMERA_LENS_COVER);
+        if (sw > 0) {
+            // Switch state: AKEY_STATE_DOWN or AKEY_STATE_VIRTUAL.
+            return CAMERA_LENS_COVERED;
+        } else if (sw == 0) {
+            // Switch state: AKEY_STATE_UP.
+            return CAMERA_LENS_UNCOVERED;
+        } else {
+            // Switch state: AKEY_STATE_UNKNOWN.
+            return CAMERA_LENS_COVER_ABSENT;
+        }
+    }
+
+    // Called by window manager policy.  Not exposed externally.
+    @Override
+    public void switchInputMethod(boolean forwardDirection) {
+        final InputMethodManagerInternal inputMethodManagerInternal =
+                LocalServices.getService(InputMethodManagerInternal.class);
+        if (inputMethodManagerInternal != null) {
+            inputMethodManagerInternal.switchInputMethod(forwardDirection);
+        }
+    }
+
+    // Called by window manager policy.  Not exposed externally.
+    @Override
+    public void shutdown(boolean confirm) {
+        // Pass in the UI context, since ShutdownThread requires it (to show UI).
+        ShutdownThread.shutdown(ActivityThread.currentActivityThread().getSystemUiContext(),
+                PowerManager.SHUTDOWN_USER_REQUESTED, confirm);
+    }
+
+    // Called by window manager policy.  Not exposed externally.
+    @Override
+    public void reboot(boolean confirm) {
+        // Pass in the UI context, since ShutdownThread requires it (to show UI).
+        ShutdownThread.reboot(ActivityThread.currentActivityThread().getSystemUiContext(),
+                PowerManager.SHUTDOWN_USER_REQUESTED, confirm);
+    }
+
+    // Called by window manager policy.  Not exposed externally.
+    @Override
+    public void rebootSafeMode(boolean confirm) {
+        // Pass in the UI context, since ShutdownThread requires it (to show UI).
+        ShutdownThread.rebootSafeMode(ActivityThread.currentActivityThread().getSystemUiContext(),
+                confirm);
+    }
+
+    public void setCurrentProfileIds(final int[] currentProfileIds) {
+        synchronized (mWindowMap) {
+            mCurrentProfileIds = currentProfileIds;
+        }
+    }
+
+    public void setCurrentUser(final int newUserId, final int[] currentProfileIds) {
+        synchronized (mWindowMap) {
+            mCurrentUserId = newUserId;
+            mCurrentProfileIds = currentProfileIds;
+            mAppTransition.setCurrentUser(newUserId);
+            mPolicy.setCurrentUserLw(newUserId);
+
+            // If keyguard was disabled, re-enable it
+            // TODO: Keep track of keyguardEnabled state per user and use here...
+            // e.g. enabled = mKeyguardDisableHandler.getEnabledStateForUser(newUserId);
+            mPolicy.enableKeyguard(true);
+
+            // Hide windows that should not be seen by the new user.
+            mRoot.switchUser();
+            mWindowPlacerLocked.performSurfacePlacement();
+
+            // Notify whether the docked stack exists for the current user
+            final DisplayContent displayContent = getDefaultDisplayContentLocked();
+            final TaskStack stack = displayContent.getDockedStackIgnoringVisibility();
+            displayContent.mDividerControllerLocked.notifyDockedStackExistsChanged(
+                    stack != null && stack.hasTaskForUser(newUserId));
+
+            // If the display is already prepared, update the density.
+            // Otherwise, we'll update it when it's prepared.
+            if (mDisplayReady) {
+                final int forcedDensity = getForcedDisplayDensityForUserLocked(newUserId);
+                final int targetDensity = forcedDensity != 0 ? forcedDensity
+                        : displayContent.mInitialDisplayDensity;
+                setForcedDisplayDensityLocked(displayContent, targetDensity);
+            }
+        }
+    }
+
+    /* Called by WindowState */
+    boolean isCurrentProfileLocked(int userId) {
+        if (userId == mCurrentUserId) return true;
+        for (int i = 0; i < mCurrentProfileIds.length; i++) {
+            if (mCurrentProfileIds[i] == userId) return true;
+        }
+        return false;
+    }
+
+    public void enableScreenAfterBoot() {
+        synchronized(mWindowMap) {
+            if (DEBUG_BOOT) {
+                RuntimeException here = new RuntimeException("here");
+                here.fillInStackTrace();
+                Slog.i(TAG_WM, "enableScreenAfterBoot: mDisplayEnabled=" + mDisplayEnabled
+                        + " mForceDisplayEnabled=" + mForceDisplayEnabled
+                        + " mShowingBootMessages=" + mShowingBootMessages
+                        + " mSystemBooted=" + mSystemBooted, here);
+            }
+            if (mSystemBooted) {
+                return;
+            }
+            mSystemBooted = true;
+            hideBootMessagesLocked();
+            // If the screen still doesn't come up after 30 seconds, give
+            // up and turn it on.
+            mH.sendEmptyMessageDelayed(H.BOOT_TIMEOUT, 30 * 1000);
+        }
+
+        mPolicy.systemBooted();
+
+        performEnableScreen();
+    }
+
+    @Override
+    public void enableScreenIfNeeded() {
+        synchronized (mWindowMap) {
+            enableScreenIfNeededLocked();
+        }
+    }
+
+    void enableScreenIfNeededLocked() {
+        if (DEBUG_BOOT) {
+            RuntimeException here = new RuntimeException("here");
+            here.fillInStackTrace();
+            Slog.i(TAG_WM, "enableScreenIfNeededLocked: mDisplayEnabled=" + mDisplayEnabled
+                    + " mForceDisplayEnabled=" + mForceDisplayEnabled
+                    + " mShowingBootMessages=" + mShowingBootMessages
+                    + " mSystemBooted=" + mSystemBooted, here);
+        }
+        if (mDisplayEnabled) {
+            return;
+        }
+        if (!mSystemBooted && !mShowingBootMessages) {
+            return;
+        }
+        mH.sendEmptyMessage(H.ENABLE_SCREEN);
+    }
+
+    public void performBootTimeout() {
+        synchronized(mWindowMap) {
+            if (mDisplayEnabled) {
+                return;
+            }
+            Slog.w(TAG_WM, "***** BOOT TIMEOUT: forcing display enabled");
+            mForceDisplayEnabled = true;
+        }
+        performEnableScreen();
+    }
+
+    /**
+     * Called when System UI has been started.
+     */
+    public void onSystemUiStarted() {
+        mPolicy.onSystemUiStarted();
+    }
+
+    private void performEnableScreen() {
+        synchronized(mWindowMap) {
+            if (DEBUG_BOOT) Slog.i(TAG_WM, "performEnableScreen: mDisplayEnabled=" + mDisplayEnabled
+                    + " mForceDisplayEnabled=" + mForceDisplayEnabled
+                    + " mShowingBootMessages=" + mShowingBootMessages
+                    + " mSystemBooted=" + mSystemBooted
+                    + " mOnlyCore=" + mOnlyCore,
+                    new RuntimeException("here").fillInStackTrace());
+            if (mDisplayEnabled) {
+                return;
+            }
+            if (!mSystemBooted && !mShowingBootMessages) {
+                return;
+            }
+
+            if (!mShowingBootMessages && !mPolicy.canDismissBootAnimation()) {
+                return;
+            }
+
+            // Don't enable the screen until all existing windows have been drawn.
+            if (!mForceDisplayEnabled
+                    // TODO(multidisplay): Expand to all displays?
+                    && getDefaultDisplayContentLocked().checkWaitingForWindows()) {
+                return;
+            }
+
+            if (!mBootAnimationStopped) {
+                Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "Stop bootanim", 0);
+                // stop boot animation
+                // formerly we would just kill the process, but we now ask it to exit so it
+                // can choose where to stop the animation.
+                SystemProperties.set("service.bootanim.exit", "1");
+                mBootAnimationStopped = true;
+            }
+
+            if (!mForceDisplayEnabled && !checkBootAnimationCompleteLocked()) {
+                if (DEBUG_BOOT) Slog.i(TAG_WM, "performEnableScreen: Waiting for anim complete");
+                return;
+            }
+
+            try {
+                IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger");
+                if (surfaceFlinger != null) {
+                    Slog.i(TAG_WM, "******* TELLING SURFACE FLINGER WE ARE BOOTED!");
+                    Parcel data = Parcel.obtain();
+                    data.writeInterfaceToken("android.ui.ISurfaceComposer");
+                    surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, // BOOT_FINISHED
+                            data, null, 0);
+                    data.recycle();
+                }
+            } catch (RemoteException ex) {
+                Slog.e(TAG_WM, "Boot completed: SurfaceFlinger is dead!");
+            }
+
+            EventLog.writeEvent(EventLogTags.WM_BOOT_ANIMATION_DONE, SystemClock.uptimeMillis());
+            Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "Stop bootanim", 0);
+            mDisplayEnabled = true;
+            if (DEBUG_SCREEN_ON || DEBUG_BOOT) Slog.i(TAG_WM, "******************** ENABLING SCREEN!");
+
+            // Enable input dispatch.
+            mInputMonitor.setEventDispatchingLw(mEventDispatchingEnabled);
+        }
+
+        try {
+            mActivityManager.bootAnimationComplete();
+        } catch (RemoteException e) {
+        }
+
+        mPolicy.enableScreenAfterBoot();
+
+        // Make sure the last requested orientation has been applied.
+        updateRotationUnchecked(false, false);
+    }
+
+    private boolean checkBootAnimationCompleteLocked() {
+        if (SystemService.isRunning(BOOT_ANIMATION_SERVICE)) {
+            mH.removeMessages(H.CHECK_IF_BOOT_ANIMATION_FINISHED);
+            mH.sendEmptyMessageDelayed(H.CHECK_IF_BOOT_ANIMATION_FINISHED,
+                    BOOT_ANIMATION_POLL_INTERVAL);
+            if (DEBUG_BOOT) Slog.i(TAG_WM, "checkBootAnimationComplete: Waiting for anim complete");
+            return false;
+        }
+        if (DEBUG_BOOT) Slog.i(TAG_WM, "checkBootAnimationComplete: Animation complete!");
+        return true;
+    }
+
+    public void showBootMessage(final CharSequence msg, final boolean always) {
+        boolean first = false;
+        synchronized(mWindowMap) {
+            if (DEBUG_BOOT) {
+                RuntimeException here = new RuntimeException("here");
+                here.fillInStackTrace();
+                Slog.i(TAG_WM, "showBootMessage: msg=" + msg + " always=" + always
+                        + " mAllowBootMessages=" + mAllowBootMessages
+                        + " mShowingBootMessages=" + mShowingBootMessages
+                        + " mSystemBooted=" + mSystemBooted, here);
+            }
+            if (!mAllowBootMessages) {
+                return;
+            }
+            if (!mShowingBootMessages) {
+                if (!always) {
+                    return;
+                }
+                first = true;
+            }
+            if (mSystemBooted) {
+                return;
+            }
+            mShowingBootMessages = true;
+            mPolicy.showBootMessage(msg, always);
+        }
+        if (first) {
+            performEnableScreen();
+        }
+    }
+
+    public void hideBootMessagesLocked() {
+        if (DEBUG_BOOT) {
+            RuntimeException here = new RuntimeException("here");
+            here.fillInStackTrace();
+            Slog.i(TAG_WM, "hideBootMessagesLocked: mDisplayEnabled=" + mDisplayEnabled
+                    + " mForceDisplayEnabled=" + mForceDisplayEnabled
+                    + " mShowingBootMessages=" + mShowingBootMessages
+                    + " mSystemBooted=" + mSystemBooted, here);
+        }
+        if (mShowingBootMessages) {
+            mShowingBootMessages = false;
+            mPolicy.hideBootMessages();
+        }
+    }
+
+    @Override
+    public void setInTouchMode(boolean mode) {
+        synchronized(mWindowMap) {
+            mInTouchMode = mode;
+        }
+    }
+
+    private void updateCircularDisplayMaskIfNeeded() {
+        if (mContext.getResources().getConfiguration().isScreenRound()
+                && mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_windowShowCircularMask)) {
+            final int currentUserId;
+            synchronized(mWindowMap) {
+                currentUserId = mCurrentUserId;
+            }
+            // Device configuration calls for a circular display mask, but we only enable the mask
+            // if the accessibility color inversion feature is disabled, as the inverted mask
+            // causes artifacts.
+            int inversionState = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                    Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0, currentUserId);
+            int showMask = (inversionState == 1) ? 0 : 1;
+            Message m = mH.obtainMessage(H.SHOW_CIRCULAR_DISPLAY_MASK);
+            m.arg1 = showMask;
+            mH.sendMessage(m);
+        }
+    }
+
+    public void showEmulatorDisplayOverlayIfNeeded() {
+        if (mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_windowEnableCircularEmulatorDisplayOverlay)
+                && SystemProperties.getBoolean(PROPERTY_EMULATOR_CIRCULAR, false)
+                && Build.IS_EMULATOR) {
+            mH.sendMessage(mH.obtainMessage(H.SHOW_EMULATOR_DISPLAY_OVERLAY));
+        }
+    }
+
+    public void showCircularMask(boolean visible) {
+        synchronized(mWindowMap) {
+
+            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
+                    ">>> OPEN TRANSACTION showCircularMask(visible=" + visible + ")");
+            openSurfaceTransaction();
+            try {
+                if (visible) {
+                    // TODO(multi-display): support multiple displays
+                    if (mCircularDisplayMask == null) {
+                        int screenOffset = mContext.getResources().getInteger(
+                                com.android.internal.R.integer.config_windowOutsetBottom);
+                        int maskThickness = mContext.getResources().getDimensionPixelSize(
+                                com.android.internal.R.dimen.circular_display_mask_thickness);
+
+                        mCircularDisplayMask = new CircularDisplayMask(
+                                getDefaultDisplayContentLocked().getDisplay(),
+                                mFxSession,
+                                mPolicy.getWindowLayerFromTypeLw(
+                                        WindowManager.LayoutParams.TYPE_POINTER)
+                                        * TYPE_LAYER_MULTIPLIER + 10, screenOffset, maskThickness);
+                    }
+                    mCircularDisplayMask.setVisibility(true);
+                } else if (mCircularDisplayMask != null) {
+                    mCircularDisplayMask.setVisibility(false);
+                    mCircularDisplayMask = null;
+                }
+            } finally {
+                closeSurfaceTransaction();
+                if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
+                        "<<< CLOSE TRANSACTION showCircularMask(visible=" + visible + ")");
+            }
+        }
+    }
+
+    public void showEmulatorDisplayOverlay() {
+        synchronized(mWindowMap) {
+
+            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
+                    ">>> OPEN TRANSACTION showEmulatorDisplayOverlay");
+            openSurfaceTransaction();
+            try {
+                if (mEmulatorDisplayOverlay == null) {
+                    mEmulatorDisplayOverlay = new EmulatorDisplayOverlay(
+                            mContext,
+                            getDefaultDisplayContentLocked().getDisplay(),
+                            mFxSession,
+                            mPolicy.getWindowLayerFromTypeLw(
+                                    WindowManager.LayoutParams.TYPE_POINTER)
+                                    * TYPE_LAYER_MULTIPLIER + 10);
+                }
+                mEmulatorDisplayOverlay.setVisibility(true);
+            } finally {
+                closeSurfaceTransaction();
+                if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
+                        "<<< CLOSE TRANSACTION showEmulatorDisplayOverlay");
+            }
+        }
+    }
+
+    // TODO: more accounting of which pid(s) turned it on, keep count,
+    // only allow disables from pids which have count on, etc.
+    @Override
+    public void showStrictModeViolation(boolean on) {
+        final int pid = Binder.getCallingPid();
+        if (on) {
+            // Show the visualization, and enqueue a second message to tear it
+            // down if we don't hear back from the app.
+            mH.sendMessage(mH.obtainMessage(H.SHOW_STRICT_MODE_VIOLATION, 1, pid));
+            mH.sendMessageDelayed(mH.obtainMessage(H.SHOW_STRICT_MODE_VIOLATION, 0, pid),
+                    DateUtils.SECOND_IN_MILLIS);
+        } else {
+            mH.sendMessage(mH.obtainMessage(H.SHOW_STRICT_MODE_VIOLATION, 0, pid));
+        }
+    }
+
+    private void showStrictModeViolation(int arg, int pid) {
+        final boolean on = arg != 0;
+        synchronized(mWindowMap) {
+            // Ignoring requests to enable the red border from clients which aren't on screen.
+            // (e.g. Broadcast Receivers in the background..)
+            if (on && !mRoot.canShowStrictModeViolation(pid)) {
+                return;
+            }
+
+            if (SHOW_VERBOSE_TRANSACTIONS) Slog.i(TAG_WM,
+                    ">>> OPEN TRANSACTION showStrictModeViolation");
+            // TODO: Modify this to use the surface trace once it is not going crazy.
+            // b/31532461
+            SurfaceControl.openTransaction();
+            try {
+                // TODO(multi-display): support multiple displays
+                if (mStrictModeFlash == null) {
+                    mStrictModeFlash = new StrictModeFlash(
+                            getDefaultDisplayContentLocked().getDisplay(), mFxSession);
+                }
+                mStrictModeFlash.setVisibility(on);
+            } finally {
+                SurfaceControl.closeTransaction();
+                if (SHOW_VERBOSE_TRANSACTIONS) Slog.i(TAG_WM,
+                        "<<< CLOSE TRANSACTION showStrictModeViolation");
+            }
+        }
+    }
+
+    @Override
+    public void setStrictModeVisualIndicatorPreference(String value) {
+        SystemProperties.set(StrictMode.VISUAL_PROPERTY, value);
+    }
+
+    @Override
+    public Bitmap screenshotWallpaper() {
+        if (!checkCallingPermission(READ_FRAME_BUFFER,
+                "screenshotWallpaper()")) {
+            throw new SecurityException("Requires READ_FRAME_BUFFER permission");
+        }
+        try {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "screenshotWallpaper");
+            return screenshotApplications(null /* appToken */, DEFAULT_DISPLAY, -1 /* width */,
+                    -1 /* height */, true /* includeFullDisplay */, 1f /* frameScale */,
+                    Bitmap.Config.ARGB_8888, true /* wallpaperOnly */, false /* includeDecor */);
+        } finally {
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
+    /**
+     * Takes a snapshot of the screen.  In landscape mode this grabs the whole screen.
+     * In portrait mode, it grabs the upper region of the screen based on the vertical dimension
+     * of the target image.
+     */
+    @Override
+    public boolean requestAssistScreenshot(final IAssistScreenshotReceiver receiver) {
+        if (!checkCallingPermission(READ_FRAME_BUFFER,
+                "requestAssistScreenshot()")) {
+            throw new SecurityException("Requires READ_FRAME_BUFFER permission");
+        }
+
+        FgThread.getHandler().post(() -> {
+            Bitmap bm = screenshotApplications(null /* appToken */, DEFAULT_DISPLAY,
+                    -1 /* width */, -1 /* height */, true /* includeFullDisplay */,
+                    1f /* frameScale */, Bitmap.Config.ARGB_8888, false /* wallpaperOnly */,
+                    false /* includeDecor */);
+            try {
+                receiver.send(bm);
+            } catch (RemoteException e) {
+            }
+        });
+
+        return true;
+    }
+
+    public TaskSnapshot getTaskSnapshot(int taskId, int userId, boolean reducedResolution) {
+        return mTaskSnapshotController.getSnapshot(taskId, userId, true /* restoreFromDisk */,
+                reducedResolution);
+    }
+
+    /**
+     * In case a task write/delete operation was lost because the system crashed, this makes sure to
+     * clean up the directory to remove obsolete files.
+     *
+     * @param persistentTaskIds A set of task ids that exist in our in-memory model.
+     * @param runningUserIds The ids of the list of users that have tasks loaded in our in-memory
+     *                       model.
+     */
+    public void removeObsoleteTaskFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
+        synchronized (mWindowMap) {
+            mTaskSnapshotController.removeObsoleteTaskFiles(persistentTaskIds, runningUserIds);
+        }
+    }
+
+    /**
+     * Takes a snapshot of the screen.  In landscape mode this grabs the whole screen.
+     * In portrait mode, it grabs the full screenshot.
+     *
+     * @param displayId the Display to take a screenshot of.
+     * @param width the width of the target bitmap
+     * @param height the height of the target bitmap
+     * @param includeFullDisplay true if the screen should not be cropped before capture
+     * @param frameScale the scale to apply to the frame, only used when width = -1 and height = -1
+     * @param config of the output bitmap
+     * @param wallpaperOnly true if only the wallpaper layer should be included in the screenshot
+     * @param includeDecor whether to include window decors, like the status or navigation bar
+     *                     background of the window
+     */
+    private Bitmap screenshotApplications(IBinder appToken, int displayId, int width,
+            int height, boolean includeFullDisplay, float frameScale, Bitmap.Config config,
+            boolean wallpaperOnly, boolean includeDecor) {
+        final DisplayContent displayContent;
+        synchronized(mWindowMap) {
+            displayContent = mRoot.getDisplayContentOrCreate(displayId);
+            if (displayContent == null) {
+                if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot of " + appToken
+                        + ": returning null. No Display for displayId=" + displayId);
+                return null;
+            }
+        }
+        return displayContent.screenshotApplications(appToken, width, height,
+                includeFullDisplay, frameScale, config, wallpaperOnly, includeDecor);
+    }
+
+    /**
+     * Freeze rotation changes.  (Enable "rotation lock".)
+     * Persists across reboots.
+     * @param rotation The desired rotation to freeze to, or -1 to use the
+     * current rotation.
+     */
+    @Override
+    public void freezeRotation(int rotation) {
+        // TODO(multi-display): Track which display is rotated.
+        if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION,
+                "freezeRotation()")) {
+            throw new SecurityException("Requires SET_ORIENTATION permission");
+        }
+        if (rotation < -1 || rotation > Surface.ROTATION_270) {
+            throw new IllegalArgumentException("Rotation argument must be -1 or a valid "
+                    + "rotation constant.");
+        }
+
+        final int defaultDisplayRotation = getDefaultDisplayRotation();
+        if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "freezeRotation: mRotation="
+                + defaultDisplayRotation);
+
+        long origId = Binder.clearCallingIdentity();
+        try {
+            mPolicy.setUserRotationMode(WindowManagerPolicy.USER_ROTATION_LOCKED,
+                    rotation == -1 ? defaultDisplayRotation : rotation);
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+
+        updateRotationUnchecked(false, false);
+    }
+
+    /**
+     * Thaw rotation changes.  (Disable "rotation lock".)
+     * Persists across reboots.
+     */
+    @Override
+    public void thawRotation() {
+        if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION,
+                "thawRotation()")) {
+            throw new SecurityException("Requires SET_ORIENTATION permission");
+        }
+
+        if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "thawRotation: mRotation="
+                + getDefaultDisplayRotation());
+
+        long origId = Binder.clearCallingIdentity();
+        try {
+            mPolicy.setUserRotationMode(WindowManagerPolicy.USER_ROTATION_FREE,
+                    777); // rot not used
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+
+        updateRotationUnchecked(false, false);
+    }
+
+    /**
+     * Recalculate the current rotation.
+     *
+     * Called by the window manager policy whenever the state of the system changes
+     * such that the current rotation might need to be updated, such as when the
+     * device is docked or rotated into a new posture.
+     */
+    @Override
+    public void updateRotation(boolean alwaysSendConfiguration, boolean forceRelayout) {
+        updateRotationUnchecked(alwaysSendConfiguration, forceRelayout);
+    }
+
+    /**
+     * Temporarily pauses rotation changes until resumed.
+     *
+     * This can be used to prevent rotation changes from occurring while the user is
+     * performing certain operations, such as drag and drop.
+     *
+     * This call nests and must be matched by an equal number of calls to
+     * {@link #resumeRotationLocked}.
+     */
+    void pauseRotationLocked() {
+        mDeferredRotationPauseCount += 1;
+    }
+
+    /**
+     * Resumes normal rotation changes after being paused.
+     */
+    void resumeRotationLocked() {
+        if (mDeferredRotationPauseCount > 0) {
+            mDeferredRotationPauseCount -= 1;
+            if (mDeferredRotationPauseCount == 0) {
+                // TODO(multi-display): Update rotation for different displays separately.
+                final DisplayContent displayContent = getDefaultDisplayContentLocked();
+                final boolean changed = displayContent.updateRotationUnchecked(
+                        false /* inTransaction */);
+                if (changed) {
+                    mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayContent.getDisplayId())
+                            .sendToTarget();
+                }
+            }
+        }
+    }
+
+    private void updateRotationUnchecked(boolean alwaysSendConfiguration, boolean forceRelayout) {
+        if(DEBUG_ORIENTATION) Slog.v(TAG_WM, "updateRotationUnchecked:"
+                + " alwaysSendConfiguration=" + alwaysSendConfiguration
+                + " forceRelayout=" + forceRelayout);
+
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateRotation");
+
+        long origId = Binder.clearCallingIdentity();
+
+        try {
+            // TODO(multi-display): Update rotation for different displays separately.
+            final boolean rotationChanged;
+            final int displayId;
+            synchronized (mWindowMap) {
+                final DisplayContent displayContent = getDefaultDisplayContentLocked();
+                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateRotation: display");
+                rotationChanged = displayContent.updateRotationUnchecked(
+                        false /* inTransaction */);
+                Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+                if (!rotationChanged || forceRelayout) {
+                    displayContent.setLayoutNeeded();
+                    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
+                            "updateRotation: performSurfacePlacement");
+                    mWindowPlacerLocked.performSurfacePlacement();
+                    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+                }
+                displayId = displayContent.getDisplayId();
+            }
+
+            if (rotationChanged || alwaysSendConfiguration) {
+                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateRotation: sendNewConfiguration");
+                sendNewConfiguration(displayId);
+                Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
+    @Override
+    public int getDefaultDisplayRotation() {
+        synchronized (mWindowMap) {
+            return getDefaultDisplayContentLocked().getRotation();
+        }
+    }
+
+    @Override
+    public boolean isRotationFrozen() {
+        return mPolicy.getUserRotationMode() == WindowManagerPolicy.USER_ROTATION_LOCKED;
+    }
+
+    @Override
+    public int watchRotation(IRotationWatcher watcher, int displayId) {
+        final IBinder watcherBinder = watcher.asBinder();
+        IBinder.DeathRecipient dr = new IBinder.DeathRecipient() {
+            @Override
+            public void binderDied() {
+                synchronized (mWindowMap) {
+                    for (int i=0; i<mRotationWatchers.size(); i++) {
+                        if (watcherBinder == mRotationWatchers.get(i).mWatcher.asBinder()) {
+                            RotationWatcher removed = mRotationWatchers.remove(i);
+                            IBinder binder = removed.mWatcher.asBinder();
+                            if (binder != null) {
+                                binder.unlinkToDeath(this, 0);
+                            }
+                            i--;
+                        }
+                    }
+                }
+            }
+        };
+
+        synchronized (mWindowMap) {
+            try {
+                watcher.asBinder().linkToDeath(dr, 0);
+                mRotationWatchers.add(new RotationWatcher(watcher, dr, displayId));
+            } catch (RemoteException e) {
+                // Client died, no cleanup needed.
+            }
+
+            return getDefaultDisplayRotation();
+        }
+    }
+
+    @Override
+    public void removeRotationWatcher(IRotationWatcher watcher) {
+        final IBinder watcherBinder = watcher.asBinder();
+        synchronized (mWindowMap) {
+            for (int i=0; i<mRotationWatchers.size(); i++) {
+                RotationWatcher rotationWatcher = mRotationWatchers.get(i);
+                if (watcherBinder == rotationWatcher.mWatcher.asBinder()) {
+                    RotationWatcher removed = mRotationWatchers.remove(i);
+                    IBinder binder = removed.mWatcher.asBinder();
+                    if (binder != null) {
+                        binder.unlinkToDeath(removed.mDeathRecipient, 0);
+                    }
+                    i--;
+                }
+            }
+        }
+    }
+
+    @Override
+    public boolean registerWallpaperVisibilityListener(IWallpaperVisibilityListener listener,
+            int displayId) {
+        synchronized (mWindowMap) {
+            final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+            if (displayContent == null) {
+                throw new IllegalArgumentException("Trying to register visibility event "
+                        + "for invalid display: " + displayId);
+            }
+            mWallpaperVisibilityListeners.registerWallpaperVisibilityListener(listener, displayId);
+            return displayContent.mWallpaperController.isWallpaperVisible();
+        }
+    }
+
+    @Override
+    public void unregisterWallpaperVisibilityListener(IWallpaperVisibilityListener listener,
+            int displayId) {
+        synchronized (mWindowMap) {
+            mWallpaperVisibilityListeners
+                    .unregisterWallpaperVisibilityListener(listener, displayId);
+        }
+    }
+
+    /**
+     * Apps that use the compact menu panel (as controlled by the panelMenuIsCompact
+     * theme attribute) on devices that feature a physical options menu key attempt to position
+     * their menu panel window along the edge of the screen nearest the physical menu key.
+     * This lowers the travel distance between invoking the menu panel and selecting
+     * a menu option.
+     *
+     * This method helps control where that menu is placed. Its current implementation makes
+     * assumptions about the menu key and its relationship to the screen based on whether
+     * the device's natural orientation is portrait (width < height) or landscape.
+     *
+     * The menu key is assumed to be located along the bottom edge of natural-portrait
+     * devices and along the right edge of natural-landscape devices. If these assumptions
+     * do not hold for the target device, this method should be changed to reflect that.
+     *
+     * @return A {@link Gravity} value for placing the options menu window
+     */
+    @Override
+    public int getPreferredOptionsPanelGravity() {
+        synchronized (mWindowMap) {
+            // TODO(multidisplay): Assume that such devices physical keys are on the main screen.
+            final DisplayContent displayContent = getDefaultDisplayContentLocked();
+            final int rotation = displayContent.getRotation();
+            if (displayContent.mInitialDisplayWidth < displayContent.mInitialDisplayHeight) {
+                // On devices with a natural orientation of portrait
+                switch (rotation) {
+                    default:
+                    case Surface.ROTATION_0:
+                        return Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+                    case Surface.ROTATION_90:
+                        return Gravity.RIGHT | Gravity.BOTTOM;
+                    case Surface.ROTATION_180:
+                        return Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+                    case Surface.ROTATION_270:
+                        return Gravity.START | Gravity.BOTTOM;
+                }
+            }
+
+            // On devices with a natural orientation of landscape
+            switch (rotation) {
+                default:
+                case Surface.ROTATION_0:
+                    return Gravity.RIGHT | Gravity.BOTTOM;
+                case Surface.ROTATION_90:
+                    return Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+                case Surface.ROTATION_180:
+                    return Gravity.START | Gravity.BOTTOM;
+                case Surface.ROTATION_270:
+                    return Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+            }
+        }
+    }
+
+    /**
+     * Starts the view server on the specified port.
+     *
+     * @param port The port to listener to.
+     *
+     * @return True if the server was successfully started, false otherwise.
+     *
+     * @see com.android.server.wm.ViewServer
+     * @see com.android.server.wm.ViewServer#VIEW_SERVER_DEFAULT_PORT
+     */
+    @Override
+    public boolean startViewServer(int port) {
+        if (isSystemSecure()) {
+            return false;
+        }
+
+        if (!checkCallingPermission(Manifest.permission.DUMP, "startViewServer")) {
+            return false;
+        }
+
+        if (port < 1024) {
+            return false;
+        }
+
+        if (mViewServer != null) {
+            if (!mViewServer.isRunning()) {
+                try {
+                    return mViewServer.start();
+                } catch (IOException e) {
+                    Slog.w(TAG_WM, "View server did not start");
+                }
+            }
+            return false;
+        }
+
+        try {
+            mViewServer = new ViewServer(this, port);
+            return mViewServer.start();
+        } catch (IOException e) {
+            Slog.w(TAG_WM, "View server did not start");
+        }
+        return false;
+    }
+
+    private boolean isSystemSecure() {
+        return "1".equals(SystemProperties.get(SYSTEM_SECURE, "1")) &&
+                "0".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0"));
+    }
+
+    /**
+     * Stops the view server if it exists.
+     *
+     * @return True if the server stopped, false if it wasn't started or
+     *         couldn't be stopped.
+     *
+     * @see com.android.server.wm.ViewServer
+     */
+    @Override
+    public boolean stopViewServer() {
+        if (isSystemSecure()) {
+            return false;
+        }
+
+        if (!checkCallingPermission(Manifest.permission.DUMP, "stopViewServer")) {
+            return false;
+        }
+
+        if (mViewServer != null) {
+            return mViewServer.stop();
+        }
+        return false;
+    }
+
+    /**
+     * Indicates whether the view server is running.
+     *
+     * @return True if the server is running, false otherwise.
+     *
+     * @see com.android.server.wm.ViewServer
+     */
+    @Override
+    public boolean isViewServerRunning() {
+        if (isSystemSecure()) {
+            return false;
+        }
+
+        if (!checkCallingPermission(Manifest.permission.DUMP, "isViewServerRunning")) {
+            return false;
+        }
+
+        return mViewServer != null && mViewServer.isRunning();
+    }
+
+    /**
+     * Lists all available windows in the system. The listing is written in the specified Socket's
+     * output stream with the following syntax: windowHashCodeInHexadecimal windowName
+     * Each line of the output represents a different window.
+     *
+     * @param client The remote client to send the listing to.
+     * @return false if an error occurred, true otherwise.
+     */
+    boolean viewServerListWindows(Socket client) {
+        if (isSystemSecure()) {
+            return false;
+        }
+
+        boolean result = true;
+
+        final ArrayList<WindowState> windows = new ArrayList();
+        synchronized (mWindowMap) {
+            mRoot.forAllWindows(w -> {
+                windows.add(w);
+            }, false /* traverseTopToBottom */);
+        }
+
+        BufferedWriter out = null;
+
+        // Any uncaught exception will crash the system process
+        try {
+            OutputStream clientStream = client.getOutputStream();
+            out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024);
+
+            final int count = windows.size();
+            for (int i = 0; i < count; i++) {
+                final WindowState w = windows.get(i);
+                out.write(Integer.toHexString(System.identityHashCode(w)));
+                out.write(' ');
+                out.append(w.mAttrs.getTitle());
+                out.write('\n');
+            }
+
+            out.write("DONE.\n");
+            out.flush();
+        } catch (Exception e) {
+            result = false;
+        } finally {
+            if (out != null) {
+                try {
+                    out.close();
+                } catch (IOException e) {
+                    result = false;
+                }
+            }
+        }
+
+        return result;
+    }
+
+    // TODO(multidisplay): Extend to multiple displays.
+    /**
+     * Returns the focused window in the following format:
+     * windowHashCodeInHexadecimal windowName
+     *
+     * @param client The remote client to send the listing to.
+     * @return False if an error occurred, true otherwise.
+     */
+    boolean viewServerGetFocusedWindow(Socket client) {
+        if (isSystemSecure()) {
+            return false;
+        }
+
+        boolean result = true;
+
+        WindowState focusedWindow = getFocusedWindow();
+
+        BufferedWriter out = null;
+
+        // Any uncaught exception will crash the system process
+        try {
+            OutputStream clientStream = client.getOutputStream();
+            out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024);
+
+            if(focusedWindow != null) {
+                out.write(Integer.toHexString(System.identityHashCode(focusedWindow)));
+                out.write(' ');
+                out.append(focusedWindow.mAttrs.getTitle());
+            }
+            out.write('\n');
+            out.flush();
+        } catch (Exception e) {
+            result = false;
+        } finally {
+            if (out != null) {
+                try {
+                    out.close();
+                } catch (IOException e) {
+                    result = false;
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Sends a command to a target window. The result of the command, if any, will be
+     * written in the output stream of the specified socket.
+     *
+     * The parameters must follow this syntax:
+     * windowHashcode extra
+     *
+     * Where XX is the length in characeters of the windowTitle.
+     *
+     * The first parameter is the target window. The window with the specified hashcode
+     * will be the target. If no target can be found, nothing happens. The extra parameters
+     * will be delivered to the target window and as parameters to the command itself.
+     *
+     * @param client The remote client to sent the result, if any, to.
+     * @param command The command to execute.
+     * @param parameters The command parameters.
+     *
+     * @return True if the command was successfully delivered, false otherwise. This does
+     *         not indicate whether the command itself was successful.
+     */
+    boolean viewServerWindowCommand(Socket client, String command, String parameters) {
+        if (isSystemSecure()) {
+            return false;
+        }
+
+        boolean success = true;
+        Parcel data = null;
+        Parcel reply = null;
+
+        BufferedWriter out = null;
+
+        // Any uncaught exception will crash the system process
+        try {
+            // Find the hashcode of the window
+            int index = parameters.indexOf(' ');
+            if (index == -1) {
+                index = parameters.length();
+            }
+            final String code = parameters.substring(0, index);
+            int hashCode = (int) Long.parseLong(code, 16);
+
+            // Extract the command's parameter after the window description
+            if (index < parameters.length()) {
+                parameters = parameters.substring(index + 1);
+            } else {
+                parameters = "";
+            }
+
+            final WindowState window = findWindow(hashCode);
+            if (window == null) {
+                return false;
+            }
+
+            data = Parcel.obtain();
+            data.writeInterfaceToken("android.view.IWindow");
+            data.writeString(command);
+            data.writeString(parameters);
+            data.writeInt(1);
+            ParcelFileDescriptor.fromSocket(client).writeToParcel(data, 0);
+
+            reply = Parcel.obtain();
+
+            final IBinder binder = window.mClient.asBinder();
+            // TODO: GET THE TRANSACTION CODE IN A SAFER MANNER
+            binder.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0);
+
+            reply.readException();
+
+            if (!client.isOutputShutdown()) {
+                out = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
+                out.write("DONE\n");
+                out.flush();
+            }
+
+        } catch (Exception e) {
+            Slog.w(TAG_WM, "Could not send command " + command + " with parameters " + parameters, e);
+            success = false;
+        } finally {
+            if (data != null) {
+                data.recycle();
+            }
+            if (reply != null) {
+                reply.recycle();
+            }
+            if (out != null) {
+                try {
+                    out.close();
+                } catch (IOException e) {
+
+                }
+            }
+        }
+
+        return success;
+    }
+
+    public void addWindowChangeListener(WindowChangeListener listener) {
+        synchronized(mWindowMap) {
+            mWindowChangeListeners.add(listener);
+        }
+    }
+
+    public void removeWindowChangeListener(WindowChangeListener listener) {
+        synchronized(mWindowMap) {
+            mWindowChangeListeners.remove(listener);
+        }
+    }
+
+    private void notifyWindowsChanged() {
+        WindowChangeListener[] windowChangeListeners;
+        synchronized(mWindowMap) {
+            if(mWindowChangeListeners.isEmpty()) {
+                return;
+            }
+            windowChangeListeners = new WindowChangeListener[mWindowChangeListeners.size()];
+            windowChangeListeners = mWindowChangeListeners.toArray(windowChangeListeners);
+        }
+        int N = windowChangeListeners.length;
+        for(int i = 0; i < N; i++) {
+            windowChangeListeners[i].windowsChanged();
+        }
+    }
+
+    private void notifyFocusChanged() {
+        WindowChangeListener[] windowChangeListeners;
+        synchronized(mWindowMap) {
+            if(mWindowChangeListeners.isEmpty()) {
+                return;
+            }
+            windowChangeListeners = new WindowChangeListener[mWindowChangeListeners.size()];
+            windowChangeListeners = mWindowChangeListeners.toArray(windowChangeListeners);
+        }
+        int N = windowChangeListeners.length;
+        for(int i = 0; i < N; i++) {
+            windowChangeListeners[i].focusChanged();
+        }
+    }
+
+    private WindowState findWindow(int hashCode) {
+        if (hashCode == -1) {
+            // TODO(multidisplay): Extend to multiple displays.
+            return getFocusedWindow();
+        }
+
+        synchronized (mWindowMap) {
+            return mRoot.getWindow((w) -> System.identityHashCode(w) == hashCode);
+        }
+    }
+
+    /**
+     * Instruct the Activity Manager to fetch and update the current display's configuration and
+     * broadcast them to config-changed listeners if appropriate.
+     * NOTE: Can't be called with the window manager lock held since it call into activity manager.
+     */
+    void sendNewConfiguration(int displayId) {
+        try {
+            final boolean configUpdated = mActivityManager.updateDisplayOverrideConfiguration(
+                    null /* values */, displayId);
+            if (!configUpdated) {
+                // Something changed (E.g. device rotation), but no configuration update is needed.
+                // E.g. changing device rotation by 180 degrees. Go ahead and perform surface
+                // placement to unfreeze the display since we froze it when the rotation was updated
+                // in DisplayContent#updateRotationUnchecked.
+                synchronized (mWindowMap) {
+                    if (mWaitingForConfig) {
+                        mWaitingForConfig = false;
+                        mLastFinishedFreezeSource = "config-unchanged";
+                        final DisplayContent dc = mRoot.getDisplayContent(displayId);
+                        if (dc != null) {
+                            dc.setLayoutNeeded();
+                        }
+                        mWindowPlacerLocked.performSurfacePlacement();
+                    }
+                }
+            }
+        } catch (RemoteException e) {
+        }
+    }
+
+    public Configuration computeNewConfiguration(int displayId) {
+        synchronized (mWindowMap) {
+            return computeNewConfigurationLocked(displayId);
+        }
+    }
+
+    private Configuration computeNewConfigurationLocked(int displayId) {
+        if (!mDisplayReady) {
+            return null;
+        }
+        final Configuration config = new Configuration();
+        final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+        displayContent.computeScreenConfiguration(config);
+        return config;
+    }
+
+    void notifyHardKeyboardStatusChange() {
+        final boolean available;
+        final WindowManagerInternal.OnHardKeyboardStatusChangeListener listener;
+        synchronized (mWindowMap) {
+            listener = mHardKeyboardStatusChangeListener;
+            available = mHardKeyboardAvailable;
+        }
+        if (listener != null) {
+            listener.onHardKeyboardStatusChange(available);
+        }
+    }
+
+    boolean startMovingTask(IWindow window, float startX, float startY) {
+        WindowState win = null;
+        synchronized (mWindowMap) {
+            win = windowForClientLocked(null, window, false);
+            // win shouldn't be null here, pass it down to startPositioningLocked
+            // to get warning if it's null.
+            if (!startPositioningLocked(
+                        win, false /*resize*/, false /*preserveOrientation*/, startX, startY)) {
+                return false;
+            }
+        }
+        try {
+            mActivityManager.setFocusedTask(win.getTask().mTaskId);
+        } catch(RemoteException e) {}
+        return true;
+    }
+
+    private void handleTapOutsideTask(DisplayContent displayContent, int x, int y) {
+        int taskId = -1;
+        synchronized (mWindowMap) {
+            final Task task = displayContent.findTaskForResizePoint(x, y);
+            if (task != null) {
+                if (!startPositioningLocked(task.getTopVisibleAppMainWindow(), true /*resize*/,
+                            task.preserveOrientationOnResize(), x, y)) {
+                    return;
+                }
+                taskId = task.mTaskId;
+            } else {
+                taskId = displayContent.taskIdFromPoint(x, y);
+            }
+        }
+        if (taskId >= 0) {
+            try {
+                mActivityManager.setFocusedTask(taskId);
+            } catch(RemoteException e) {}
+        }
+    }
+
+    private boolean startPositioningLocked(WindowState win, boolean resize,
+            boolean preserveOrientation, float startX, float startY) {
+        if (DEBUG_TASK_POSITIONING)
+            Slog.d(TAG_WM, "startPositioningLocked: "
+                            + "win=" + win + ", resize=" + resize + ", preserveOrientation="
+                            + preserveOrientation + ", {" + startX + ", " + startY + "}");
+
+        if (win == null || win.getAppToken() == null) {
+            Slog.w(TAG_WM, "startPositioningLocked: Bad window " + win);
+            return false;
+        }
+        if (win.mInputChannel == null) {
+            Slog.wtf(TAG_WM, "startPositioningLocked: " + win + " has no input channel, "
+                    + " probably being removed");
+            return false;
+        }
+
+        final DisplayContent displayContent = win.getDisplayContent();
+        if (displayContent == null) {
+            Slog.w(TAG_WM, "startPositioningLocked: Invalid display content " + win);
+            return false;
+        }
+
+        Display display = displayContent.getDisplay();
+        mTaskPositioner = new TaskPositioner(this);
+        mTaskPositioner.register(display);
+        mInputMonitor.updateInputWindowsLw(true /*force*/);
+
+        // We need to grab the touch focus so that the touch events during the
+        // resizing/scrolling are not sent to the app. 'win' is the main window
+        // of the app, it may not have focus since there might be other windows
+        // on top (eg. a dialog window).
+        WindowState transferFocusFromWin = win;
+        if (mCurrentFocus != null && mCurrentFocus != win
+                && mCurrentFocus.mAppToken == win.mAppToken) {
+            transferFocusFromWin = mCurrentFocus;
+        }
+        if (!mInputManager.transferTouchFocus(
+                transferFocusFromWin.mInputChannel, mTaskPositioner.mServerChannel)) {
+            Slog.e(TAG_WM, "startPositioningLocked: Unable to transfer touch focus");
+            mTaskPositioner.unregister();
+            mTaskPositioner = null;
+            mInputMonitor.updateInputWindowsLw(true /*force*/);
+            return false;
+        }
+
+        mTaskPositioner.startDrag(win, resize, preserveOrientation, startX, startY);
+        return true;
+    }
+
+    private void finishPositioning() {
+        if (DEBUG_TASK_POSITIONING) {
+            Slog.d(TAG_WM, "finishPositioning");
+        }
+        synchronized (mWindowMap) {
+            if (mTaskPositioner != null) {
+                mTaskPositioner.unregister();
+                mTaskPositioner = null;
+                mInputMonitor.updateInputWindowsLw(true /*force*/);
+            }
+        }
+    }
+
+    // -------------------------------------------------------------
+    // Drag and drop
+    // -------------------------------------------------------------
+
+    IBinder prepareDragSurface(IWindow window, SurfaceSession session,
+            int flags, int width, int height, Surface outSurface) {
+        if (DEBUG_DRAG) {
+            Slog.d(TAG_WM, "prepare drag surface: w=" + width + " h=" + height
+                    + " flags=" + Integer.toHexString(flags) + " win=" + window
+                    + " asbinder=" + window.asBinder());
+        }
+
+        final int callerPid = Binder.getCallingPid();
+        final int callerUid = Binder.getCallingUid();
+        final long origId = Binder.clearCallingIdentity();
+        IBinder token = null;
+
+        try {
+            synchronized (mWindowMap) {
+                try {
+                    if (mDragState == null) {
+                        // TODO(multi-display): support other displays
+                        final DisplayContent displayContent = getDefaultDisplayContentLocked();
+                        final Display display = displayContent.getDisplay();
+
+                        SurfaceControl surface = new SurfaceControl(session, "drag surface",
+                                width, height, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+                        surface.setLayerStack(display.getLayerStack());
+                        float alpha = 1;
+                        if ((flags & View.DRAG_FLAG_OPAQUE) == 0) {
+                            alpha = DRAG_SHADOW_ALPHA_TRANSPARENT;
+                        }
+                        surface.setAlpha(alpha);
+
+                        if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, "  DRAG "
+                                + surface + ": CREATE");
+                        outSurface.copyFrom(surface);
+                        final IBinder winBinder = window.asBinder();
+                        token = new Binder();
+                        mDragState = new DragState(this, token, surface, flags, winBinder);
+                        mDragState.mPid = callerPid;
+                        mDragState.mUid = callerUid;
+                        mDragState.mOriginalAlpha = alpha;
+                        token = mDragState.mToken = new Binder();
+
+                        // 5 second timeout for this window to actually begin the drag
+                        mH.removeMessages(H.DRAG_START_TIMEOUT, winBinder);
+                        Message msg = mH.obtainMessage(H.DRAG_START_TIMEOUT, winBinder);
+                        mH.sendMessageDelayed(msg, 5000);
+                    } else {
+                        Slog.w(TAG_WM, "Drag already in progress");
+                    }
+                } catch (OutOfResourcesException e) {
+                    Slog.e(TAG_WM, "Can't allocate drag surface w=" + width + " h=" + height, e);
+                    if (mDragState != null) {
+                        mDragState.reset();
+                        mDragState = null;
+                    }
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+
+        return token;
+    }
+
+    // -------------------------------------------------------------
+    // Input Events and Focus Management
+    // -------------------------------------------------------------
+
+    final InputMonitor mInputMonitor = new InputMonitor(this);
+    private boolean mEventDispatchingEnabled;
+
+    @Override
+    public void setEventDispatching(boolean enabled) {
+        if (!checkCallingPermission(MANAGE_APP_TOKENS, "setEventDispatching()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        }
+
+        synchronized (mWindowMap) {
+            mEventDispatchingEnabled = enabled;
+            if (mDisplayEnabled) {
+                mInputMonitor.setEventDispatchingLw(enabled);
+            }
+        }
+    }
+
+    private WindowState getFocusedWindow() {
+        synchronized (mWindowMap) {
+            return getFocusedWindowLocked();
+        }
+    }
+
+    private WindowState getFocusedWindowLocked() {
+        return mCurrentFocus;
+    }
+
+    TaskStack getImeFocusStackLocked() {
+        // Don't use mCurrentFocus.getStack() because it returns home stack for system windows.
+        // Also don't use mInputMethodTarget's stack, because some window with FLAG_NOT_FOCUSABLE
+        // and FLAG_ALT_FOCUSABLE_IM flags both set might be set to IME target so they're moved
+        // to make room for IME, but the window is not the focused window that's taking input.
+        return (mFocusedApp != null && mFocusedApp.getTask() != null) ?
+                mFocusedApp.getTask().mStack : null;
+    }
+
+    public boolean detectSafeMode() {
+        if (!mInputMonitor.waitForInputDevicesReady(
+                INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS)) {
+            Slog.w(TAG_WM, "Devices still not ready after waiting "
+                   + INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS
+                   + " milliseconds before attempting to detect safe mode.");
+        }
+
+        if (Settings.Global.getInt(
+                mContext.getContentResolver(), Settings.Global.SAFE_BOOT_DISALLOWED, 0) != 0) {
+            return false;
+        }
+
+        int menuState = mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_ANY,
+                KeyEvent.KEYCODE_MENU);
+        int sState = mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_ANY, KeyEvent.KEYCODE_S);
+        int dpadState = mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_DPAD,
+                KeyEvent.KEYCODE_DPAD_CENTER);
+        int trackballState = mInputManager.getScanCodeState(-1, InputDevice.SOURCE_TRACKBALL,
+                InputManagerService.BTN_MOUSE);
+        int volumeDownState = mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_ANY,
+                KeyEvent.KEYCODE_VOLUME_DOWN);
+        mSafeMode = menuState > 0 || sState > 0 || dpadState > 0 || trackballState > 0
+                || volumeDownState > 0;
+        try {
+            if (SystemProperties.getInt(ShutdownThread.REBOOT_SAFEMODE_PROPERTY, 0) != 0
+                    || SystemProperties.getInt(ShutdownThread.RO_SAFEMODE_PROPERTY, 0) != 0) {
+                mSafeMode = true;
+                SystemProperties.set(ShutdownThread.REBOOT_SAFEMODE_PROPERTY, "");
+            }
+        } catch (IllegalArgumentException e) {
+        }
+        if (mSafeMode) {
+            Log.i(TAG_WM, "SAFE MODE ENABLED (menu=" + menuState + " s=" + sState
+                    + " dpad=" + dpadState + " trackball=" + trackballState + ")");
+            SystemProperties.set(ShutdownThread.RO_SAFEMODE_PROPERTY, "1");
+        } else {
+            Log.i(TAG_WM, "SAFE MODE not enabled");
+        }
+        mPolicy.setSafeMode(mSafeMode);
+        return mSafeMode;
+    }
+
+    public void displayReady() {
+        for (Display display : mDisplays) {
+            displayReady(display.getDisplayId());
+        }
+
+        synchronized(mWindowMap) {
+            final DisplayContent displayContent = getDefaultDisplayContentLocked();
+            if (mMaxUiWidth > 0) {
+                displayContent.setMaxUiWidth(mMaxUiWidth);
+            }
+            readForcedDisplayPropertiesLocked(displayContent);
+            mDisplayReady = true;
+        }
+
+        try {
+            mActivityManager.updateConfiguration(null);
+        } catch (RemoteException e) {
+        }
+
+        synchronized(mWindowMap) {
+            mIsTouchDevice = mContext.getPackageManager().hasSystemFeature(
+                    PackageManager.FEATURE_TOUCHSCREEN);
+            configureDisplayPolicyLocked(getDefaultDisplayContentLocked());
+        }
+
+        try {
+            mActivityManager.updateConfiguration(null);
+        } catch (RemoteException e) {
+        }
+
+        updateCircularDisplayMaskIfNeeded();
+    }
+
+    private void displayReady(int displayId) {
+        synchronized(mWindowMap) {
+            final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+            if (displayContent != null) {
+                mAnimator.addDisplayLocked(displayId);
+                displayContent.initializeDisplayBaseInfo();
+            }
+        }
+    }
+
+    public void systemReady() {
+        mPolicy.systemReady();
+        mTaskSnapshotController.systemReady();
+        mHasWideColorGamutSupport = queryWideColorGamutSupport();
+    }
+
+    private static boolean queryWideColorGamutSupport() {
+        try {
+            ISurfaceFlingerConfigs surfaceFlinger = ISurfaceFlingerConfigs.getService();
+            OptionalBool hasWideColor = surfaceFlinger.hasWideColorDisplay();
+            if (hasWideColor != null) {
+                return hasWideColor.value;
+            }
+        } catch (RemoteException e) {
+            // Ignore, we're in big trouble if we can't talk to SurfaceFlinger's config store
+        }
+        return false;
+    }
+
+    // -------------------------------------------------------------
+    // Async Handler
+    // -------------------------------------------------------------
+
+    final class H extends android.os.Handler {
+        public static final int REPORT_FOCUS_CHANGE = 2;
+        public static final int REPORT_LOSING_FOCUS = 3;
+        public static final int WINDOW_FREEZE_TIMEOUT = 11;
+
+        public static final int APP_TRANSITION_TIMEOUT = 13;
+        public static final int PERSIST_ANIMATION_SCALE = 14;
+        public static final int FORCE_GC = 15;
+        public static final int ENABLE_SCREEN = 16;
+        public static final int APP_FREEZE_TIMEOUT = 17;
+        public static final int SEND_NEW_CONFIGURATION = 18;
+        public static final int REPORT_WINDOWS_CHANGE = 19;
+        public static final int DRAG_START_TIMEOUT = 20;
+        public static final int DRAG_END_TIMEOUT = 21;
+        public static final int REPORT_HARD_KEYBOARD_STATUS_CHANGE = 22;
+        public static final int BOOT_TIMEOUT = 23;
+        public static final int WAITING_FOR_DRAWN_TIMEOUT = 24;
+        public static final int SHOW_STRICT_MODE_VIOLATION = 25;
+        public static final int DO_ANIMATION_CALLBACK = 26;
+
+        public static final int CLIENT_FREEZE_TIMEOUT = 30;
+        public static final int TAP_OUTSIDE_TASK = 31;
+        public static final int NOTIFY_ACTIVITY_DRAWN = 32;
+
+        public static final int ALL_WINDOWS_DRAWN = 33;
+
+        public static final int NEW_ANIMATOR_SCALE = 34;
+
+        public static final int SHOW_CIRCULAR_DISPLAY_MASK = 35;
+        public static final int SHOW_EMULATOR_DISPLAY_OVERLAY = 36;
+
+        public static final int CHECK_IF_BOOT_ANIMATION_FINISHED = 37;
+        public static final int RESET_ANR_MESSAGE = 38;
+        public static final int WALLPAPER_DRAW_PENDING_TIMEOUT = 39;
+
+        public static final int FINISH_TASK_POSITIONING = 40;
+
+        public static final int UPDATE_DOCKED_STACK_DIVIDER = 41;
+
+        public static final int TEAR_DOWN_DRAG_AND_DROP_INPUT = 44;
+
+        public static final int WINDOW_REPLACEMENT_TIMEOUT = 46;
+
+        public static final int NOTIFY_APP_TRANSITION_STARTING = 47;
+        public static final int NOTIFY_APP_TRANSITION_CANCELLED = 48;
+        public static final int NOTIFY_APP_TRANSITION_FINISHED = 49;
+        public static final int UPDATE_ANIMATION_SCALE = 51;
+        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;
+        public static final int RESTORE_POINTER_ICON = 55;
+        public static final int NOTIFY_KEYGUARD_FLAGS_CHANGED = 56;
+        public static final int NOTIFY_KEYGUARD_TRUSTED_CHANGED = 57;
+        public static final int SET_HAS_OVERLAY_UI = 58;
+
+        /**
+         * Used to denote that an integer field in a message will not be used.
+         */
+        public static final int UNUSED = 0;
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (DEBUG_WINDOW_TRACE) {
+                Slog.v(TAG_WM, "handleMessage: entry what=" + msg.what);
+            }
+            switch (msg.what) {
+                case REPORT_FOCUS_CHANGE: {
+                    WindowState lastFocus;
+                    WindowState newFocus;
+
+                    AccessibilityController accessibilityController = null;
+
+                    synchronized(mWindowMap) {
+                        // TODO(multidisplay): Accessibility supported only of default desiplay.
+                        if (mAccessibilityController != null && getDefaultDisplayContentLocked()
+                                .getDisplayId() == DEFAULT_DISPLAY) {
+                            accessibilityController = mAccessibilityController;
+                        }
+
+                        lastFocus = mLastFocus;
+                        newFocus = mCurrentFocus;
+                        if (lastFocus == newFocus) {
+                            // Focus is not changing, so nothing to do.
+                            return;
+                        }
+                        mLastFocus = newFocus;
+                        if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Focus moving from " + lastFocus +
+                                " to " + newFocus);
+                        if (newFocus != null && lastFocus != null
+                                && !newFocus.isDisplayedLw()) {
+                            //Slog.i(TAG_WM, "Delaying loss of focus...");
+                            mLosingFocus.add(lastFocus);
+                            lastFocus = null;
+                        }
+                    }
+
+                    // First notify the accessibility manager for the change so it has
+                    // the windows before the newly focused one starts firing eventgs.
+                    if (accessibilityController != null) {
+                        accessibilityController.onWindowFocusChangedNotLocked();
+                    }
+
+                    //System.out.println("Changing focus from " + lastFocus
+                    //                   + " to " + newFocus);
+                    if (newFocus != null) {
+                        if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Gaining focus: " + newFocus);
+                        newFocus.reportFocusChangedSerialized(true, mInTouchMode);
+                        notifyFocusChanged();
+                    }
+
+                    if (lastFocus != null) {
+                        if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Losing focus: " + lastFocus);
+                        lastFocus.reportFocusChangedSerialized(false, mInTouchMode);
+                    }
+                } break;
+
+                case REPORT_LOSING_FOCUS: {
+                    ArrayList<WindowState> losers;
+
+                    synchronized(mWindowMap) {
+                        losers = mLosingFocus;
+                        mLosingFocus = new ArrayList<WindowState>();
+                    }
+
+                    final int N = losers.size();
+                    for (int i=0; i<N; i++) {
+                        if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Losing delayed focus: " +
+                                losers.get(i));
+                        losers.get(i).reportFocusChangedSerialized(false, mInTouchMode);
+                    }
+                } break;
+
+                case WINDOW_FREEZE_TIMEOUT: {
+                    // TODO(multidisplay): Can non-default displays rotate?
+                    synchronized (mWindowMap) {
+                        getDefaultDisplayContentLocked().onWindowFreezeTimeout();
+                    }
+                    break;
+                }
+
+                case APP_TRANSITION_TIMEOUT: {
+                    synchronized (mWindowMap) {
+                        if (mAppTransition.isTransitionSet() || !mOpeningApps.isEmpty()
+                                    || !mClosingApps.isEmpty()) {
+                            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "*** APP TRANSITION TIMEOUT."
+                                    + " isTransitionSet()=" + mAppTransition.isTransitionSet()
+                                    + " mOpeningApps.size()=" + mOpeningApps.size()
+                                    + " mClosingApps.size()=" + mClosingApps.size());
+                            mAppTransition.setTimeout();
+                            mWindowPlacerLocked.performSurfacePlacement();
+                        }
+                    }
+                    break;
+                }
+
+                case PERSIST_ANIMATION_SCALE: {
+                    Settings.Global.putFloat(mContext.getContentResolver(),
+                            Settings.Global.WINDOW_ANIMATION_SCALE, mWindowAnimationScaleSetting);
+                    Settings.Global.putFloat(mContext.getContentResolver(),
+                            Settings.Global.TRANSITION_ANIMATION_SCALE,
+                            mTransitionAnimationScaleSetting);
+                    Settings.Global.putFloat(mContext.getContentResolver(),
+                            Settings.Global.ANIMATOR_DURATION_SCALE, mAnimatorDurationScaleSetting);
+                    break;
+                }
+
+                case UPDATE_ANIMATION_SCALE: {
+                    @UpdateAnimationScaleMode
+                    final int mode = msg.arg1;
+                    switch (mode) {
+                        case WINDOW_ANIMATION_SCALE: {
+                            mWindowAnimationScaleSetting = Settings.Global.getFloat(
+                                    mContext.getContentResolver(),
+                                    Settings.Global.WINDOW_ANIMATION_SCALE,
+                                    mWindowAnimationScaleSetting);
+                            break;
+                        }
+                        case TRANSITION_ANIMATION_SCALE: {
+                            mTransitionAnimationScaleSetting = Settings.Global.getFloat(
+                                    mContext.getContentResolver(),
+                                    Settings.Global.TRANSITION_ANIMATION_SCALE,
+                                    mTransitionAnimationScaleSetting);
+                            break;
+                        }
+                        case ANIMATION_DURATION_SCALE: {
+                            mAnimatorDurationScaleSetting = Settings.Global.getFloat(
+                                    mContext.getContentResolver(),
+                                    Settings.Global.ANIMATOR_DURATION_SCALE,
+                                    mAnimatorDurationScaleSetting);
+                            dispatchNewAnimatorScaleLocked(null);
+                            break;
+                        }
+                    }
+                    break;
+                }
+
+                case FORCE_GC: {
+                    synchronized (mWindowMap) {
+                        // Since we're holding both mWindowMap and mAnimator we don't need to
+                        // hold mAnimator.mLayoutToAnim.
+                        if (mAnimator.isAnimating() || mAnimator.isAnimationScheduled()) {
+                            // If we are animating, don't do the gc now but
+                            // delay a bit so we don't interrupt the animation.
+                            sendEmptyMessageDelayed(H.FORCE_GC, 2000);
+                            return;
+                        }
+                        // If we are currently rotating the display, it will
+                        // schedule a new message when done.
+                        if (mDisplayFrozen) {
+                            return;
+                        }
+                    }
+                    Runtime.getRuntime().gc();
+                    break;
+                }
+
+                case ENABLE_SCREEN: {
+                    performEnableScreen();
+                    break;
+                }
+
+                case APP_FREEZE_TIMEOUT: {
+                    synchronized (mWindowMap) {
+                        Slog.w(TAG_WM, "App freeze timeout expired.");
+                        mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
+                        for (int i = mAppFreezeListeners.size() - 1; i >=0 ; --i) {
+                            mAppFreezeListeners.get(i).onAppFreezeTimeout();
+                        }
+                    }
+                    break;
+                }
+
+                case CLIENT_FREEZE_TIMEOUT: {
+                    synchronized (mWindowMap) {
+                        if (mClientFreezingScreen) {
+                            mClientFreezingScreen = false;
+                            mLastFinishedFreezeSource = "client-timeout";
+                            stopFreezingDisplayLocked();
+                        }
+                    }
+                    break;
+                }
+
+                case SEND_NEW_CONFIGURATION: {
+                    removeMessages(SEND_NEW_CONFIGURATION, msg.obj);
+                    final int displayId = (Integer) msg.obj;
+                    if (mRoot.getDisplayContent(displayId) != null) {
+                        sendNewConfiguration(displayId);
+                    } else {
+                        // Message could come after display has already been removed.
+                        if (DEBUG_CONFIGURATION) {
+                            Slog.w(TAG, "Trying to send configuration to non-existing displayId="
+                                    + displayId);
+                        }
+                    }
+                    break;
+                }
+
+                case REPORT_WINDOWS_CHANGE: {
+                    if (mWindowsChanged) {
+                        synchronized (mWindowMap) {
+                            mWindowsChanged = false;
+                        }
+                        notifyWindowsChanged();
+                    }
+                    break;
+                }
+
+                case DRAG_START_TIMEOUT: {
+                    IBinder win = (IBinder)msg.obj;
+                    if (DEBUG_DRAG) {
+                        Slog.w(TAG_WM, "Timeout starting drag by win " + win);
+                    }
+                    synchronized (mWindowMap) {
+                        // !!! TODO: ANR the app that has failed to start the drag in time
+                        if (mDragState != null) {
+                            mDragState.unregister();
+                            mDragState.reset();
+                            mDragState = null;
+                        }
+                    }
+                    break;
+                }
+
+                case DRAG_END_TIMEOUT: {
+                    IBinder win = (IBinder)msg.obj;
+                    if (DEBUG_DRAG) {
+                        Slog.w(TAG_WM, "Timeout ending drag to win " + win);
+                    }
+                    synchronized (mWindowMap) {
+                        // !!! TODO: ANR the drag-receiving app
+                        if (mDragState != null) {
+                            mDragState.mDragResult = false;
+                            mDragState.endDragLw();
+                        }
+                    }
+                    break;
+                }
+
+                case TEAR_DOWN_DRAG_AND_DROP_INPUT: {
+                    if (DEBUG_DRAG) Slog.d(TAG_WM, "Drag ending; tearing down input channel");
+                    DragState.InputInterceptor interceptor = (DragState.InputInterceptor) msg.obj;
+                    if (interceptor != null) {
+                        synchronized (mWindowMap) {
+                            interceptor.tearDown();
+                        }
+                    }
+                }
+                break;
+
+                case REPORT_HARD_KEYBOARD_STATUS_CHANGE: {
+                    notifyHardKeyboardStatusChange();
+                    break;
+                }
+
+                case BOOT_TIMEOUT: {
+                    performBootTimeout();
+                    break;
+                }
+
+                case WAITING_FOR_DRAWN_TIMEOUT: {
+                    Runnable callback = null;
+                    synchronized (mWindowMap) {
+                        Slog.w(TAG_WM, "Timeout waiting for drawn: undrawn=" + mWaitingForDrawn);
+                        mWaitingForDrawn.clear();
+                        callback = mWaitingForDrawnCallback;
+                        mWaitingForDrawnCallback = null;
+                    }
+                    if (callback != null) {
+                        callback.run();
+                    }
+                    break;
+                }
+
+                case SHOW_STRICT_MODE_VIOLATION: {
+                    showStrictModeViolation(msg.arg1, msg.arg2);
+                    break;
+                }
+
+                case SHOW_CIRCULAR_DISPLAY_MASK: {
+                    showCircularMask(msg.arg1 == 1);
+                    break;
+                }
+
+                case SHOW_EMULATOR_DISPLAY_OVERLAY: {
+                    showEmulatorDisplayOverlay();
+                    break;
+                }
+
+                case DO_ANIMATION_CALLBACK: {
+                    try {
+                        ((IRemoteCallback)msg.obj).sendResult(null);
+                    } catch (RemoteException e) {
+                    }
+                    break;
+                }
+
+                case TAP_OUTSIDE_TASK: {
+                    handleTapOutsideTask((DisplayContent)msg.obj, msg.arg1, msg.arg2);
+                }
+                break;
+
+                case FINISH_TASK_POSITIONING: {
+                    finishPositioning();
+                }
+                break;
+
+                case NOTIFY_ACTIVITY_DRAWN:
+                    try {
+                        mActivityManager.notifyActivityDrawn((IBinder) msg.obj);
+                    } catch (RemoteException e) {
+                    }
+                    break;
+                case ALL_WINDOWS_DRAWN: {
+                    Runnable callback;
+                    synchronized (mWindowMap) {
+                        callback = mWaitingForDrawnCallback;
+                        mWaitingForDrawnCallback = null;
+                    }
+                    if (callback != null) {
+                        callback.run();
+                    }
+                    break;
+                }
+                case NEW_ANIMATOR_SCALE: {
+                    float scale = getCurrentAnimatorScale();
+                    ValueAnimator.setDurationScale(scale);
+                    Session session = (Session)msg.obj;
+                    if (session != null) {
+                        try {
+                            session.mCallback.onAnimatorScaleChanged(scale);
+                        } catch (RemoteException e) {
+                        }
+                    } else {
+                        ArrayList<IWindowSessionCallback> callbacks
+                                = new ArrayList<IWindowSessionCallback>();
+                        synchronized (mWindowMap) {
+                            for (int i=0; i<mSessions.size(); i++) {
+                                callbacks.add(mSessions.valueAt(i).mCallback);
+                            }
+
+                        }
+                        for (int i=0; i<callbacks.size(); i++) {
+                            try {
+                                callbacks.get(i).onAnimatorScaleChanged(scale);
+                            } catch (RemoteException e) {
+                            }
+                        }
+                    }
+                }
+                break;
+                case CHECK_IF_BOOT_ANIMATION_FINISHED: {
+                    final boolean bootAnimationComplete;
+                    synchronized (mWindowMap) {
+                        if (DEBUG_BOOT) Slog.i(TAG_WM, "CHECK_IF_BOOT_ANIMATION_FINISHED:");
+                        bootAnimationComplete = checkBootAnimationCompleteLocked();
+                    }
+                    if (bootAnimationComplete) {
+                        performEnableScreen();
+                    }
+                }
+                break;
+                case RESET_ANR_MESSAGE: {
+                    synchronized (mWindowMap) {
+                        mLastANRState = null;
+                    }
+                    mAmInternal.clearSavedANRState();
+                }
+                break;
+                case WALLPAPER_DRAW_PENDING_TIMEOUT: {
+                    synchronized (mWindowMap) {
+                        if (mRoot.mWallpaperController.processWallpaperDrawPendingTimeout()) {
+                            mWindowPlacerLocked.performSurfacePlacement();
+                        }
+                    }
+                }
+                break;
+                case UPDATE_DOCKED_STACK_DIVIDER: {
+                    synchronized (mWindowMap) {
+                        final DisplayContent displayContent = getDefaultDisplayContentLocked();
+                        displayContent.getDockedDividerController().reevaluateVisibility(false);
+                        displayContent.adjustForImeIfNeeded();
+                    }
+                }
+                break;
+                case WINDOW_REPLACEMENT_TIMEOUT: {
+                    synchronized (mWindowMap) {
+                        for (int i = mWindowReplacementTimeouts.size() - 1; i >= 0; i--) {
+                            final AppWindowToken token = mWindowReplacementTimeouts.get(i);
+                            token.onWindowReplacementTimeout();
+                        }
+                        mWindowReplacementTimeouts.clear();
+                    }
+                }
+                break;
+                case NOTIFY_APP_TRANSITION_STARTING: {
+                    mAmInternal.notifyAppTransitionStarting((SparseIntArray) msg.obj,
+                            msg.getWhen());
+                }
+                break;
+                case NOTIFY_APP_TRANSITION_CANCELLED: {
+                    mAmInternal.notifyAppTransitionCancelled();
+                }
+                break;
+                case NOTIFY_APP_TRANSITION_FINISHED: {
+                    mAmInternal.notifyAppTransitionFinished();
+                }
+                break;
+                case WINDOW_HIDE_TIMEOUT: {
+                    final WindowState window = (WindowState) msg.obj;
+                    synchronized(mWindowMap) {
+                        // TODO: This is all about fixing b/21693547
+                        // where partially initialized Toasts get stuck
+                        // around and keep the screen on. We'd like
+                        // to just remove the toast...but this can cause clients
+                        // who miss the timeout due to normal circumstances (e.g.
+                        // 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. 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.hidePermanentlyLw();
+                        window.setDisplayLayoutNeeded();
+                        mWindowPlacerLocked.performSurfacePlacement();
+                    }
+                }
+                break;
+                case NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED: {
+                    mAmInternal.notifyDockedStackMinimizedChanged(msg.arg1 == 1);
+                }
+                break;
+                case RESTORE_POINTER_ICON: {
+                    synchronized (mWindowMap) {
+                        restorePointerIconLocked((DisplayContent)msg.obj, msg.arg1, msg.arg2);
+                    }
+                }
+                break;
+                case SEAMLESS_ROTATION_TIMEOUT: {
+                    // Rotation only supported on primary display.
+                    // TODO(multi-display)
+                    synchronized(mWindowMap) {
+                        final DisplayContent dc = getDefaultDisplayContentLocked();
+                        dc.onSeamlessRotationTimeout();
+                    }
+                }
+                break;
+                case NOTIFY_KEYGUARD_FLAGS_CHANGED: {
+                    mAmInternal.notifyKeyguardFlagsChanged((Runnable) msg.obj);
+                }
+                break;
+                case NOTIFY_KEYGUARD_TRUSTED_CHANGED: {
+                    mAmInternal.notifyKeyguardTrustedChanged();
+                }
+                break;
+                case SET_HAS_OVERLAY_UI: {
+                    mAmInternal.setHasOverlayUi(msg.arg1, msg.arg2 == 1);
+                }
+                break;
+            }
+            if (DEBUG_WINDOW_TRACE) {
+                Slog.v(TAG_WM, "handleMessage: exit");
+            }
+        }
+    }
+
+    void destroyPreservedSurfaceLocked() {
+        for (int i = mDestroyPreservedSurface.size() - 1; i >= 0 ; i--) {
+            final WindowState w = mDestroyPreservedSurface.get(i);
+            w.mWinAnimator.destroyPreservedSurfaceLocked();
+        }
+        mDestroyPreservedSurface.clear();
+    }
+
+    // -------------------------------------------------------------
+    // IWindowManager API
+    // -------------------------------------------------------------
+
+    @Override
+    public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
+            IInputContext inputContext) {
+        if (client == null) throw new IllegalArgumentException("null client");
+        if (inputContext == null) throw new IllegalArgumentException("null inputContext");
+        Session session = new Session(this, callback, client, inputContext);
+        return session;
+    }
+
+    @Override
+    public boolean inputMethodClientHasFocus(IInputMethodClient client) {
+        synchronized (mWindowMap) {
+            // TODO: multi-display
+            if (getDefaultDisplayContentLocked().inputMethodClientHasFocus(client)) {
+                return true;
+            }
+
+            // Okay, how about this...  what is the current focus?
+            // It seems in some cases we may not have moved the IM
+            // target window, such as when it was in a pop-up window,
+            // so let's also look at the current focus.  (An example:
+            // go to Gmail, start searching so the keyboard goes up,
+            // press home.  Sometimes the IME won't go down.)
+            // Would be nice to fix this more correctly, but it's
+            // way at the end of a release, and this should be good enough.
+            if (mCurrentFocus != null && mCurrentFocus.mSession.mClient != null
+                    && mCurrentFocus.mSession.mClient.asBinder() == client.asBinder()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void getInitialDisplaySize(int displayId, Point size) {
+        synchronized (mWindowMap) {
+            final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+            if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
+                size.x = displayContent.mInitialDisplayWidth;
+                size.y = displayContent.mInitialDisplayHeight;
+            }
+        }
+    }
+
+    @Override
+    public void getBaseDisplaySize(int displayId, Point size) {
+        synchronized (mWindowMap) {
+            final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+            if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
+                size.x = displayContent.mBaseDisplayWidth;
+                size.y = displayContent.mBaseDisplayHeight;
+            }
+        }
+    }
+
+    @Override
+    public void setForcedDisplaySize(int displayId, int width, int height) {
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
+                PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Must hold permission " +
+                    android.Manifest.permission.WRITE_SECURE_SETTINGS);
+        }
+        if (displayId != DEFAULT_DISPLAY) {
+            throw new IllegalArgumentException("Can only set the default display");
+        }
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized(mWindowMap) {
+                // Set some sort of reasonable bounds on the size of the display that we
+                // will try to emulate.
+                final int MIN_WIDTH = 200;
+                final int MIN_HEIGHT = 200;
+                final int MAX_SCALE = 2;
+                final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+                if (displayContent != null) {
+                    width = Math.min(Math.max(width, MIN_WIDTH),
+                            displayContent.mInitialDisplayWidth * MAX_SCALE);
+                    height = Math.min(Math.max(height, MIN_HEIGHT),
+                            displayContent.mInitialDisplayHeight * MAX_SCALE);
+                    setForcedDisplaySizeLocked(displayContent, width, height);
+                    Settings.Global.putString(mContext.getContentResolver(),
+                            Settings.Global.DISPLAY_SIZE_FORCED, width + "," + height);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
+    public void setForcedDisplayScalingMode(int displayId, int mode) {
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
+                PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Must hold permission " +
+                    android.Manifest.permission.WRITE_SECURE_SETTINGS);
+        }
+        if (displayId != DEFAULT_DISPLAY) {
+            throw new IllegalArgumentException("Can only set the default display");
+        }
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized(mWindowMap) {
+                final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+                if (displayContent != null) {
+                    if (mode < 0 || mode > 1) {
+                        mode = 0;
+                    }
+                    setForcedDisplayScalingModeLocked(displayContent, mode);
+                    Settings.Global.putInt(mContext.getContentResolver(),
+                            Settings.Global.DISPLAY_SCALING_FORCE, mode);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void setForcedDisplayScalingModeLocked(DisplayContent displayContent, int mode) {
+        Slog.i(TAG_WM, "Using display scaling mode: " + (mode == 0 ? "auto" : "off"));
+        displayContent.mDisplayScalingDisabled = (mode != 0);
+        reconfigureDisplayLocked(displayContent);
+    }
+
+    private void readForcedDisplayPropertiesLocked(final DisplayContent displayContent) {
+        // Display size.
+        String sizeStr = Settings.Global.getString(mContext.getContentResolver(),
+                Settings.Global.DISPLAY_SIZE_FORCED);
+        if (sizeStr == null || sizeStr.length() == 0) {
+            sizeStr = SystemProperties.get(SIZE_OVERRIDE, null);
+        }
+        if (sizeStr != null && sizeStr.length() > 0) {
+            final int pos = sizeStr.indexOf(',');
+            if (pos > 0 && sizeStr.lastIndexOf(',') == pos) {
+                int width, height;
+                try {
+                    width = Integer.parseInt(sizeStr.substring(0, pos));
+                    height = Integer.parseInt(sizeStr.substring(pos+1));
+                    if (displayContent.mBaseDisplayWidth != width
+                            || displayContent.mBaseDisplayHeight != height) {
+                        Slog.i(TAG_WM, "FORCED DISPLAY SIZE: " + width + "x" + height);
+                        displayContent.updateBaseDisplayMetrics(width, height,
+                                displayContent.mBaseDisplayDensity);
+                    }
+                } catch (NumberFormatException ex) {
+                }
+            }
+        }
+
+        // Display density.
+        final int density = getForcedDisplayDensityForUserLocked(mCurrentUserId);
+        if (density != 0) {
+            displayContent.mBaseDisplayDensity = density;
+        }
+
+        // Display scaling mode.
+        int mode = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.DISPLAY_SCALING_FORCE, 0);
+        if (mode != 0) {
+            Slog.i(TAG_WM, "FORCED DISPLAY SCALING DISABLED");
+            displayContent.mDisplayScalingDisabled = true;
+        }
+    }
+
+    // displayContent must not be null
+    private void setForcedDisplaySizeLocked(DisplayContent displayContent, int width, int height) {
+        Slog.i(TAG_WM, "Using new display size: " + width + "x" + height);
+        displayContent.updateBaseDisplayMetrics(width, height, displayContent.mBaseDisplayDensity);
+        reconfigureDisplayLocked(displayContent);
+    }
+
+    @Override
+    public void clearForcedDisplaySize(int displayId) {
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
+                PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Must hold permission " +
+                    android.Manifest.permission.WRITE_SECURE_SETTINGS);
+        }
+        if (displayId != DEFAULT_DISPLAY) {
+            throw new IllegalArgumentException("Can only set the default display");
+        }
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized(mWindowMap) {
+                final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+                if (displayContent != null) {
+                    setForcedDisplaySizeLocked(displayContent, displayContent.mInitialDisplayWidth,
+                            displayContent.mInitialDisplayHeight);
+                    Settings.Global.putString(mContext.getContentResolver(),
+                            Settings.Global.DISPLAY_SIZE_FORCED, "");
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
+    public int getInitialDisplayDensity(int displayId) {
+        synchronized (mWindowMap) {
+            final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+            if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
+                return displayContent.mInitialDisplayDensity;
+            }
+        }
+        return -1;
+    }
+
+    @Override
+    public int getBaseDisplayDensity(int displayId) {
+        synchronized (mWindowMap) {
+            final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+            if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
+                return displayContent.mBaseDisplayDensity;
+            }
+        }
+        return -1;
+    }
+
+    @Override
+    public void setForcedDisplayDensityForUser(int displayId, int density, int userId) {
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
+                PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Must hold permission " +
+                    android.Manifest.permission.WRITE_SECURE_SETTINGS);
+        }
+        if (displayId != DEFAULT_DISPLAY) {
+            throw new IllegalArgumentException("Can only set the default display");
+        }
+
+        final int targetUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+                Binder.getCallingUid(), userId, false, true, "setForcedDisplayDensityForUser",
+                null);
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized(mWindowMap) {
+                final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+                if (displayContent != null && mCurrentUserId == targetUserId) {
+                    setForcedDisplayDensityLocked(displayContent, density);
+                }
+                Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                        Settings.Secure.DISPLAY_DENSITY_FORCED,
+                        Integer.toString(density), targetUserId);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
+    public void clearForcedDisplayDensityForUser(int displayId, int userId) {
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
+                PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Must hold permission " +
+                    android.Manifest.permission.WRITE_SECURE_SETTINGS);
+        }
+        if (displayId != DEFAULT_DISPLAY) {
+            throw new IllegalArgumentException("Can only set the default display");
+        }
+
+        final int callingUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+                Binder.getCallingUid(), userId, false, true, "clearForcedDisplayDensityForUser",
+                null);
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized(mWindowMap) {
+                final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+                if (displayContent != null && mCurrentUserId == callingUserId) {
+                    setForcedDisplayDensityLocked(displayContent,
+                            displayContent.mInitialDisplayDensity);
+                }
+                Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                        Settings.Secure.DISPLAY_DENSITY_FORCED, "", callingUserId);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    /**
+     * @param userId the ID of the user
+     * @return the forced display density for the specified user, if set, or
+     *         {@code 0} if not set
+     */
+    private int getForcedDisplayDensityForUserLocked(int userId) {
+        String densityStr = Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                Settings.Secure.DISPLAY_DENSITY_FORCED, userId);
+        if (densityStr == null || densityStr.length() == 0) {
+            densityStr = SystemProperties.get(DENSITY_OVERRIDE, null);
+        }
+        if (densityStr != null && densityStr.length() > 0) {
+            try {
+                return Integer.parseInt(densityStr);
+            } catch (NumberFormatException ex) {
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Forces the given display to the use the specified density.
+     *
+     * @param displayContent the display to modify
+     * @param density the density in DPI to use
+     */
+    private void setForcedDisplayDensityLocked(@NonNull DisplayContent displayContent,
+            int density) {
+        displayContent.mBaseDisplayDensity = density;
+        reconfigureDisplayLocked(displayContent);
+    }
+
+    void reconfigureDisplayLocked(@NonNull DisplayContent displayContent) {
+        if (!mDisplayReady) {
+            return;
+        }
+        configureDisplayPolicyLocked(displayContent);
+        displayContent.setLayoutNeeded();
+
+        final int displayId = displayContent.getDisplayId();
+        boolean configChanged = updateOrientationFromAppTokensLocked(false /* inTransaction */,
+                displayId);
+        final Configuration currentDisplayConfig = displayContent.getConfiguration();
+        mTempConfiguration.setTo(currentDisplayConfig);
+        displayContent.computeScreenConfiguration(mTempConfiguration);
+        configChanged |= currentDisplayConfig.diff(mTempConfiguration) != 0;
+
+        if (configChanged) {
+            mWaitingForConfig = true;
+            startFreezingDisplayLocked(false /* inTransaction */, 0 /* exitAnim */,
+                    0 /* enterAnim */, displayContent);
+            mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayId).sendToTarget();
+        }
+
+        mWindowPlacerLocked.performSurfacePlacement();
+    }
+
+    void configureDisplayPolicyLocked(DisplayContent displayContent) {
+        mPolicy.setInitialDisplaySize(displayContent.getDisplay(),
+                displayContent.mBaseDisplayWidth,
+                displayContent.mBaseDisplayHeight,
+                displayContent.mBaseDisplayDensity);
+
+        DisplayInfo displayInfo = displayContent.getDisplayInfo();
+        mPolicy.setDisplayOverscan(displayContent.getDisplay(),
+                displayInfo.overscanLeft, displayInfo.overscanTop,
+                displayInfo.overscanRight, displayInfo.overscanBottom);
+    }
+
+    /**
+     * Get an array with display ids ordered by focus priority - last items should be given
+     * focus first. Sparse array just maps position to displayId.
+     */
+    // TODO: Maintain display list in focus order in ActivityManager and remove this call.
+    public void getDisplaysInFocusOrder(SparseIntArray displaysInFocusOrder) {
+        synchronized(mWindowMap) {
+            mRoot.getDisplaysInFocusOrder(displaysInFocusOrder);
+        }
+    }
+
+    @Override
+    public void setOverscan(int displayId, int left, int top, int right, int bottom) {
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
+                PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Must hold permission " +
+                    android.Manifest.permission.WRITE_SECURE_SETTINGS);
+        }
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized(mWindowMap) {
+                DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+                if (displayContent != null) {
+                    setOverscanLocked(displayContent, left, top, right, bottom);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void setOverscanLocked(DisplayContent displayContent,
+            int left, int top, int right, int bottom) {
+        final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+        displayInfo.overscanLeft = left;
+        displayInfo.overscanTop = top;
+        displayInfo.overscanRight = right;
+        displayInfo.overscanBottom = bottom;
+
+        mDisplaySettings.setOverscanLocked(displayInfo.uniqueId, displayInfo.name, left, top,
+                right, bottom);
+        mDisplaySettings.writeSettingsLocked();
+
+        reconfigureDisplayLocked(displayContent);
+    }
+
+    // -------------------------------------------------------------
+    // Internals
+    // -------------------------------------------------------------
+
+    final WindowState windowForClientLocked(Session session, IWindow client, boolean throwOnError) {
+        return windowForClientLocked(session, client.asBinder(), throwOnError);
+    }
+
+    final WindowState windowForClientLocked(Session session, IBinder client, boolean throwOnError) {
+        WindowState win = mWindowMap.get(client);
+        if (localLOGV) Slog.v(TAG_WM, "Looking up client " + client + ": " + win);
+        if (win == null) {
+            if (throwOnError) {
+                throw new IllegalArgumentException(
+                        "Requested window " + client + " does not exist");
+            }
+            Slog.w(TAG_WM, "Failed looking up window callers=" + Debug.getCallers(3));
+            return null;
+        }
+        if (session != null && win.mSession != session) {
+            if (throwOnError) {
+                throw new IllegalArgumentException("Requested window " + client + " is in session "
+                        + win.mSession + ", not " + session);
+            }
+            Slog.w(TAG_WM, "Failed looking up window callers=" + Debug.getCallers(3));
+            return null;
+        }
+
+        return win;
+    }
+
+    void makeWindowFreezingScreenIfNeededLocked(WindowState w) {
+        // If the screen is currently frozen or off, then keep
+        // it frozen/off until this window draws at its new
+        // orientation.
+        if (!w.mToken.okToDisplay() && mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
+            if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Changing surface while display frozen: " + w);
+            w.setOrientationChanging(true);
+            w.mLastFreezeDuration = 0;
+            mRoot.mOrientationChangeComplete = false;
+            if (mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_NONE) {
+                mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
+                // XXX should probably keep timeout from
+                // when we first froze the display.
+                mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT);
+                mH.sendEmptyMessageDelayed(H.WINDOW_FREEZE_TIMEOUT,
+                        WINDOW_FREEZE_TIMEOUT_DURATION);
+            }
+        }
+    }
+
+    /**
+     * @return bitmap indicating if another pass through layout must be made.
+     */
+    int handleAnimatingStoppedAndTransitionLocked() {
+        int changes = 0;
+
+        mAppTransition.setIdle();
+
+        for (int i = mNoAnimationNotifyOnTransitionFinished.size() - 1; i >= 0; i--) {
+            final IBinder token = mNoAnimationNotifyOnTransitionFinished.get(i);
+            mAppTransition.notifyAppTransitionFinishedLocked(token);
+        }
+        mNoAnimationNotifyOnTransitionFinished.clear();
+
+        // TODO: multi-display.
+        final DisplayContent dc = getDefaultDisplayContentLocked();
+
+        dc.mWallpaperController.hideDeferredWallpapersIfNeeded();
+
+        dc.onAppTransitionDone();
+
+        changes |= FINISH_LAYOUT_REDO_LAYOUT;
+        if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG_WM,
+                "Wallpaper layer changed: assigning layers + relayout");
+        dc.computeImeTarget(true /* updateImeTarget */);
+        mRoot.mWallpaperMayChange = true;
+        // Since the window list has been rebuilt, focus might have to be recomputed since the
+        // actual order of windows might have changed again.
+        mFocusMayChange = true;
+
+        return changes;
+    }
+
+    void checkDrawnWindowsLocked() {
+        if (mWaitingForDrawn.isEmpty() || mWaitingForDrawnCallback == null) {
+            return;
+        }
+        for (int j = mWaitingForDrawn.size() - 1; j >= 0; j--) {
+            WindowState win = mWaitingForDrawn.get(j);
+            if (DEBUG_SCREEN_ON) Slog.i(TAG_WM, "Waiting for drawn " + win +
+                    ": removed=" + win.mRemoved + " visible=" + win.isVisibleLw() +
+                    " mHasSurface=" + win.mHasSurface +
+                    " drawState=" + win.mWinAnimator.mDrawState);
+            if (win.mRemoved || !win.mHasSurface || !win.mPolicyVisibility) {
+                // Window has been removed or hidden; no draw will now happen, so stop waiting.
+                if (DEBUG_SCREEN_ON) Slog.w(TAG_WM, "Aborted waiting for drawn: " + win);
+                mWaitingForDrawn.remove(win);
+            } else if (win.hasDrawnLw()) {
+                // Window is now drawn (and shown).
+                if (DEBUG_SCREEN_ON) Slog.d(TAG_WM, "Window drawn win=" + win);
+                mWaitingForDrawn.remove(win);
+            }
+        }
+        if (mWaitingForDrawn.isEmpty()) {
+            if (DEBUG_SCREEN_ON) Slog.d(TAG_WM, "All windows drawn!");
+            mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT);
+            mH.sendEmptyMessage(H.ALL_WINDOWS_DRAWN);
+        }
+    }
+
+    void setHoldScreenLocked(final Session newHoldScreen) {
+        final boolean hold = newHoldScreen != null;
+
+        if (hold && mHoldingScreenOn != newHoldScreen) {
+            mHoldingScreenWakeLock.setWorkSource(new WorkSource(newHoldScreen.mUid));
+        }
+        mHoldingScreenOn = newHoldScreen;
+
+        final boolean state = mHoldingScreenWakeLock.isHeld();
+        if (hold != state) {
+            if (hold) {
+                if (DEBUG_KEEP_SCREEN_ON) {
+                    Slog.d(TAG_KEEP_SCREEN_ON, "Acquiring screen wakelock due to "
+                            + mRoot.mHoldScreenWindow);
+                }
+                mLastWakeLockHoldingWindow = mRoot.mHoldScreenWindow;
+                mLastWakeLockObscuringWindow = null;
+                mHoldingScreenWakeLock.acquire();
+                mPolicy.keepScreenOnStartedLw();
+            } else {
+                if (DEBUG_KEEP_SCREEN_ON) {
+                    Slog.d(TAG_KEEP_SCREEN_ON, "Releasing screen wakelock, obscured by "
+                            + mRoot.mObscuringWindow);
+                }
+                mLastWakeLockHoldingWindow = null;
+                mLastWakeLockObscuringWindow = mRoot.mObscuringWindow;
+                mPolicy.keepScreenOnStoppedLw();
+                mHoldingScreenWakeLock.release();
+            }
+        }
+    }
+
+    void requestTraversal() {
+        synchronized (mWindowMap) {
+            mWindowPlacerLocked.requestTraversal();
+        }
+    }
+
+    /** Note that Locked in this case is on mLayoutToAnim */
+    void scheduleAnimationLocked() {
+        mAnimator.scheduleAnimation();
+    }
+
+    // TODO: Move to DisplayContent
+    boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
+        WindowState newFocus = mRoot.computeFocusedWindow();
+        if (mCurrentFocus != newFocus) {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus");
+            // This check makes sure that we don't already have the focus
+            // change message pending.
+            mH.removeMessages(H.REPORT_FOCUS_CHANGE);
+            mH.sendEmptyMessage(H.REPORT_FOCUS_CHANGE);
+            // TODO(multidisplay): Focused windows on default display only.
+            final DisplayContent displayContent = getDefaultDisplayContentLocked();
+            boolean imWindowChanged = false;
+            if (mInputMethodWindow != null) {
+                final WindowState prevTarget = mInputMethodTarget;
+                final WindowState newTarget =
+                        displayContent.computeImeTarget(true /* updateImeTarget*/);
+
+                imWindowChanged = prevTarget != newTarget;
+
+                if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS
+                        && mode != UPDATE_FOCUS_WILL_PLACE_SURFACES) {
+                    final int prevImeAnimLayer = mInputMethodWindow.mWinAnimator.mAnimLayer;
+                    displayContent.assignWindowLayers(false /* setLayoutNeeded */);
+                    imWindowChanged |=
+                            prevImeAnimLayer != mInputMethodWindow.mWinAnimator.mAnimLayer;
+                }
+            }
+
+            if (imWindowChanged) {
+                mWindowsChanged = true;
+                displayContent.setLayoutNeeded();
+                newFocus = mRoot.computeFocusedWindow();
+            }
+
+            if (DEBUG_FOCUS_LIGHT || localLOGV) Slog.v(TAG_WM, "Changing focus from " +
+                    mCurrentFocus + " to " + newFocus + " Callers=" + Debug.getCallers(4));
+            final WindowState oldFocus = mCurrentFocus;
+            mCurrentFocus = newFocus;
+            mLosingFocus.remove(newFocus);
+
+            if (mCurrentFocus != null) {
+                mWinAddedSinceNullFocus.clear();
+                mWinRemovedSinceNullFocus.clear();
+            }
+
+            int focusChanged = mPolicy.focusChangedLw(oldFocus, newFocus);
+
+            if (imWindowChanged && oldFocus != mInputMethodWindow) {
+                // Focus of the input method window changed. Perform layout if needed.
+                if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
+                    displayContent.performLayout(true /*initial*/,  updateInputWindows);
+                    focusChanged &= ~FINISH_LAYOUT_REDO_LAYOUT;
+                } else if (mode == UPDATE_FOCUS_WILL_PLACE_SURFACES) {
+                    // Client will do the layout, but we need to assign layers
+                    // for handleNewWindowLocked() below.
+                    displayContent.assignWindowLayers(false /* setLayoutNeeded */);
+                }
+            }
+
+            if ((focusChanged & FINISH_LAYOUT_REDO_LAYOUT) != 0) {
+                // The change in focus caused us to need to do a layout.  Okay.
+                displayContent.setLayoutNeeded();
+                if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
+                    displayContent.performLayout(true /*initial*/, updateInputWindows);
+                }
+            }
+
+            if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) {
+                // If we defer assigning layers, then the caller is responsible for
+                // doing this part.
+                mInputMonitor.setInputFocusLw(mCurrentFocus, updateInputWindows);
+            }
+
+            displayContent.adjustForImeIfNeeded();
+
+            // 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.
+            displayContent.scheduleToastWindowsTimeoutIfNeededLocked(oldFocus, newFocus);
+
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+            return true;
+        }
+        return false;
+    }
+
+    void startFreezingDisplayLocked(boolean inTransaction, int exitAnim, int enterAnim) {
+        startFreezingDisplayLocked(inTransaction, exitAnim, enterAnim,
+                getDefaultDisplayContentLocked());
+    }
+
+    void startFreezingDisplayLocked(boolean inTransaction, int exitAnim, int enterAnim,
+            DisplayContent displayContent) {
+        if (mDisplayFrozen) {
+            return;
+        }
+
+        if (!displayContent.isReady() || !mPolicy.isScreenOn() || !displayContent.okToAnimate()) {
+            // No need to freeze the screen before the display is ready,  if the screen is off,
+            // or we can't currently animate.
+            return;
+        }
+
+        if (DEBUG_ORIENTATION) Slog.d(TAG_WM,
+                "startFreezingDisplayLocked: inTransaction=" + inTransaction
+                + " exitAnim=" + exitAnim + " enterAnim=" + enterAnim
+                + " called by " + Debug.getCallers(8));
+        mScreenFrozenLock.acquire();
+
+        mDisplayFrozen = true;
+        mDisplayFreezeTime = SystemClock.elapsedRealtime();
+        mLastFinishedFreezeSource = null;
+
+        // {@link mDisplayFrozen} prevents us from freezing on multiple displays at the same time.
+        // As a result, we only track the display that has initially froze the screen.
+        mFrozenDisplayId = displayContent.getDisplayId();
+
+        mInputMonitor.freezeInputDispatchingLw();
+
+        // Clear the last input window -- that is just used for
+        // clean transitions between IMEs, and if we are freezing
+        // the screen then the whole world is changing behind the scenes.
+        mPolicy.setLastInputMethodWindowLw(null, null);
+
+        if (mAppTransition.isTransitionSet()) {
+            mAppTransition.freeze();
+        }
+
+        if (PROFILE_ORIENTATION) {
+            File file = new File("/data/system/frozen");
+            Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
+        }
+
+        // TODO(multidisplay): rotation on non-default displays
+        if (CUSTOM_SCREEN_ROTATION && displayContent.isDefaultDisplay) {
+            mExitAnimId = exitAnim;
+            mEnterAnimId = enterAnim;
+            ScreenRotationAnimation screenRotationAnimation =
+                    mAnimator.getScreenRotationAnimationLocked(mFrozenDisplayId);
+            if (screenRotationAnimation != null) {
+                screenRotationAnimation.kill();
+            }
+
+            // Check whether the current screen contains any secure content.
+            boolean isSecure = displayContent.hasSecureWindowOnScreen();
+
+            displayContent.updateDisplayInfo();
+            screenRotationAnimation = new ScreenRotationAnimation(mContext, displayContent,
+                    mFxSession, inTransaction, mPolicy.isDefaultOrientationForced(), isSecure,
+                    this);
+            mAnimator.setScreenRotationAnimationLocked(mFrozenDisplayId,
+                    screenRotationAnimation);
+        }
+    }
+
+    void stopFreezingDisplayLocked() {
+        if (!mDisplayFrozen) {
+            return;
+        }
+
+        if (mWaitingForConfig || mAppsFreezingScreen > 0
+                || mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_ACTIVE
+                || mClientFreezingScreen || !mOpeningApps.isEmpty()) {
+            if (DEBUG_ORIENTATION) Slog.d(TAG_WM,
+                "stopFreezingDisplayLocked: Returning mWaitingForConfig=" + mWaitingForConfig
+                + ", mAppsFreezingScreen=" + mAppsFreezingScreen
+                + ", mWindowsFreezingScreen=" + mWindowsFreezingScreen
+                + ", mClientFreezingScreen=" + mClientFreezingScreen
+                + ", mOpeningApps.size()=" + mOpeningApps.size());
+            return;
+        }
+
+        if (DEBUG_ORIENTATION) Slog.d(TAG_WM,
+                "stopFreezingDisplayLocked: Unfreezing now");
+
+        final DisplayContent displayContent = mRoot.getDisplayContent(mFrozenDisplayId);
+
+        // We must make a local copy of the displayId as it can be potentially overwritten later on
+        // in this method. For example, {@link startFreezingDisplayLocked} may be called as a result
+        // of update rotation, but we reference the frozen display after that call in this method.
+        final int displayId = mFrozenDisplayId;
+        mFrozenDisplayId = INVALID_DISPLAY;
+        mDisplayFrozen = false;
+        mLastDisplayFreezeDuration = (int)(SystemClock.elapsedRealtime() - mDisplayFreezeTime);
+        StringBuilder sb = new StringBuilder(128);
+        sb.append("Screen frozen for ");
+        TimeUtils.formatDuration(mLastDisplayFreezeDuration, sb);
+        if (mLastFinishedFreezeSource != null) {
+            sb.append(" due to ");
+            sb.append(mLastFinishedFreezeSource);
+        }
+        Slog.i(TAG_WM, sb.toString());
+        mH.removeMessages(H.APP_FREEZE_TIMEOUT);
+        mH.removeMessages(H.CLIENT_FREEZE_TIMEOUT);
+        if (PROFILE_ORIENTATION) {
+            Debug.stopMethodTracing();
+        }
+
+        boolean updateRotation = false;
+
+        ScreenRotationAnimation screenRotationAnimation =
+                mAnimator.getScreenRotationAnimationLocked(displayId);
+        if (CUSTOM_SCREEN_ROTATION && screenRotationAnimation != null
+                && screenRotationAnimation.hasScreenshot()) {
+            if (DEBUG_ORIENTATION) Slog.i(TAG_WM, "**** Dismissing screen rotation animation");
+            // TODO(multidisplay): rotation on main screen only.
+            DisplayInfo displayInfo = displayContent.getDisplayInfo();
+            // Get rotation animation again, with new top window
+            boolean isDimming = displayContent.isDimming();
+            if (!mPolicy.validateRotationAnimationLw(mExitAnimId, mEnterAnimId, isDimming)) {
+                mExitAnimId = mEnterAnimId = 0;
+            }
+            if (screenRotationAnimation.dismiss(mFxSession, MAX_ANIMATION_DURATION,
+                    getTransitionAnimationScaleLocked(), displayInfo.logicalWidth,
+                        displayInfo.logicalHeight, mExitAnimId, mEnterAnimId)) {
+                scheduleAnimationLocked();
+            } else {
+                screenRotationAnimation.kill();
+                mAnimator.setScreenRotationAnimationLocked(displayId, null);
+                updateRotation = true;
+            }
+        } else {
+            if (screenRotationAnimation != null) {
+                screenRotationAnimation.kill();
+                mAnimator.setScreenRotationAnimationLocked(displayId, null);
+            }
+            updateRotation = true;
+        }
+
+        mInputMonitor.thawInputDispatchingLw();
+
+        boolean configChanged;
+
+        // While the display is frozen we don't re-compute the orientation
+        // to avoid inconsistent states.  However, something interesting
+        // could have actually changed during that time so re-evaluate it
+        // now to catch that.
+        configChanged = updateOrientationFromAppTokensLocked(false, displayId);
+
+        // A little kludge: a lot could have happened while the
+        // display was frozen, so now that we are coming back we
+        // do a gc so that any remote references the system
+        // processes holds on others can be released if they are
+        // no longer needed.
+        mH.removeMessages(H.FORCE_GC);
+        mH.sendEmptyMessageDelayed(H.FORCE_GC, 2000);
+
+        mScreenFrozenLock.release();
+
+        if (updateRotation) {
+            if (DEBUG_ORIENTATION) Slog.d(TAG_WM, "Performing post-rotate rotation");
+            configChanged |= displayContent.updateRotationUnchecked(
+                    false /* inTransaction */);
+        }
+
+        if (configChanged) {
+            mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayId).sendToTarget();
+        }
+    }
+
+    static int getPropertyInt(String[] tokens, int index, int defUnits, int defDps,
+            DisplayMetrics dm) {
+        if (index < tokens.length) {
+            String str = tokens[index];
+            if (str != null && str.length() > 0) {
+                try {
+                    int val = Integer.parseInt(str);
+                    return val;
+                } catch (Exception e) {
+                }
+            }
+        }
+        if (defUnits == TypedValue.COMPLEX_UNIT_PX) {
+            return defDps;
+        }
+        int val = (int)TypedValue.applyDimension(defUnits, defDps, dm);
+        return val;
+    }
+
+    void createWatermarkInTransaction() {
+        if (mWatermark != null) {
+            return;
+        }
+
+        File file = new File("/system/etc/setup.conf");
+        FileInputStream in = null;
+        DataInputStream ind = null;
+        try {
+            in = new FileInputStream(file);
+            ind = new DataInputStream(in);
+            String line = ind.readLine();
+            if (line != null) {
+                String[] toks = line.split("%");
+                if (toks != null && toks.length > 0) {
+                    // TODO(multi-display): Show watermarks on secondary displays.
+                    final DisplayContent displayContent = getDefaultDisplayContentLocked();
+                    mWatermark = new Watermark(displayContent.getDisplay(),
+                            displayContent.mRealDisplayMetrics, mFxSession, toks);
+                }
+            }
+        } catch (FileNotFoundException e) {
+        } catch (IOException e) {
+        } finally {
+            if (ind != null) {
+                try {
+                    ind.close();
+                } catch (IOException e) {
+                }
+            } else if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+    }
+
+    @Override
+    public void setRecentsVisibility(boolean visible) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Caller does not hold permission "
+                    + android.Manifest.permission.STATUS_BAR);
+        }
+
+        synchronized (mWindowMap) {
+            mPolicy.setRecentsVisibilityLw(visible);
+        }
+    }
+
+    @Override
+    public void setPipVisibility(boolean visible) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Caller does not hold permission "
+                    + android.Manifest.permission.STATUS_BAR);
+        }
+
+        synchronized (mWindowMap) {
+            mPolicy.setPipVisibilityLw(visible);
+        }
+    }
+
+    @Override
+    public void statusBarVisibilityChanged(int visibility) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Caller does not hold permission "
+                    + android.Manifest.permission.STATUS_BAR);
+        }
+
+        synchronized (mWindowMap) {
+            mLastStatusBarVisibility = visibility;
+            visibility = mPolicy.adjustSystemUiVisibilityLw(visibility);
+            updateStatusBarVisibilityLocked(visibility);
+        }
+    }
+
+    // TODO(multidisplay): StatusBar on multiple screens?
+    private boolean updateStatusBarVisibilityLocked(int visibility) {
+        if (mLastDispatchedSystemUiVisibility == visibility) {
+            return false;
+        }
+        final int globalDiff = (visibility ^ mLastDispatchedSystemUiVisibility)
+                // We are only interested in differences of one of the
+                // clearable flags...
+                & View.SYSTEM_UI_CLEARABLE_FLAGS
+                // ...if it has actually been cleared.
+                & ~visibility;
+
+        mLastDispatchedSystemUiVisibility = visibility;
+        mInputManager.setSystemUiVisibility(visibility);
+        getDefaultDisplayContentLocked().updateSystemUiVisibility(visibility, globalDiff);
+        return true;
+    }
+
+    @Override
+    public void reevaluateStatusBarVisibility() {
+        synchronized (mWindowMap) {
+            int visibility = mPolicy.adjustSystemUiVisibilityLw(mLastStatusBarVisibility);
+            if (updateStatusBarVisibilityLocked(visibility)) {
+                mWindowPlacerLocked.requestTraversal();
+            }
+        }
+    }
+
+    /**
+     * Used by ActivityManager to determine where to position an app with aspect ratio shorter then
+     * the screen is.
+     * @see WindowManagerPolicy#getNavBarPosition()
+     */
+    public int getNavBarPosition() {
+        synchronized (mWindowMap) {
+            // Perform layout if it was scheduled before to make sure that we get correct nav bar
+            // position when doing rotations.
+            final DisplayContent defaultDisplayContent = getDefaultDisplayContentLocked();
+            defaultDisplayContent.performLayout(false /* initial */,
+                    false /* updateInputWindows */);
+            return mPolicy.getNavBarPosition();
+        }
+    }
+
+    @Override
+    public WindowManagerPolicy.InputConsumer createInputConsumer(Looper looper, String name,
+            InputEventReceiver.Factory inputEventReceiverFactory) {
+        synchronized (mWindowMap) {
+            return mInputMonitor.createInputConsumer(looper, name, inputEventReceiverFactory);
+        }
+    }
+
+    @Override
+    public void createInputConsumer(String name, InputChannel inputChannel) {
+        synchronized (mWindowMap) {
+            mInputMonitor.createInputConsumer(name, inputChannel);
+        }
+    }
+
+    @Override
+    public boolean destroyInputConsumer(String name) {
+        synchronized (mWindowMap) {
+            return mInputMonitor.destroyInputConsumer(name);
+        }
+    }
+
+    @Override
+    public Region getCurrentImeTouchRegion() {
+        if (mContext.checkCallingOrSelfPermission(RESTRICTED_VR_ACCESS) != PERMISSION_GRANTED) {
+            throw new SecurityException("getCurrentImeTouchRegion is restricted to VR services");
+        }
+        synchronized (mWindowMap) {
+            final Region r = new Region();
+            if (mInputMethodWindow != null) {
+                mInputMethodWindow.getTouchableRegion(r);
+            }
+            return r;
+        }
+    }
+
+    @Override
+    public boolean hasNavigationBar() {
+        return mPolicy.hasNavigationBar();
+    }
+
+    @Override
+    public void lockNow(Bundle options) {
+        mPolicy.lockNow(options);
+    }
+
+    public void showRecentApps(boolean fromHome) {
+        mPolicy.showRecentApps(fromHome);
+    }
+
+    @Override
+    public boolean isSafeModeEnabled() {
+        return mSafeMode;
+    }
+
+    @Override
+    public boolean clearWindowContentFrameStats(IBinder token) {
+        if (!checkCallingPermission(Manifest.permission.FRAME_STATS,
+                "clearWindowContentFrameStats()")) {
+            throw new SecurityException("Requires FRAME_STATS permission");
+        }
+        synchronized (mWindowMap) {
+            WindowState windowState = mWindowMap.get(token);
+            if (windowState == null) {
+                return false;
+            }
+            WindowSurfaceController surfaceController = windowState.mWinAnimator.mSurfaceController;
+            if (surfaceController == null) {
+                return false;
+            }
+            return surfaceController.clearWindowContentFrameStats();
+        }
+    }
+
+    @Override
+    public WindowContentFrameStats getWindowContentFrameStats(IBinder token) {
+        if (!checkCallingPermission(Manifest.permission.FRAME_STATS,
+                "getWindowContentFrameStats()")) {
+            throw new SecurityException("Requires FRAME_STATS permission");
+        }
+        synchronized (mWindowMap) {
+            WindowState windowState = mWindowMap.get(token);
+            if (windowState == null) {
+                return null;
+            }
+            WindowSurfaceController surfaceController = windowState.mWinAnimator.mSurfaceController;
+            if (surfaceController == null) {
+                return null;
+            }
+            if (mTempWindowRenderStats == null) {
+                mTempWindowRenderStats = new WindowContentFrameStats();
+            }
+            WindowContentFrameStats stats = mTempWindowRenderStats;
+            if (!surfaceController.getWindowContentFrameStats(stats)) {
+                return null;
+            }
+            return stats;
+        }
+    }
+
+    public void notifyAppRelaunching(IBinder token) {
+        synchronized (mWindowMap) {
+            final AppWindowToken appWindow = mRoot.getAppWindowToken(token);
+            if (appWindow != null) {
+                appWindow.startRelaunching();
+            }
+        }
+    }
+
+    public void notifyAppRelaunchingFinished(IBinder token) {
+        synchronized (mWindowMap) {
+            final AppWindowToken appWindow = mRoot.getAppWindowToken(token);
+            if (appWindow != null) {
+                appWindow.finishRelaunching();
+            }
+        }
+    }
+
+    public void notifyAppRelaunchesCleared(IBinder token) {
+        synchronized (mWindowMap) {
+            final AppWindowToken appWindow = mRoot.getAppWindowToken(token);
+            if (appWindow != null) {
+                appWindow.clearRelaunching();
+            }
+        }
+    }
+
+    public void notifyAppResumedFinished(IBinder token) {
+        synchronized (mWindowMap) {
+            final AppWindowToken appWindow = mRoot.getAppWindowToken(token);
+            if (appWindow != null) {
+                mUnknownAppVisibilityController.notifyAppResumedFinished(appWindow);
+            }
+        }
+    }
+
+    /**
+     * Called when a task has been removed from the recent tasks list.
+     * <p>
+     * Note: This doesn't go through {@link TaskWindowContainerController} yet as the window
+     * container may not exist when this happens.
+     */
+    public void notifyTaskRemovedFromRecents(int taskId, int userId) {
+        synchronized (mWindowMap) {
+            mTaskSnapshotController.notifyTaskRemovedFromRecents(taskId, userId);
+        }
+    }
+
+    @Override
+    public int getDockedDividerInsetsLw() {
+        return getDefaultDisplayContentLocked().getDockedDividerController().getContentInsets();
+    }
+
+    private void dumpPolicyLocked(PrintWriter pw, String[] args, boolean dumpAll) {
+        pw.println("WINDOW MANAGER POLICY STATE (dumpsys window policy)");
+        mPolicy.dump("    ", pw, args);
+    }
+
+    private void dumpAnimatorLocked(PrintWriter pw, String[] args, boolean dumpAll) {
+        pw.println("WINDOW MANAGER ANIMATOR STATE (dumpsys window animator)");
+        mAnimator.dumpLocked(pw, "    ", dumpAll);
+    }
+
+    private void dumpTokensLocked(PrintWriter pw, boolean dumpAll) {
+        pw.println("WINDOW MANAGER TOKENS (dumpsys window tokens)");
+        mRoot.dumpTokens(pw, dumpAll);
+        if (!mOpeningApps.isEmpty() || !mClosingApps.isEmpty()) {
+            pw.println();
+            if (mOpeningApps.size() > 0) {
+                pw.print("  mOpeningApps="); pw.println(mOpeningApps);
+            }
+            if (mClosingApps.size() > 0) {
+                pw.print("  mClosingApps="); pw.println(mClosingApps);
+            }
+        }
+    }
+
+    private void dumpSessionsLocked(PrintWriter pw, boolean dumpAll) {
+        pw.println("WINDOW MANAGER SESSIONS (dumpsys window sessions)");
+        for (int i=0; i<mSessions.size(); i++) {
+            Session s = mSessions.valueAt(i);
+            pw.print("  Session "); pw.print(s); pw.println(':');
+            s.dump(pw, "    ");
+        }
+    }
+
+    private void writeToProtoLocked(ProtoOutputStream proto) {
+        mPolicy.writeToProto(proto, POLICY);
+        mRoot.writeToProto(proto);
+        if (mCurrentFocus != null) {
+            mCurrentFocus.writeIdentifierToProto(proto, FOCUSED_WINDOW);
+        }
+        if (mFocusedApp != null) {
+            mFocusedApp.writeNameToProto(proto, FOCUSED_APP);
+        }
+        if (mInputMethodWindow != null) {
+            mInputMethodWindow.writeIdentifierToProto(proto, INPUT_METHOD_WINDOW);
+        }
+        proto.write(DISPLAY_FROZEN, mDisplayFrozen);
+        final DisplayContent defaultDisplayContent = getDefaultDisplayContentLocked();
+        proto.write(ROTATION, defaultDisplayContent.getRotation());
+        proto.write(LAST_ORIENTATION, defaultDisplayContent.getLastOrientation());
+        mAppTransition.writeToProto(proto, APP_TRANSITION);
+    }
+
+    private void dumpWindowsLocked(PrintWriter pw, boolean dumpAll,
+            ArrayList<WindowState> windows) {
+        pw.println("WINDOW MANAGER WINDOWS (dumpsys window windows)");
+        dumpWindowsNoHeaderLocked(pw, dumpAll, windows);
+    }
+
+    private void dumpWindowsNoHeaderLocked(PrintWriter pw, boolean dumpAll,
+            ArrayList<WindowState> windows) {
+        mRoot.dumpWindowsNoHeader(pw, dumpAll, windows);
+
+        if (!mHidingNonSystemOverlayWindows.isEmpty()) {
+            pw.println();
+            pw.println("  Hiding System Alert Windows:");
+            for (int i = mHidingNonSystemOverlayWindows.size() - 1; i >= 0; i--) {
+                final WindowState w = mHidingNonSystemOverlayWindows.get(i);
+                pw.print("  #"); pw.print(i); pw.print(' ');
+                pw.print(w);
+                if (dumpAll) {
+                    pw.println(":");
+                    w.dump(pw, "    ", true);
+                } else {
+                    pw.println();
+                }
+            }
+        }
+        if (mPendingRemove.size() > 0) {
+            pw.println();
+            pw.println("  Remove pending for:");
+            for (int i=mPendingRemove.size()-1; i>=0; i--) {
+                WindowState w = mPendingRemove.get(i);
+                if (windows == null || windows.contains(w)) {
+                    pw.print("  Remove #"); pw.print(i); pw.print(' ');
+                            pw.print(w);
+                    if (dumpAll) {
+                        pw.println(":");
+                        w.dump(pw, "    ", true);
+                    } else {
+                        pw.println();
+                    }
+                }
+            }
+        }
+        if (mForceRemoves != null && mForceRemoves.size() > 0) {
+            pw.println();
+            pw.println("  Windows force removing:");
+            for (int i=mForceRemoves.size()-1; i>=0; i--) {
+                WindowState w = mForceRemoves.get(i);
+                pw.print("  Removing #"); pw.print(i); pw.print(' ');
+                        pw.print(w);
+                if (dumpAll) {
+                    pw.println(":");
+                    w.dump(pw, "    ", true);
+                } else {
+                    pw.println();
+                }
+            }
+        }
+        if (mDestroySurface.size() > 0) {
+            pw.println();
+            pw.println("  Windows waiting to destroy their surface:");
+            for (int i=mDestroySurface.size()-1; i>=0; i--) {
+                WindowState w = mDestroySurface.get(i);
+                if (windows == null || windows.contains(w)) {
+                    pw.print("  Destroy #"); pw.print(i); pw.print(' ');
+                            pw.print(w);
+                    if (dumpAll) {
+                        pw.println(":");
+                        w.dump(pw, "    ", true);
+                    } else {
+                        pw.println();
+                    }
+                }
+            }
+        }
+        if (mLosingFocus.size() > 0) {
+            pw.println();
+            pw.println("  Windows losing focus:");
+            for (int i=mLosingFocus.size()-1; i>=0; i--) {
+                WindowState w = mLosingFocus.get(i);
+                if (windows == null || windows.contains(w)) {
+                    pw.print("  Losing #"); pw.print(i); pw.print(' ');
+                            pw.print(w);
+                    if (dumpAll) {
+                        pw.println(":");
+                        w.dump(pw, "    ", true);
+                    } else {
+                        pw.println();
+                    }
+                }
+            }
+        }
+        if (mResizingWindows.size() > 0) {
+            pw.println();
+            pw.println("  Windows waiting to resize:");
+            for (int i=mResizingWindows.size()-1; i>=0; i--) {
+                WindowState w = mResizingWindows.get(i);
+                if (windows == null || windows.contains(w)) {
+                    pw.print("  Resizing #"); pw.print(i); pw.print(' ');
+                            pw.print(w);
+                    if (dumpAll) {
+                        pw.println(":");
+                        w.dump(pw, "    ", true);
+                    } else {
+                        pw.println();
+                    }
+                }
+            }
+        }
+        if (mWaitingForDrawn.size() > 0) {
+            pw.println();
+            pw.println("  Clients waiting for these windows to be drawn:");
+            for (int i=mWaitingForDrawn.size()-1; i>=0; i--) {
+                WindowState win = mWaitingForDrawn.get(i);
+                pw.print("  Waiting #"); pw.print(i); pw.print(' '); pw.print(win);
+            }
+        }
+        pw.println();
+        pw.print("  mGlobalConfiguration="); pw.println(mRoot.getConfiguration());
+        pw.print("  mHasPermanentDpad="); pw.println(mHasPermanentDpad);
+        pw.print("  mCurrentFocus="); pw.println(mCurrentFocus);
+        if (mLastFocus != mCurrentFocus) {
+            pw.print("  mLastFocus="); pw.println(mLastFocus);
+        }
+        pw.print("  mFocusedApp="); pw.println(mFocusedApp);
+        if (mInputMethodTarget != null) {
+            pw.print("  mInputMethodTarget="); pw.println(mInputMethodTarget);
+        }
+        pw.print("  mInTouchMode="); pw.print(mInTouchMode);
+                pw.print(" mLayoutSeq="); pw.println(mLayoutSeq);
+        pw.print("  mLastDisplayFreezeDuration=");
+                TimeUtils.formatDuration(mLastDisplayFreezeDuration, pw);
+                if ( mLastFinishedFreezeSource != null) {
+                    pw.print(" due to ");
+                    pw.print(mLastFinishedFreezeSource);
+                }
+                pw.println();
+        pw.print("  mLastWakeLockHoldingWindow=");pw.print(mLastWakeLockHoldingWindow);
+                pw.print(" mLastWakeLockObscuringWindow="); pw.print(mLastWakeLockObscuringWindow);
+                pw.println();
+
+        mInputMonitor.dump(pw, "  ");
+        mUnknownAppVisibilityController.dump(pw, "  ");
+        mTaskSnapshotController.dump(pw, "  ");
+
+        if (dumpAll) {
+            pw.print("  mSystemDecorLayer="); pw.print(mSystemDecorLayer);
+                    pw.print(" mScreenRect="); pw.println(mScreenRect.toShortString());
+            if (mLastStatusBarVisibility != 0) {
+                pw.print("  mLastStatusBarVisibility=0x");
+                        pw.println(Integer.toHexString(mLastStatusBarVisibility));
+            }
+            if (mInputMethodWindow != null) {
+                pw.print("  mInputMethodWindow="); pw.println(mInputMethodWindow);
+            }
+            mWindowPlacerLocked.dump(pw, "  ");
+            mRoot.mWallpaperController.dump(pw, "  ");
+            pw.print("  mSystemBooted="); pw.print(mSystemBooted);
+                    pw.print(" mDisplayEnabled="); pw.println(mDisplayEnabled);
+
+            mRoot.dumpLayoutNeededDisplayIds(pw);
+
+            pw.print("  mTransactionSequence="); pw.println(mTransactionSequence);
+            pw.print("  mDisplayFrozen="); pw.print(mDisplayFrozen);
+                    pw.print(" windows="); pw.print(mWindowsFreezingScreen);
+                    pw.print(" client="); pw.print(mClientFreezingScreen);
+                    pw.print(" apps="); pw.print(mAppsFreezingScreen);
+                    pw.print(" waitingForConfig="); pw.println(mWaitingForConfig);
+            final DisplayContent defaultDisplayContent = getDefaultDisplayContentLocked();
+            pw.print("  mRotation="); pw.print(defaultDisplayContent.getRotation());
+                    pw.print(" mAltOrientation=");
+                            pw.println(defaultDisplayContent.getAltOrientation());
+            pw.print("  mLastWindowForcedOrientation=");
+                    pw.print(defaultDisplayContent.getLastWindowForcedOrientation());
+                    pw.print(" mLastOrientation=");
+                            pw.println(defaultDisplayContent.getLastOrientation());
+            pw.print("  mDeferredRotationPauseCount="); pw.println(mDeferredRotationPauseCount);
+            pw.print("  Animation settings: disabled="); pw.print(mAnimationsDisabled);
+                    pw.print(" window="); pw.print(mWindowAnimationScaleSetting);
+                    pw.print(" transition="); pw.print(mTransitionAnimationScaleSetting);
+                    pw.print(" animator="); pw.println(mAnimatorDurationScaleSetting);
+            pw.print("  mSkipAppTransitionAnimation=");pw.println(mSkipAppTransitionAnimation);
+            pw.println("  mLayoutToAnim:");
+            mAppTransition.dump(pw, "    ");
+        }
+    }
+
+    private boolean dumpWindows(PrintWriter pw, String name, String[] args, int opti,
+            boolean dumpAll) {
+        final ArrayList<WindowState> windows = new ArrayList();
+        if ("apps".equals(name) || "visible".equals(name) || "visible-apps".equals(name)) {
+            final boolean appsOnly = name.contains("apps");
+            final boolean visibleOnly = name.contains("visible");
+            synchronized(mWindowMap) {
+                if (appsOnly) {
+                    mRoot.dumpDisplayContents(pw);
+                }
+
+                mRoot.forAllWindows((w) -> {
+                    if ((!visibleOnly || w.mWinAnimator.getShown())
+                            && (!appsOnly || w.mAppToken != null)) {
+                        windows.add(w);
+                    }
+                }, true /* traverseTopToBottom */);
+            }
+        } else {
+            synchronized(mWindowMap) {
+                mRoot.getWindowsByName(windows, name);
+            }
+        }
+
+        if (windows.size() <= 0) {
+            return false;
+        }
+
+        synchronized(mWindowMap) {
+            dumpWindowsLocked(pw, dumpAll, windows);
+        }
+        return true;
+    }
+
+    private void dumpLastANRLocked(PrintWriter pw) {
+        pw.println("WINDOW MANAGER LAST ANR (dumpsys window lastanr)");
+        if (mLastANRState == null) {
+            pw.println("  <no ANR has occurred since boot>");
+        } else {
+            pw.println(mLastANRState);
+        }
+    }
+
+    /**
+     * Saves information about the state of the window manager at
+     * the time an ANR occurred before anything else in the system changes
+     * in response.
+     *
+     * @param appWindowToken The application that ANR'd, may be null.
+     * @param windowState The window that ANR'd, may be null.
+     * @param reason The reason for the ANR, may be null.
+     */
+    void saveANRStateLocked(AppWindowToken appWindowToken, WindowState windowState, String reason) {
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new FastPrintWriter(sw, false, 1024);
+        pw.println("  ANR time: " + DateFormat.getDateTimeInstance().format(new Date()));
+        if (appWindowToken != null) {
+            pw.println("  Application at fault: " + appWindowToken.stringName);
+        }
+        if (windowState != null) {
+            pw.println("  Window at fault: " + windowState.mAttrs.getTitle());
+        }
+        if (reason != null) {
+            pw.println("  Reason: " + reason);
+        }
+        if (!mWinAddedSinceNullFocus.isEmpty()) {
+            pw.println("  Windows added since null focus: " + mWinAddedSinceNullFocus);
+        }
+        if (!mWinRemovedSinceNullFocus.isEmpty()) {
+            pw.println("  Windows removed since null focus: " + mWinRemovedSinceNullFocus);
+        }
+        pw.println();
+        dumpWindowsNoHeaderLocked(pw, true, null);
+        pw.println();
+        pw.println("Last ANR continued");
+        mRoot.dumpDisplayContents(pw);
+        pw.close();
+        mLastANRState = sw.toString();
+
+        mH.removeMessages(H.RESET_ANR_MESSAGE);
+        mH.sendEmptyMessageDelayed(H.RESET_ANR_MESSAGE, LAST_ANR_LIFETIME_DURATION_MSECS);
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+
+        boolean dumpAll = false;
+        boolean useProto = false;
+
+        int opti = 0;
+        while (opti < args.length) {
+            String opt = args[opti];
+            if (opt == null || opt.length() <= 0 || opt.charAt(0) != '-') {
+                break;
+            }
+            opti++;
+            if ("-a".equals(opt)) {
+                dumpAll = true;
+            } else if ("--proto".equals(opt)) {
+                useProto = true;
+            } else if ("-h".equals(opt)) {
+                pw.println("Window manager dump options:");
+                pw.println("  [-a] [-h] [cmd] ...");
+                pw.println("  cmd may be one of:");
+                pw.println("    l[astanr]: last ANR information");
+                pw.println("    p[policy]: policy state");
+                pw.println("    a[animator]: animator state");
+                pw.println("    s[essions]: active sessions");
+                pw.println("    surfaces: active surfaces (debugging enabled only)");
+                pw.println("    d[isplays]: active display contents");
+                pw.println("    t[okens]: token list");
+                pw.println("    w[indows]: window list");
+                pw.println("  cmd may also be a NAME to dump windows.  NAME may");
+                pw.println("    be a partial substring in a window name, a");
+                pw.println("    Window hex object identifier, or");
+                pw.println("    \"all\" for all windows, or");
+                pw.println("    \"visible\" for the visible windows.");
+                pw.println("    \"visible-apps\" for the visible app windows.");
+                pw.println("  -a: include all available server state.");
+                pw.println("  --proto: output dump in protocol buffer format.");
+                return;
+            } else {
+                pw.println("Unknown argument: " + opt + "; use -h for help");
+            }
+        }
+
+        if (useProto) {
+            final ProtoOutputStream proto = new ProtoOutputStream(fd);
+            synchronized (mWindowMap) {
+                writeToProtoLocked(proto);
+            }
+            proto.flush();
+            return;
+        }
+        // Is the caller requesting to dump a particular piece of data?
+        if (opti < args.length) {
+            String cmd = args[opti];
+            opti++;
+            if ("lastanr".equals(cmd) || "l".equals(cmd)) {
+                synchronized(mWindowMap) {
+                    dumpLastANRLocked(pw);
+                }
+                return;
+            } else if ("policy".equals(cmd) || "p".equals(cmd)) {
+                synchronized(mWindowMap) {
+                    dumpPolicyLocked(pw, args, true);
+                }
+                return;
+            } else if ("animator".equals(cmd) || "a".equals(cmd)) {
+                synchronized(mWindowMap) {
+                    dumpAnimatorLocked(pw, args, true);
+                }
+                return;
+            } else if ("sessions".equals(cmd) || "s".equals(cmd)) {
+                synchronized(mWindowMap) {
+                    dumpSessionsLocked(pw, true);
+                }
+                return;
+            } else if ("surfaces".equals(cmd)) {
+                synchronized(mWindowMap) {
+                    WindowSurfaceController.SurfaceTrace.dumpAllSurfaces(pw, null);
+                }
+                return;
+            } else if ("displays".equals(cmd) || "d".equals(cmd)) {
+                synchronized(mWindowMap) {
+                    mRoot.dumpDisplayContents(pw);
+                }
+                return;
+            } else if ("tokens".equals(cmd) || "t".equals(cmd)) {
+                synchronized(mWindowMap) {
+                    dumpTokensLocked(pw, true);
+                }
+                return;
+            } else if ("windows".equals(cmd) || "w".equals(cmd)) {
+                synchronized(mWindowMap) {
+                    dumpWindowsLocked(pw, true, null);
+                }
+                return;
+            } else if ("all".equals(cmd) || "a".equals(cmd)) {
+                synchronized(mWindowMap) {
+                    dumpWindowsLocked(pw, true, null);
+                }
+                return;
+            } else if ("containers".equals(cmd)) {
+                synchronized(mWindowMap) {
+                    StringBuilder output = new StringBuilder();
+                    mRoot.dumpChildrenNames(output, " ");
+                    pw.println(output.toString());
+                    pw.println(" ");
+                    mRoot.forAllWindows(w -> {pw.println(w);}, true /* traverseTopToBottom */);
+                }
+                return;
+            } else {
+                // Dumping a single name?
+                if (!dumpWindows(pw, cmd, args, opti, dumpAll)) {
+                    pw.println("Bad window command, or no windows match: " + cmd);
+                    pw.println("Use -h for help.");
+                }
+                return;
+            }
+        }
+
+        synchronized(mWindowMap) {
+            pw.println();
+            if (dumpAll) {
+                pw.println("-------------------------------------------------------------------------------");
+            }
+            dumpLastANRLocked(pw);
+            pw.println();
+            if (dumpAll) {
+                pw.println("-------------------------------------------------------------------------------");
+            }
+            dumpPolicyLocked(pw, args, dumpAll);
+            pw.println();
+            if (dumpAll) {
+                pw.println("-------------------------------------------------------------------------------");
+            }
+            dumpAnimatorLocked(pw, args, dumpAll);
+            pw.println();
+            if (dumpAll) {
+                pw.println("-------------------------------------------------------------------------------");
+            }
+            dumpSessionsLocked(pw, dumpAll);
+            pw.println();
+            if (dumpAll) {
+                pw.println("-------------------------------------------------------------------------------");
+            }
+            WindowSurfaceController.SurfaceTrace.dumpAllSurfaces(pw, dumpAll ?
+                    "-------------------------------------------------------------------------------"
+                    : null);
+            pw.println();
+            if (dumpAll) {
+                pw.println("-------------------------------------------------------------------------------");
+            }
+            mRoot.dumpDisplayContents(pw);
+            pw.println();
+            if (dumpAll) {
+                pw.println("-------------------------------------------------------------------------------");
+            }
+            dumpTokensLocked(pw, dumpAll);
+            pw.println();
+            if (dumpAll) {
+                pw.println("-------------------------------------------------------------------------------");
+            }
+            dumpWindowsLocked(pw, dumpAll, null);
+        }
+    }
+
+    // Called by the heartbeat to ensure locks are not held indefnitely (for deadlock detection).
+    @Override
+    public void monitor() {
+        synchronized (mWindowMap) { }
+    }
+
+    // TODO: All the display method below should probably be moved into the RootWindowContainer...
+    private void createDisplayContentLocked(final Display display) {
+        if (display == null) {
+            throw new IllegalArgumentException("getDisplayContent: display must not be null");
+        }
+        mRoot.getDisplayContentOrCreate(display.getDisplayId());
+    }
+
+    // There is an inherent assumption that this will never return null.
+    // TODO(multi-display): Inspect all the call-points of this method to see if they make sense to
+    // support non-default display.
+    DisplayContent getDefaultDisplayContentLocked() {
+        return mRoot.getDisplayContentOrCreate(DEFAULT_DISPLAY);
+    }
+
+    public void onDisplayAdded(int displayId) {
+        synchronized (mWindowMap) {
+            final Display display = mDisplayManager.getDisplay(displayId);
+            if (display != null) {
+                createDisplayContentLocked(display);
+                displayReady(displayId);
+            }
+            mWindowPlacerLocked.requestTraversal();
+        }
+    }
+
+    public void onDisplayRemoved(int displayId) {
+        synchronized (mWindowMap) {
+            final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+            if (displayContent != null) {
+                displayContent.removeIfPossible();
+            }
+            mAnimator.removeDisplayLocked(displayId);
+            mWindowPlacerLocked.requestTraversal();
+        }
+    }
+
+    public void onDisplayChanged(int displayId) {
+        synchronized (mWindowMap) {
+            final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+            if (displayContent != null) {
+                displayContent.updateDisplayInfo();
+            }
+            mWindowPlacerLocked.requestTraversal();
+        }
+    }
+
+    @Override
+    public Object getWindowManagerLock() {
+        return mWindowMap;
+    }
+
+    /**
+     * Hint to a token that its activity will relaunch, which will trigger removal and addition of
+     * a window.
+     * @param token Application token for which the activity will be relaunched.
+     */
+    public void setWillReplaceWindow(IBinder token, boolean animate) {
+        synchronized (mWindowMap) {
+            final AppWindowToken appWindowToken = mRoot.getAppWindowToken(token);
+            if (appWindowToken == null || !appWindowToken.hasContentToDisplay()) {
+                Slog.w(TAG_WM, "Attempted to set replacing window on non-existing app token "
+                        + token);
+                return;
+            }
+            appWindowToken.setWillReplaceWindows(animate);
+        }
+    }
+
+    /**
+     * Hint to a token that its windows will be replaced across activity relaunch.
+     * The windows would otherwise be removed  shortly following this as the
+     * activity is torn down.
+     * @param token Application token for which the activity will be relaunched.
+     * @param childrenOnly Whether to mark only child windows for replacement
+     *                     (for the case where main windows are being preserved/
+     *                     reused rather than replaced).
+     *
+     */
+    // TODO: The s at the end of the method name is the only difference with the name of the method
+    // above. We should combine them or find better names.
+    void setWillReplaceWindows(IBinder token, boolean childrenOnly) {
+        synchronized (mWindowMap) {
+            final AppWindowToken appWindowToken = mRoot.getAppWindowToken(token);
+            if (appWindowToken == null || !appWindowToken.hasContentToDisplay()) {
+                Slog.w(TAG_WM, "Attempted to set replacing window on non-existing app token "
+                        + token);
+                return;
+            }
+
+            if (childrenOnly) {
+                appWindowToken.setWillReplaceChildWindows();
+            } else {
+                appWindowToken.setWillReplaceWindows(false /* animate */);
+            }
+
+            scheduleClearWillReplaceWindows(token, true /* replacing */);
+        }
+    }
+
+    /**
+     * If we're replacing the window, schedule a timer to clear the replaced window
+     * after a timeout, in case the replacing window is not coming.
+     *
+     * If we're not replacing the window, clear the replace window settings of the app.
+     *
+     * @param token Application token for the activity whose window might be replaced.
+     * @param replacing Whether the window is being replaced or not.
+     */
+    public void scheduleClearWillReplaceWindows(IBinder token, boolean replacing) {
+        synchronized (mWindowMap) {
+            final AppWindowToken appWindowToken = mRoot.getAppWindowToken(token);
+            if (appWindowToken == null) {
+                Slog.w(TAG_WM, "Attempted to reset replacing window on non-existing app token "
+                        + token);
+                return;
+            }
+            if (replacing) {
+                scheduleWindowReplacementTimeouts(appWindowToken);
+            } else {
+                appWindowToken.clearWillReplaceWindows();
+            }
+        }
+    }
+
+    void scheduleWindowReplacementTimeouts(AppWindowToken appWindowToken) {
+        if (!mWindowReplacementTimeouts.contains(appWindowToken)) {
+            mWindowReplacementTimeouts.add(appWindowToken);
+        }
+        mH.removeMessages(H.WINDOW_REPLACEMENT_TIMEOUT);
+        mH.sendEmptyMessageDelayed(
+                H.WINDOW_REPLACEMENT_TIMEOUT, WINDOW_REPLACEMENT_TIMEOUT_DURATION);
+    }
+
+    @Override
+    public int getDockedStackSide() {
+        synchronized (mWindowMap) {
+            final TaskStack dockedStack = getDefaultDisplayContentLocked()
+                    .getDockedStackIgnoringVisibility();
+            return dockedStack == null ? DOCKED_INVALID : dockedStack.getDockSide();
+        }
+    }
+
+    @Override
+    public void setDockedStackResizing(boolean resizing) {
+        synchronized (mWindowMap) {
+            getDefaultDisplayContentLocked().getDockedDividerController().setResizing(resizing);
+            requestTraversal();
+        }
+    }
+
+    @Override
+    public void setDockedStackDividerTouchRegion(Rect touchRegion) {
+        synchronized (mWindowMap) {
+            getDefaultDisplayContentLocked().getDockedDividerController()
+                    .setTouchRegion(touchRegion);
+            setFocusTaskRegionLocked(null);
+        }
+    }
+
+    @Override
+    public void setResizeDimLayer(boolean visible, int targetStackId, float alpha) {
+        synchronized (mWindowMap) {
+            getDefaultDisplayContentLocked().getDockedDividerController().setResizeDimLayer(
+                    visible, targetStackId, alpha);
+        }
+    }
+
+    public void setForceResizableTasks(boolean forceResizableTasks) {
+        synchronized (mWindowMap) {
+            mForceResizableTasks = forceResizableTasks;
+        }
+    }
+
+    public void setSupportsPictureInPicture(boolean supportsPictureInPicture) {
+        synchronized (mWindowMap) {
+            mSupportsPictureInPicture = supportsPictureInPicture;
+        }
+    }
+
+    static int dipToPixel(int dip, DisplayMetrics displayMetrics) {
+        return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, displayMetrics);
+    }
+
+    @Override
+    public void registerDockedStackListener(IDockedStackListener listener) {
+        if (!checkCallingPermission(REGISTER_WINDOW_MANAGER_LISTENERS,
+                "registerDockedStackListener()")) {
+            return;
+        }
+        synchronized (mWindowMap) {
+            // TODO(multi-display): The listener is registered on the default display only.
+            getDefaultDisplayContentLocked().mDividerControllerLocked.registerDockedStackListener(
+                    listener);
+        }
+    }
+
+    @Override
+    public void registerPinnedStackListener(int displayId, IPinnedStackListener listener) {
+        if (!checkCallingPermission(REGISTER_WINDOW_MANAGER_LISTENERS,
+                "registerPinnedStackListener()")) {
+            return;
+        }
+        if (!mSupportsPictureInPicture) {
+            return;
+        }
+        synchronized (mWindowMap) {
+            final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+            displayContent.getPinnedStackController().registerPinnedStackListener(listener);
+        }
+    }
+
+    @Override
+    public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
+        try {
+            WindowState focusedWindow = getFocusedWindow();
+            if (focusedWindow != null && focusedWindow.mClient != null) {
+                getFocusedWindow().mClient.requestAppKeyboardShortcuts(receiver, deviceId);
+            }
+        } catch (RemoteException e) {
+        }
+    }
+
+    @Override
+    public void getStableInsets(int displayId, Rect outInsets) throws RemoteException {
+        synchronized (mWindowMap) {
+            getStableInsetsLocked(displayId, outInsets);
+        }
+    }
+
+    void getStableInsetsLocked(int displayId, Rect outInsets) {
+        outInsets.setEmpty();
+        final DisplayContent dc = mRoot.getDisplayContent(displayId);
+        if (dc != null) {
+            final DisplayInfo di = dc.getDisplayInfo();
+            mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight, outInsets);
+        }
+    }
+
+    void intersectDisplayInsetBounds(Rect display, Rect insets, Rect inOutBounds) {
+        mTmpRect3.set(display);
+        mTmpRect3.inset(insets);
+        inOutBounds.intersect(mTmpRect3);
+    }
+
+    MousePositionTracker mMousePositionTracker = new MousePositionTracker();
+
+    private static class MousePositionTracker implements PointerEventListener {
+        private boolean mLatestEventWasMouse;
+        private float mLatestMouseX;
+        private float mLatestMouseY;
+
+        void updatePosition(float x, float y) {
+            synchronized (this) {
+                mLatestEventWasMouse = true;
+                mLatestMouseX = x;
+                mLatestMouseY = y;
+            }
+        }
+
+        @Override
+        public void onPointerEvent(MotionEvent motionEvent) {
+            if (motionEvent.isFromSource(InputDevice.SOURCE_MOUSE)) {
+                updatePosition(motionEvent.getRawX(), motionEvent.getRawY());
+            } else {
+                synchronized (this) {
+                    mLatestEventWasMouse = false;
+                }
+            }
+        }
+    };
+
+    void updatePointerIcon(IWindow client) {
+        float mouseX, mouseY;
+
+        synchronized(mMousePositionTracker) {
+            if (!mMousePositionTracker.mLatestEventWasMouse) {
+                return;
+            }
+            mouseX = mMousePositionTracker.mLatestMouseX;
+            mouseY = mMousePositionTracker.mLatestMouseY;
+        }
+
+        synchronized (mWindowMap) {
+            if (mDragState != null) {
+                // Drag cursor overrides the app cursor.
+                return;
+            }
+            WindowState callingWin = windowForClientLocked(null, client, false);
+            if (callingWin == null) {
+                Slog.w(TAG_WM, "Bad requesting window " + client);
+                return;
+            }
+            final DisplayContent displayContent = callingWin.getDisplayContent();
+            if (displayContent == null) {
+                return;
+            }
+            WindowState windowUnderPointer =
+                    displayContent.getTouchableWinAtPointLocked(mouseX, mouseY);
+            if (windowUnderPointer != callingWin) {
+                return;
+            }
+            try {
+                windowUnderPointer.mClient.updatePointerIcon(
+                        windowUnderPointer.translateToWindowX(mouseX),
+                        windowUnderPointer.translateToWindowY(mouseY));
+            } catch (RemoteException e) {
+                Slog.w(TAG_WM, "unable to update pointer icon");
+            }
+        }
+    }
+
+    void restorePointerIconLocked(DisplayContent displayContent, float latestX, float latestY) {
+        // Mouse position tracker has not been getting updates while dragging, update it now.
+        mMousePositionTracker.updatePosition(latestX, latestY);
+
+        WindowState windowUnderPointer =
+                displayContent.getTouchableWinAtPointLocked(latestX, latestY);
+        if (windowUnderPointer != null) {
+            try {
+                windowUnderPointer.mClient.updatePointerIcon(
+                        windowUnderPointer.translateToWindowX(latestX),
+                        windowUnderPointer.translateToWindowY(latestY));
+            } catch (RemoteException e) {
+                Slog.w(TAG_WM, "unable to restore pointer icon");
+            }
+        } else {
+            InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_DEFAULT);
+        }
+    }
+
+    @Override
+    public void registerShortcutKey(long shortcutCode, IShortcutService shortcutKeyReceiver)
+            throws RemoteException {
+        if (!checkCallingPermission(REGISTER_WINDOW_MANAGER_LISTENERS, "registerShortcutKey")) {
+            throw new SecurityException(
+                    "Requires REGISTER_WINDOW_MANAGER_LISTENERS permission");
+        }
+        mPolicy.registerShortcutKey(shortcutCode, shortcutKeyReceiver);
+    }
+
+    void markForSeamlessRotation(WindowState w, boolean seamlesslyRotated) {
+        if (seamlesslyRotated == w.mSeamlesslyRotated) {
+            return;
+        }
+        w.mSeamlesslyRotated = seamlesslyRotated;
+        if (seamlesslyRotated) {
+            mSeamlessRotationCount++;
+        } else {
+            mSeamlessRotationCount--;
+        }
+        if (mSeamlessRotationCount == 0) {
+            if (DEBUG_ORIENTATION) {
+                Slog.i(TAG, "Performing post-rotate rotation after seamless rotation");
+            }
+            final DisplayContent displayContent = w.getDisplayContent();
+            if (displayContent.updateRotationUnchecked(false /* inTransaction */)) {
+                mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayContent.getDisplayId())
+                        .sendToTarget();
+            }
+        }
+    }
+
+    private final class LocalService extends WindowManagerInternal {
+        @Override
+        public void requestTraversalFromDisplayManager() {
+            requestTraversal();
+        }
+
+        @Override
+        public void setMagnificationSpec(MagnificationSpec spec) {
+            synchronized (mWindowMap) {
+                if (mAccessibilityController != null) {
+                    mAccessibilityController.setMagnificationSpecLocked(spec);
+                } else {
+                    throw new IllegalStateException("Magnification callbacks not set!");
+                }
+            }
+            if (Binder.getCallingPid() != myPid()) {
+                spec.recycle();
+            }
+        }
+
+        @Override
+        public void setForceShowMagnifiableBounds(boolean show) {
+            synchronized (mWindowMap) {
+                if (mAccessibilityController != null) {
+                    mAccessibilityController.setForceShowMagnifiableBoundsLocked(show);
+                } else {
+                    throw new IllegalStateException("Magnification callbacks not set!");
+                }
+            }
+        }
+
+        @Override
+        public void getMagnificationRegion(@NonNull Region magnificationRegion) {
+            synchronized (mWindowMap) {
+                if (mAccessibilityController != null) {
+                    mAccessibilityController.getMagnificationRegionLocked(magnificationRegion);
+                } else {
+                    throw new IllegalStateException("Magnification callbacks not set!");
+                }
+            }
+        }
+
+        @Override
+        public MagnificationSpec getCompatibleMagnificationSpecForWindow(IBinder windowToken) {
+            synchronized (mWindowMap) {
+                WindowState windowState = mWindowMap.get(windowToken);
+                if (windowState == null) {
+                    return null;
+                }
+                MagnificationSpec spec = null;
+                if (mAccessibilityController != null) {
+                    spec = mAccessibilityController.getMagnificationSpecForWindowLocked(windowState);
+                }
+                if ((spec == null || spec.isNop()) && windowState.mGlobalScale == 1.0f) {
+                    return null;
+                }
+                spec = (spec == null) ? MagnificationSpec.obtain() : MagnificationSpec.obtain(spec);
+                spec.scale *= windowState.mGlobalScale;
+                return spec;
+            }
+        }
+
+        @Override
+        public void setMagnificationCallbacks(@Nullable MagnificationCallbacks callbacks) {
+            synchronized (mWindowMap) {
+                if (mAccessibilityController == null) {
+                    mAccessibilityController = new AccessibilityController(
+                            WindowManagerService.this);
+                }
+                mAccessibilityController.setMagnificationCallbacksLocked(callbacks);
+                if (!mAccessibilityController.hasCallbacksLocked()) {
+                    mAccessibilityController = null;
+                }
+            }
+        }
+
+        @Override
+        public void setWindowsForAccessibilityCallback(WindowsForAccessibilityCallback callback) {
+            synchronized (mWindowMap) {
+                if (mAccessibilityController == null) {
+                    mAccessibilityController = new AccessibilityController(
+                            WindowManagerService.this);
+                }
+                mAccessibilityController.setWindowsForAccessibilityCallback(callback);
+                if (!mAccessibilityController.hasCallbacksLocked()) {
+                    mAccessibilityController = null;
+                }
+            }
+        }
+
+        @Override
+        public void setInputFilter(IInputFilter filter) {
+            mInputManager.setInputFilter(filter);
+        }
+
+        @Override
+        public IBinder getFocusedWindowToken() {
+            synchronized (mWindowMap) {
+                WindowState windowState = getFocusedWindowLocked();
+                if (windowState != null) {
+                    return windowState.mClient.asBinder();
+                }
+                return null;
+            }
+        }
+
+        @Override
+        public boolean isKeyguardLocked() {
+            return WindowManagerService.this.isKeyguardLocked();
+        }
+
+        @Override
+        public boolean isKeyguardGoingAway() {
+            return WindowManagerService.this.mKeyguardGoingAway;
+        }
+
+        @Override
+        public boolean isKeyguardShowingAndNotOccluded() {
+            return WindowManagerService.this.isKeyguardShowingAndNotOccluded();
+        }
+
+        @Override
+        public void showGlobalActions() {
+            WindowManagerService.this.showGlobalActions();
+        }
+
+        @Override
+        public void getWindowFrame(IBinder token, Rect outBounds) {
+            synchronized (mWindowMap) {
+                WindowState windowState = mWindowMap.get(token);
+                if (windowState != null) {
+                    outBounds.set(windowState.mFrame);
+                } else {
+                    outBounds.setEmpty();
+                }
+            }
+        }
+
+        @Override
+        public void waitForAllWindowsDrawn(Runnable callback, long timeout) {
+            boolean allWindowsDrawn = false;
+            synchronized (mWindowMap) {
+                mWaitingForDrawnCallback = callback;
+                getDefaultDisplayContentLocked().waitForAllWindowsDrawn();
+                mWindowPlacerLocked.requestTraversal();
+                mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT);
+                if (mWaitingForDrawn.isEmpty()) {
+                    allWindowsDrawn = true;
+                } else {
+                    mH.sendEmptyMessageDelayed(H.WAITING_FOR_DRAWN_TIMEOUT, timeout);
+                    checkDrawnWindowsLocked();
+                }
+            }
+            if (allWindowsDrawn) {
+                callback.run();
+            }
+        }
+
+        @Override
+        public void addWindowToken(IBinder token, int type, int displayId) {
+            WindowManagerService.this.addWindowToken(token, type, displayId);
+        }
+
+        @Override
+        public void removeWindowToken(IBinder binder, boolean removeWindows, int displayId) {
+            synchronized(mWindowMap) {
+                if (removeWindows) {
+                    final DisplayContent dc = mRoot.getDisplayContent(displayId);
+                    if (dc == null) {
+                        Slog.w(TAG_WM, "removeWindowToken: Attempted to remove token: " + binder
+                                + " for non-exiting displayId=" + displayId);
+                        return;
+                    }
+
+                    final WindowToken token = dc.removeWindowToken(binder);
+                    if (token == null) {
+                        Slog.w(TAG_WM, "removeWindowToken: Attempted to remove non-existing token: "
+                                + binder);
+                        return;
+                    }
+
+                    token.removeAllWindowsIfPossible();
+                }
+                WindowManagerService.this.removeWindowToken(binder, displayId);
+            }
+        }
+
+        @Override
+        public void registerAppTransitionListener(AppTransitionListener listener) {
+            synchronized (mWindowMap) {
+                mAppTransition.registerListenerLocked(listener);
+            }
+        }
+
+        @Override
+        public int getInputMethodWindowVisibleHeight() {
+            synchronized (mWindowMap) {
+                return mPolicy.getInputMethodWindowVisibleHeightLw();
+            }
+        }
+
+        @Override
+        public void saveLastInputMethodWindowForTransition() {
+            synchronized (mWindowMap) {
+                if (mInputMethodWindow != null) {
+                    mPolicy.setLastInputMethodWindowLw(mInputMethodWindow, mInputMethodTarget);
+                }
+            }
+        }
+
+        @Override
+        public void clearLastInputMethodWindowForTransition() {
+            synchronized (mWindowMap) {
+                mPolicy.setLastInputMethodWindowLw(null, null);
+            }
+        }
+
+        @Override
+        public void updateInputMethodWindowStatus(@NonNull IBinder imeToken,
+                boolean imeWindowVisible, boolean dismissImeOnBackKeyPressed,
+                @Nullable IBinder targetWindowToken) {
+            // TODO (b/34628091): Use this method to address the window animation issue.
+            if (DEBUG_INPUT_METHOD) {
+                Slog.w(TAG_WM, "updateInputMethodWindowStatus: imeToken=" + imeToken
+                        + " dismissImeOnBackKeyPressed=" + dismissImeOnBackKeyPressed
+                        + " imeWindowVisible=" + imeWindowVisible
+                        + " targetWindowToken=" + targetWindowToken);
+            }
+            mPolicy.setDismissImeOnBackKeyPressed(dismissImeOnBackKeyPressed);
+        }
+
+        @Override
+        public boolean isHardKeyboardAvailable() {
+            synchronized (mWindowMap) {
+                return mHardKeyboardAvailable;
+            }
+        }
+
+        @Override
+        public void setOnHardKeyboardStatusChangeListener(
+                OnHardKeyboardStatusChangeListener listener) {
+            synchronized (mWindowMap) {
+                mHardKeyboardStatusChangeListener = listener;
+            }
+        }
+
+        @Override
+        public boolean isStackVisible(int stackId) {
+            synchronized (mWindowMap) {
+                final DisplayContent dc = getDefaultDisplayContentLocked();
+                return dc.isStackVisible(stackId);
+            }
+        }
+
+        @Override
+        public boolean isDockedDividerResizing() {
+            synchronized (mWindowMap) {
+                return getDefaultDisplayContentLocked().getDockedDividerController().isResizing();
+            }
+        }
+
+        @Override
+        public void computeWindowsForAccessibility() {
+            final AccessibilityController accessibilityController;
+            synchronized (mWindowMap) {
+                accessibilityController = mAccessibilityController;
+            }
+            if (accessibilityController != null) {
+                accessibilityController.performComputeChangedWindowsNotLocked();
+            }
+        }
+
+        @Override
+        public void setVr2dDisplayId(int vr2dDisplayId) {
+            if (DEBUG_DISPLAY) {
+                Slog.d(TAG, "setVr2dDisplayId called for: " + vr2dDisplayId);
+            }
+            synchronized (WindowManagerService.this) {
+                mVr2dDisplayId = vr2dDisplayId;
+            }
+        }
+    }
+
+    void registerAppFreezeListener(AppFreezeListener listener) {
+        if (!mAppFreezeListeners.contains(listener)) {
+            mAppFreezeListeners.add(listener);
+        }
+    }
+
+    void unregisterAppFreezeListener(AppFreezeListener listener) {
+        mAppFreezeListeners.remove(listener);
+    }
+
+    /**
+     * WARNING: This interrupts surface updates, be careful! Don't
+     * execute within the transaction for longer than you would
+     * execute on an animation thread.
+     * WARNING: This holds the WindowManager lock, so if exec will acquire
+     * the ActivityManager lock, you should hold it BEFORE calling this
+     * otherwise there is a risk of deadlock if another thread holding the AM
+     * lock waits on the WM lock.
+     * WARNING: This method contains locks known to the State of California
+     * to cause Deadlocks and other conditions.
+     *
+     * Begins a surface transaction with which the AM can batch operations.
+     * All Surface updates performed by the WindowManager following this
+     * will not appear on screen until after the call to
+     * closeSurfaceTransaction.
+     *
+     * ActivityManager can use this to ensure multiple 'commands' will all
+     * be reflected in a single frame. For example when reparenting a window
+     * which was previously hidden due to it's parent properties, we may
+     * need to ensure it is hidden in the same frame that the properties
+     * from the new parent are inherited, otherwise it could be revealed
+     * mistakenly.
+     *
+     * TODO(b/36393204): We can investigate totally replacing #deferSurfaceLayout
+     * with something like this but it seems that some existing cases of
+     * deferSurfaceLayout may be a little too broad, in particular the total
+     * enclosure of startActivityUnchecked which could run for quite some time.
+     */
+    public void inSurfaceTransaction(Runnable exec) {
+        // We hold the WindowManger lock to ensure relayoutWindow
+        // does not return while a Surface transaction is opening.
+        // The client depends on us to have resized the surface
+        // by that point (b/36462635)
+
+        synchronized (mWindowMap) {
+            SurfaceControl.openTransaction();
+            try {
+                exec.run();
+            } finally {
+                SurfaceControl.closeTransaction();
+            }
+        }
+    }
+
+    /** Called to inform window manager if non-Vr UI shoul be disabled or not. */
+    public void disableNonVrUi(boolean disable) {
+        synchronized (mWindowMap) {
+            // Allow alert window notifications to be shown if non-vr UI is enabled.
+            final boolean showAlertWindowNotifications = !disable;
+            if (showAlertWindowNotifications == mShowAlertWindowNotifications) {
+                return;
+            }
+            mShowAlertWindowNotifications = showAlertWindowNotifications;
+
+            for (int i = mSessions.size() - 1; i >= 0; --i) {
+                final Session s = mSessions.valueAt(i);
+                s.setShowingAlertWindowNotificationAllowed(mShowAlertWindowNotifications);
+            }
+        }
+    }
+
+    boolean hasWideColorGamutSupport() {
+        return mHasWideColorGamutSupport;
+    }
+
+    void updateNonSystemOverlayWindowsVisibilityIfNeeded(WindowState win, boolean surfaceShown) {
+        if (!win.hideNonSystemOverlayWindowsWhenVisible()) {
+            return;
+        }
+        final boolean systemAlertWindowsHidden = !mHidingNonSystemOverlayWindows.isEmpty();
+        if (surfaceShown) {
+            if (!mHidingNonSystemOverlayWindows.contains(win)) {
+                mHidingNonSystemOverlayWindows.add(win);
+            }
+        } else {
+            mHidingNonSystemOverlayWindows.remove(win);
+        }
+
+        final boolean hideSystemAlertWindows = !mHidingNonSystemOverlayWindows.isEmpty();
+
+        if (systemAlertWindowsHidden == hideSystemAlertWindows) {
+            return;
+        }
+
+        mRoot.forAllWindows((w) -> {
+            w.setForceHideNonSystemOverlayWindowIfNeeded(hideSystemAlertWindows);
+        }, false /* traverseTopToBottom */);
+    }
+}
diff --git a/com/android/server/wm/WindowManagerThreadPriorityBooster.java b/com/android/server/wm/WindowManagerThreadPriorityBooster.java
new file mode 100644
index 0000000..1b2eb46
--- /dev/null
+++ b/com/android/server/wm/WindowManagerThreadPriorityBooster.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static android.os.Process.THREAD_PRIORITY_DISPLAY;
+import static android.os.Process.myTid;
+import static android.os.Process.setThreadPriority;
+
+import static com.android.server.LockGuard.INDEX_WINDOW;
+import static com.android.server.am.ActivityManagerService.TOP_APP_PRIORITY_BOOST;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.AnimationThread;
+import com.android.server.ThreadPriorityBooster;
+
+/**
+ * Window manager version of {@link ThreadPriorityBooster} that boosts even more during app
+ * transitions.
+ */
+class WindowManagerThreadPriorityBooster extends ThreadPriorityBooster {
+
+    private final Object mLock = new Object();
+
+    private final int mAnimationThreadId;
+
+    @GuardedBy("mLock")
+    private boolean mAppTransitionRunning;
+    @GuardedBy("mLock")
+    private boolean mBoundsAnimationRunning;
+
+    WindowManagerThreadPriorityBooster() {
+        super(THREAD_PRIORITY_DISPLAY, INDEX_WINDOW);
+        mAnimationThreadId = AnimationThread.get().getThreadId();
+    }
+
+    @Override
+    public void boost() {
+
+        // Do not boost the animation thread. As the animation thread is changing priorities,
+        // boosting it might mess up the priority because we reset it the the previous priority.
+        if (myTid() == mAnimationThreadId) {
+            return;
+        }
+        super.boost();
+    }
+
+    @Override
+    public void reset() {
+
+        // See comment in boost().
+        if (myTid() == mAnimationThreadId) {
+            return;
+        }
+        super.reset();
+    }
+
+    void setAppTransitionRunning(boolean running) {
+        synchronized (mLock) {
+            if (mAppTransitionRunning != running) {
+                mAppTransitionRunning = running;
+                updatePriorityLocked();
+            }
+        }
+    }
+
+    void setBoundsAnimationRunning(boolean running) {
+        synchronized (mLock) {
+            if (mBoundsAnimationRunning != running) {
+                mBoundsAnimationRunning = running;
+                updatePriorityLocked();
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void updatePriorityLocked() {
+        int priority = (mAppTransitionRunning || mBoundsAnimationRunning)
+                ? TOP_APP_PRIORITY_BOOST : THREAD_PRIORITY_DISPLAY;
+        setBoostToPriority(priority);
+        setThreadPriority(mAnimationThreadId, priority);
+    }
+}
diff --git a/com/android/server/wm/WindowState.java b/com/android/server/wm/WindowState.java
new file mode 100644
index 0000000..1b05566
--- /dev/null
+++ b/com/android/server/wm/WindowState.java
@@ -0,0 +1,4395 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.ActivityManager.StackId;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE;
+import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
+import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
+import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
+import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
+import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
+import static android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
+import static android.view.WindowManager.LayoutParams.FORMAT_CHANGED;
+import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
+import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
+import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED;
+import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM;
+import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
+import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
+import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static android.view.WindowManagerPolicy.TRANSIT_ENTER;
+import static android.view.WindowManagerPolicy.TRANSIT_EXIT;
+import static android.view.WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_POWER;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_RESIZE;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.H.SEND_NEW_CONFIGURATION;
+import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER;
+import static com.android.server.wm.WindowManagerService.TYPE_LAYER_OFFSET;
+import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
+import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
+import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT;
+import static com.android.server.wm.WindowManagerService.localLOGV;
+import static com.android.server.wm.WindowStateAnimator.COMMIT_DRAW_PENDING;
+import static com.android.server.wm.WindowStateAnimator.DRAW_PENDING;
+import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN;
+import static com.android.server.wm.WindowStateAnimator.READY_TO_SHOW;
+import static com.android.server.wm.proto.IdentifierProto.HASH_CODE;
+import static com.android.server.wm.proto.IdentifierProto.TITLE;
+import static com.android.server.wm.proto.IdentifierProto.USER_ID;
+import static com.android.server.wm.proto.WindowStateProto.ANIMATING_EXIT;
+import static com.android.server.wm.proto.WindowStateProto.ANIMATOR;
+import static com.android.server.wm.proto.WindowStateProto.ATTRIBUTES;
+import static com.android.server.wm.proto.WindowStateProto.CHILD_WINDOWS;
+import static com.android.server.wm.proto.WindowStateProto.CONTAINING_FRAME;
+import static com.android.server.wm.proto.WindowStateProto.CONTENT_FRAME;
+import static com.android.server.wm.proto.WindowStateProto.CONTENT_INSETS;
+import static com.android.server.wm.proto.WindowStateProto.DISPLAY_ID;
+import static com.android.server.wm.proto.WindowStateProto.FRAME;
+import static com.android.server.wm.proto.WindowStateProto.GIVEN_CONTENT_INSETS;
+import static com.android.server.wm.proto.WindowStateProto.IDENTIFIER;
+import static com.android.server.wm.proto.WindowStateProto.PARENT_FRAME;
+import static com.android.server.wm.proto.WindowStateProto.STACK_ID;
+import static com.android.server.wm.proto.WindowStateProto.SURFACE_INSETS;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Matrix;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.Binder;
+import android.os.Debug;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.util.DisplayMetrics;
+import android.util.MergedConfiguration;
+import android.util.Slog;
+import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+import android.view.DisplayInfo;
+import android.view.Gravity;
+import android.view.IApplicationToken;
+import android.view.IWindow;
+import android.view.IWindowFocusObserver;
+import android.view.IWindowId;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.WindowInfo;
+import android.view.WindowManager;
+import android.view.WindowManagerPolicy;
+
+import com.android.internal.util.ToBooleanFunction;
+import com.android.server.input.InputWindowHandle;
+
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.function.Predicate;
+
+/** A window in the window manager. */
+class WindowState extends WindowContainer<WindowState> implements WindowManagerPolicy.WindowState {
+    static final String TAG = TAG_WITH_CLASS_NAME ? "WindowState" : TAG_WM;
+
+    // The minimal size of a window within the usable area of the freeform stack.
+    // TODO(multi-window): fix the min sizes when we have mininum width/height support,
+    //                     use hard-coded min sizes for now.
+    static final int MINIMUM_VISIBLE_WIDTH_IN_DP = 48;
+    static final int MINIMUM_VISIBLE_HEIGHT_IN_DP = 32;
+
+    // The thickness of a window resize handle outside the window bounds on the free form workspace
+    // to capture touch events in that area.
+    static final int RESIZE_HANDLE_WIDTH_IN_DP = 30;
+
+    final WindowManagerService mService;
+    final WindowManagerPolicy mPolicy;
+    final Context mContext;
+    final Session mSession;
+    final IWindow mClient;
+    final int mAppOp;
+    // UserId and appId of the owner. Don't display windows of non-current user.
+    final int mOwnerUid;
+    /** The owner has {@link android.Manifest.permission#INTERNAL_SYSTEM_WINDOW} */
+    final boolean mOwnerCanAddInternalSystemWindow;
+    final WindowId mWindowId;
+    WindowToken mToken;
+    // The same object as mToken if this is an app window and null for non-app windows.
+    AppWindowToken mAppToken;
+
+    // mAttrs.flags is tested in animation without being locked. If the bits tested are ever
+    // modified they will need to be locked.
+    final WindowManager.LayoutParams mAttrs = new WindowManager.LayoutParams();
+    final DeathRecipient mDeathRecipient;
+    private boolean mIsChildWindow;
+    final int mBaseLayer;
+    final int mSubLayer;
+    final boolean mLayoutAttached;
+    final boolean mIsImWindow;
+    final boolean mIsWallpaper;
+    private final boolean mIsFloatingLayer;
+    int mSeq;
+    boolean mEnforceSizeCompat;
+    int mViewVisibility;
+    int mSystemUiVisibility;
+    /**
+     * The visibility of the window based on policy like {@link WindowManagerPolicy}.
+     * Normally set by calling {@link #showLw} and {@link #hideLw}.
+     */
+    boolean mPolicyVisibility = true;
+    /**
+     * What {@link #mPolicyVisibility} should be set to after a transition animation.
+     * For example, {@link #mPolicyVisibility} might true during an exit animation to hide it and
+     * then set to the value of {@link #mPolicyVisibilityAfterAnim} which is false after the exit
+     * animation is done.
+     */
+    boolean mPolicyVisibilityAfterAnim = true;
+    private boolean mAppOpVisibility = true;
+    boolean mPermanentlyHidden; // the window should never be shown again
+    // This is a non-system overlay window that is currently force hidden.
+    private boolean mForceHideNonSystemOverlayWindow;
+    boolean mAppFreezing;
+    boolean mHidden;    // Used to determine if to show child windows.
+    boolean mWallpaperVisible;  // for wallpaper, what was last vis report?
+    private boolean mDragResizing;
+    private boolean mDragResizingChangeReported = true;
+    private int mResizeMode;
+
+    private RemoteCallbackList<IWindowFocusObserver> mFocusCallbacks;
+
+    /**
+     * The window size that was requested by the application.  These are in
+     * the application's coordinate space (without compatibility scale applied).
+     */
+    int mRequestedWidth;
+    int mRequestedHeight;
+    private int mLastRequestedWidth;
+    private int mLastRequestedHeight;
+
+    int mLayer;
+    boolean mHaveFrame;
+    boolean mObscured;
+    boolean mTurnOnScreen;
+
+    int mLayoutSeq = -1;
+
+    /**
+     * Used to store last reported to client configuration and check if we have newer available.
+     * We'll send configuration to client only if it is different from the last applied one and
+     * client won't perform unnecessary updates.
+     */
+    private final Configuration mLastReportedConfiguration = new Configuration();
+
+    /**
+     * Actual position of the surface shown on-screen (may be modified by animation). These are
+     * in the screen's coordinate space (WITH the compatibility scale applied).
+     */
+    final Point mShownPosition = new Point();
+
+    /**
+     * Insets that determine the actually visible area.  These are in the application's
+     * coordinate space (without compatibility scale applied).
+     */
+    final Rect mVisibleInsets = new Rect();
+    private final Rect mLastVisibleInsets = new Rect();
+    private boolean mVisibleInsetsChanged;
+
+    /**
+     * Insets that are covered by system windows (such as the status bar) and
+     * transient docking windows (such as the IME).  These are in the application's
+     * coordinate space (without compatibility scale applied).
+     */
+    final Rect mContentInsets = new Rect();
+    final Rect mLastContentInsets = new Rect();
+
+    /**
+     * The last content insets returned to the client in relayout. We use
+     * these in the bounds animation to ensure we only observe inset changes
+     * at the same time that a client resizes it's surface so that we may use
+     * the geometryAppliesWithResize synchronization mechanism to keep
+     * the contents in place.
+     */
+    final Rect mLastRelayoutContentInsets = new Rect();
+
+    private boolean mContentInsetsChanged;
+
+    /**
+     * Insets that determine the area covered by the display overscan region.  These are in the
+     * application's coordinate space (without compatibility scale applied).
+     */
+    final Rect mOverscanInsets = new Rect();
+    private final Rect mLastOverscanInsets = new Rect();
+    private boolean mOverscanInsetsChanged;
+
+    /**
+     * Insets that determine the area covered by the stable system windows.  These are in the
+     * application's coordinate space (without compatibility scale applied).
+     */
+    final Rect mStableInsets = new Rect();
+    private final Rect mLastStableInsets = new Rect();
+    private boolean mStableInsetsChanged;
+
+    /**
+     * Outsets determine the area outside of the surface where we want to pretend that it's possible
+     * to draw anyway.
+     */
+    final Rect mOutsets = new Rect();
+    private final Rect mLastOutsets = new Rect();
+    private boolean mOutsetsChanged = false;
+
+    /**
+     * Set to true if we are waiting for this window to receive its
+     * given internal insets before laying out other windows based on it.
+     */
+    boolean mGivenInsetsPending;
+
+    /**
+     * These are the content insets that were given during layout for
+     * this window, to be applied to windows behind it.
+     */
+    final Rect mGivenContentInsets = new Rect();
+
+    /**
+     * These are the visible insets that were given during layout for
+     * this window, to be applied to windows behind it.
+     */
+    final Rect mGivenVisibleInsets = new Rect();
+
+    /**
+     * This is the given touchable area relative to the window frame, or null if none.
+     */
+    final Region mGivenTouchableRegion = new Region();
+
+    /**
+     * Flag indicating whether the touchable region should be adjusted by
+     * the visible insets; if false the area outside the visible insets is
+     * NOT touchable, so we must use those to adjust the frame during hit
+     * tests.
+     */
+    int mTouchableInsets = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
+
+    // Current transformation being applied.
+    float mGlobalScale=1;
+    float mInvGlobalScale=1;
+    float mHScale=1, mVScale=1;
+    float mLastHScale=1, mLastVScale=1;
+    final Matrix mTmpMatrix = new Matrix();
+
+    // "Real" frame that the application sees, in display coordinate space.
+    final Rect mFrame = new Rect();
+    final Rect mLastFrame = new Rect();
+    private boolean mFrameSizeChanged = false;
+    // Frame that is scaled to the application's coordinate space when in
+    // screen size compatibility mode.
+    final Rect mCompatFrame = new Rect();
+
+    final Rect mContainingFrame = new Rect();
+
+    private final Rect mParentFrame = new Rect();
+
+    // The entire screen area of the {@link TaskStack} this window is in. Usually equal to the
+    // screen area of the device.
+    final Rect mDisplayFrame = new Rect();
+
+    // The region of the display frame that the display type supports displaying content on. This
+    // is mostly a special case for TV where some displays don’t have the entire display usable.
+    // {@link WindowManager.LayoutParams#FLAG_LAYOUT_IN_OVERSCAN} flag can be used to allow
+    // window display contents to extend into the overscan region.
+    private final Rect mOverscanFrame = new Rect();
+
+    // The display frame minus the stable insets. This value is always constant regardless of if
+    // the status bar or navigation bar is visible.
+    private final Rect mStableFrame = new Rect();
+
+    // The area not occupied by the status and navigation bars. So, if both status and navigation
+    // bars are visible, the decor frame is equal to the stable frame.
+    final Rect mDecorFrame = new Rect();
+
+    // Equal to the decor frame if the IME (e.g. keyboard) is not present. Equal to the decor frame
+    // minus the area occupied by the IME if the IME is present.
+    private final Rect mContentFrame = new Rect();
+
+    // Legacy stuff. Generally equal to the content frame expect when the IME for older apps
+    // displays hint text.
+    final Rect mVisibleFrame = new Rect();
+
+    // Frame that includes dead area outside of the surface but where we want to pretend that it's
+    // possible to draw.
+    private final Rect mOutsetFrame = new Rect();
+
+    /**
+     * Usually empty. Set to the task's tempInsetFrame. See
+     *{@link android.app.IActivityManager#resizeDockedStack}.
+     */
+    private final Rect mInsetFrame = new Rect();
+
+    boolean mContentChanged;
+
+    // If a window showing a wallpaper: the requested offset for the
+    // wallpaper; if a wallpaper window: the currently applied offset.
+    float mWallpaperX = -1;
+    float mWallpaperY = -1;
+
+    // If a window showing a wallpaper: what fraction of the offset
+    // range corresponds to a full virtual screen.
+    float mWallpaperXStep = -1;
+    float mWallpaperYStep = -1;
+
+    // If a window showing a wallpaper: a raw pixel offset to forcibly apply
+    // to its window; if a wallpaper window: not used.
+    int mWallpaperDisplayOffsetX = Integer.MIN_VALUE;
+    int mWallpaperDisplayOffsetY = Integer.MIN_VALUE;
+
+    // Wallpaper windows: pixels offset based on above variables.
+    int mXOffset;
+    int mYOffset;
+
+    /**
+     * This is set after IWindowSession.relayout() has been called at
+     * least once for the window.  It allows us to detect the situation
+     * where we don't yet have a surface, but should have one soon, so
+     * we can give the window focus before waiting for the relayout.
+     */
+    boolean mRelayoutCalled;
+
+    boolean mInRelayout;
+
+    /**
+     * If the application has called relayout() with changes that can
+     * impact its window's size, we need to perform a layout pass on it
+     * even if it is not currently visible for layout.  This is set
+     * when in that case until the layout is done.
+     */
+    boolean mLayoutNeeded;
+
+    /** Currently running an exit animation? */
+    boolean mAnimatingExit;
+
+    /** Currently on the mDestroySurface list? */
+    boolean mDestroying;
+
+    /** Completely remove from window manager after exit animation? */
+    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.
+     */
+    private boolean mOrientationChanging;
+
+    /**
+     * Sometimes in addition to the mOrientationChanging
+     * flag we report that the orientation is changing
+     * due to a mismatch in current and reported configuration.
+     *
+     * In the case of timeout we still need to make sure we
+     * leave the orientation changing state though, so we
+     * use this as a special time out escape hatch.
+     */
+    private boolean mOrientationChangeTimedOut;
+
+    /**
+     * The orientation during the last visible call to relayout. If our
+     * current orientation is different, the window can't be ready
+     * to be shown.
+     */
+    int mLastVisibleLayoutRotation = -1;
+
+    /**
+     * Set when we need to report the orientation change to client to trigger a relayout.
+     */
+    boolean mReportOrientationChanged;
+
+    /**
+     * How long we last kept the screen frozen.
+     */
+    int mLastFreezeDuration;
+
+    /** Is this window now (or just being) removed? */
+    boolean mRemoved;
+
+    /**
+     * It is save to remove the window and destroy the surface because the client requested removal
+     * or some other higher level component said so (e.g. activity manager).
+     * TODO: We should either have different booleans for the removal reason or use a bit-field.
+     */
+    boolean mWindowRemovalAllowed;
+
+    // Input channel and input window handle used by the input dispatcher.
+    final InputWindowHandle mInputWindowHandle;
+    InputChannel mInputChannel;
+    private InputChannel mClientChannel;
+
+    // Used to improve performance of toString()
+    private String mStringNameCache;
+    private CharSequence mLastTitle;
+    private boolean mWasExiting;
+
+    final WindowStateAnimator mWinAnimator;
+
+    boolean mHasSurface = false;
+
+    /** When true this window can be displayed on screens owther than mOwnerUid's */
+    private boolean mShowToOwnerOnly;
+
+    // Whether the window was visible when we set the app to invisible last time. WM uses
+    // this as a hint to restore the surface (if available) for early animation next time
+    // the app is brought visible.
+    private boolean mWasVisibleBeforeClientHidden;
+
+    // This window will be replaced due to relaunch. This allows window manager
+    // to differentiate between simple removal of a window and replacement. In the latter case it
+    // will preserve the old window until the new one is drawn.
+    boolean mWillReplaceWindow = false;
+    // If true, the replaced window was already requested to be removed.
+    private boolean mReplacingRemoveRequested = false;
+    // Whether the replacement of the window should trigger app transition animation.
+    private boolean mAnimateReplacingWindow = false;
+    // If not null, the window that will be used to replace the old one. This is being set when
+    // the window is added and unset when this window reports its first draw.
+    private WindowState mReplacementWindow = null;
+    // For the new window in the replacement transition, if we have
+    // requested to replace without animation, then we should
+    // make sure we also don't apply an enter animation for
+    // the new window.
+    boolean mSkipEnterAnimationForSeamlessReplacement = false;
+    // Whether this window is being moved via the resize API
+    private boolean mMovedByResize;
+
+    /**
+     * Wake lock for drawing.
+     * Even though it's slightly more expensive to do so, we will use a separate wake lock
+     * for each app that is requesting to draw while dozing so that we can accurately track
+     * who is preventing the system from suspending.
+     * This lock is only acquired on first use.
+     */
+    private PowerManager.WakeLock mDrawLock;
+
+    final private Rect mTmpRect = new Rect();
+
+    /**
+     * Whether the window was resized by us while it was gone for layout.
+     */
+    boolean mResizedWhileGone = false;
+
+    /** @see #isResizedWhileNotDragResizing(). */
+    private boolean mResizedWhileNotDragResizing;
+
+    /** @see #isResizedWhileNotDragResizingReported(). */
+    private boolean mResizedWhileNotDragResizingReported;
+
+    /**
+     * During seamless rotation we have two phases, first the old window contents
+     * are rotated to look as if they didn't move in the new coordinate system. Then we
+     * have to freeze updates to this layer (to preserve the transformation) until
+     * the resize actually occurs. This is true from when the transformation is set
+     * and false until the transaction to resize is sent.
+     */
+    boolean mSeamlesslyRotated = false;
+
+    /**
+     * Surface insets from the previous call to relayout(), used to track
+     * if we are changing the Surface insets.
+     */
+    final Rect mLastSurfaceInsets = new Rect();
+
+    /**
+     * A flag set by the {@link WindowState} parent to indicate that the parent has examined this
+     * {@link WindowState} in its overall drawing context. This book-keeping allows the parent to
+     * make sure all children have been considered.
+     */
+    private boolean mDrawnStateEvaluated;
+
+    /**
+     * Compares two window sub-layers and returns -1 if the first is lesser than the second in terms
+     * of z-order and 1 otherwise.
+     */
+    private static final Comparator<WindowState> sWindowSubLayerComparator =
+            new Comparator<WindowState>() {
+                @Override
+                public int compare(WindowState w1, WindowState w2) {
+                    final int layer1 = w1.mSubLayer;
+                    final int layer2 = w2.mSubLayer;
+                    if (layer1 < layer2 || (layer1 == layer2 && layer2 < 0 )) {
+                        // We insert the child window into the list ordered by
+                        // the sub-layer.  For same sub-layers, the negative one
+                        // should go below others; the positive one should go
+                        // above others.
+                        return -1;
+                    }
+                    return 1;
+                };
+            };
+
+    WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
+           WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
+           int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow) {
+        mService = service;
+        mSession = s;
+        mClient = c;
+        mAppOp = appOp;
+        mToken = token;
+        mAppToken = mToken.asAppWindowToken();
+        mOwnerUid = ownerId;
+        mOwnerCanAddInternalSystemWindow = ownerCanAddInternalSystemWindow;
+        mWindowId = new WindowId(this);
+        mAttrs.copyFrom(a);
+        mViewVisibility = viewVisibility;
+        mPolicy = mService.mPolicy;
+        mContext = mService.mContext;
+        DeathRecipient deathRecipient = new DeathRecipient();
+        mSeq = seq;
+        mEnforceSizeCompat = (mAttrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0;
+        if (localLOGV) Slog.v(
+            TAG, "Window " + this + " client=" + c.asBinder()
+            + " token=" + token + " (" + mAttrs.token + ")" + " params=" + a);
+        try {
+            c.asBinder().linkToDeath(deathRecipient, 0);
+        } catch (RemoteException e) {
+            mDeathRecipient = null;
+            mIsChildWindow = false;
+            mLayoutAttached = false;
+            mIsImWindow = false;
+            mIsWallpaper = false;
+            mIsFloatingLayer = false;
+            mBaseLayer = 0;
+            mSubLayer = 0;
+            mInputWindowHandle = null;
+            mWinAnimator = null;
+            return;
+        }
+        mDeathRecipient = deathRecipient;
+
+        if (mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW) {
+            // The multiplier here is to reserve space for multiple
+            // windows in the same type layer.
+            mBaseLayer = mPolicy.getWindowLayerLw(parentWindow)
+                    * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
+            mSubLayer = mPolicy.getSubWindowLayerFromTypeLw(a.type);
+            mIsChildWindow = true;
+
+            if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + this + " to " + parentWindow);
+            parentWindow.addChild(this, sWindowSubLayerComparator);
+
+            mLayoutAttached = mAttrs.type !=
+                    WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+            mIsImWindow = parentWindow.mAttrs.type == TYPE_INPUT_METHOD
+                    || parentWindow.mAttrs.type == TYPE_INPUT_METHOD_DIALOG;
+            mIsWallpaper = parentWindow.mAttrs.type == TYPE_WALLPAPER;
+        } else {
+            // The multiplier here is to reserve space for multiple
+            // windows in the same type layer.
+            mBaseLayer = mPolicy.getWindowLayerLw(this)
+                    * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
+            mSubLayer = 0;
+            mIsChildWindow = false;
+            mLayoutAttached = false;
+            mIsImWindow = mAttrs.type == TYPE_INPUT_METHOD
+                    || mAttrs.type == TYPE_INPUT_METHOD_DIALOG;
+            mIsWallpaper = mAttrs.type == TYPE_WALLPAPER;
+        }
+        mIsFloatingLayer = mIsImWindow || mIsWallpaper;
+
+        if (mAppToken != null && mAppToken.mShowForAllUsers) {
+            // Windows for apps that can show for all users should also show when the device is
+            // locked.
+            mAttrs.flags |= FLAG_SHOW_WHEN_LOCKED;
+        }
+
+        mWinAnimator = new WindowStateAnimator(this);
+        mWinAnimator.mAlpha = a.alpha;
+
+        mRequestedWidth = 0;
+        mRequestedHeight = 0;
+        mLastRequestedWidth = 0;
+        mLastRequestedHeight = 0;
+        mXOffset = 0;
+        mYOffset = 0;
+        mLayer = 0;
+        mInputWindowHandle = new InputWindowHandle(
+                mAppToken != null ? mAppToken.mInputApplicationHandle : null, this, c,
+                    getDisplayId());
+    }
+
+    void attach() {
+        if (localLOGV) Slog.v(TAG, "Attaching " + this + " token=" + mToken);
+        mSession.windowAddedLocked(mAttrs.packageName);
+    }
+
+    /**
+     * Returns whether this {@link WindowState} has been considered for drawing by its parent.
+     */
+    boolean getDrawnStateEvaluated() {
+        return mDrawnStateEvaluated;
+    }
+
+    /**
+     * Sets whether this {@link WindowState} has been considered for drawing by its parent. Should
+     * be cleared when detached from parent.
+     */
+    void setDrawnStateEvaluated(boolean evaluated) {
+        mDrawnStateEvaluated = evaluated;
+    }
+
+    @Override
+    void onParentSet() {
+        super.onParentSet();
+        setDrawnStateEvaluated(false /*evaluated*/);
+    }
+
+    @Override
+    public int getOwningUid() {
+        return mOwnerUid;
+    }
+
+    @Override
+    public String getOwningPackage() {
+        return mAttrs.packageName;
+    }
+
+    @Override
+    public boolean canAddInternalSystemWindow() {
+        return mOwnerCanAddInternalSystemWindow;
+    }
+
+    @Override
+    public boolean canAcquireSleepToken() {
+        return mSession.mCanAcquireSleepToken;
+    }
+
+    /**
+     * Subtracts the insets calculated by intersecting {@param layoutFrame} with {@param insetFrame}
+     * from {@param frame}. In other words, it applies the insets that would result if
+     * {@param frame} would be shifted to {@param layoutFrame} and then applying the insets from
+     * {@param insetFrame}. Also it respects {@param displayFrame} in case window has minimum
+     * width/height applied and insets should be overridden.
+     */
+    private void subtractInsets(Rect frame, Rect layoutFrame, Rect insetFrame, Rect displayFrame) {
+        final int left = Math.max(0, insetFrame.left - Math.max(layoutFrame.left, displayFrame.left));
+        final int top = Math.max(0, insetFrame.top - Math.max(layoutFrame.top, displayFrame.top));
+        final int right = Math.max(0, Math.min(layoutFrame.right, displayFrame.right) - insetFrame.right);
+        final int bottom = Math.max(0, Math.min(layoutFrame.bottom, displayFrame.bottom) - insetFrame.bottom);
+        frame.inset(left, top, right, bottom);
+    }
+
+    @Override
+    public void computeFrameLw(Rect parentFrame, Rect displayFrame, Rect overscanFrame,
+            Rect contentFrame, Rect visibleFrame, Rect decorFrame, Rect stableFrame,
+            Rect outsetFrame) {
+        if (mWillReplaceWindow && (mAnimatingExit || !mReplacingRemoveRequested)) {
+            // This window is being replaced and either already got information that it's being
+            // removed or we are still waiting for some information. Because of this we don't
+            // want to apply any more changes to it, so it remains in this state until new window
+            // appears.
+            return;
+        }
+        mHaveFrame = true;
+
+        final Task task = getTask();
+        final boolean inFullscreenContainer = inFullscreenContainer();
+        final boolean windowsAreFloating = task != null && task.isFloating();
+        final DisplayContent dc = getDisplayContent();
+
+        // If the task has temp inset bounds set, we have to make sure all its windows uses
+        // the temp inset frame. Otherwise different display frames get applied to the main
+        // window and the child window, making them misaligned.
+        if (inFullscreenContainer || isLetterboxedAppWindow()) {
+            mInsetFrame.setEmpty();
+        } else if (task != null && isInMultiWindowMode()) {
+            task.getTempInsetBounds(mInsetFrame);
+        }
+
+        // Denotes the actual frame used to calculate the insets and to perform the layout. When
+        // resizing in docked mode, we'd like to freeze the layout, so we also need to freeze the
+        // insets temporarily. By the notion of a task having a different layout frame, we can
+        // achieve that while still moving the task around.
+        final Rect layoutContainingFrame;
+        final Rect layoutDisplayFrame;
+
+        // The offset from the layout containing frame to the actual containing frame.
+        final int layoutXDiff;
+        final int layoutYDiff;
+        if (inFullscreenContainer || layoutInParentFrame()) {
+            // We use the parent frame as the containing frame for fullscreen and child windows
+            mContainingFrame.set(parentFrame);
+            mDisplayFrame.set(displayFrame);
+            layoutDisplayFrame = displayFrame;
+            layoutContainingFrame = parentFrame;
+            layoutXDiff = 0;
+            layoutYDiff = 0;
+        } else {
+            getContainerBounds(mContainingFrame);
+            if (mAppToken != null && !mAppToken.mFrozenBounds.isEmpty()) {
+
+                // If the bounds are frozen, we still want to translate the window freely and only
+                // freeze the size.
+                Rect frozen = mAppToken.mFrozenBounds.peek();
+                mContainingFrame.right = mContainingFrame.left + frozen.width();
+                mContainingFrame.bottom = mContainingFrame.top + frozen.height();
+            }
+            final WindowState imeWin = mService.mInputMethodWindow;
+            // IME is up and obscuring this window. Adjust the window position so it is visible.
+            if (imeWin != null && imeWin.isVisibleNow() && mService.mInputMethodTarget == this) {
+                final int stackId = getStackId();
+                if (stackId == FREEFORM_WORKSPACE_STACK_ID
+                        && mContainingFrame.bottom > contentFrame.bottom) {
+                    // In freeform we want to move the top up directly.
+                    // TODO: Investigate why this is contentFrame not parentFrame.
+                    mContainingFrame.top -= mContainingFrame.bottom - contentFrame.bottom;
+                } else if (stackId != PINNED_STACK_ID
+                        && mContainingFrame.bottom > parentFrame.bottom) {
+                    // But in docked we want to behave like fullscreen and behave as if the task
+                    // were given smaller bounds for the purposes of layout. Skip adjustments for
+                    // the pinned stack, they are handled separately in the PinnedStackController.
+                    mContainingFrame.bottom = parentFrame.bottom;
+                }
+            }
+
+            if (windowsAreFloating) {
+                // In floating modes (e.g. freeform, pinned) we have only to set the rectangle
+                // if it wasn't set already. No need to intersect it with the (visible)
+                // "content frame" since it is allowed to be outside the visible desktop.
+                if (mContainingFrame.isEmpty()) {
+                    mContainingFrame.set(contentFrame);
+                }
+            }
+            mDisplayFrame.set(mContainingFrame);
+            layoutXDiff = !mInsetFrame.isEmpty() ? mInsetFrame.left - mContainingFrame.left : 0;
+            layoutYDiff = !mInsetFrame.isEmpty() ? mInsetFrame.top - mContainingFrame.top : 0;
+            layoutContainingFrame = !mInsetFrame.isEmpty() ? mInsetFrame : mContainingFrame;
+            mTmpRect.set(0, 0, dc.getDisplayInfo().logicalWidth, dc.getDisplayInfo().logicalHeight);
+            subtractInsets(mDisplayFrame, layoutContainingFrame, displayFrame, mTmpRect);
+            if (!layoutInParentFrame()) {
+                subtractInsets(mContainingFrame, layoutContainingFrame, parentFrame, mTmpRect);
+                subtractInsets(mInsetFrame, layoutContainingFrame, parentFrame, mTmpRect);
+            }
+            layoutDisplayFrame = displayFrame;
+            layoutDisplayFrame.intersect(layoutContainingFrame);
+        }
+
+        final int pw = mContainingFrame.width();
+        final int ph = mContainingFrame.height();
+
+        if (!mParentFrame.equals(parentFrame)) {
+            //Slog.i(TAG_WM, "Window " + this + " content frame from " + mParentFrame
+            //        + " to " + parentFrame);
+            mParentFrame.set(parentFrame);
+            mContentChanged = true;
+        }
+        if (mRequestedWidth != mLastRequestedWidth || mRequestedHeight != mLastRequestedHeight) {
+            mLastRequestedWidth = mRequestedWidth;
+            mLastRequestedHeight = mRequestedHeight;
+            mContentChanged = true;
+        }
+
+        mOverscanFrame.set(overscanFrame);
+        mContentFrame.set(contentFrame);
+        mVisibleFrame.set(visibleFrame);
+        mDecorFrame.set(decorFrame);
+        mStableFrame.set(stableFrame);
+        final boolean hasOutsets = outsetFrame != null;
+        if (hasOutsets) {
+            mOutsetFrame.set(outsetFrame);
+        }
+
+        final int fw = mFrame.width();
+        final int fh = mFrame.height();
+
+        applyGravityAndUpdateFrame(layoutContainingFrame, layoutDisplayFrame);
+
+        // Calculate the outsets before the content frame gets shrinked to the window frame.
+        if (hasOutsets) {
+            mOutsets.set(Math.max(mContentFrame.left - mOutsetFrame.left, 0),
+                    Math.max(mContentFrame.top - mOutsetFrame.top, 0),
+                    Math.max(mOutsetFrame.right - mContentFrame.right, 0),
+                    Math.max(mOutsetFrame.bottom - mContentFrame.bottom, 0));
+        } else {
+            mOutsets.set(0, 0, 0, 0);
+        }
+
+        // Make sure the content and visible frames are inside of the
+        // final window frame.
+        if (windowsAreFloating && !mFrame.isEmpty()) {
+            // For pinned workspace the frame isn't limited in any particular
+            // way since SystemUI controls the bounds. For freeform however
+            // we want to keep things inside the content frame.
+            final Rect limitFrame = task.inPinnedWorkspace() ? mFrame : mContentFrame;
+            // Keep the frame out of the blocked system area, limit it in size to the content area
+            // and make sure that there is always a minimum visible so that the user can drag it
+            // into a usable area..
+            final int height = Math.min(mFrame.height(), limitFrame.height());
+            final int width = Math.min(limitFrame.width(), mFrame.width());
+            final DisplayMetrics displayMetrics = getDisplayContent().getDisplayMetrics();
+            final int minVisibleHeight = Math.min(height, WindowManagerService.dipToPixel(
+                    MINIMUM_VISIBLE_HEIGHT_IN_DP, displayMetrics));
+            final int minVisibleWidth = Math.min(width, WindowManagerService.dipToPixel(
+                    MINIMUM_VISIBLE_WIDTH_IN_DP, displayMetrics));
+            final int top = Math.max(limitFrame.top,
+                    Math.min(mFrame.top, limitFrame.bottom - minVisibleHeight));
+            final int left = Math.max(limitFrame.left + minVisibleWidth - width,
+                    Math.min(mFrame.left, limitFrame.right - minVisibleWidth));
+            mFrame.set(left, top, left + width, top + height);
+            mContentFrame.set(mFrame);
+            mVisibleFrame.set(mContentFrame);
+            mStableFrame.set(mContentFrame);
+        } else if (mAttrs.type == TYPE_DOCK_DIVIDER) {
+            dc.getDockedDividerController().positionDockedStackedDivider(mFrame);
+            mContentFrame.set(mFrame);
+            if (!mFrame.equals(mLastFrame)) {
+                mMovedByResize = true;
+            }
+        } else {
+            mContentFrame.set(Math.max(mContentFrame.left, mFrame.left),
+                    Math.max(mContentFrame.top, mFrame.top),
+                    Math.min(mContentFrame.right, mFrame.right),
+                    Math.min(mContentFrame.bottom, mFrame.bottom));
+
+            mVisibleFrame.set(Math.max(mVisibleFrame.left, mFrame.left),
+                    Math.max(mVisibleFrame.top, mFrame.top),
+                    Math.min(mVisibleFrame.right, mFrame.right),
+                    Math.min(mVisibleFrame.bottom, mFrame.bottom));
+
+            mStableFrame.set(Math.max(mStableFrame.left, mFrame.left),
+                    Math.max(mStableFrame.top, mFrame.top),
+                    Math.min(mStableFrame.right, mFrame.right),
+                    Math.min(mStableFrame.bottom, mFrame.bottom));
+        }
+
+        if (inFullscreenContainer && !windowsAreFloating) {
+            // Windows that are not fullscreen can be positioned outside of the display frame,
+            // but that is not a reason to provide them with overscan insets.
+            mOverscanInsets.set(Math.max(mOverscanFrame.left - layoutContainingFrame.left, 0),
+                    Math.max(mOverscanFrame.top - layoutContainingFrame.top, 0),
+                    Math.max(layoutContainingFrame.right - mOverscanFrame.right, 0),
+                    Math.max(layoutContainingFrame.bottom - mOverscanFrame.bottom, 0));
+        }
+
+        if (mAttrs.type == TYPE_DOCK_DIVIDER) {
+            // For the docked divider, we calculate the stable insets like a full-screen window
+            // so it can use it to calculate the snap positions.
+            mStableInsets.set(Math.max(mStableFrame.left - mDisplayFrame.left, 0),
+                    Math.max(mStableFrame.top - mDisplayFrame.top, 0),
+                    Math.max(mDisplayFrame.right - mStableFrame.right, 0),
+                    Math.max(mDisplayFrame.bottom - mStableFrame.bottom, 0));
+
+            // The divider doesn't care about insets in any case, so set it to empty so we don't
+            // trigger a relayout when moving it.
+            mContentInsets.setEmpty();
+            mVisibleInsets.setEmpty();
+        } else {
+            getDisplayContent().getLogicalDisplayRect(mTmpRect);
+            // Override right and/or bottom insets in case if the frame doesn't fit the screen in
+            // non-fullscreen mode.
+            boolean overrideRightInset = !windowsAreFloating && !inFullscreenContainer
+                    && mFrame.right > mTmpRect.right;
+            boolean overrideBottomInset = !windowsAreFloating && !inFullscreenContainer
+                    && mFrame.bottom > mTmpRect.bottom;
+            mContentInsets.set(mContentFrame.left - mFrame.left,
+                    mContentFrame.top - mFrame.top,
+                    overrideRightInset ? mTmpRect.right - mContentFrame.right
+                            : mFrame.right - mContentFrame.right,
+                    overrideBottomInset ? mTmpRect.bottom - mContentFrame.bottom
+                            : mFrame.bottom - mContentFrame.bottom);
+
+            mVisibleInsets.set(mVisibleFrame.left - mFrame.left,
+                    mVisibleFrame.top - mFrame.top,
+                    overrideRightInset ? mTmpRect.right - mVisibleFrame.right
+                            : mFrame.right - mVisibleFrame.right,
+                    overrideBottomInset ? mTmpRect.bottom - mVisibleFrame.bottom
+                            : mFrame.bottom - mVisibleFrame.bottom);
+
+            mStableInsets.set(Math.max(mStableFrame.left - mFrame.left, 0),
+                    Math.max(mStableFrame.top - mFrame.top, 0),
+                    overrideRightInset ? Math.max(mTmpRect.right - mStableFrame.right, 0)
+                            : Math.max(mFrame.right - mStableFrame.right, 0),
+                    overrideBottomInset ? Math.max(mTmpRect.bottom - mStableFrame.bottom, 0)
+                            :  Math.max(mFrame.bottom - mStableFrame.bottom, 0));
+        }
+
+        // Offset the actual frame by the amount layout frame is off.
+        mFrame.offset(-layoutXDiff, -layoutYDiff);
+        mCompatFrame.offset(-layoutXDiff, -layoutYDiff);
+        mContentFrame.offset(-layoutXDiff, -layoutYDiff);
+        mVisibleFrame.offset(-layoutXDiff, -layoutYDiff);
+        mStableFrame.offset(-layoutXDiff, -layoutYDiff);
+
+        mCompatFrame.set(mFrame);
+        if (mEnforceSizeCompat) {
+            // If there is a size compatibility scale being applied to the
+            // window, we need to apply this to its insets so that they are
+            // reported to the app in its coordinate space.
+            mOverscanInsets.scale(mInvGlobalScale);
+            mContentInsets.scale(mInvGlobalScale);
+            mVisibleInsets.scale(mInvGlobalScale);
+            mStableInsets.scale(mInvGlobalScale);
+            mOutsets.scale(mInvGlobalScale);
+
+            // Also the scaled frame that we report to the app needs to be
+            // adjusted to be in its coordinate space.
+            mCompatFrame.scale(mInvGlobalScale);
+        }
+
+        if (mIsWallpaper && (fw != mFrame.width() || fh != mFrame.height())) {
+            final DisplayContent displayContent = getDisplayContent();
+            if (displayContent != null) {
+                final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+                getDisplayContent().mWallpaperController.updateWallpaperOffset(
+                        this, displayInfo.logicalWidth, displayInfo.logicalHeight, false);
+            }
+        }
+
+        if (DEBUG_LAYOUT || localLOGV) Slog.v(TAG,
+                "Resolving (mRequestedWidth="
+                + mRequestedWidth + ", mRequestedheight="
+                + mRequestedHeight + ") to" + " (pw=" + pw + ", ph=" + ph
+                + "): frame=" + mFrame.toShortString()
+                + " ci=" + mContentInsets.toShortString()
+                + " vi=" + mVisibleInsets.toShortString()
+                + " si=" + mStableInsets.toShortString()
+                + " of=" + mOutsets.toShortString());
+    }
+
+    @Override
+    public Rect getFrameLw() {
+        return mFrame;
+    }
+
+    @Override
+    public Point getShownPositionLw() {
+        return mShownPosition;
+    }
+
+    @Override
+    public Rect getDisplayFrameLw() {
+        return mDisplayFrame;
+    }
+
+    @Override
+    public Rect getOverscanFrameLw() {
+        return mOverscanFrame;
+    }
+
+    @Override
+    public Rect getContentFrameLw() {
+        return mContentFrame;
+    }
+
+    @Override
+    public Rect getVisibleFrameLw() {
+        return mVisibleFrame;
+    }
+
+    Rect getStableFrameLw() {
+        return mStableFrame;
+    }
+
+    @Override
+    public boolean getGivenInsetsPendingLw() {
+        return mGivenInsetsPending;
+    }
+
+    @Override
+    public Rect getGivenContentInsetsLw() {
+        return mGivenContentInsets;
+    }
+
+    @Override
+    public Rect getGivenVisibleInsetsLw() {
+        return mGivenVisibleInsets;
+    }
+
+    @Override
+    public WindowManager.LayoutParams getAttrs() {
+        return mAttrs;
+    }
+
+    @Override
+    public boolean getNeedsMenuLw(WindowManagerPolicy.WindowState bottom) {
+        return getDisplayContent().getNeedsMenu(this, bottom);
+    }
+
+    @Override
+    public int getSystemUiVisibility() {
+        return mSystemUiVisibility;
+    }
+
+    @Override
+    public int getSurfaceLayer() {
+        return mLayer;
+    }
+
+    @Override
+    public int getBaseType() {
+        return getTopParentWindow().mAttrs.type;
+    }
+
+    @Override
+    public IApplicationToken getAppToken() {
+        return mAppToken != null ? mAppToken.appToken : null;
+    }
+
+    @Override
+    public boolean isVoiceInteraction() {
+        return mAppToken != null && mAppToken.mVoiceInteraction;
+    }
+
+    boolean setReportResizeHints() {
+        mOverscanInsetsChanged |= !mLastOverscanInsets.equals(mOverscanInsets);
+        mContentInsetsChanged |= !mLastContentInsets.equals(mContentInsets);
+        mVisibleInsetsChanged |= !mLastVisibleInsets.equals(mVisibleInsets);
+        mStableInsetsChanged |= !mLastStableInsets.equals(mStableInsets);
+        mOutsetsChanged |= !mLastOutsets.equals(mOutsets);
+        mFrameSizeChanged |= (mLastFrame.width() != mFrame.width()) ||
+                (mLastFrame.height() != mFrame.height());
+        return mOverscanInsetsChanged || mContentInsetsChanged || mVisibleInsetsChanged
+                || mOutsetsChanged || mFrameSizeChanged;
+    }
+
+    /**
+     * Adds the window to the resizing list if any of the parameters we use to track the window
+     * dimensions or insets have changed.
+     */
+    void updateResizingWindowIfNeeded() {
+        final WindowStateAnimator winAnimator = mWinAnimator;
+        if (!mHasSurface || mService.mLayoutSeq != mLayoutSeq || isGoneForLayoutLw()) {
+            return;
+        }
+
+        final Task task = getTask();
+        // In the case of stack bound animations, the window frames will update (unlike other
+        // animations which just modify various transformation properties). We don't want to
+        // notify the client of frame changes in this case. Not only is it a lot of churn, but
+        // the frame may not correspond to the surface size or the onscreen area at various
+        // phases in the animation, and the client will become sad and confused.
+        if (task != null && task.mStack.isAnimatingBounds()) {
+            return;
+        }
+
+        setReportResizeHints();
+        boolean configChanged = isConfigChanged();
+        if (DEBUG_CONFIGURATION && configChanged) {
+            Slog.v(TAG_WM, "Win " + this + " config changed: " + getConfiguration());
+        }
+
+        final boolean dragResizingChanged = isDragResizeChanged()
+                && !isDragResizingChangeReported();
+
+        if (localLOGV) Slog.v(TAG_WM, "Resizing " + this + ": configChanged=" + configChanged
+                + " dragResizingChanged=" + dragResizingChanged + " last=" + mLastFrame
+                + " frame=" + mFrame);
+
+        // We update mLastFrame always rather than in the conditional with the last inset
+        // variables, because mFrameSizeChanged only tracks the width and height changing.
+        mLastFrame.set(mFrame);
+
+        if (mContentInsetsChanged
+                || mVisibleInsetsChanged
+                || winAnimator.mSurfaceResized
+                || mOutsetsChanged
+                || mFrameSizeChanged
+                || configChanged
+                || dragResizingChanged
+                || !isResizedWhileNotDragResizingReported()
+                || mReportOrientationChanged) {
+            if (DEBUG_RESIZE || DEBUG_ORIENTATION) {
+                Slog.v(TAG_WM, "Resize reasons for w=" + this + ": "
+                        + " contentInsetsChanged=" + mContentInsetsChanged
+                        + " " + mContentInsets.toShortString()
+                        + " visibleInsetsChanged=" + mVisibleInsetsChanged
+                        + " " + mVisibleInsets.toShortString()
+                        + " stableInsetsChanged=" + mStableInsetsChanged
+                        + " " + mStableInsets.toShortString()
+                        + " outsetsChanged=" + mOutsetsChanged
+                        + " " + mOutsets.toShortString()
+                        + " surfaceResized=" + winAnimator.mSurfaceResized
+                        + " configChanged=" + configChanged
+                        + " dragResizingChanged=" + dragResizingChanged
+                        + " resizedWhileNotDragResizingReported="
+                        + isResizedWhileNotDragResizingReported()
+                        + " reportOrientationChanged=" + mReportOrientationChanged);
+            }
+
+            // 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 (mAppToken != null && mAppDied) {
+                mAppToken.removeDeadWindows();
+                return;
+            }
+
+            updateLastInsetValues();
+            mService.makeWindowFreezingScreenIfNeededLocked(this);
+
+            // If the orientation is changing, or we're starting or ending a drag resizing action,
+            // then we need to hold off on unfreezing the display until this window has been
+            // redrawn; to do that, we need to go through the process of getting informed by the
+            // application when it has finished drawing.
+            if (getOrientationChanging() || dragResizingChanged
+                    || isResizedWhileNotDragResizing()) {
+                if (DEBUG_SURFACE_TRACE || DEBUG_ANIM || DEBUG_ORIENTATION || DEBUG_RESIZE) {
+                    Slog.v(TAG_WM, "Orientation or resize start waiting for draw"
+                            + ", mDrawState=DRAW_PENDING in " + this
+                            + ", surfaceController " + winAnimator.mSurfaceController);
+                }
+                winAnimator.mDrawState = DRAW_PENDING;
+                if (mAppToken != null) {
+                    mAppToken.clearAllDrawn();
+                }
+            }
+            if (!mService.mResizingWindows.contains(this)) {
+                if (DEBUG_RESIZE || DEBUG_ORIENTATION) Slog.v(TAG_WM, "Resizing window " + this);
+                mService.mResizingWindows.add(this);
+            }
+        } else if (getOrientationChanging()) {
+            if (isDrawnLw()) {
+                if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Orientation not waiting for draw in "
+                        + this + ", surfaceController " + winAnimator.mSurfaceController);
+                setOrientationChanging(false);
+                mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
+                        - mService.mDisplayFreezeTime);
+            }
+        }
+    }
+
+    boolean getOrientationChanging() {
+        // In addition to the local state flag, we must also consider the difference in the last
+        // reported configuration vs. the current state. If the client code has not been informed of
+        // the change, logic dependent on having finished processing the orientation, such as
+        // unfreezing, could be improperly triggered.
+        // TODO(b/62846907): Checking against {@link mLastReportedConfiguration} could be flaky as
+        //                   this is not necessarily what the client has processed yet. Find a
+        //                   better indicator consistent with the client.
+        return (mOrientationChanging || (isVisible()
+                && getConfiguration().orientation != mLastReportedConfiguration.orientation))
+                && !mSeamlesslyRotated
+                && !mOrientationChangeTimedOut;
+    }
+
+    void setOrientationChanging(boolean changing) {
+        mOrientationChanging = changing;
+        mOrientationChangeTimedOut = false;
+    }
+
+    void orientationChangeTimedOut() {
+        mOrientationChangeTimedOut = true;
+    }
+
+    DisplayContent getDisplayContent() {
+        return mToken.getDisplayContent();
+    }
+
+    DisplayInfo getDisplayInfo() {
+        final DisplayContent displayContent = getDisplayContent();
+        return displayContent != null ? displayContent.getDisplayInfo() : null;
+    }
+
+    @Override
+    public int getDisplayId() {
+        final DisplayContent displayContent = getDisplayContent();
+        if (displayContent == null) {
+            return -1;
+        }
+        return displayContent.getDisplayId();
+    }
+
+    Task getTask() {
+        return mAppToken != null ? mAppToken.getTask() : null;
+    }
+
+    TaskStack getStack() {
+        Task task = getTask();
+        if (task != null) {
+            if (task.mStack != null) {
+                return task.mStack;
+            }
+        }
+        // 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.
+        final DisplayContent dc = getDisplayContent();
+        return mAttrs.type >= FIRST_SYSTEM_WINDOW && dc != null ? dc.getHomeStack() : null;
+    }
+
+    /**
+     * Retrieves the visible bounds of the window.
+     * @param bounds The rect which gets the bounds.
+     */
+    void getVisibleBounds(Rect bounds) {
+        final Task task = getTask();
+        boolean intersectWithStackBounds = task != null && task.cropWindowsToStackBounds();
+        bounds.setEmpty();
+        mTmpRect.setEmpty();
+        if (intersectWithStackBounds) {
+            final TaskStack stack = task.mStack;
+            if (stack != null) {
+                stack.getDimBounds(mTmpRect);
+            } else {
+                intersectWithStackBounds = false;
+            }
+        }
+
+        bounds.set(mVisibleFrame);
+        if (intersectWithStackBounds) {
+            bounds.intersect(mTmpRect);
+        }
+
+        if (bounds.isEmpty()) {
+            bounds.set(mFrame);
+            if (intersectWithStackBounds) {
+                bounds.intersect(mTmpRect);
+            }
+            return;
+        }
+    }
+
+    public long getInputDispatchingTimeoutNanos() {
+        return mAppToken != null
+                ? mAppToken.mInputDispatchingTimeoutNanos
+                : WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+    }
+
+    @Override
+    public boolean hasAppShownWindows() {
+        return mAppToken != null && (mAppToken.firstWindowDrawn || mAppToken.startingDisplayed);
+    }
+
+    boolean isIdentityMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
+        if (dsdx < .99999f || dsdx > 1.00001f) return false;
+        if (dtdy < .99999f || dtdy > 1.00001f) return false;
+        if (dtdx < -.000001f || dtdx > .000001f) return false;
+        if (dsdy < -.000001f || dsdy > .000001f) return false;
+        return true;
+    }
+
+    void prelayout() {
+        if (mEnforceSizeCompat) {
+            mGlobalScale = getDisplayContent().mCompatibleScreenScale;
+            mInvGlobalScale = 1 / mGlobalScale;
+        } else {
+            mGlobalScale = mInvGlobalScale = 1;
+        }
+    }
+
+    @Override
+    boolean hasContentToDisplay() {
+        if (!mAppFreezing && isDrawnLw() && (mViewVisibility == View.VISIBLE
+                || (mWinAnimator.isAnimationSet() && !mService.mAppTransition.isTransitionSet()))) {
+            return true;
+        }
+
+        return super.hasContentToDisplay();
+    }
+
+    @Override
+    boolean isVisible() {
+        return wouldBeVisibleIfPolicyIgnored() && mPolicyVisibility;
+    }
+
+    /**
+     * @return True if the window would be visible if we'd ignore policy visibility, false
+     *         otherwise.
+     */
+    boolean wouldBeVisibleIfPolicyIgnored() {
+        return mHasSurface && !isParentWindowHidden()
+                && !mAnimatingExit && !mDestroying && (!mIsWallpaper || mWallpaperVisible);
+    }
+
+    @Override
+    public boolean isVisibleLw() {
+        return isVisible();
+    }
+
+    /**
+     * Is this window visible, ignoring its app token? It is not visible if there is no surface,
+     * or we are in the process of running an exit animation that will remove the surface.
+     */
+    // TODO: Can we consolidate this with #isVisible() or have a more appropriate name for this?
+    boolean isWinVisibleLw() {
+        return (mAppToken == null || !mAppToken.hiddenRequested || mAppToken.mAppAnimator.animating)
+                && isVisible();
+    }
+
+    /**
+     * The same as isVisible(), but follows the current hidden state of the associated app token,
+     * not the pending requested hidden state.
+     */
+    boolean isVisibleNow() {
+        return (!mToken.hidden || mAttrs.type == TYPE_APPLICATION_STARTING)
+                && isVisible();
+    }
+
+    /**
+     * Can this window possibly be a drag/drop target?  The test here is
+     * a combination of the above "visible now" with the check that the
+     * Input Manager uses when discarding windows from input consideration.
+     */
+    boolean isPotentialDragTarget() {
+        return isVisibleNow() && !mRemoved
+                && mInputChannel != null && mInputWindowHandle != null;
+    }
+
+    /**
+     * Same as isVisible(), but we also count it as visible between the
+     * call to IWindowSession.add() and the first relayout().
+     */
+    boolean isVisibleOrAdding() {
+        final AppWindowToken atoken = mAppToken;
+        return (mHasSurface || (!mRelayoutCalled && mViewVisibility == View.VISIBLE))
+                && mPolicyVisibility && !isParentWindowHidden()
+                && (atoken == null || !atoken.hiddenRequested)
+                && !mAnimatingExit && !mDestroying;
+    }
+
+    /**
+     * Is this window currently on-screen?  It is on-screen either if it
+     * is visible or it is currently running an animation before no longer
+     * being visible.
+     */
+    boolean isOnScreen() {
+        if (!mHasSurface || mDestroying || !mPolicyVisibility) {
+            return false;
+        }
+        final AppWindowToken atoken = mAppToken;
+        if (atoken != null) {
+            return ((!isParentWindowHidden() && !atoken.hiddenRequested)
+                    || mWinAnimator.mAnimation != null || atoken.mAppAnimator.animation != null);
+        }
+        return !isParentWindowHidden() || mWinAnimator.mAnimation != null;
+    }
+
+    /**
+     * Whether this window's drawn state might affect the drawn states of the app token.
+     *
+     * @return true if the window should be considered while evaluating allDrawn flags.
+     */
+    boolean mightAffectAllDrawn() {
+        final boolean isAppType = mWinAnimator.mAttrType == TYPE_BASE_APPLICATION
+                || mWinAnimator.mAttrType == TYPE_DRAWN_APPLICATION;
+        return (isOnScreen() || isAppType) && !mAnimatingExit && !mDestroying;
+    }
+
+    /**
+     * Whether this window is "interesting" when evaluating allDrawn. If it's interesting,
+     * it must be drawn before allDrawn can become true.
+     */
+    boolean isInteresting() {
+        return mAppToken != null && !mAppDied
+                && (!mAppToken.mAppAnimator.freezingScreen || !mAppFreezing);
+    }
+
+    /**
+     * Like isOnScreen(), but we don't return true if the window is part
+     * of a transition that has not yet been started.
+     */
+    boolean isReadyForDisplay() {
+        if (mToken.waitingToShow && mService.mAppTransition.isTransitionSet()) {
+            return false;
+        }
+        return mHasSurface && mPolicyVisibility && !mDestroying
+                && ((!isParentWindowHidden() && mViewVisibility == View.VISIBLE && !mToken.hidden)
+                        || mWinAnimator.mAnimation != null
+                        || ((mAppToken != null) && (mAppToken.mAppAnimator.animation != null)));
+    }
+
+    // TODO: Another visibility method that was added late in the release to minimize risk.
+    @Override
+    public boolean canAffectSystemUiFlags() {
+        final boolean shown = mWinAnimator.getShown();
+
+        // We only consider the app to be exiting when the animation has started. After the app
+        // transition is executed the windows are marked exiting before the new windows have been
+        // shown. Thus, wait considering a window to be exiting after the animation has actually
+        // started.
+        final boolean appAnimationStarting = mAppToken != null
+                && mAppToken.mAppAnimator.isAnimationStarting();
+        final boolean exitingSelf = mAnimatingExit && (!mWinAnimator.isAnimationStarting()
+                && !appAnimationStarting);
+        final boolean appExiting = mAppToken != null && mAppToken.hidden && !appAnimationStarting;
+
+        final boolean exiting = exitingSelf || mDestroying || appExiting;
+        final boolean translucent = mAttrs.alpha == 0.0f;
+
+        // If we are entering with a dummy animation, avoid affecting SystemUI flags until the
+        // transition is starting.
+        final boolean enteringWithDummyAnimation =
+                mWinAnimator.isDummyAnimation() && mWinAnimator.mShownAlpha == 0f;
+        return shown && !exiting && !translucent && !enteringWithDummyAnimation;
+    }
+
+    /**
+     * Like isOnScreen, but returns false if the surface hasn't yet
+     * been drawn.
+     */
+    @Override
+    public boolean isDisplayedLw() {
+        final AppWindowToken atoken = mAppToken;
+        return isDrawnLw() && mPolicyVisibility
+            && ((!isParentWindowHidden() &&
+                    (atoken == null || !atoken.hiddenRequested))
+                        || mWinAnimator.mAnimating
+                        || (atoken != null && atoken.mAppAnimator.animation != null));
+    }
+
+    /**
+     * Return true if this window or its app token is currently animating.
+     */
+    @Override
+    public boolean isAnimatingLw() {
+        return mWinAnimator.mAnimation != null
+                || (mAppToken != null && mAppToken.mAppAnimator.animation != null);
+    }
+
+    @Override
+    public boolean isGoneForLayoutLw() {
+        final AppWindowToken atoken = mAppToken;
+        return mViewVisibility == View.GONE
+                || !mRelayoutCalled
+                || (atoken == null && mToken.hidden)
+                || (atoken != null && atoken.hiddenRequested)
+                || isParentWindowHidden()
+                || (mAnimatingExit && !isAnimatingLw())
+                || mDestroying;
+    }
+
+    /**
+     * Returns true if the window has a surface that it has drawn a
+     * complete UI in to.
+     */
+    public boolean isDrawFinishedLw() {
+        return mHasSurface && !mDestroying &&
+                (mWinAnimator.mDrawState == COMMIT_DRAW_PENDING
+                || mWinAnimator.mDrawState == READY_TO_SHOW
+                || mWinAnimator.mDrawState == HAS_DRAWN);
+    }
+
+    /**
+     * Returns true if the window has a surface that it has drawn a
+     * complete UI in to.
+     */
+    @Override
+    public boolean isDrawnLw() {
+        return mHasSurface && !mDestroying &&
+                (mWinAnimator.mDrawState == READY_TO_SHOW || mWinAnimator.mDrawState == HAS_DRAWN);
+    }
+
+    /**
+     * Return true if the window is opaque and fully drawn.  This indicates
+     * it may obscure windows behind it.
+     */
+    private boolean isOpaqueDrawn() {
+        // When there is keyguard, wallpaper could be placed over the secure app
+        // window but invisible. We need to check wallpaper visibility explicitly
+        // to determine if it's occluding apps.
+        return ((!mIsWallpaper && mAttrs.format == PixelFormat.OPAQUE)
+                || (mIsWallpaper && mWallpaperVisible))
+                && isDrawnLw() && mWinAnimator.mAnimation == null
+                && (mAppToken == null || mAppToken.mAppAnimator.animation == null);
+    }
+
+    @Override
+    void onMovedByResize() {
+        if (DEBUG_RESIZE) Slog.d(TAG, "onMovedByResize: Moving " + this);
+        mMovedByResize = true;
+        super.onMovedByResize();
+    }
+
+    boolean onAppVisibilityChanged(boolean visible, boolean runningAppAnimation) {
+        boolean changed = false;
+
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowState c = mChildren.get(i);
+            changed |= c.onAppVisibilityChanged(visible, runningAppAnimation);
+        }
+
+        if (mAttrs.type == TYPE_APPLICATION_STARTING) {
+            // Starting window that's exiting will be removed when the animation finishes.
+            // Mark all relevant flags for that onExitAnimationDone will proceed all the way
+            // to actually remove it.
+            if (!visible && isVisibleNow() && mAppToken.mAppAnimator.isAnimating()) {
+                mAnimatingExit = true;
+                mRemoveOnExit = true;
+                mWindowRemovalAllowed = true;
+            }
+            return changed;
+        }
+
+        // Next up we will notify the client that it's visibility has changed.
+        // We need to prevent it from destroying child surfaces until
+        // the animation has finished.
+        if (!visible && isVisibleNow()) {
+            mWinAnimator.detachChildren();
+        }
+
+        if (visible != isVisibleNow()) {
+            if (!runningAppAnimation) {
+                final AccessibilityController accessibilityController =
+                        mService.mAccessibilityController;
+                final int winTransit = visible ? TRANSIT_ENTER : TRANSIT_EXIT;
+                mWinAnimator.applyAnimationLocked(winTransit, visible);
+                //TODO (multidisplay): Magnification is supported only for the default
+                if (accessibilityController != null && getDisplayId() == DEFAULT_DISPLAY) {
+                    accessibilityController.onWindowTransitionLocked(this, winTransit);
+                }
+            }
+            changed = true;
+            setDisplayLayoutNeeded();
+        }
+
+        return changed;
+    }
+
+    boolean onSetAppExiting() {
+        final DisplayContent displayContent = getDisplayContent();
+        boolean changed = false;
+
+        if (isVisibleNow()) {
+            mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false);
+            //TODO (multidisplay): Magnification is supported only for the default
+            if (mService.mAccessibilityController != null && isDefaultDisplay()) {
+                mService.mAccessibilityController.onWindowTransitionLocked(this, TRANSIT_EXIT);
+            }
+            changed = true;
+            if (displayContent != null) {
+                displayContent.setLayoutNeeded();
+            }
+        }
+
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowState c = mChildren.get(i);
+            changed |= c.onSetAppExiting();
+        }
+
+        return changed;
+    }
+
+    @Override
+    void onResize() {
+        final ArrayList<WindowState> resizingWindows = mService.mResizingWindows;
+        if (mHasSurface && !resizingWindows.contains(this)) {
+            if (DEBUG_RESIZE) Slog.d(TAG, "onResize: Resizing " + this);
+            resizingWindows.add(this);
+
+            // If we are not drag resizing, force recreating of a new surface so updating
+            // the content and positioning that surface will be in sync.
+            //
+            // As we use this flag as a hint to freeze surface boundary updates, we'd like to only
+            // apply this to TYPE_BASE_APPLICATION, windows of TYPE_APPLICATION like dialogs, could
+            // appear to not be drag resizing while they resize, but we'd still like to manipulate
+            // their frame to update crop, etc...
+            //
+            // Anyway we don't need to synchronize position and content updates for these
+            // windows since they aren't at the base layer and could be moved around anyway.
+            if (!computeDragResizing() && mAttrs.type == TYPE_BASE_APPLICATION &&
+                    !mWinAnimator.isForceScaled() && !isGoneForLayoutLw() &&
+                    !getTask().inPinnedWorkspace()) {
+                setResizedWhileNotDragResizing(true);
+            }
+        }
+        if (isGoneForLayoutLw()) {
+            mResizedWhileGone = true;
+        }
+
+        super.onResize();
+    }
+
+    void onUnfreezeBounds() {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowState c = mChildren.get(i);
+            c.onUnfreezeBounds();
+        }
+
+        if (!mHasSurface) {
+            return;
+        }
+
+        mLayoutNeeded = true;
+        setDisplayLayoutNeeded();
+        if (!mService.mResizingWindows.contains(this)) {
+            mService.mResizingWindows.add(this);
+        }
+    }
+
+    /**
+     * If the window has moved due to its containing content frame changing, then notify the
+     * listeners and optionally animate it. Simply checking a change of position is not enough,
+     * because being move due to dock divider is not a trigger for animation.
+     */
+    void handleWindowMovedIfNeeded() {
+        if (!hasMoved()) {
+            return;
+        }
+
+        // Frame has moved, containing content frame has also moved, and we're not currently
+        // animating... let's do something.
+        final int left = mFrame.left;
+        final int top = mFrame.top;
+        final Task task = getTask();
+        final boolean adjustedForMinimizedDockOrIme = task != null
+                && (task.mStack.isAdjustedForMinimizedDockedStack()
+                || task.mStack.isAdjustedForIme());
+        if (mToken.okToAnimate()
+                && (mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0
+                && !isDragResizing() && !adjustedForMinimizedDockOrIme
+                && getWindowConfiguration().hasMovementAnimations()
+                && !mWinAnimator.mLastHidden) {
+            mWinAnimator.setMoveAnimation(left, top);
+        }
+
+        //TODO (multidisplay): Accessibility supported only for the default display.
+        if (mService.mAccessibilityController != null
+                && getDisplayContent().getDisplayId() == DEFAULT_DISPLAY) {
+            mService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
+        }
+
+        try {
+            mClient.moved(left, top);
+        } catch (RemoteException e) {
+        }
+        mMovedByResize = false;
+    }
+
+    /**
+     * Return whether this window has moved. (Only makes
+     * sense to call from performLayoutAndPlaceSurfacesLockedInner().)
+     */
+    private boolean hasMoved() {
+        return mHasSurface && (mContentChanged || mMovedByResize)
+                && !mAnimatingExit
+                && (mFrame.top != mLastFrame.top || mFrame.left != mLastFrame.left)
+                && (!mIsChildWindow || !getParentWindow().hasMoved());
+    }
+
+    boolean isObscuringDisplay() {
+        Task task = getTask();
+        if (task != null && task.mStack != null && !task.mStack.fillsParent()) {
+            return false;
+        }
+        return isOpaqueDrawn() && fillsDisplay();
+    }
+
+    boolean fillsDisplay() {
+        final DisplayInfo displayInfo = getDisplayInfo();
+        return mFrame.left <= 0 && mFrame.top <= 0
+                && mFrame.right >= displayInfo.appWidth && mFrame.bottom >= displayInfo.appHeight;
+    }
+
+    /** Returns true if last applied config was not yet requested by client. */
+    boolean isConfigChanged() {
+        return !mLastReportedConfiguration.equals(getConfiguration());
+    }
+
+    void onWindowReplacementTimeout() {
+        if (mWillReplaceWindow) {
+            // Since the window already timed out, remove it immediately now.
+            // Use WindowState#removeImmediately() instead of WindowState#removeIfPossible(), as the latter
+            // delays removal on certain conditions, which will leave the stale window in the
+            // stack and marked mWillReplaceWindow=false, so the window will never be removed.
+            //
+            // Also removes child windows.
+            removeImmediately();
+        } else {
+            for (int i = mChildren.size() - 1; i >= 0; --i) {
+                final WindowState c = mChildren.get(i);
+                c.onWindowReplacementTimeout();
+            }
+        }
+    }
+
+    @Override
+    void forceWindowsScaleableInTransaction(boolean force) {
+        if (mWinAnimator != null && mWinAnimator.hasSurface()) {
+            mWinAnimator.mSurfaceController.forceScaleableInTransaction(force);
+        }
+
+        super.forceWindowsScaleableInTransaction(force);
+    }
+
+    @Override
+    void removeImmediately() {
+        super.removeImmediately();
+
+        if (mRemoved) {
+            // Nothing to do.
+            if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
+                    "WS.removeImmediately: " + this + " Already removed...");
+            return;
+        }
+
+        mRemoved = true;
+
+        mWillReplaceWindow = false;
+        if (mReplacementWindow != null) {
+            mReplacementWindow.mSkipEnterAnimationForSeamlessReplacement = false;
+        }
+
+        final DisplayContent dc = getDisplayContent();
+        if (mService.mInputMethodTarget == this) {
+            dc.computeImeTarget(true /* updateImeTarget */);
+        }
+
+        final int type = mAttrs.type;
+        if (WindowManagerService.excludeWindowTypeFromTapOutTask(type)) {
+            dc.mTapExcludedWindows.remove(this);
+        }
+        mPolicy.removeWindowLw(this);
+
+        disposeInputChannel();
+
+        mWinAnimator.destroyDeferredSurfaceLocked();
+        mWinAnimator.destroySurfaceLocked();
+        mSession.windowRemovedLocked();
+        try {
+            mClient.asBinder().unlinkToDeath(mDeathRecipient, 0);
+        } catch (RuntimeException e) {
+            // Ignore if it has already been removed (usually because
+            // we are doing this as part of processing a death note.)
+        }
+
+        mService.postWindowRemoveCleanupLocked(this);
+    }
+
+    @Override
+    void removeIfPossible() {
+        super.removeIfPossible();
+        removeIfPossible(false /*keepVisibleDeadWindow*/);
+    }
+
+    private void removeIfPossible(boolean keepVisibleDeadWindow) {
+        mWindowRemovalAllowed = true;
+        if (DEBUG_ADD_REMOVE) Slog.v(TAG,
+                "removeIfPossible: " + this + " callers=" + Debug.getCallers(5));
+
+        final boolean startingWindow = mAttrs.type == TYPE_APPLICATION_STARTING;
+        if (startingWindow && DEBUG_STARTING_WINDOW) Slog.d(TAG_WM,
+                "Starting window removed " + this);
+
+        if (localLOGV || DEBUG_FOCUS || DEBUG_FOCUS_LIGHT && this == mService.mCurrentFocus)
+            Slog.v(TAG_WM, "Remove " + this + " client="
+                        + Integer.toHexString(System.identityHashCode(mClient.asBinder()))
+                        + ", surfaceController=" + mWinAnimator.mSurfaceController + " Callers="
+                        + Debug.getCallers(5));
+
+        final long origId = Binder.clearCallingIdentity();
+
+        disposeInputChannel();
+
+        if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "Remove " + this
+                + ": mSurfaceController=" + mWinAnimator.mSurfaceController
+                + " mAnimatingExit=" + mAnimatingExit
+                + " mRemoveOnExit=" + mRemoveOnExit
+                + " mHasSurface=" + mHasSurface
+                + " surfaceShowing=" + mWinAnimator.getShown()
+                + " isAnimationSet=" + mWinAnimator.isAnimationSet()
+                + " app-animation="
+                + (mAppToken != null ? mAppToken.mAppAnimator.animation : null)
+                + " mWillReplaceWindow=" + mWillReplaceWindow
+                + " inPendingTransaction="
+                + (mAppToken != null ? mAppToken.inPendingTransaction : false)
+                + " mDisplayFrozen=" + mService.mDisplayFrozen
+                + " callers=" + Debug.getCallers(6));
+
+        // Visibility of the removed window. Will be used later to update orientation later on.
+        boolean wasVisible = false;
+
+        final int displayId = getDisplayId();
+
+        // First, see if we need to run an animation. If we do, we have to hold off on removing the
+        // window until the animation is done. If the display is frozen, just remove immediately,
+        // since the animation wouldn't be seen.
+        if (mHasSurface && mToken.okToAnimate()) {
+            if (mWillReplaceWindow) {
+                // This window is going to be replaced. We need to keep it around until the new one
+                // gets added, then we will get rid of this one.
+                if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
+                        "Preserving " + this + " until the new one is " + "added");
+                // TODO: We are overloading mAnimatingExit flag to prevent the window state from
+                // been removed. We probably need another flag to indicate that window removal
+                // should be deffered vs. overloading the flag that says we are playing an exit
+                // animation.
+                mAnimatingExit = true;
+                mReplacingRemoveRequested = true;
+                Binder.restoreCallingIdentity(origId);
+                return;
+            }
+
+            // If we are not currently running the exit animation, we need to see about starting one
+            wasVisible = isWinVisibleLw();
+
+            if (keepVisibleDeadWindow) {
+                if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
+                        "Not removing " + this + " because app died while it's visible");
+
+                mAppDied = true;
+                setDisplayLayoutNeeded();
+                mService.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.
+                openInputChannel(null);
+                mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
+
+                Binder.restoreCallingIdentity(origId);
+                return;
+            }
+
+            if (wasVisible) {
+                final int transit = (!startingWindow) ? TRANSIT_EXIT : TRANSIT_PREVIEW_DONE;
+
+                // Try starting an animation.
+                if (mWinAnimator.applyAnimationLocked(transit, false)) {
+                    mAnimatingExit = true;
+                }
+                //TODO (multidisplay): Magnification is supported only for the default display.
+                if (mService.mAccessibilityController != null && displayId == DEFAULT_DISPLAY) {
+                    mService.mAccessibilityController.onWindowTransitionLocked(this, transit);
+                }
+            }
+            final boolean isAnimating =
+                    mWinAnimator.isAnimationSet() && !mWinAnimator.isDummyAnimation();
+            final boolean lastWindowIsStartingWindow = startingWindow && mAppToken != null
+                    && mAppToken.isLastWindow(this);
+            // We delay the removal of a window if it has a showing surface that can be used to run
+            // exit animation and it is marked as exiting.
+            // Also, If isn't the an animating starting window that is the last window in the app.
+            // We allow the removal of the non-animating starting window now as there is no
+            // additional window or animation that will trigger its removal.
+            if (mWinAnimator.getShown() && mAnimatingExit
+                    && (!lastWindowIsStartingWindow || isAnimating)) {
+                // The exit animation is running or should run... wait for it!
+                if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
+                        "Not removing " + this + " due to exit animation ");
+                setupWindowForRemoveOnExit();
+                if (mAppToken != null) {
+                    mAppToken.updateReportedVisibilityLocked();
+                }
+                Binder.restoreCallingIdentity(origId);
+                return;
+            }
+        }
+
+        removeImmediately();
+        // Removing a visible window will effect the computed orientation
+        // So just update orientation if needed.
+        if (wasVisible && mService.updateOrientationFromAppTokensLocked(false, displayId)) {
+            mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget();
+        }
+        mService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
+        Binder.restoreCallingIdentity(origId);
+    }
+
+    private void setupWindowForRemoveOnExit() {
+        mRemoveOnExit = true;
+        setDisplayLayoutNeeded();
+        // Request a focus update as this window's input channel is already gone. Otherwise
+        // we could have no focused window in input manager.
+        final boolean focusChanged = mService.updateFocusedWindowLocked(
+                UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/);
+        mService.mWindowPlacerLocked.performSurfacePlacement();
+        if (focusChanged) {
+            mService.mInputMonitor.updateInputWindowsLw(false /*force*/);
+        }
+    }
+
+    void setHasSurface(boolean hasSurface) {
+        mHasSurface = hasSurface;
+    }
+
+    int getAnimLayerAdjustment() {
+        if (mIsImWindow && mService.mInputMethodTarget != null) {
+            final AppWindowToken appToken = mService.mInputMethodTarget.mAppToken;
+            if (appToken != null) {
+                return appToken.getAnimLayerAdjustment();
+            }
+        }
+
+        return mToken.getAnimLayerAdjustment();
+    }
+
+    int getSpecialWindowAnimLayerAdjustment() {
+        int specialAdjustment = 0;
+        if (mIsImWindow) {
+            specialAdjustment = getDisplayContent().mInputMethodAnimLayerAdjustment;
+        } else if (mIsWallpaper) {
+            specialAdjustment = getDisplayContent().mWallpaperController.getAnimLayerAdjustment();
+        }
+
+        return mLayer + specialAdjustment;
+    }
+
+    boolean canBeImeTarget() {
+        if (mIsImWindow) {
+            // IME windows can't be IME targets. IME targets are required to be below the IME
+            // windows and that wouldn't be possible if the IME window is its own target...silly.
+            return false;
+        }
+
+        final boolean windowsAreFocusable = mAppToken == null || mAppToken.windowsAreFocusable();
+        if (!windowsAreFocusable) {
+            // This window can't be an IME target if the app's windows should not be focusable.
+            return false;
+        }
+
+        final int fl = mAttrs.flags & (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM);
+        final int type = mAttrs.type;
+
+        // Can only be an IME target if both FLAG_NOT_FOCUSABLE and FLAG_ALT_FOCUSABLE_IM are set or
+        // both are cleared...and not a starting window.
+        if (fl != 0 && fl != (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM)
+                && type != TYPE_APPLICATION_STARTING) {
+            return false;
+        }
+
+        if (DEBUG_INPUT_METHOD) {
+            Slog.i(TAG_WM, "isVisibleOrAdding " + this + ": " + isVisibleOrAdding());
+            if (!isVisibleOrAdding()) {
+                Slog.i(TAG_WM, "  mSurfaceController=" + mWinAnimator.mSurfaceController
+                        + " relayoutCalled=" + mRelayoutCalled
+                        + " viewVis=" + mViewVisibility
+                        + " policyVis=" + mPolicyVisibility
+                        + " policyVisAfterAnim=" + mPolicyVisibilityAfterAnim
+                        + " parentHidden=" + isParentWindowHidden()
+                        + " exiting=" + mAnimatingExit + " destroying=" + mDestroying);
+                if (mAppToken != null) {
+                    Slog.i(TAG_WM, "  mAppToken.hiddenRequested=" + mAppToken.hiddenRequested);
+                }
+            }
+        }
+        return isVisibleOrAdding();
+    }
+
+    void scheduleAnimationIfDimming() {
+        final DisplayContent dc = getDisplayContent();
+        if (dc == null) {
+            return;
+        }
+
+        // If layout is currently deferred, we want to hold of with updating the layers.
+        if (mService.mWindowPlacerLocked.isLayoutDeferred()) {
+            return;
+        }
+        final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
+        if (dimLayerUser != null && dc.mDimLayerController.isDimming(dimLayerUser, mWinAnimator)) {
+            // Force an animation pass just to update the mDimLayer layer.
+            mService.scheduleAnimationLocked();
+        }
+    }
+
+    private final class DeadWindowEventReceiver extends InputEventReceiver {
+        DeadWindowEventReceiver(InputChannel inputChannel) {
+            super(inputChannel, mService.mH.getLooper());
+        }
+        @Override
+        public void onInputEvent(InputEvent event, int displayId) {
+            finishInputEvent(event, true);
+        }
+    }
+    /**
+     *  Dummy event receiver for windows that died visible.
+     */
+    private DeadWindowEventReceiver mDeadWindowEventReceiver;
+
+    void openInputChannel(InputChannel outInputChannel) {
+        if (mInputChannel != null) {
+            throw new IllegalStateException("Window already has an input channel.");
+        }
+        String name = getName();
+        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;
+        } else {
+            // If the window died visible, we setup a dummy input channel, so that taps
+            // can still detected by input monitor channel, and we can relaunch the app.
+            // Create dummy event receiver that simply reports all events as handled.
+            mDeadWindowEventReceiver = new DeadWindowEventReceiver(mClientChannel);
+        }
+        mService.mInputManager.registerInputChannel(mInputChannel, mInputWindowHandle);
+    }
+
+    void disposeInputChannel() {
+        if (mDeadWindowEventReceiver != null) {
+            mDeadWindowEventReceiver.dispose();
+            mDeadWindowEventReceiver = null;
+        }
+
+        // unregister server channel first otherwise it complains about broken channel
+        if (mInputChannel != null) {
+            mService.mInputManager.unregisterInputChannel(mInputChannel);
+            mInputChannel.dispose();
+            mInputChannel = null;
+        }
+        if (mClientChannel != null) {
+            mClientChannel.dispose();
+            mClientChannel = null;
+        }
+        mInputWindowHandle.inputChannel = null;
+    }
+
+    void applyDimLayerIfNeeded() {
+        // When the app is terminated (eg. from Recents), the task might have already been
+        // removed with the window pending removal. Don't apply dim in such cases, as there
+        // will be no more updateDimLayer() calls, which leaves the dimlayer invalid.
+        final AppWindowToken token = mAppToken;
+        if (token != null && token.removed) {
+            return;
+        }
+
+        final DisplayContent dc = getDisplayContent();
+        if (!mAnimatingExit && mAppDied) {
+            // If app died visible, apply a dim over the window to indicate that it's inactive
+            dc.mDimLayerController.applyDimAbove(getDimLayerUser(), mWinAnimator);
+        } else if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0
+                && dc != null && !mAnimatingExit && isVisible()) {
+            dc.mDimLayerController.applyDimBehind(getDimLayerUser(), mWinAnimator);
+        }
+    }
+
+    private DimLayer.DimLayerUser getDimLayerUser() {
+        Task task = getTask();
+        if (task != null) {
+            return task;
+        }
+        return getStack();
+    }
+
+    /** Returns true if the replacement window was removed. */
+    boolean removeReplacedWindowIfNeeded(WindowState replacement) {
+        if (mWillReplaceWindow && mReplacementWindow == replacement && replacement.hasDrawnLw()) {
+            replacement.mSkipEnterAnimationForSeamlessReplacement = false;
+            removeReplacedWindow();
+            return true;
+        }
+
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowState c = mChildren.get(i);
+            if (c.removeReplacedWindowIfNeeded(replacement)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void removeReplacedWindow() {
+        if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replaced window: " + this);
+        if (isDimming()) {
+            transferDimToReplacement();
+        }
+        mWillReplaceWindow = false;
+        mAnimateReplacingWindow = false;
+        mReplacingRemoveRequested = false;
+        mReplacementWindow = null;
+        if (mAnimatingExit || !mAnimateReplacingWindow) {
+            removeImmediately();
+        }
+    }
+
+    boolean setReplacementWindowIfNeeded(WindowState replacementCandidate) {
+        boolean replacementSet = false;
+
+        if (mWillReplaceWindow && mReplacementWindow == null
+                && getWindowTag().toString().equals(replacementCandidate.getWindowTag().toString())) {
+
+            mReplacementWindow = replacementCandidate;
+            replacementCandidate.mSkipEnterAnimationForSeamlessReplacement = !mAnimateReplacingWindow;
+            replacementSet = true;
+        }
+
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowState c = mChildren.get(i);
+            replacementSet |= c.setReplacementWindowIfNeeded(replacementCandidate);
+        }
+
+        return replacementSet;
+    }
+
+    void setDisplayLayoutNeeded() {
+        final DisplayContent dc = getDisplayContent();
+        if (dc != null) {
+            dc.setLayoutNeeded();
+        }
+    }
+
+    // TODO: Strange usage of word workspace here and above.
+    boolean inPinnedWorkspace() {
+        final Task task = getTask();
+        return task != null && task.inPinnedWorkspace();
+    }
+
+    void applyAdjustForImeIfNeeded() {
+        final Task task = getTask();
+        if (task != null && task.mStack != null && task.mStack.isAdjustedForIme()) {
+            task.mStack.applyAdjustForImeIfNeeded(task);
+        }
+    }
+
+    @Override
+    void switchUser() {
+        super.switchUser();
+        if (isHiddenFromUserLocked()) {
+            if (DEBUG_VISIBILITY) Slog.w(TAG_WM, "user changing, hiding " + this
+                    + ", attrs=" + mAttrs.type + ", belonging to " + mOwnerUid);
+            hideLw(false);
+        }
+    }
+
+    int getTouchableRegion(Region region, int flags) {
+        final boolean modal = (flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0;
+        if (modal && mAppToken != null) {
+            // Limit the outer touch to the activity stack region.
+            flags |= FLAG_NOT_TOUCH_MODAL;
+            // If this is a modal window we need to dismiss it if it's not full screen and the
+            // touch happens outside of the frame that displays the content. This means we
+            // need to intercept touches outside of that window. The dim layer user
+            // associated with the window (task or stack) will give us the good bounds, as
+            // they would be used to display the dim layer.
+            final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
+            if (dimLayerUser != null) {
+                dimLayerUser.getDimBounds(mTmpRect);
+            } else {
+                getVisibleBounds(mTmpRect);
+            }
+            if (inFreeformWorkspace()) {
+                // For freeform windows we the touch region to include the whole surface for the
+                // shadows.
+                final DisplayMetrics displayMetrics = getDisplayContent().getDisplayMetrics();
+                final int delta = WindowManagerService.dipToPixel(
+                        RESIZE_HANDLE_WIDTH_IN_DP, displayMetrics);
+                mTmpRect.inset(-delta, -delta);
+            }
+            region.set(mTmpRect);
+            cropRegionToStackBoundsIfNeeded(region);
+        } else {
+            // Not modal or full screen modal
+            getTouchableRegion(region);
+        }
+        return flags;
+    }
+
+    void checkPolicyVisibilityChange() {
+        if (mPolicyVisibility != mPolicyVisibilityAfterAnim) {
+            if (DEBUG_VISIBILITY) {
+                Slog.v(TAG, "Policy visibility changing after anim in " +
+                        mWinAnimator + ": " + mPolicyVisibilityAfterAnim);
+            }
+            mPolicyVisibility = mPolicyVisibilityAfterAnim;
+            setDisplayLayoutNeeded();
+            if (!mPolicyVisibility) {
+                if (mService.mCurrentFocus == this) {
+                    if (DEBUG_FOCUS_LIGHT) Slog.i(TAG,
+                            "setAnimationLocked: setting mFocusMayChange true");
+                    mService.mFocusMayChange = true;
+                }
+                // Window is no longer visible -- make sure if we were waiting
+                // for it to be displayed before enabling the display, that
+                // we allow the display to be enabled now.
+                mService.enableScreenIfNeededLocked();
+            }
+        }
+    }
+
+    void setRequestedSize(int requestedWidth, int requestedHeight) {
+        if ((mRequestedWidth != requestedWidth || mRequestedHeight != requestedHeight)) {
+            mLayoutNeeded = true;
+            mRequestedWidth = requestedWidth;
+            mRequestedHeight = requestedHeight;
+        }
+    }
+
+    void prepareWindowToDisplayDuringRelayout(boolean wasVisible) {
+        // We need to turn on screen regardless of visibility.
+        if ((mAttrs.flags & FLAG_TURN_SCREEN_ON) != 0) {
+            if (DEBUG_VISIBILITY) Slog.v(TAG, "Relayout window turning screen on: " + this);
+            mTurnOnScreen = true;
+        }
+
+        // If we were already visible, skip rest of preparation.
+        if (wasVisible) {
+            if (DEBUG_VISIBILITY) Slog.v(TAG,
+                    "Already visible and does not turn on screen, skip preparing: " + this);
+            return;
+        }
+
+        if ((mAttrs.softInputMode & SOFT_INPUT_MASK_ADJUST)
+                == SOFT_INPUT_ADJUST_RESIZE) {
+            mLayoutNeeded = true;
+        }
+
+        if (isDrawnLw() && mToken.okToAnimate()) {
+            mWinAnimator.applyEnterAnimationLocked();
+        }
+    }
+
+    void getMergedConfiguration(MergedConfiguration outConfiguration) {
+        final Configuration globalConfig = mService.mRoot.getConfiguration();
+        final Configuration overrideConfig = getMergedOverrideConfiguration();
+        outConfiguration.setConfiguration(globalConfig, overrideConfig);
+    }
+
+    void setReportedConfiguration(MergedConfiguration config) {
+        mLastReportedConfiguration.setTo(config.getMergedConfiguration());
+    }
+
+    void adjustStartingWindowFlags() {
+        if (mAttrs.type == TYPE_BASE_APPLICATION && mAppToken != null
+                && mAppToken.startingWindow != null) {
+            // Special handling of starting window over the base
+            // window of the app: propagate lock screen flags to it,
+            // to provide the correct semantics while starting.
+            final int mask = FLAG_SHOW_WHEN_LOCKED | FLAG_DISMISS_KEYGUARD
+                    | FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
+            WindowManager.LayoutParams sa = mAppToken.startingWindow.mAttrs;
+            sa.flags = (sa.flags & ~mask) | (mAttrs.flags & mask);
+        }
+    }
+
+    void setWindowScale(int requestedWidth, int requestedHeight) {
+        final boolean scaledWindow = (mAttrs.flags & FLAG_SCALED) != 0;
+
+        if (scaledWindow) {
+            // requested{Width|Height} Surface's physical size
+            // attrs.{width|height} Size on screen
+            // TODO: We don't check if attrs != null here. Is it implicitly checked?
+            mHScale = (mAttrs.width  != requestedWidth)  ?
+                    (mAttrs.width  / (float)requestedWidth) : 1.0f;
+            mVScale = (mAttrs.height != requestedHeight) ?
+                    (mAttrs.height / (float)requestedHeight) : 1.0f;
+        } else {
+            mHScale = mVScale = 1;
+        }
+    }
+
+    private class DeathRecipient implements IBinder.DeathRecipient {
+        @Override
+        public void binderDied() {
+            try {
+                synchronized(mService.mWindowMap) {
+                    final WindowState win = mService.windowForClientLocked(mSession, mClient, false);
+                    Slog.i(TAG, "WIN DEATH: " + win);
+                    if (win != null) {
+                        final DisplayContent dc = getDisplayContent();
+                        if (win.mAppToken != null && win.mAppToken.findMainWindow() == win) {
+                            mService.mTaskSnapshotController.onAppDied(win.mAppToken);
+                        }
+                        win.removeIfPossible(shouldKeepVisibleDeadAppWindow());
+                        if (win.mAttrs.type == TYPE_DOCK_DIVIDER) {
+                            // The owner of the docked divider died :( We reset the docked stack,
+                            // just in case they have the divider at an unstable position. Better
+                            // also reset drag resizing state, because the owner can't do it
+                            // anymore.
+                            final TaskStack stack = dc.getDockedStackIgnoringVisibility();
+                            if (stack != null) {
+                                stack.resetDockedStackToMiddle();
+                            }
+                            mService.setDockedStackResizing(false);
+                        }
+                    } else if (mHasSurface) {
+                        Slog.e(TAG, "!!! LEAK !!! Window removed but surface still valid.");
+                        WindowState.this.removeIfPossible();
+                    }
+                }
+            } catch (IllegalArgumentException ex) {
+                // This will happen if the window has already been removed.
+            }
+        }
+    }
+
+    /**
+     * Returns true if this window is visible and belongs to a dead app and shouldn't be removed,
+     * because we want to preserve its location on screen to be re-activated later when the user
+     * interacts with it.
+     */
+    private boolean shouldKeepVisibleDeadAppWindow() {
+        if (!isWinVisibleLw() || mAppToken == null || mAppToken.isClientHidden()) {
+            // Not a visible app window or the app isn't dead.
+            return false;
+        }
+
+        if (mAttrs.token != mClient.asBinder()) {
+            // The window was add by a client using another client's app token. We don't want to
+            // keep the dead window around for this case since this is meant for 'real' apps.
+            return false;
+        }
+
+        if (mAttrs.type == TYPE_APPLICATION_STARTING) {
+            // We don't keep starting windows since they were added by the window manager before
+            // the app even launched.
+            return false;
+        }
+
+        return getWindowConfiguration().keepVisibleDeadAppWindowOnScreen();
+    }
+
+    /** @return true if this window desires key events. */
+    boolean canReceiveKeys() {
+        return isVisibleOrAdding()
+                && (mViewVisibility == View.VISIBLE) && !mRemoveOnExit
+                && ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0)
+                && (mAppToken == null || mAppToken.windowsAreFocusable())
+                && !canReceiveTouchInput();
+    }
+
+    /** @return true if this window desires touch events. */
+    boolean canReceiveTouchInput() {
+        return mAppToken != null && mAppToken.getTask() != null
+                && mAppToken.getTask().mStack.shouldIgnoreInput();
+    }
+
+    @Override
+    public boolean hasDrawnLw() {
+        return mWinAnimator.mDrawState == WindowStateAnimator.HAS_DRAWN;
+    }
+
+    @Override
+    public boolean showLw(boolean doAnimation) {
+        return showLw(doAnimation, true);
+    }
+
+    boolean showLw(boolean doAnimation, boolean requestAnim) {
+        if (isHiddenFromUserLocked()) {
+            return false;
+        }
+        if (!mAppOpVisibility) {
+            // 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 (mForceHideNonSystemOverlayWindow) {
+            // This is an alert window that is currently force hidden.
+            return false;
+        }
+        if (mPolicyVisibility && mPolicyVisibilityAfterAnim) {
+            // Already showing.
+            return false;
+        }
+        if (DEBUG_VISIBILITY) Slog.v(TAG, "Policy visibility true: " + this);
+        if (doAnimation) {
+            if (DEBUG_VISIBILITY) Slog.v(TAG, "doAnimation: mPolicyVisibility="
+                    + mPolicyVisibility + " mAnimation=" + mWinAnimator.mAnimation);
+            if (!mToken.okToAnimate()) {
+                doAnimation = false;
+            } else if (mPolicyVisibility && mWinAnimator.mAnimation == null) {
+                // Check for the case where we are currently visible and
+                // not animating; we do not want to do animation at such a
+                // point to become visible when we already are.
+                doAnimation = false;
+            }
+        }
+        mPolicyVisibility = true;
+        mPolicyVisibilityAfterAnim = true;
+        if (doAnimation) {
+            mWinAnimator.applyAnimationLocked(WindowManagerPolicy.TRANSIT_ENTER, true);
+        }
+        if (requestAnim) {
+            mService.scheduleAnimationLocked();
+        }
+        if ((mAttrs.flags & FLAG_NOT_FOCUSABLE) == 0) {
+            mService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, false /* updateImWindows */);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean hideLw(boolean doAnimation) {
+        return hideLw(doAnimation, true);
+    }
+
+    boolean hideLw(boolean doAnimation, boolean requestAnim) {
+        if (doAnimation) {
+            if (!mToken.okToAnimate()) {
+                doAnimation = false;
+            }
+        }
+        boolean current = doAnimation ? mPolicyVisibilityAfterAnim : mPolicyVisibility;
+        if (!current) {
+            // Already hiding.
+            return false;
+        }
+        if (doAnimation) {
+            mWinAnimator.applyAnimationLocked(WindowManagerPolicy.TRANSIT_EXIT, false);
+            if (mWinAnimator.mAnimation == null) {
+                doAnimation = false;
+            }
+        }
+        mPolicyVisibilityAfterAnim = false;
+        if (!doAnimation) {
+            if (DEBUG_VISIBILITY) Slog.v(TAG, "Policy visibility false: " + this);
+            mPolicyVisibility = false;
+            // Window is no longer visible -- make sure if we were waiting
+            // for it to be displayed before enabling the display, that
+            // we allow the display to be enabled now.
+            mService.enableScreenIfNeededLocked();
+            if (mService.mCurrentFocus == this) {
+                if (DEBUG_FOCUS_LIGHT) Slog.i(TAG,
+                        "WindowState.hideLw: setting mFocusMayChange true");
+                mService.mFocusMayChange = true;
+            }
+        }
+        if (requestAnim) {
+            mService.scheduleAnimationLocked();
+        }
+        if (mService.mCurrentFocus == this) {
+            mService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, false /* updateImWindows */);
+        }
+        return true;
+    }
+
+    void setForceHideNonSystemOverlayWindowIfNeeded(boolean forceHide) {
+        if (mOwnerCanAddInternalSystemWindow
+                || (!isSystemAlertWindowType(mAttrs.type) && mAttrs.type != TYPE_TOAST)) {
+            return;
+        }
+        if (mForceHideNonSystemOverlayWindow == forceHide) {
+            return;
+        }
+        mForceHideNonSystemOverlayWindow = forceHide;
+        if (forceHide) {
+            hideLw(true /* doAnimation */, true /* requestAnim */);
+        } else {
+            showLw(true /* doAnimation */, true /* requestAnim */);
+        }
+    }
+
+    public void setAppOpVisibilityLw(boolean state) {
+        if (mAppOpVisibility != state) {
+            mAppOpVisibility = state;
+            if (state) {
+                // If the policy visibility had last been to hide, then this
+                // will incorrectly show at this point since we lost that
+                // information.  Not a big deal -- for the windows that have app
+                // ops modifies they should only be hidden by policy due to the
+                // lock screen, and the user won't be changing this if locked.
+                // Plus it will quickly be fixed the next time we do a layout.
+                showLw(true, true);
+            } else {
+                hideLw(true, true);
+            }
+        }
+    }
+
+    public void hidePermanentlyLw() {
+        if (!mPermanentlyHidden) {
+            mPermanentlyHidden = true;
+            hideLw(true, true);
+        }
+    }
+
+    public void pokeDrawLockLw(long timeout) {
+        if (isVisibleOrAdding()) {
+            if (mDrawLock == null) {
+                // We want the tag name to be somewhat stable so that it is easier to correlate
+                // in wake lock statistics.  So in particular, we don't want to include the
+                // window's hash code as in toString().
+                final CharSequence tag = getWindowTag();
+                mDrawLock = mService.mPowerManager.newWakeLock(
+                        PowerManager.DRAW_WAKE_LOCK, "Window:" + tag);
+                mDrawLock.setReferenceCounted(false);
+                mDrawLock.setWorkSource(new WorkSource(mOwnerUid, mAttrs.packageName));
+            }
+            // Each call to acquire resets the timeout.
+            if (DEBUG_POWER) {
+                Slog.d(TAG, "pokeDrawLock: poking draw lock on behalf of visible window owned by "
+                        + mAttrs.packageName);
+            }
+            mDrawLock.acquire(timeout);
+        } else if (DEBUG_POWER) {
+            Slog.d(TAG, "pokeDrawLock: suppressed draw lock request for invisible window "
+                    + "owned by " + mAttrs.packageName);
+        }
+    }
+
+    @Override
+    public boolean isAlive() {
+        return mClient.asBinder().isBinderAlive();
+    }
+
+    boolean isClosing() {
+        return mAnimatingExit || (mService.mClosingApps.contains(mAppToken));
+    }
+
+    @Override
+    boolean isAnimating() {
+        if (mWinAnimator.isAnimationSet() || mAnimatingExit) {
+            return true;
+        }
+        return super.isAnimating();
+    }
+
+    void addWinAnimatorToList(ArrayList<WindowStateAnimator> animators) {
+        animators.add(mWinAnimator);
+
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowState c = mChildren.get(i);
+            c.addWinAnimatorToList(animators);
+        }
+    }
+
+    void sendAppVisibilityToClients() {
+        super.sendAppVisibilityToClients();
+
+        final boolean clientHidden = mAppToken.isClientHidden();
+        if (mAttrs.type == TYPE_APPLICATION_STARTING && clientHidden) {
+            // Don't hide the starting window.
+            return;
+        }
+
+        if (clientHidden) {
+            // Once we are notifying the client that it's visibility has changed, we need to prevent
+            // it from destroying child surfaces until the animation has finished. We do this by
+            // detaching any surface control the client added from the client.
+            for (int i = mChildren.size() - 1; i >= 0; --i) {
+                final WindowState c = mChildren.get(i);
+                c.mWinAnimator.detachChildren();
+            }
+
+            mWinAnimator.detachChildren();
+        }
+
+        try {
+            if (DEBUG_VISIBILITY) Slog.v(TAG,
+                    "Setting visibility of " + this + ": " + (!clientHidden));
+            mClient.dispatchAppVisibility(!clientHidden);
+        } catch (RemoteException e) {
+        }
+    }
+
+    void onStartFreezingScreen() {
+        mAppFreezing = true;
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowState c = mChildren.get(i);
+            c.onStartFreezingScreen();
+        }
+    }
+
+    boolean onStopFreezingScreen() {
+        boolean unfrozeWindows = false;
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowState c = mChildren.get(i);
+            unfrozeWindows |= c.onStopFreezingScreen();
+        }
+
+        if (!mAppFreezing) {
+            return unfrozeWindows;
+        }
+
+        mAppFreezing = false;
+
+        if (mHasSurface && !getOrientationChanging()
+                && mService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
+            if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "set mOrientationChanging of " + this);
+            setOrientationChanging(true);
+            mService.mRoot.mOrientationChangeComplete = false;
+        }
+        mLastFreezeDuration = 0;
+        setDisplayLayoutNeeded();
+        return true;
+    }
+
+    boolean destroySurface(boolean cleanupOnResume, boolean appStopped) {
+        boolean destroyedSomething = false;
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowState c = mChildren.get(i);
+            destroyedSomething |= c.destroySurface(cleanupOnResume, appStopped);
+        }
+
+        if (!(appStopped || mWindowRemovalAllowed || cleanupOnResume)) {
+            return destroyedSomething;
+        }
+
+        if (appStopped || mWindowRemovalAllowed) {
+            mWinAnimator.destroyPreservedSurfaceLocked();
+        }
+
+        if (mDestroying) {
+            if (DEBUG_ADD_REMOVE) Slog.e(TAG_WM, "win=" + this
+                    + " destroySurfaces: appStopped=" + appStopped
+                    + " win.mWindowRemovalAllowed=" + mWindowRemovalAllowed
+                    + " win.mRemoveOnExit=" + mRemoveOnExit);
+            if (!cleanupOnResume || mRemoveOnExit) {
+                destroySurfaceUnchecked();
+            }
+            if (mRemoveOnExit) {
+                removeImmediately();
+            }
+            if (cleanupOnResume) {
+                requestUpdateWallpaperIfNeeded();
+            }
+            mDestroying = false;
+            destroyedSomething = true;
+        }
+
+        return destroyedSomething;
+    }
+
+    // Destroy or save the application surface without checking
+    // various indicators of whether the client has released the surface.
+    // This is in general unsafe, and most callers should use {@link #destroySurface}
+    void destroySurfaceUnchecked() {
+        mWinAnimator.destroySurfaceLocked();
+
+        // Clear animating flags now, since the surface is now gone. (Note this is true even
+        // if the surface is saved, to outside world the surface is still NO_SURFACE.)
+        mAnimatingExit = false;
+    }
+
+    @Override
+    public boolean isDefaultDisplay() {
+        final DisplayContent displayContent = getDisplayContent();
+        if (displayContent == null) {
+            // Only a window that was on a non-default display can be detached from it.
+            return false;
+        }
+        return displayContent.isDefaultDisplay;
+    }
+
+    @Override
+    public boolean isDimming() {
+        final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
+        final DisplayContent dc = getDisplayContent();
+        return dimLayerUser != null && dc != null
+                && dc.mDimLayerController.isDimming(dimLayerUser, mWinAnimator);
+    }
+
+    void setShowToOwnerOnlyLocked(boolean showToOwnerOnly) {
+        mShowToOwnerOnly = showToOwnerOnly;
+    }
+
+    private boolean isHiddenFromUserLocked() {
+        // Child windows are evaluated based on their parent window.
+        final WindowState win = getTopParentWindow();
+        if (win.mAttrs.type < FIRST_SYSTEM_WINDOW
+                && win.mAppToken != null && win.mAppToken.mShowForAllUsers) {
+
+            // All window frames that are fullscreen extend above status bar, but some don't extend
+            // below navigation bar. Thus, check for display frame for top/left and stable frame for
+            // bottom right.
+            if (win.mFrame.left <= win.mDisplayFrame.left
+                    && win.mFrame.top <= win.mDisplayFrame.top
+                    && win.mFrame.right >= win.mStableFrame.right
+                    && win.mFrame.bottom >= win.mStableFrame.bottom) {
+                // Is a fullscreen window, like the clock alarm. Show to everyone.
+                return false;
+            }
+        }
+
+        return win.mShowToOwnerOnly
+                && !mService.isCurrentProfileLocked(UserHandle.getUserId(win.mOwnerUid));
+    }
+
+    private static void applyInsets(Region outRegion, Rect frame, Rect inset) {
+        outRegion.set(
+                frame.left + inset.left, frame.top + inset.top,
+                frame.right - inset.right, frame.bottom - inset.bottom);
+    }
+
+    void getTouchableRegion(Region outRegion) {
+        final Rect frame = mFrame;
+        switch (mTouchableInsets) {
+            default:
+            case TOUCHABLE_INSETS_FRAME:
+                outRegion.set(frame);
+                break;
+            case TOUCHABLE_INSETS_CONTENT:
+                applyInsets(outRegion, frame, mGivenContentInsets);
+                break;
+            case TOUCHABLE_INSETS_VISIBLE:
+                applyInsets(outRegion, frame, mGivenVisibleInsets);
+                break;
+            case TOUCHABLE_INSETS_REGION: {
+                outRegion.set(mGivenTouchableRegion);
+                outRegion.translate(frame.left, frame.top);
+                break;
+            }
+        }
+        cropRegionToStackBoundsIfNeeded(outRegion);
+    }
+
+    private void cropRegionToStackBoundsIfNeeded(Region region) {
+        final Task task = getTask();
+        if (task == null || !task.cropWindowsToStackBounds()) {
+            return;
+        }
+
+        final TaskStack stack = task.mStack;
+        if (stack == null) {
+            return;
+        }
+
+        stack.getDimBounds(mTmpRect);
+        region.op(mTmpRect, Region.Op.INTERSECT);
+    }
+
+    /**
+     * Report a focus change.  Must be called with no locks held, and consistently
+     * from the same serialized thread (such as dispatched from a handler).
+     */
+    void reportFocusChangedSerialized(boolean focused, boolean inTouchMode) {
+        try {
+            mClient.windowFocusChanged(focused, inTouchMode);
+        } catch (RemoteException e) {
+        }
+        if (mFocusCallbacks != null) {
+            final int N = mFocusCallbacks.beginBroadcast();
+            for (int i=0; i<N; i++) {
+                IWindowFocusObserver obs = mFocusCallbacks.getBroadcastItem(i);
+                try {
+                    if (focused) {
+                        obs.focusGained(mWindowId.asBinder());
+                    } else {
+                        obs.focusLost(mWindowId.asBinder());
+                    }
+                } catch (RemoteException e) {
+                }
+            }
+            mFocusCallbacks.finishBroadcast();
+        }
+    }
+
+    @Override
+    public Configuration getConfiguration() {
+        if (mAppToken != null && mAppToken.mFrozenMergedConfig.size() > 0) {
+            return mAppToken.mFrozenMergedConfig.peek();
+        }
+
+        return super.getConfiguration();
+    }
+
+    void reportResized() {
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wm.reportResized_" + getWindowTag());
+        try {
+            if (DEBUG_RESIZE || DEBUG_ORIENTATION) Slog.v(TAG, "Reporting new frame to " + this
+                    + ": " + mCompatFrame);
+            final MergedConfiguration mergedConfiguration =
+                    new MergedConfiguration(mService.mRoot.getConfiguration(),
+                    getMergedOverrideConfiguration());
+
+            setReportedConfiguration(mergedConfiguration);
+
+            if (DEBUG_ORIENTATION && mWinAnimator.mDrawState == DRAW_PENDING)
+                Slog.i(TAG, "Resizing " + this + " WITH DRAW PENDING");
+
+            final Rect frame = mFrame;
+            final Rect overscanInsets = mLastOverscanInsets;
+            final Rect contentInsets = mLastContentInsets;
+            final Rect visibleInsets = mLastVisibleInsets;
+            final Rect stableInsets = mLastStableInsets;
+            final Rect outsets = mLastOutsets;
+            final boolean reportDraw = mWinAnimator.mDrawState == DRAW_PENDING;
+            final boolean reportOrientation = mReportOrientationChanged;
+            final int displayId = getDisplayId();
+            if (mAttrs.type != WindowManager.LayoutParams.TYPE_APPLICATION_STARTING
+                    && mClient instanceof IWindow.Stub) {
+                // To prevent deadlock simulate one-way call if win.mClient is a local object.
+                mService.mH.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        try {
+                            dispatchResized(frame, overscanInsets, contentInsets, visibleInsets,
+                                    stableInsets, outsets, reportDraw, mergedConfiguration,
+                                    reportOrientation, displayId);
+                        } catch (RemoteException e) {
+                            // Not a remote call, RemoteException won't be raised.
+                        }
+                    }
+                });
+            } else {
+                dispatchResized(frame, overscanInsets, contentInsets, visibleInsets, stableInsets,
+                        outsets, reportDraw, mergedConfiguration, reportOrientation, displayId);
+            }
+
+            //TODO (multidisplay): Accessibility supported only for the default display.
+            if (mService.mAccessibilityController != null && getDisplayId() == DEFAULT_DISPLAY) {
+                mService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
+            }
+
+            mOverscanInsetsChanged = false;
+            mContentInsetsChanged = false;
+            mVisibleInsetsChanged = false;
+            mStableInsetsChanged = false;
+            mOutsetsChanged = false;
+            mFrameSizeChanged = false;
+            mResizedWhileNotDragResizingReported = true;
+            mWinAnimator.mSurfaceResized = false;
+            mReportOrientationChanged = false;
+        } catch (RemoteException e) {
+            setOrientationChanging(false);
+            mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
+                    - mService.mDisplayFreezeTime);
+            // We are assuming the hosting process is dead or in a zombie state.
+            Slog.w(TAG, "Failed to report 'resized' to the client of " + this
+                    + ", removing this window.");
+            mService.mPendingRemove.add(this);
+            mService.mWindowPlacerLocked.requestTraversal();
+        }
+        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+    }
+
+    Rect getBackdropFrame(Rect frame) {
+        // When the task is docked, we send fullscreen sized backDropFrame as soon as resizing
+        // start even if we haven't received the relayout window, so that the client requests
+        // the relayout sooner. When dragging stops, backDropFrame needs to stay fullscreen
+        // until the window to small size, otherwise the multithread renderer will shift last
+        // one or more frame to wrong offset. So here we send fullscreen backdrop if either
+        // isDragResizing() or isDragResizeChanged() is true.
+        boolean resizing = isDragResizing() || isDragResizeChanged();
+        if (getWindowConfiguration().useWindowFrameForBackdrop() || !resizing) {
+            return frame;
+        }
+        final DisplayInfo displayInfo = getDisplayInfo();
+        mTmpRect.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
+        return mTmpRect;
+    }
+
+    @Override
+    public int getStackId() {
+        final TaskStack stack = getStack();
+        if (stack == null) {
+            return INVALID_STACK_ID;
+        }
+        return stack.mStackId;
+    }
+
+    private void dispatchResized(Rect frame, Rect overscanInsets, Rect contentInsets,
+            Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw,
+            MergedConfiguration mergedConfiguration, boolean reportOrientation, int displayId)
+            throws RemoteException {
+        final boolean forceRelayout = isDragResizeChanged() || mResizedWhileNotDragResizing
+                || reportOrientation;
+
+        mClient.resized(frame, overscanInsets, contentInsets, visibleInsets, stableInsets, outsets,
+                reportDraw, mergedConfiguration, getBackdropFrame(frame), forceRelayout,
+                mPolicy.isNavBarForcedShownLw(this), displayId);
+        mDragResizingChangeReported = true;
+    }
+
+    public void registerFocusObserver(IWindowFocusObserver observer) {
+        synchronized(mService.mWindowMap) {
+            if (mFocusCallbacks == null) {
+                mFocusCallbacks = new RemoteCallbackList<IWindowFocusObserver>();
+            }
+            mFocusCallbacks.register(observer);
+        }
+    }
+
+    public void unregisterFocusObserver(IWindowFocusObserver observer) {
+        synchronized(mService.mWindowMap) {
+            if (mFocusCallbacks != null) {
+                mFocusCallbacks.unregister(observer);
+            }
+        }
+    }
+
+    public boolean isFocused() {
+        synchronized(mService.mWindowMap) {
+            return mService.mCurrentFocus == this;
+        }
+    }
+
+    boolean inFreeformWorkspace() {
+        final Task task = getTask();
+        return task != null && task.inFreeformWorkspace();
+    }
+
+    @Override
+    public boolean isInMultiWindowMode() {
+        final Task task = getTask();
+        return task != null && !task.isFullscreen();
+    }
+
+    /** Is this window in a container that takes up the entire screen space? */
+    private boolean inFullscreenContainer() {
+        if (mAppToken == null) {
+            return true;
+        }
+        if (mAppToken.hasBounds()) {
+            return false;
+        }
+        return !isInMultiWindowMode();
+    }
+
+    /** @return true when the window is in fullscreen task, but has non-fullscreen bounds set. */
+    boolean isLetterboxedAppWindow() {
+        final Task task = getTask();
+        final boolean taskIsFullscreen = task != null && task.isFullscreen();
+        final boolean appWindowIsFullscreen = mAppToken != null && !mAppToken.hasBounds();
+
+        return taskIsFullscreen && !appWindowIsFullscreen;
+    }
+
+    /** Returns the appropriate bounds to use for computing frames. */
+    private void getContainerBounds(Rect outBounds) {
+        if (isInMultiWindowMode()) {
+            getTask().getBounds(outBounds);
+        } else if (mAppToken != null){
+            mAppToken.getBounds(outBounds);
+        } else {
+            outBounds.setEmpty();
+        }
+    }
+
+    boolean isDragResizeChanged() {
+        return mDragResizing != computeDragResizing();
+    }
+
+    @Override
+    void setWaitingForDrawnIfResizingChanged() {
+        if (isDragResizeChanged()) {
+            mService.mWaitingForDrawn.add(this);
+        }
+        super.setWaitingForDrawnIfResizingChanged();
+    }
+
+    /**
+     * @return Whether we reported a drag resize change to the application or not already.
+     */
+    private boolean isDragResizingChangeReported() {
+        return mDragResizingChangeReported;
+    }
+
+    /**
+     * Resets the state whether we reported a drag resize change to the app.
+     */
+    @Override
+    void resetDragResizingChangeReported() {
+        mDragResizingChangeReported = false;
+        super.resetDragResizingChangeReported();
+    }
+
+    /**
+     * Set whether we got resized but drag resizing flag was false.
+     * @see #isResizedWhileNotDragResizing().
+     */
+    private void setResizedWhileNotDragResizing(boolean resizedWhileNotDragResizing) {
+        mResizedWhileNotDragResizing = resizedWhileNotDragResizing;
+        mResizedWhileNotDragResizingReported = !resizedWhileNotDragResizing;
+    }
+
+    /**
+     * Indicates whether we got resized but drag resizing flag was false. In this case, we also
+     * need to recreate the surface and defer surface bound updates in order to make sure the
+     * buffer contents and the positioning/size stay in sync.
+     */
+    boolean isResizedWhileNotDragResizing() {
+        return mResizedWhileNotDragResizing;
+    }
+
+    /**
+     * @return Whether we reported "resize while not drag resizing" to the application.
+     * @see #isResizedWhileNotDragResizing()
+     */
+    private boolean isResizedWhileNotDragResizingReported() {
+        return mResizedWhileNotDragResizingReported;
+    }
+
+    int getResizeMode() {
+        return mResizeMode;
+    }
+
+    private boolean computeDragResizing() {
+        final Task task = getTask();
+        if (task == null) {
+            return false;
+        }
+        if (!StackId.isStackAffectedByDragResizing(getStackId())) {
+            return false;
+        }
+        if (mAttrs.width != MATCH_PARENT || mAttrs.height != MATCH_PARENT) {
+            // Floating windows never enter drag resize mode.
+            return false;
+        }
+        if (task.isDragResizing()) {
+            return true;
+        }
+
+        // If the bounds are currently frozen, it means that the layout size that the app sees
+        // and the bounds we clip this window to might be different. In order to avoid holes, we
+        // simulate that we are still resizing so the app fills the hole with the resizing
+        // background.
+        return (getDisplayContent().mDividerControllerLocked.isResizing()
+                        || mAppToken != null && !mAppToken.mFrozenBounds.isEmpty()) &&
+                !task.inFreeformWorkspace() && !isGoneForLayoutLw();
+
+    }
+
+    void setDragResizing() {
+        final boolean resizing = computeDragResizing();
+        if (resizing == mDragResizing) {
+            return;
+        }
+        mDragResizing = resizing;
+        final Task task = getTask();
+        if (task != null && task.isDragResizing()) {
+            mResizeMode = task.getDragResizeMode();
+        } else {
+            mResizeMode = mDragResizing && getDisplayContent().mDividerControllerLocked.isResizing()
+                    ? DRAG_RESIZE_MODE_DOCKED_DIVIDER
+                    : DRAG_RESIZE_MODE_FREEFORM;
+        }
+    }
+
+    boolean isDragResizing() {
+        return mDragResizing;
+    }
+
+    boolean isDockedResizing() {
+        return (mDragResizing && getResizeMode() == DRAG_RESIZE_MODE_DOCKED_DIVIDER)
+                || (isChildWindow() && getParentWindow().isDockedResizing());
+    }
+
+    void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        writeIdentifierToProto(proto, IDENTIFIER);
+        proto.write(DISPLAY_ID, getDisplayId());
+        proto.write(STACK_ID, getStackId());
+        mAttrs.writeToProto(proto, ATTRIBUTES);
+        mGivenContentInsets.writeToProto(proto, GIVEN_CONTENT_INSETS);
+        mFrame.writeToProto(proto, FRAME);
+        mContainingFrame.writeToProto(proto, CONTAINING_FRAME);
+        mParentFrame.writeToProto(proto, PARENT_FRAME);
+        mContentFrame.writeToProto(proto, CONTENT_FRAME);
+        mContentInsets.writeToProto(proto, CONTENT_INSETS);
+        mAttrs.surfaceInsets.writeToProto(proto, SURFACE_INSETS);
+        mWinAnimator.writeToProto(proto, ANIMATOR);
+        proto.write(ANIMATING_EXIT, mAnimatingExit);
+        for (int i = 0; i < mChildren.size(); i++) {
+            mChildren.get(i).writeToProto(proto, CHILD_WINDOWS);
+        }
+        proto.end(token);
+    }
+
+    void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(HASH_CODE, System.identityHashCode(this));
+        proto.write(USER_ID, UserHandle.getUserId(mOwnerUid));
+        final CharSequence title = getWindowTag();
+        if (title != null) {
+            proto.write(TITLE, title.toString());
+        }
+        proto.end(token);
+    }
+
+    void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+        final TaskStack stack = getStack();
+        pw.print(prefix); pw.print("mDisplayId="); pw.print(getDisplayId());
+                if (stack != null) {
+                    pw.print(" stackId="); pw.print(stack.mStackId);
+                }
+                pw.print(" mSession="); pw.print(mSession);
+                pw.print(" mClient="); pw.println(mClient.asBinder());
+        pw.print(prefix); pw.print("mOwnerUid="); pw.print(mOwnerUid);
+                pw.print(" mShowToOwnerOnly="); pw.print(mShowToOwnerOnly);
+                pw.print(" package="); pw.print(mAttrs.packageName);
+                pw.print(" appop="); pw.println(AppOpsManager.opToName(mAppOp));
+        pw.print(prefix); pw.print("mAttrs="); pw.println(mAttrs);
+        pw.print(prefix); pw.print("Requested w="); pw.print(mRequestedWidth);
+                pw.print(" h="); pw.print(mRequestedHeight);
+                pw.print(" mLayoutSeq="); pw.println(mLayoutSeq);
+        if (mRequestedWidth != mLastRequestedWidth || mRequestedHeight != mLastRequestedHeight) {
+            pw.print(prefix); pw.print("LastRequested w="); pw.print(mLastRequestedWidth);
+                    pw.print(" h="); pw.println(mLastRequestedHeight);
+        }
+        if (mIsChildWindow || mLayoutAttached) {
+            pw.print(prefix); pw.print("mParentWindow="); pw.print(getParentWindow());
+                    pw.print(" mLayoutAttached="); pw.println(mLayoutAttached);
+        }
+        if (mIsImWindow || mIsWallpaper || mIsFloatingLayer) {
+            pw.print(prefix); pw.print("mIsImWindow="); pw.print(mIsImWindow);
+                    pw.print(" mIsWallpaper="); pw.print(mIsWallpaper);
+                    pw.print(" mIsFloatingLayer="); pw.print(mIsFloatingLayer);
+                    pw.print(" mWallpaperVisible="); pw.println(mWallpaperVisible);
+        }
+        if (dumpAll) {
+            pw.print(prefix); pw.print("mBaseLayer="); pw.print(mBaseLayer);
+                    pw.print(" mSubLayer="); pw.print(mSubLayer);
+                    pw.print(" mAnimLayer="); pw.print(mLayer); pw.print("+");
+                    pw.print(getAnimLayerAdjustment());
+                    pw.print("="); pw.print(mWinAnimator.mAnimLayer);
+                    pw.print(" mLastLayer="); pw.println(mWinAnimator.mLastLayer);
+        }
+        if (dumpAll) {
+            pw.print(prefix); pw.print("mToken="); pw.println(mToken);
+            if (mAppToken != null) {
+                pw.print(prefix); pw.print("mAppToken="); pw.println(mAppToken);
+                pw.print(prefix); pw.print(" isAnimatingWithSavedSurface()=");
+                pw.print(" mAppDied=");pw.print(mAppDied);
+                pw.print(prefix); pw.print("drawnStateEvaluated=");
+                        pw.print(getDrawnStateEvaluated());
+                pw.print(prefix); pw.print("mightAffectAllDrawn=");
+                        pw.println(mightAffectAllDrawn());
+            }
+            pw.print(prefix); pw.print("mViewVisibility=0x");
+            pw.print(Integer.toHexString(mViewVisibility));
+            pw.print(" mHaveFrame="); pw.print(mHaveFrame);
+            pw.print(" mObscured="); pw.println(mObscured);
+            pw.print(prefix); pw.print("mSeq="); pw.print(mSeq);
+            pw.print(" mSystemUiVisibility=0x");
+            pw.println(Integer.toHexString(mSystemUiVisibility));
+        }
+        if (!mPolicyVisibility || !mPolicyVisibilityAfterAnim || !mAppOpVisibility
+                || isParentWindowHidden()|| mPermanentlyHidden || mForceHideNonSystemOverlayWindow) {
+            pw.print(prefix); pw.print("mPolicyVisibility=");
+                    pw.print(mPolicyVisibility);
+                    pw.print(" mPolicyVisibilityAfterAnim=");
+                    pw.print(mPolicyVisibilityAfterAnim);
+                    pw.print(" mAppOpVisibility=");
+                    pw.print(mAppOpVisibility);
+                    pw.print(" parentHidden="); pw.print(isParentWindowHidden());
+                    pw.print(" mPermanentlyHidden="); pw.print(mPermanentlyHidden);
+                    pw.print(" mForceHideNonSystemOverlayWindow="); pw.println(
+                    mForceHideNonSystemOverlayWindow);
+        }
+        if (!mRelayoutCalled || mLayoutNeeded) {
+            pw.print(prefix); pw.print("mRelayoutCalled="); pw.print(mRelayoutCalled);
+                    pw.print(" mLayoutNeeded="); pw.println(mLayoutNeeded);
+        }
+        if (mXOffset != 0 || mYOffset != 0) {
+            pw.print(prefix); pw.print("Offsets x="); pw.print(mXOffset);
+                    pw.print(" y="); pw.println(mYOffset);
+        }
+        if (dumpAll) {
+            pw.print(prefix); pw.print("mGivenContentInsets=");
+                    mGivenContentInsets.printShortString(pw);
+                    pw.print(" mGivenVisibleInsets=");
+                    mGivenVisibleInsets.printShortString(pw);
+                    pw.println();
+            if (mTouchableInsets != 0 || mGivenInsetsPending) {
+                pw.print(prefix); pw.print("mTouchableInsets="); pw.print(mTouchableInsets);
+                        pw.print(" mGivenInsetsPending="); pw.println(mGivenInsetsPending);
+                Region region = new Region();
+                getTouchableRegion(region);
+                pw.print(prefix); pw.print("touchable region="); pw.println(region);
+            }
+            pw.print(prefix); pw.print("mFullConfiguration="); pw.println(getConfiguration());
+            pw.print(prefix); pw.print("mLastReportedConfiguration=");
+                    pw.println(mLastReportedConfiguration);
+        }
+        pw.print(prefix); pw.print("mHasSurface="); pw.print(mHasSurface);
+                pw.print(" mShownPosition="); mShownPosition.printShortString(pw);
+                pw.print(" isReadyForDisplay()="); pw.print(isReadyForDisplay());
+                pw.print(" mWindowRemovalAllowed="); pw.println(mWindowRemovalAllowed);
+        if (dumpAll) {
+            pw.print(prefix); pw.print("mFrame="); mFrame.printShortString(pw);
+                    pw.print(" last="); mLastFrame.printShortString(pw);
+                    pw.println();
+        }
+        if (mEnforceSizeCompat) {
+            pw.print(prefix); pw.print("mCompatFrame="); mCompatFrame.printShortString(pw);
+                    pw.println();
+        }
+        if (dumpAll) {
+            pw.print(prefix); pw.print("Frames: containing=");
+                    mContainingFrame.printShortString(pw);
+                    pw.print(" parent="); mParentFrame.printShortString(pw);
+                    pw.println();
+            pw.print(prefix); pw.print("    display="); mDisplayFrame.printShortString(pw);
+                    pw.print(" overscan="); mOverscanFrame.printShortString(pw);
+                    pw.println();
+            pw.print(prefix); pw.print("    content="); mContentFrame.printShortString(pw);
+                    pw.print(" visible="); mVisibleFrame.printShortString(pw);
+                    pw.println();
+            pw.print(prefix); pw.print("    decor="); mDecorFrame.printShortString(pw);
+                    pw.println();
+            pw.print(prefix); pw.print("    outset="); mOutsetFrame.printShortString(pw);
+                    pw.println();
+            pw.print(prefix); pw.print("Cur insets: overscan=");
+                    mOverscanInsets.printShortString(pw);
+                    pw.print(" content="); mContentInsets.printShortString(pw);
+                    pw.print(" visible="); mVisibleInsets.printShortString(pw);
+                    pw.print(" stable="); mStableInsets.printShortString(pw);
+                    pw.print(" surface="); mAttrs.surfaceInsets.printShortString(pw);
+                    pw.print(" outsets="); mOutsets.printShortString(pw);
+                    pw.println();
+            pw.print(prefix); pw.print("Lst insets: overscan=");
+                    mLastOverscanInsets.printShortString(pw);
+                    pw.print(" content="); mLastContentInsets.printShortString(pw);
+                    pw.print(" visible="); mLastVisibleInsets.printShortString(pw);
+                    pw.print(" stable="); mLastStableInsets.printShortString(pw);
+                    pw.print(" physical="); mLastOutsets.printShortString(pw);
+                    pw.print(" outset="); mLastOutsets.printShortString(pw);
+                    pw.println();
+        }
+        pw.print(prefix); pw.print(mWinAnimator); pw.println(":");
+        mWinAnimator.dump(pw, prefix + "  ", dumpAll);
+        if (mAnimatingExit || mRemoveOnExit || mDestroying || mRemoved) {
+            pw.print(prefix); pw.print("mAnimatingExit="); pw.print(mAnimatingExit);
+                    pw.print(" mRemoveOnExit="); pw.print(mRemoveOnExit);
+                    pw.print(" mDestroying="); pw.print(mDestroying);
+                    pw.print(" mRemoved="); pw.println(mRemoved);
+        }
+        if (getOrientationChanging() || mAppFreezing || mTurnOnScreen
+                || mReportOrientationChanged) {
+            pw.print(prefix); pw.print("mOrientationChanging=");
+                    pw.print(mOrientationChanging);
+                    pw.print(" configOrientationChanging=");
+                    pw.print(mLastReportedConfiguration.orientation
+                            != getConfiguration().orientation);
+                    pw.print(" mAppFreezing="); pw.print(mAppFreezing);
+                    pw.print(" mTurnOnScreen="); pw.print(mTurnOnScreen);
+                    pw.print(" mReportOrientationChanged="); pw.println(mReportOrientationChanged);
+        }
+        if (mLastFreezeDuration != 0) {
+            pw.print(prefix); pw.print("mLastFreezeDuration=");
+                    TimeUtils.formatDuration(mLastFreezeDuration, pw); pw.println();
+        }
+        if (mHScale != 1 || mVScale != 1) {
+            pw.print(prefix); pw.print("mHScale="); pw.print(mHScale);
+                    pw.print(" mVScale="); pw.println(mVScale);
+        }
+        if (mWallpaperX != -1 || mWallpaperY != -1) {
+            pw.print(prefix); pw.print("mWallpaperX="); pw.print(mWallpaperX);
+                    pw.print(" mWallpaperY="); pw.println(mWallpaperY);
+        }
+        if (mWallpaperXStep != -1 || mWallpaperYStep != -1) {
+            pw.print(prefix); pw.print("mWallpaperXStep="); pw.print(mWallpaperXStep);
+                    pw.print(" mWallpaperYStep="); pw.println(mWallpaperYStep);
+        }
+        if (mWallpaperDisplayOffsetX != Integer.MIN_VALUE
+                || mWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
+            pw.print(prefix); pw.print("mWallpaperDisplayOffsetX=");
+                    pw.print(mWallpaperDisplayOffsetX);
+                    pw.print(" mWallpaperDisplayOffsetY=");
+                    pw.println(mWallpaperDisplayOffsetY);
+        }
+        if (mDrawLock != null) {
+            pw.print(prefix); pw.println("mDrawLock=" + mDrawLock);
+        }
+        if (isDragResizing()) {
+            pw.print(prefix); pw.println("isDragResizing=" + isDragResizing());
+        }
+        if (computeDragResizing()) {
+            pw.print(prefix); pw.println("computeDragResizing=" + computeDragResizing());
+        }
+        pw.print(prefix); pw.println("isOnScreen=" + isOnScreen());
+        pw.print(prefix); pw.println("isVisible=" + isVisible());
+    }
+
+    @Override
+    String getName() {
+        return Integer.toHexString(System.identityHashCode(this))
+            + " " + getWindowTag();
+    }
+
+    CharSequence getWindowTag() {
+        CharSequence tag = mAttrs.getTitle();
+        if (tag == null || tag.length() <= 0) {
+            tag = mAttrs.packageName;
+        }
+        return tag;
+    }
+
+    @Override
+    public String toString() {
+        final CharSequence title = getWindowTag();
+        if (mStringNameCache == null || mLastTitle != title || mWasExiting != mAnimatingExit) {
+            mLastTitle = title;
+            mWasExiting = mAnimatingExit;
+            mStringNameCache = "Window{" + Integer.toHexString(System.identityHashCode(this))
+                    + " u" + UserHandle.getUserId(mOwnerUid)
+                    + " " + mLastTitle + (mAnimatingExit ? " EXITING}" : "}");
+        }
+        return mStringNameCache;
+    }
+
+    void transformClipRectFromScreenToSurfaceSpace(Rect clipRect) {
+         if (mHScale >= 0) {
+            clipRect.left = (int) (clipRect.left / mHScale);
+            clipRect.right = (int) Math.ceil(clipRect.right / mHScale);
+        }
+        if (mVScale >= 0) {
+            clipRect.top = (int) (clipRect.top / mVScale);
+            clipRect.bottom = (int) Math.ceil(clipRect.bottom / mVScale);
+        }
+    }
+
+    void applyGravityAndUpdateFrame(Rect containingFrame, Rect displayFrame) {
+        final int pw = containingFrame.width();
+        final int ph = containingFrame.height();
+        final Task task = getTask();
+        final boolean inNonFullscreenContainer = !inFullscreenContainer();
+        final boolean noLimits = (mAttrs.flags & FLAG_LAYOUT_NO_LIMITS) != 0;
+
+        // We need to fit it to the display if either
+        // a) The window is in a fullscreen container, or we don't have a task (we assume fullscreen
+        // for the taskless windows)
+        // b) If it's a secondary app window, we also need to fit it to the display unless
+        // FLAG_LAYOUT_NO_LIMITS is set. This is so we place Popups, dialogs, and similar windows on
+        // screen, but SurfaceViews want to be always at a specific location so we don't fit it to
+        // the display.
+        final boolean fitToDisplay = (task == null || !inNonFullscreenContainer)
+                || ((mAttrs.type != TYPE_BASE_APPLICATION) && !noLimits);
+        float x, y;
+        int w,h;
+
+        if ((mAttrs.flags & FLAG_SCALED) != 0) {
+            if (mAttrs.width < 0) {
+                w = pw;
+            } else if (mEnforceSizeCompat) {
+                w = (int)(mAttrs.width * mGlobalScale + .5f);
+            } else {
+                w = mAttrs.width;
+            }
+            if (mAttrs.height < 0) {
+                h = ph;
+            } else if (mEnforceSizeCompat) {
+                h = (int)(mAttrs.height * mGlobalScale + .5f);
+            } else {
+                h = mAttrs.height;
+            }
+        } else {
+            if (mAttrs.width == MATCH_PARENT) {
+                w = pw;
+            } else if (mEnforceSizeCompat) {
+                w = (int)(mRequestedWidth * mGlobalScale + .5f);
+            } else {
+                w = mRequestedWidth;
+            }
+            if (mAttrs.height == MATCH_PARENT) {
+                h = ph;
+            } else if (mEnforceSizeCompat) {
+                h = (int)(mRequestedHeight * mGlobalScale + .5f);
+            } else {
+                h = mRequestedHeight;
+            }
+        }
+
+        if (mEnforceSizeCompat) {
+            x = mAttrs.x * mGlobalScale;
+            y = mAttrs.y * mGlobalScale;
+        } else {
+            x = mAttrs.x;
+            y = mAttrs.y;
+        }
+
+        if (inNonFullscreenContainer && !layoutInParentFrame()) {
+            // Make sure window fits in containing frame since it is in a non-fullscreen task as
+            // required by {@link Gravity#apply} call.
+            w = Math.min(w, pw);
+            h = Math.min(h, ph);
+        }
+
+        // Set mFrame
+        Gravity.apply(mAttrs.gravity, w, h, containingFrame,
+                (int) (x + mAttrs.horizontalMargin * pw),
+                (int) (y + mAttrs.verticalMargin * ph), mFrame);
+
+        // Now make sure the window fits in the overall display frame.
+        if (fitToDisplay) {
+            Gravity.applyDisplay(mAttrs.gravity, displayFrame, mFrame);
+        }
+
+        // We need to make sure we update the CompatFrame as it is used for
+        // cropping decisions, etc, on systems where we lack a decor layer.
+        mCompatFrame.set(mFrame);
+        if (mEnforceSizeCompat) {
+            // See comparable block in computeFrameLw.
+            mCompatFrame.scale(mInvGlobalScale);
+        }
+    }
+
+    boolean isChildWindow() {
+        return mIsChildWindow;
+    }
+
+    boolean layoutInParentFrame() {
+        return mIsChildWindow
+                && (mAttrs.privateFlags & PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME) != 0;
+    }
+
+    /**
+     * Returns true if any window added by an application process that if of type
+     * {@link android.view.WindowManager.LayoutParams#TYPE_TOAST} or that requires that requires
+     * {@link android.app.AppOpsManager#OP_SYSTEM_ALERT_WINDOW} permission should be hidden when
+     * this window is visible.
+     */
+    boolean hideNonSystemOverlayWindowsWhenVisible() {
+        return (mAttrs.privateFlags & PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) != 0
+                && mSession.mCanHideNonSystemOverlayWindows;
+    }
+
+    /** Returns the parent window if this is a child of another window, else null. */
+    WindowState getParentWindow() {
+        // NOTE: We are not calling getParent() directly as the WindowState might be a child of a
+        // WindowContainer that isn't a WindowState.
+        return (mIsChildWindow) ? ((WindowState) super.getParent()) : null;
+    }
+
+    /** Returns the topmost parent window if this is a child of another window, else this. */
+    WindowState getTopParentWindow() {
+        WindowState current = this;
+        WindowState topParent = current;
+        while (current != null && current.mIsChildWindow) {
+            current = current.getParentWindow();
+            // Parent window can be null if the child is detached from it's parent already, but
+            // someone still has a reference to access it. So, we return the top parent value we
+            // already have instead of null.
+            if (current != null) {
+                topParent = current;
+            }
+        }
+        return topParent;
+    }
+
+    boolean isParentWindowHidden() {
+        final WindowState parent = getParentWindow();
+        return parent != null && parent.mHidden;
+    }
+
+    void setWillReplaceWindow(boolean animate) {
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final WindowState c = mChildren.get(i);
+            c.setWillReplaceWindow(animate);
+        }
+
+        if ((mAttrs.privateFlags & PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH) != 0
+                || mAttrs.type == TYPE_APPLICATION_STARTING) {
+            // We don't set replacing on starting windows since they are added by window manager and
+            // not the client so won't be replaced by the client.
+            return;
+        }
+
+        mWillReplaceWindow = true;
+        mReplacementWindow = null;
+        mAnimateReplacingWindow = animate;
+    }
+
+    void clearWillReplaceWindow() {
+        mWillReplaceWindow = false;
+        mReplacementWindow = null;
+        mAnimateReplacingWindow = false;
+
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final WindowState c = mChildren.get(i);
+            c.clearWillReplaceWindow();
+        }
+    }
+
+    boolean waitingForReplacement() {
+        if (mWillReplaceWindow) {
+            return true;
+        }
+
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final WindowState c = mChildren.get(i);
+            if (c.waitingForReplacement()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    void requestUpdateWallpaperIfNeeded() {
+        final DisplayContent dc = getDisplayContent();
+        if (dc != null && (mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
+            dc.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+            dc.setLayoutNeeded();
+            mService.mWindowPlacerLocked.requestTraversal();
+        }
+
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final WindowState c = mChildren.get(i);
+            c.requestUpdateWallpaperIfNeeded();
+        }
+    }
+
+    float translateToWindowX(float x) {
+        float winX = x - mFrame.left;
+        if (mEnforceSizeCompat) {
+            winX *= mGlobalScale;
+        }
+        return winX;
+    }
+
+    float translateToWindowY(float y) {
+        float winY = y - mFrame.top;
+        if (mEnforceSizeCompat) {
+            winY *= mGlobalScale;
+        }
+        return winY;
+    }
+
+    private void transferDimToReplacement() {
+        final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
+        final DisplayContent dc = getDisplayContent();
+        if (dimLayerUser != null && dc != null) {
+            dc.mDimLayerController.applyDim(dimLayerUser,
+                    mReplacementWindow.mWinAnimator, (mAttrs.flags & FLAG_DIM_BEHIND) != 0);
+        }
+    }
+
+    // During activity relaunch due to resize, we sometimes use window replacement
+    // for only child windows (as the main window is handled by window preservation)
+    // and the big surface.
+    //
+    // Though windows of TYPE_APPLICATION or TYPE_DRAWN_APPLICATION (as opposed to
+    // TYPE_BASE_APPLICATION) are not children in the sense of an attached window,
+    // we also want to replace them at such phases, as they won't be covered by window
+    // preservation, and in general we expect them to return following relaunch.
+    boolean shouldBeReplacedWithChildren() {
+        return mIsChildWindow || mAttrs.type == TYPE_APPLICATION
+                || mAttrs.type == TYPE_DRAWN_APPLICATION;
+    }
+
+    void setWillReplaceChildWindows() {
+        if (shouldBeReplacedWithChildren()) {
+            setWillReplaceWindow(false /* animate */);
+        }
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final WindowState c = mChildren.get(i);
+            c.setWillReplaceChildWindows();
+        }
+    }
+
+    WindowState getReplacingWindow() {
+        if (mAnimatingExit && mWillReplaceWindow && mAnimateReplacingWindow) {
+            return this;
+        }
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final WindowState c = mChildren.get(i);
+            final WindowState replacing = c.getReplacingWindow();
+            if (replacing != null) {
+                return replacing;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public int getRotationAnimationHint() {
+        if (mAppToken != null) {
+            return mAppToken.mRotationAnimationHint;
+        } else {
+            return -1;
+        }
+    }
+
+    @Override
+    public boolean isInputMethodWindow() {
+        return mIsImWindow;
+    }
+
+    // This must be called while inside a transaction.
+    boolean performShowLocked() {
+        if (isHiddenFromUserLocked()) {
+            if (DEBUG_VISIBILITY) Slog.w(TAG, "hiding " + this + ", belonging to " + mOwnerUid);
+            hideLw(false);
+            return false;
+        }
+
+        logPerformShow("performShow on ");
+
+        final int drawState = mWinAnimator.mDrawState;
+        if ((drawState == HAS_DRAWN || drawState == READY_TO_SHOW)
+                && mAttrs.type != TYPE_APPLICATION_STARTING && mAppToken != null) {
+            mAppToken.onFirstWindowDrawn(this, mWinAnimator);
+        }
+
+        if (mWinAnimator.mDrawState != READY_TO_SHOW || !isReadyForDisplay()) {
+            return false;
+        }
+
+        logPerformShow("Showing ");
+
+        mService.enableScreenIfNeededLocked();
+        mWinAnimator.applyEnterAnimationLocked();
+
+        // Force the show in the next prepareSurfaceLocked() call.
+        mWinAnimator.mLastAlpha = -1;
+        if (DEBUG_SURFACE_TRACE || DEBUG_ANIM) Slog.v(TAG,
+                "performShowLocked: mDrawState=HAS_DRAWN in " + this);
+        mWinAnimator.mDrawState = HAS_DRAWN;
+        mService.scheduleAnimationLocked();
+
+        if (mHidden) {
+            mHidden = false;
+            final DisplayContent displayContent = getDisplayContent();
+
+            for (int i = mChildren.size() - 1; i >= 0; --i) {
+                final WindowState c = mChildren.get(i);
+                if (c.mWinAnimator.mSurfaceController != null) {
+                    c.performShowLocked();
+                    // It hadn't been shown, which means layout not performed on it, so now we
+                    // want to make sure to do a layout.  If called from within the transaction
+                    // loop, this will cause it to restart with a new layout.
+                    if (displayContent != null) {
+                        displayContent.setLayoutNeeded();
+                    }
+                }
+            }
+        }
+
+        if (mAttrs.type == TYPE_INPUT_METHOD) {
+            getDisplayContent().mDividerControllerLocked.resetImeHideRequested();
+        }
+
+        return true;
+    }
+
+    private void logPerformShow(String prefix) {
+        if (DEBUG_VISIBILITY
+                || (DEBUG_STARTING_WINDOW_VERBOSE && mAttrs.type == TYPE_APPLICATION_STARTING)) {
+            Slog.v(TAG, prefix + this
+                    + ": mDrawState=" + mWinAnimator.drawStateToString()
+                    + " readyForDisplay=" + isReadyForDisplay()
+                    + " starting=" + (mAttrs.type == TYPE_APPLICATION_STARTING)
+                    + " during animation: policyVis=" + mPolicyVisibility
+                    + " parentHidden=" + isParentWindowHidden()
+                    + " tok.hiddenRequested="
+                    + (mAppToken != null && mAppToken.hiddenRequested)
+                    + " tok.hidden=" + (mAppToken != null && mAppToken.hidden)
+                    + " animating=" + mWinAnimator.mAnimating
+                    + " tok animating="
+                    + (mWinAnimator.mAppAnimator != null && mWinAnimator.mAppAnimator.animating)
+                    + " Callers=" + Debug.getCallers(4));
+        }
+    }
+
+    WindowInfo getWindowInfo() {
+        WindowInfo windowInfo = WindowInfo.obtain();
+        windowInfo.type = mAttrs.type;
+        windowInfo.layer = mLayer;
+        windowInfo.token = mClient.asBinder();
+        if (mAppToken != null) {
+            windowInfo.activityToken = mAppToken.appToken.asBinder();
+        }
+        windowInfo.title = mAttrs.accessibilityTitle;
+        windowInfo.accessibilityIdOfAnchor = mAttrs.accessibilityIdOfAnchor;
+        windowInfo.focused = isFocused();
+        Task task = getTask();
+        windowInfo.inPictureInPicture = (task != null) && task.inPinnedWorkspace();
+
+        if (mIsChildWindow) {
+            windowInfo.parentToken = getParentWindow().mClient.asBinder();
+        }
+
+        final int childCount = mChildren.size();
+        if (childCount > 0) {
+            if (windowInfo.childTokens == null) {
+                windowInfo.childTokens = new ArrayList(childCount);
+            }
+            for (int j = 0; j < childCount; j++) {
+                final WindowState child = mChildren.get(j);
+                windowInfo.childTokens.add(child.mClient.asBinder());
+            }
+        }
+        return windowInfo;
+    }
+
+    int getHighestAnimLayer() {
+        int highest = mWinAnimator.mAnimLayer;
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final WindowState c = mChildren.get(i);
+            final int childLayer = c.getHighestAnimLayer();
+            if (childLayer > highest) {
+                highest = childLayer;
+            }
+        }
+        return highest;
+    }
+
+    @Override
+    boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
+        if (mChildren.isEmpty()) {
+            // The window has no children so we just return it.
+            return applyInOrderWithImeWindows(callback, traverseTopToBottom);
+        }
+
+        if (traverseTopToBottom) {
+            return forAllWindowTopToBottom(callback);
+        } else {
+            return forAllWindowBottomToTop(callback);
+        }
+    }
+
+    private boolean forAllWindowBottomToTop(ToBooleanFunction<WindowState> callback) {
+        // We want to consume the negative sublayer children first because they need to appear
+        // below the parent, then this window (the parent), and then the positive sublayer children
+        // because they need to appear above the parent.
+        int i = 0;
+        final int count = mChildren.size();
+        WindowState child = mChildren.get(i);
+
+        while (i < count && child.mSubLayer < 0) {
+            if (child.applyInOrderWithImeWindows(callback, false /* traverseTopToBottom */)) {
+                return true;
+            }
+            i++;
+            if (i >= count) {
+                break;
+            }
+            child = mChildren.get(i);
+        }
+
+        if (applyInOrderWithImeWindows(callback, false /* traverseTopToBottom */)) {
+            return true;
+        }
+
+        while (i < count) {
+            if (child.applyInOrderWithImeWindows(callback, false /* traverseTopToBottom */)) {
+                return true;
+            }
+            i++;
+            if (i >= count) {
+                break;
+            }
+            child = mChildren.get(i);
+        }
+
+        return false;
+    }
+
+    private boolean forAllWindowTopToBottom(ToBooleanFunction<WindowState> callback) {
+        // We want to consume the positive sublayer children first because they need to appear
+        // above the parent, then this window (the parent), and then the negative sublayer children
+        // because they need to appear above the parent.
+        int i = mChildren.size() - 1;
+        WindowState child = mChildren.get(i);
+
+        while (i >= 0 && child.mSubLayer >= 0) {
+            if (child.applyInOrderWithImeWindows(callback, true /* traverseTopToBottom */)) {
+                return true;
+            }
+            --i;
+            if (i < 0) {
+                break;
+            }
+            child = mChildren.get(i);
+        }
+
+        if (applyInOrderWithImeWindows(callback, true /* traverseTopToBottom */)) {
+            return true;
+        }
+
+        while (i >= 0) {
+            if (child.applyInOrderWithImeWindows(callback, true /* traverseTopToBottom */)) {
+                return true;
+            }
+            --i;
+            if (i < 0) {
+                break;
+            }
+            child = mChildren.get(i);
+        }
+
+        return false;
+    }
+
+    private boolean applyInOrderWithImeWindows(ToBooleanFunction<WindowState> callback,
+            boolean traverseTopToBottom) {
+        if (traverseTopToBottom) {
+            if (mService.mInputMethodTarget == this) {
+                // This window is the current IME target, so we need to process the IME windows
+                // directly above it.
+                if (getDisplayContent().forAllImeWindows(callback, traverseTopToBottom)) {
+                    return true;
+                }
+            }
+            if (callback.apply(this)) {
+                return true;
+            }
+        } else {
+            if (callback.apply(this)) {
+                return true;
+            }
+            if (mService.mInputMethodTarget == this) {
+                // This window is the current IME target, so we need to process the IME windows
+                // directly above it.
+                if (getDisplayContent().forAllImeWindows(callback, traverseTopToBottom)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    WindowState getWindow(Predicate<WindowState> callback) {
+        if (mChildren.isEmpty()) {
+            return callback.test(this) ? this : null;
+        }
+
+        // We want to consume the positive sublayer children first because they need to appear
+        // above the parent, then this window (the parent), and then the negative sublayer children
+        // because they need to appear above the parent.
+        int i = mChildren.size() - 1;
+        WindowState child = mChildren.get(i);
+
+        while (i >= 0 && child.mSubLayer >= 0) {
+            if (callback.test(child)) {
+                return child;
+            }
+            --i;
+            if (i < 0) {
+                break;
+            }
+            child = mChildren.get(i);
+        }
+
+        if (callback.test(this)) {
+            return this;
+        }
+
+        while (i >= 0) {
+            if (callback.test(child)) {
+                return child;
+            }
+            --i;
+            if (i < 0) {
+                break;
+            }
+            child = mChildren.get(i);
+        }
+
+        return null;
+    }
+
+    boolean isWindowAnimationSet() {
+        if (mWinAnimator.isWindowAnimationSet()) {
+            return true;
+        }
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowState c = mChildren.get(i);
+            if (c.isWindowAnimationSet()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    void onExitAnimationDone() {
+        if (DEBUG_ANIM) Slog.v(TAG, "onExitAnimationDone in " + this
+                + ": exiting=" + mAnimatingExit + " remove=" + mRemoveOnExit
+                + " windowAnimating=" + mWinAnimator.isWindowAnimationSet());
+
+        if (!mChildren.isEmpty()) {
+            // Copying to a different list as multiple children can be removed.
+            // TODO: Not sure if we really need to copy this into a different list.
+            final LinkedList<WindowState> childWindows = new LinkedList(mChildren);
+            for (int i = childWindows.size() - 1; i >= 0; i--) {
+                childWindows.get(i).onExitAnimationDone();
+            }
+        }
+
+        if (mWinAnimator.mEnteringAnimation) {
+            mWinAnimator.mEnteringAnimation = false;
+            mService.requestTraversal();
+            // System windows don't have an activity and an app token as a result, but need a way
+            // to be informed about their entrance animation end.
+            if (mAppToken == null) {
+                try {
+                    mClient.dispatchWindowShown();
+                } catch (RemoteException e) {
+                }
+            }
+        }
+
+        if (!mWinAnimator.isWindowAnimationSet()) {
+            //TODO (multidisplay): Accessibility is supported only for the default display.
+            if (mService.mAccessibilityController != null && getDisplayId() == DEFAULT_DISPLAY) {
+                mService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
+            }
+        }
+
+        if (!mAnimatingExit) {
+            return;
+        }
+
+        if (mWinAnimator.isWindowAnimationSet()) {
+            return;
+        }
+
+        if (localLOGV || DEBUG_ADD_REMOVE) Slog.v(TAG,
+                "Exit animation finished in " + this + ": remove=" + mRemoveOnExit);
+
+        mDestroying = true;
+
+        final boolean hasSurface = mWinAnimator.hasSurface();
+        if (hasSurface) {
+            mWinAnimator.hide("onExitAnimationDone");
+        }
+
+        // If we have an app token, we ask it to destroy the surface for us, so that it can take
+        // care to ensure the activity has actually stopped and the surface is not still in use.
+        // Otherwise we add the service to mDestroySurface and allow it to be processed in our next
+        // transaction.
+        if (mAppToken != null) {
+            mAppToken.destroySurfaces();
+        } else {
+            if (hasSurface) {
+                mService.mDestroySurface.add(this);
+            }
+            if (mRemoveOnExit) {
+                mService.mPendingRemove.add(this);
+                mRemoveOnExit = false;
+            }
+        }
+        mAnimatingExit = false;
+        getDisplayContent().mWallpaperController.hideWallpapers(this);
+    }
+
+    boolean clearAnimatingFlags() {
+        boolean didSomething = false;
+        // We don't want to clear it out for windows that get replaced, because the
+        // animation depends on the flag to remove the replaced window.
+        //
+        // We also don't clear the mAnimatingExit flag for windows which have the
+        // mRemoveOnExit flag. This indicates an explicit remove request has been issued
+        // by the client. We should let animation proceed and not clear this flag or
+        // they won't eventually be removed by WindowStateAnimator#finishExit.
+        if (!mWillReplaceWindow && !mRemoveOnExit) {
+            // Clear mAnimating flag together with mAnimatingExit. When animation
+            // changes from exiting to entering, we need to clear this flag until the
+            // new animation gets applied, so that isAnimationStarting() becomes true
+            // until then.
+            // Otherwise applySurfaceChangesTransaction will fail to skip surface
+            // placement for this window during this period, one or more frame will
+            // show up with wrong position or scale.
+            if (mAnimatingExit) {
+                mAnimatingExit = false;
+                didSomething = true;
+            }
+            if (mWinAnimator.mAnimating) {
+                mWinAnimator.mAnimating = false;
+                didSomething = true;
+            }
+            if (mDestroying) {
+                mDestroying = false;
+                mService.mDestroySurface.remove(this);
+                didSomething = true;
+            }
+        }
+
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            didSomething |= (mChildren.get(i)).clearAnimatingFlags();
+        }
+
+        return didSomething;
+    }
+
+    public boolean isRtl() {
+        return getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+    }
+
+    void hideWallpaperWindow(boolean wasDeferred, String reason) {
+        for (int j = mChildren.size() - 1; j >= 0; --j) {
+            final WindowState c = mChildren.get(j);
+            c.hideWallpaperWindow(wasDeferred, reason);
+        }
+        if (!mWinAnimator.mLastHidden || wasDeferred) {
+            mWinAnimator.hide(reason);
+            dispatchWallpaperVisibility(false);
+            final DisplayContent displayContent = getDisplayContent();
+            if (displayContent != null) {
+                displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+            }
+        }
+    }
+
+    /**
+     * Check wallpaper window for visibility change and notify window if so.
+     * @param visible Current visibility.
+     */
+    void dispatchWallpaperVisibility(final boolean visible) {
+        final boolean hideAllowed =
+                getDisplayContent().mWallpaperController.mDeferredHideWallpaper == null;
+
+        // Only send notification if the visibility actually changed and we are not trying to hide
+        // the wallpaper when we are deferring hiding of the wallpaper.
+        if (mWallpaperVisible != visible && (hideAllowed || visible)) {
+            mWallpaperVisible = visible;
+            try {
+                if (DEBUG_VISIBILITY || DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
+                        "Updating vis of wallpaper " + this
+                                + ": " + visible + " from:\n" + Debug.getCallers(4, "  "));
+                mClient.dispatchAppVisibility(visible);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    boolean hasVisibleNotDrawnWallpaper() {
+        if (mWallpaperVisible && !isDrawnLw()) {
+            return true;
+        }
+        for (int j = mChildren.size() - 1; j >= 0; --j) {
+            final WindowState c = mChildren.get(j);
+            if (c.hasVisibleNotDrawnWallpaper()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    void updateReportedVisibility(UpdateReportedVisibilityResults results) {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowState c = mChildren.get(i);
+            c.updateReportedVisibility(results);
+        }
+
+        if (mAppFreezing || mViewVisibility != View.VISIBLE
+                || mAttrs.type == TYPE_APPLICATION_STARTING
+                || mDestroying) {
+            return;
+        }
+        if (DEBUG_VISIBILITY) {
+            Slog.v(TAG, "Win " + this + ": isDrawn=" + isDrawnLw()
+                    + ", isAnimationSet=" + mWinAnimator.isAnimationSet());
+            if (!isDrawnLw()) {
+                Slog.v(TAG, "Not displayed: s=" + mWinAnimator.mSurfaceController
+                        + " pv=" + mPolicyVisibility
+                        + " mDrawState=" + mWinAnimator.mDrawState
+                        + " ph=" + isParentWindowHidden()
+                        + " th=" + (mAppToken != null ? mAppToken.hiddenRequested : false)
+                        + " a=" + mWinAnimator.mAnimating);
+            }
+        }
+
+        results.numInteresting++;
+        if (isDrawnLw()) {
+            results.numDrawn++;
+            if (!mWinAnimator.isAnimationSet()) {
+                results.numVisible++;
+            }
+            results.nowGone = false;
+        } else if (mWinAnimator.isAnimationSet()) {
+            results.nowGone = false;
+        }
+    }
+
+    /**
+     * Calculate the window crop according to system decor policy. In general this is
+     * the system decor rect (see #calculateSystemDecorRect), but we also have some
+     * special cases. This rectangle is in screen space.
+     */
+    void calculatePolicyCrop(Rect policyCrop) {
+        final DisplayContent displayContent = getDisplayContent();
+        final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+
+        if (!isDefaultDisplay()) {
+            // On a different display there is no system decor. Crop the window
+            // by the screen boundaries.
+            // TODO(multi-display)
+            policyCrop.set(0, 0, mCompatFrame.width(), mCompatFrame.height());
+            policyCrop.intersect(-mCompatFrame.left, -mCompatFrame.top,
+                    displayInfo.logicalWidth - mCompatFrame.left,
+                    displayInfo.logicalHeight - mCompatFrame.top);
+        } else if (mLayer >= mService.mSystemDecorLayer) {
+            // Above the decor layer is easy, just use the entire window
+            policyCrop.set(0, 0, mCompatFrame.width(), mCompatFrame.height());
+        } else if (mDecorFrame.isEmpty()) {
+            // Windows without policy decor aren't cropped.
+            policyCrop.set(0, 0, mCompatFrame.width(), mCompatFrame.height());
+        } else {
+            // Crop to the system decor specified by policy.
+            calculateSystemDecorRect(policyCrop);
+        }
+    }
+
+    /**
+     * The system decor rect is the region of the window which is not covered
+     * by system decorations.
+     */
+    private void calculateSystemDecorRect(Rect systemDecorRect) {
+        final Rect decorRect = mDecorFrame;
+        final int width = mFrame.width();
+        final int height = mFrame.height();
+
+        // Compute the offset of the window in relation to the decor rect.
+        final int left = mXOffset + mFrame.left;
+        final int top = mYOffset + mFrame.top;
+
+        // Initialize the decor rect to the entire frame.
+        if (isDockedResizing()) {
+            // If we are resizing with the divider, the task bounds might be smaller than the
+            // stack bounds. The system decor is used to clip to the task bounds, which we don't
+            // want in this case in order to avoid holes.
+            //
+            // We take care to not shrink the width, for surfaces which are larger than
+            // the display region. Of course this area will not eventually be visible
+            // but if we truncate the width now, we will calculate incorrectly
+            // when adjusting to the stack bounds.
+            final DisplayInfo displayInfo = getDisplayContent().getDisplayInfo();
+            systemDecorRect.set(0, 0,
+                    Math.max(width, displayInfo.logicalWidth),
+                    Math.max(height, displayInfo.logicalHeight));
+        } else {
+            systemDecorRect.set(0, 0, width, height);
+        }
+
+        // If a freeform window is animating from a position where it would be cutoff, it would be
+        // cutoff during the animation. We don't want that, so for the duration of the animation
+        // we ignore the decor cropping and depend on layering to position windows correctly.
+        final boolean cropToDecor = !(inFreeformWorkspace() && isAnimatingLw());
+        if (cropToDecor) {
+            // Intersect with the decor rect, offsetted by window position.
+            systemDecorRect.intersect(decorRect.left - left, decorRect.top - top,
+                    decorRect.right - left, decorRect.bottom - top);
+        }
+
+        // If size compatibility is being applied to the window, the
+        // surface is scaled relative to the screen.  Also apply this
+        // scaling to the crop rect.  We aren't using the standard rect
+        // scale function because we want to round things to make the crop
+        // always round to a larger rect to ensure we don't crop too
+        // much and hide part of the window that should be seen.
+        if (mEnforceSizeCompat && mInvGlobalScale != 1.0f) {
+            final float scale = mInvGlobalScale;
+            systemDecorRect.left = (int) (systemDecorRect.left * scale - 0.5f);
+            systemDecorRect.top = (int) (systemDecorRect.top * scale - 0.5f);
+            systemDecorRect.right = (int) ((systemDecorRect.right + 1) * scale - 0.5f);
+            systemDecorRect.bottom = (int) ((systemDecorRect.bottom + 1) * scale - 0.5f);
+        }
+
+    }
+
+    /**
+     * Expand the given rectangle by this windows surface insets. This
+     * takes you from the 'window size' to the 'surface size'.
+     * The surface insets are positive in each direction, so we inset by
+     * the inverse.
+     */
+    void expandForSurfaceInsets(Rect r) {
+        r.inset(-mAttrs.surfaceInsets.left,
+                -mAttrs.surfaceInsets.top,
+                -mAttrs.surfaceInsets.right,
+                -mAttrs.surfaceInsets.bottom);
+    }
+
+    boolean surfaceInsetsChanging() {
+        return !mLastSurfaceInsets.equals(mAttrs.surfaceInsets);
+    }
+
+    int relayoutVisibleWindow(int result, int attrChanges, int oldVisibility) {
+        final boolean wasVisible = isVisibleLw();
+
+        result |= (!wasVisible || !isDrawnLw()) ? RELAYOUT_RES_FIRST_TIME : 0;
+        if (mAnimatingExit) {
+            Slog.d(TAG, "relayoutVisibleWindow: " + this + " mAnimatingExit=true, mRemoveOnExit="
+                    + mRemoveOnExit + ", mDestroying=" + mDestroying);
+
+            mWinAnimator.cancelExitAnimationForNextAnimationLocked();
+            mAnimatingExit = false;
+        }
+        if (mDestroying) {
+            mDestroying = false;
+            mService.mDestroySurface.remove(this);
+        }
+        if (oldVisibility == View.GONE) {
+            mWinAnimator.mEnterAnimationPending = true;
+        }
+
+        mLastVisibleLayoutRotation = getDisplayContent().getRotation();
+
+        mWinAnimator.mEnteringAnimation = true;
+
+        prepareWindowToDisplayDuringRelayout(wasVisible);
+
+        if ((attrChanges & FORMAT_CHANGED) != 0) {
+            // If the format can't be changed in place, preserve the old surface until the app draws
+            // on the new one. This prevents blinking when we change elevation of freeform and
+            // pinned windows.
+            if (!mWinAnimator.tryChangeFormatInPlaceLocked()) {
+                mWinAnimator.preserveSurfaceLocked();
+                result |= RELAYOUT_RES_SURFACE_CHANGED
+                        | RELAYOUT_RES_FIRST_TIME;
+            }
+        }
+
+        // When we change the Surface size, in scenarios which may require changing
+        // the surface position in sync with the resize, we use a preserved surface
+        // so we can freeze it while waiting for the client to report draw on the newly
+        // sized surface.  Don't preserve surfaces if the insets change while animating the pinned
+        // stack since it can lead to issues if a new surface is created while calculating the
+        // scale for the animation using the source hint rect
+        // (see WindowStateAnimator#setSurfaceBoundariesLocked()).
+        if (isDragResizeChanged() || isResizedWhileNotDragResizing()
+                || (surfaceInsetsChanging() && !inPinnedWorkspace())) {
+            mLastSurfaceInsets.set(mAttrs.surfaceInsets);
+
+            setDragResizing();
+            setResizedWhileNotDragResizing(false);
+            // We can only change top level windows to the full-screen surface when
+            // resizing (as we only have one full-screen surface). So there is no need
+            // to preserve and destroy windows which are attached to another, they
+            // will keep their surface and its size may change over time.
+            if (mHasSurface && !isChildWindow()) {
+                mWinAnimator.preserveSurfaceLocked();
+                result |= RELAYOUT_RES_SURFACE_CHANGED |
+                    RELAYOUT_RES_FIRST_TIME;
+            }
+        }
+        final boolean freeformResizing = isDragResizing()
+                && getResizeMode() == DRAG_RESIZE_MODE_FREEFORM;
+        final boolean dockedResizing = isDragResizing()
+                && getResizeMode() == DRAG_RESIZE_MODE_DOCKED_DIVIDER;
+        result |= freeformResizing ? RELAYOUT_RES_DRAG_RESIZING_FREEFORM : 0;
+        result |= dockedResizing ? RELAYOUT_RES_DRAG_RESIZING_DOCKED : 0;
+        return result;
+    }
+
+    /**
+     * @return True if this window has been laid out at least once; false otherwise.
+     */
+    boolean isLaidOut() {
+        return mLayoutSeq != -1;
+    }
+
+    /**
+     * Updates the last inset values to the current ones.
+     */
+    void updateLastInsetValues() {
+        mLastOverscanInsets.set(mOverscanInsets);
+        mLastContentInsets.set(mContentInsets);
+        mLastVisibleInsets.set(mVisibleInsets);
+        mLastStableInsets.set(mStableInsets);
+        mLastOutsets.set(mOutsets);
+    }
+
+    // TODO: Hack to work around the number of states AppWindowToken needs to access without having
+    // access to its windows children. Need to investigate re-writing
+    // {@link AppWindowToken#updateReportedVisibilityLocked} so this can be removed.
+    static final class UpdateReportedVisibilityResults {
+        int numInteresting;
+        int numVisible;
+        int numDrawn;
+        boolean nowGone = true;
+
+        void reset() {
+            numInteresting = 0;
+            numVisible = 0;
+            numDrawn = 0;
+            nowGone = true;
+        }
+    }
+
+    private static final class WindowId extends IWindowId.Stub {
+        private final WeakReference<WindowState> mOuter;
+
+        private WindowId(WindowState outer) {
+
+            // Use a weak reference for the outer class. This is important to prevent the following
+            // leak: Since we send this class to the client process, binder will keep it alive as
+            // long as the client keeps it alive. Now, if the window is removed, we need to clear
+            // out our reference so even though this class is kept alive we don't leak WindowState,
+            // which can keep a whole lot of classes alive.
+            mOuter = new WeakReference<>(outer);
+        }
+
+        @Override
+        public void registerFocusObserver(IWindowFocusObserver observer) {
+            final WindowState outer = mOuter.get();
+            if (outer != null) {
+                outer.registerFocusObserver(observer);
+            }
+        }
+        @Override
+        public void unregisterFocusObserver(IWindowFocusObserver observer) {
+            final WindowState outer = mOuter.get();
+            if (outer != null) {
+                outer.unregisterFocusObserver(observer);
+            }
+        }
+        @Override
+        public boolean isFocused() {
+            final WindowState outer = mOuter.get();
+            return outer != null && outer.isFocused();
+        }
+    }
+
+    boolean usesRelativeZOrdering() {
+        if (!isChildWindow()) {
+            return false;
+        } else if (mAttrs.type == TYPE_APPLICATION_MEDIA_OVERLAY) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+}
diff --git a/com/android/server/wm/WindowStateAnimator.java b/com/android/server/wm/WindowStateAnimator.java
new file mode 100644
index 0000000..1b7e527
--- /dev/null
+++ b/com/android/server/wm/WindowStateAnimator.java
@@ -0,0 +1,2114 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static com.android.server.wm.AppWindowAnimator.sDummyAnimation;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_CROP;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER;
+import static com.android.server.wm.WindowManagerService.localLOGV;
+import static com.android.server.wm.WindowManagerService.logWithStack;
+import static com.android.server.wm.WindowSurfacePlacer.SET_ORIENTATION_CHANGE_COMPLETE;
+import static com.android.server.wm.WindowSurfacePlacer.SET_TURN_ON_SCREEN;
+import static com.android.server.wm.proto.WindowStateAnimatorProto.LAST_CLIP_RECT;
+import static com.android.server.wm.proto.WindowStateAnimatorProto.SURFACE;
+
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.os.Debug;
+import android.os.Trace;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+import android.view.DisplayInfo;
+import android.view.MagnificationSpec;
+import android.view.Surface.OutOfResourcesException;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.view.WindowManagerPolicy;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Transformation;
+
+import java.io.PrintWriter;
+import java.io.FileDescriptor;
+
+/**
+ * Keep track of animations and surface operations for a single WindowState.
+ **/
+class WindowStateAnimator {
+    static final String TAG = TAG_WITH_CLASS_NAME ? "WindowStateAnimator" : TAG_WM;
+    static final int WINDOW_FREEZE_LAYER = TYPE_LAYER_MULTIPLIER * 200;
+
+    /**
+     * Mode how the window gets clipped by the stack bounds during an animation: The clipping should
+     * be applied after applying the animation transformation, i.e. the stack bounds don't move
+     * during the animation.
+     */
+    static final int STACK_CLIP_AFTER_ANIM = 0;
+
+    /**
+     * Mode how the window gets clipped by the stack bounds: The clipping should be applied before
+     * applying the animation transformation, i.e. the stack bounds move with the window.
+     */
+    static final int STACK_CLIP_BEFORE_ANIM = 1;
+
+    /**
+     * Mode how window gets clipped by the stack bounds during an animation: Don't clip the window
+     * by the stack bounds.
+     */
+    static final int STACK_CLIP_NONE = 2;
+
+    // Unchanging local convenience fields.
+    final WindowManagerService mService;
+    final WindowState mWin;
+    private final WindowStateAnimator mParentWinAnimator;
+    final WindowAnimator mAnimator;
+    AppWindowAnimator mAppAnimator;
+    final Session mSession;
+    final WindowManagerPolicy mPolicy;
+    final Context mContext;
+    final boolean mIsWallpaper;
+    private final WallpaperController mWallpaperControllerLocked;
+
+    // Currently running animation.
+    boolean mAnimating;
+    boolean mLocalAnimating;
+    Animation mAnimation;
+    boolean mAnimationIsEntrance;
+    boolean mHasTransformation;
+    boolean mHasLocalTransformation;
+    final Transformation mTransformation = new Transformation();
+    boolean mWasAnimating;      // Were we animating going into the most recent animation step?
+    int mAnimLayer;
+    int mLastLayer;
+    long mAnimationStartTime;
+    long mLastAnimationTime;
+    int mStackClip = STACK_CLIP_BEFORE_ANIM;
+
+    /**
+     * Set when we have changed the size of the surface, to know that
+     * we must tell them application to resize (and thus redraw itself).
+     */
+    boolean mSurfaceResized;
+    /**
+     * Whether we should inform the client on next relayoutWindow that
+     * the surface has been resized since last time.
+     */
+    boolean mReportSurfaceResized;
+    WindowSurfaceController mSurfaceController;
+    private WindowSurfaceController mPendingDestroySurface;
+
+    /**
+     * Set if the client has asked that the destroy of its surface be delayed
+     * until it explicitly says it is okay.
+     */
+    boolean mSurfaceDestroyDeferred;
+
+    private boolean mDestroyPreservedSurfaceUponRedraw;
+    float mShownAlpha = 0;
+    float mAlpha = 0;
+    float mLastAlpha = 0;
+
+    boolean mHasClipRect;
+    Rect mClipRect = new Rect();
+    Rect mTmpClipRect = new Rect();
+    Rect mTmpFinalClipRect = new Rect();
+    Rect mLastClipRect = new Rect();
+    Rect mLastFinalClipRect = new Rect();
+    Rect mTmpStackBounds = new Rect();
+    private Rect mTmpAnimatingBounds = new Rect();
+    private Rect mTmpSourceBounds = new Rect();
+
+    /**
+     * This is rectangle of the window's surface that is not covered by
+     * system decorations.
+     */
+    private final Rect mSystemDecorRect = new Rect();
+    private final Rect mLastSystemDecorRect = new Rect();
+
+    // Used to save animation distances between the time they are calculated and when they are used.
+    private int mAnimDx;
+    private int mAnimDy;
+
+    /** Is the next animation to be started a window move animation? */
+    private boolean mAnimateMove = false;
+
+    float mDsDx=1, mDtDx=0, mDsDy=0, mDtDy=1;
+    private float mLastDsDx=1, mLastDtDx=0, mLastDsDy=0, mLastDtDy=1;
+
+    boolean mHaveMatrix;
+
+    // Set to true if, when the window gets displayed, it should perform
+    // an enter animation.
+    boolean mEnterAnimationPending;
+
+    /** Used to indicate that this window is undergoing an enter animation. Used for system
+     * windows to make the callback to View.dispatchOnWindowShownCallback(). Set when the
+     * window is first added or shown, cleared when the callback has been made. */
+    boolean mEnteringAnimation;
+
+    private boolean mAnimationStartDelayed;
+
+    /** The pixel format of the underlying SurfaceControl */
+    int mSurfaceFormat;
+
+    /** This is set when there is no Surface */
+    static final int NO_SURFACE = 0;
+    /** This is set after the Surface has been created but before the window has been drawn. During
+     * this time the surface is hidden. */
+    static final int DRAW_PENDING = 1;
+    /** This is set after the window has finished drawing for the first time but before its surface
+     * is shown.  The surface will be displayed when the next layout is run. */
+    static final int COMMIT_DRAW_PENDING = 2;
+    /** This is set during the time after the window's drawing has been committed, and before its
+     * surface is actually shown.  It is used to delay showing the surface until all windows in a
+     * token are ready to be shown. */
+    static final int READY_TO_SHOW = 3;
+    /** Set when the window has been shown in the screen the first time. */
+    static final int HAS_DRAWN = 4;
+
+    String drawStateToString() {
+        switch (mDrawState) {
+            case NO_SURFACE: return "NO_SURFACE";
+            case DRAW_PENDING: return "DRAW_PENDING";
+            case COMMIT_DRAW_PENDING: return "COMMIT_DRAW_PENDING";
+            case READY_TO_SHOW: return "READY_TO_SHOW";
+            case HAS_DRAWN: return "HAS_DRAWN";
+            default: return Integer.toString(mDrawState);
+        }
+    }
+    int mDrawState;
+
+    /** Was this window last hidden? */
+    boolean mLastHidden;
+
+    int mAttrType;
+
+    static final long PENDING_TRANSACTION_FINISH_WAIT_TIME = 100;
+
+    boolean mForceScaleUntilResize;
+
+    // WindowState.mHScale and WindowState.mVScale contain the
+    // scale according to client specified layout parameters (e.g.
+    // one layout size, with another surface size, creates such scaling).
+    // Here we track an additional scaling factor used to follow stack
+    // scaling (as in the case of the Pinned stack animation).
+    float mExtraHScale = (float) 1.0;
+    float mExtraVScale = (float) 1.0;
+
+    private final Rect mTmpSize = new Rect();
+
+    WindowStateAnimator(final WindowState win) {
+        final WindowManagerService service = win.mService;
+
+        mService = service;
+        mAnimator = service.mAnimator;
+        mPolicy = service.mPolicy;
+        mContext = service.mContext;
+        final DisplayContent displayContent = win.getDisplayContent();
+        if (displayContent != null) {
+            final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+            mAnimDx = displayInfo.appWidth;
+            mAnimDy = displayInfo.appHeight;
+        } else {
+            Slog.w(TAG, "WindowStateAnimator ctor: Display has been removed");
+            // This is checked on return and dealt with.
+        }
+
+        mWin = win;
+        mParentWinAnimator = !win.isChildWindow() ? null : win.getParentWindow().mWinAnimator;
+        mAppAnimator = win.mAppToken == null ? null : win.mAppToken.mAppAnimator;
+        mSession = win.mSession;
+        mAttrType = win.mAttrs.type;
+        mIsWallpaper = win.mIsWallpaper;
+        mWallpaperControllerLocked = mService.mRoot.mWallpaperController;
+    }
+
+    public void setAnimation(Animation anim, long startTime, int stackClip) {
+        if (localLOGV) Slog.v(TAG, "Setting animation in " + this + ": " + anim);
+        mAnimating = false;
+        mLocalAnimating = false;
+        mAnimation = anim;
+        mAnimation.restrictDuration(WindowManagerService.MAX_ANIMATION_DURATION);
+        mAnimation.scaleCurrentDuration(mService.getWindowAnimationScaleLocked());
+        // Start out animation gone if window is gone, or visible if window is visible.
+        mTransformation.clear();
+        mTransformation.setAlpha(mLastHidden ? 0 : 1);
+        mHasLocalTransformation = true;
+        mAnimationStartTime = startTime;
+        mStackClip = stackClip;
+    }
+
+    public void setAnimation(Animation anim, int stackClip) {
+        setAnimation(anim, -1, stackClip);
+    }
+
+    public void setAnimation(Animation anim) {
+        setAnimation(anim, -1, STACK_CLIP_AFTER_ANIM);
+    }
+
+    public void clearAnimation() {
+        if (mAnimation != null) {
+            mAnimating = true;
+            mLocalAnimating = false;
+            mAnimation.cancel();
+            mAnimation = null;
+            mStackClip = STACK_CLIP_BEFORE_ANIM;
+        }
+    }
+
+    /**
+     * Is the window or its container currently set to animate or currently animating?
+     */
+    boolean isAnimationSet() {
+        return mAnimation != null
+                || (mParentWinAnimator != null && mParentWinAnimator.mAnimation != null)
+                || (mAppAnimator != null && mAppAnimator.isAnimating());
+    }
+
+    /**
+     * @return whether an animation is about to start, i.e. the animation is set already but we
+     *         haven't processed the first frame yet.
+     */
+    boolean isAnimationStarting() {
+        return isAnimationSet() && !mAnimating;
+    }
+
+    /** Is the window animating the DummyAnimation? */
+    boolean isDummyAnimation() {
+        return mAppAnimator != null
+                && mAppAnimator.animation == sDummyAnimation;
+    }
+
+    /**
+     * Is this window currently set to animate or currently animating?
+     */
+    boolean isWindowAnimationSet() {
+        return mAnimation != null;
+    }
+
+    /**
+     * Is this window currently waiting to run an opening animation?
+     */
+    boolean isWaitingForOpening() {
+        return mService.mAppTransition.isTransitionSet() && isDummyAnimation()
+                && mService.mOpeningApps.contains(mWin.mAppToken);
+    }
+
+    void cancelExitAnimationForNextAnimationLocked() {
+        if (DEBUG_ANIM) Slog.d(TAG,
+                "cancelExitAnimationForNextAnimationLocked: " + mWin);
+
+        if (mAnimation != null) {
+            mAnimation.cancel();
+            mAnimation = null;
+            mLocalAnimating = false;
+            mWin.destroySurfaceUnchecked();
+        }
+    }
+
+    private boolean stepAnimation(long currentTime) {
+        if ((mAnimation == null) || !mLocalAnimating) {
+            return false;
+        }
+        currentTime = getAnimationFrameTime(mAnimation, currentTime);
+        mTransformation.clear();
+        final boolean more = mAnimation.getTransformation(currentTime, mTransformation);
+        if (mAnimationStartDelayed && mAnimationIsEntrance) {
+            mTransformation.setAlpha(0f);
+        }
+        if (false && DEBUG_ANIM) Slog.v(TAG, "Stepped animation in " + this + ": more=" + more
+                + ", xform=" + mTransformation);
+        return more;
+    }
+
+    // This must be called while inside a transaction.  Returns true if
+    // there is more animation to run.
+    boolean stepAnimationLocked(long currentTime) {
+        // Save the animation state as it was before this step so WindowManagerService can tell if
+        // we just started or just stopped animating by comparing mWasAnimating with isAnimationSet().
+        mWasAnimating = mAnimating;
+        final DisplayContent displayContent = mWin.getDisplayContent();
+        if (mWin.mToken.okToAnimate()) {
+            // We will run animations as long as the display isn't frozen.
+
+            if (mWin.isDrawnLw() && mAnimation != null) {
+                mHasTransformation = true;
+                mHasLocalTransformation = true;
+                if (!mLocalAnimating) {
+                    if (DEBUG_ANIM) Slog.v(
+                        TAG, "Starting animation in " + this +
+                        " @ " + currentTime + ": ww=" + mWin.mFrame.width() +
+                        " wh=" + mWin.mFrame.height() +
+                        " dx=" + mAnimDx + " dy=" + mAnimDy +
+                        " scale=" + mService.getWindowAnimationScaleLocked());
+                    final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+                    if (mAnimateMove) {
+                        mAnimateMove = false;
+                        mAnimation.initialize(mWin.mFrame.width(), mWin.mFrame.height(),
+                                mAnimDx, mAnimDy);
+                    } else {
+                        mAnimation.initialize(mWin.mFrame.width(), mWin.mFrame.height(),
+                                displayInfo.appWidth, displayInfo.appHeight);
+                    }
+                    mAnimDx = displayInfo.appWidth;
+                    mAnimDy = displayInfo.appHeight;
+                    mAnimation.setStartTime(mAnimationStartTime != -1
+                            ? mAnimationStartTime
+                            : currentTime);
+                    mLocalAnimating = true;
+                    mAnimating = true;
+                }
+                if ((mAnimation != null) && mLocalAnimating) {
+                    mLastAnimationTime = currentTime;
+                    if (stepAnimation(currentTime)) {
+                        return true;
+                    }
+                }
+                if (DEBUG_ANIM) Slog.v(
+                    TAG, "Finished animation in " + this +
+                    " @ " + currentTime);
+                //WindowManagerService.this.dump();
+            }
+            mHasLocalTransformation = false;
+            if ((!mLocalAnimating || mAnimationIsEntrance) && mAppAnimator != null
+                    && mAppAnimator.animation != null) {
+                // When our app token is animating, we kind-of pretend like
+                // we are as well.  Note the mLocalAnimating mAnimationIsEntrance
+                // part of this check means that we will only do this if
+                // our window is not currently exiting, or it is not
+                // locally animating itself.  The idea being that one that
+                // is exiting and doing a local animation should be removed
+                // once that animation is done.
+                mAnimating = true;
+                mHasTransformation = true;
+                mTransformation.clear();
+                return false;
+            } else if (mHasTransformation) {
+                // Little trick to get through the path below to act like
+                // we have finished an animation.
+                mAnimating = true;
+            } else if (isAnimationSet()) {
+                mAnimating = true;
+            }
+        } else if (mAnimation != null) {
+            // If the display is frozen, and there is a pending animation,
+            // clear it and make sure we run the cleanup code.
+            mAnimating = true;
+        }
+
+        if (!mAnimating && !mLocalAnimating) {
+            return false;
+        }
+
+        // Done animating, clean up.
+        if (DEBUG_ANIM) Slog.v(
+            TAG, "Animation done in " + this + ": exiting=" + mWin.mAnimatingExit
+            + ", reportedVisible="
+            + (mWin.mAppToken != null ? mWin.mAppToken.reportedVisible : false));
+
+        mAnimating = false;
+        mLocalAnimating = false;
+        if (mAnimation != null) {
+            mAnimation.cancel();
+            mAnimation = null;
+        }
+        if (mAnimator.mWindowDetachedWallpaper == mWin) {
+            mAnimator.mWindowDetachedWallpaper = null;
+        }
+        mAnimLayer = mWin.getSpecialWindowAnimLayerAdjustment();
+        if (DEBUG_LAYERS) Slog.v(TAG, "Stepping win " + this + " anim layer: " + mAnimLayer);
+        mHasTransformation = false;
+        mHasLocalTransformation = false;
+        mStackClip = STACK_CLIP_BEFORE_ANIM;
+        mWin.checkPolicyVisibilityChange();
+        mTransformation.clear();
+        if (mAttrType == LayoutParams.TYPE_STATUS_BAR && mWin.mPolicyVisibility) {
+            // Upon completion of a not-visible to visible status bar animation a relayout is
+            // required.
+            if (displayContent != null) {
+                displayContent.setLayoutNeeded();
+            }
+        }
+
+        mWin.onExitAnimationDone();
+        final int displayId = mWin.getDisplayId();
+        mAnimator.setPendingLayoutChanges(displayId, WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM);
+        if (DEBUG_LAYOUT_REPEATS)
+            mService.mWindowPlacerLocked.debugLayoutRepeats(
+                    "WindowStateAnimator", mAnimator.getPendingLayoutChanges(displayId));
+
+        if (mWin.mAppToken != null) {
+            mWin.mAppToken.updateReportedVisibilityLocked();
+        }
+
+        return false;
+    }
+
+    void hide(String reason) {
+        if (!mLastHidden) {
+            //dump();
+            mLastHidden = true;
+            if (mSurfaceController != null) {
+                mSurfaceController.hideInTransaction(reason);
+            }
+        }
+    }
+
+    boolean finishDrawingLocked() {
+        final boolean startingWindow =
+                mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+        if (DEBUG_STARTING_WINDOW && startingWindow) {
+            Slog.v(TAG, "Finishing drawing window " + mWin + ": mDrawState="
+                    + drawStateToString());
+        }
+
+        boolean layoutNeeded = false;
+
+        if (mDrawState == DRAW_PENDING) {
+            if (DEBUG_SURFACE_TRACE || DEBUG_ANIM || SHOW_TRANSACTIONS || DEBUG_ORIENTATION)
+                Slog.v(TAG, "finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING " + mWin + " in "
+                        + mSurfaceController);
+            if (DEBUG_STARTING_WINDOW && startingWindow) {
+                Slog.v(TAG, "Draw state now committed in " + mWin);
+            }
+            mDrawState = COMMIT_DRAW_PENDING;
+            layoutNeeded = true;
+        }
+
+        return layoutNeeded;
+    }
+
+    // This must be called while inside a transaction.
+    boolean commitFinishDrawingLocked() {
+        if (DEBUG_STARTING_WINDOW_VERBOSE &&
+                mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) {
+            Slog.i(TAG, "commitFinishDrawingLocked: " + mWin + " cur mDrawState="
+                    + drawStateToString());
+        }
+        if (mDrawState != COMMIT_DRAW_PENDING && mDrawState != READY_TO_SHOW) {
+            return false;
+        }
+        if (DEBUG_SURFACE_TRACE || DEBUG_ANIM) {
+            Slog.i(TAG, "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW " + mSurfaceController);
+        }
+        mDrawState = READY_TO_SHOW;
+        boolean result = false;
+        final AppWindowToken atoken = mWin.mAppToken;
+        if (atoken == null || atoken.allDrawn || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
+            result = mWin.performShowLocked();
+        }
+        return result;
+    }
+
+    void preserveSurfaceLocked() {
+        if (mDestroyPreservedSurfaceUponRedraw) {
+            // This could happen when switching the surface mode very fast. For example,
+            // we preserved a surface when dragResizing changed to true. Then before the
+            // preserved surface is removed, dragResizing changed to false again.
+            // In this case, we need to leave the preserved surface alone, and destroy
+            // the actual surface, so that the createSurface call could create a surface
+            // of the proper size. The preserved surface will still be removed when client
+            // finishes drawing to the new surface.
+            mSurfaceDestroyDeferred = false;
+            destroySurfaceLocked();
+            mSurfaceDestroyDeferred = true;
+            return;
+        }
+        if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(mWin, "SET FREEZE LAYER", false);
+        if (mSurfaceController != null) {
+            mSurfaceController.setLayer(mAnimLayer + 1);
+        }
+        mDestroyPreservedSurfaceUponRedraw = true;
+        mSurfaceDestroyDeferred = true;
+        destroySurfaceLocked();
+    }
+
+    void destroyPreservedSurfaceLocked() {
+        if (!mDestroyPreservedSurfaceUponRedraw) {
+            return;
+        }
+        if (mSurfaceController != null) {
+            if (mPendingDestroySurface != null) {
+                // If we are preserving a surface but we aren't relaunching that means
+                // we are just doing an in-place switch. In that case any SurfaceFlinger side
+                // child layers need to be reparented to the new surface to make this
+                // transparent to the app.
+                if (mWin.mAppToken == null || mWin.mAppToken.isRelaunching() == false) {
+                    SurfaceControl.openTransaction();
+                    mPendingDestroySurface.reparentChildrenInTransaction(mSurfaceController);
+                    SurfaceControl.closeTransaction();
+                }
+            }
+        }
+
+        destroyDeferredSurfaceLocked();
+        mDestroyPreservedSurfaceUponRedraw = false;
+    }
+
+    void markPreservedSurfaceForDestroy() {
+        if (mDestroyPreservedSurfaceUponRedraw
+                && !mService.mDestroyPreservedSurface.contains(mWin)) {
+            mService.mDestroyPreservedSurface.add(mWin);
+        }
+    }
+
+    private int getLayerStack() {
+        return mWin.getDisplayContent().getDisplay().getLayerStack();
+    }
+
+    void updateLayerStackInTransaction() {
+        if (mSurfaceController != null) {
+            mSurfaceController.setLayerStackInTransaction(
+                    getLayerStack());
+        }
+    }
+
+    void resetDrawState() {
+        mDrawState = DRAW_PENDING;
+
+        if (mWin.mAppToken == null) {
+            return;
+        }
+
+        if (mWin.mAppToken.mAppAnimator.animation == null) {
+            mWin.mAppToken.clearAllDrawn();
+        } else {
+            // Currently animating, persist current state of allDrawn until animation
+            // is complete.
+            mWin.mAppToken.deferClearAllDrawn = true;
+        }
+    }
+
+    WindowSurfaceController createSurfaceLocked(int windowType, int ownerUid) {
+        final WindowState w = mWin;
+
+        if (mSurfaceController != null) {
+            return mSurfaceController;
+        }
+
+        if ((mWin.mAttrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0) {
+            windowType = SurfaceControl.WINDOW_TYPE_DONT_SCREENSHOT;
+        }
+
+        w.setHasSurface(false);
+
+        if (DEBUG_ANIM || DEBUG_ORIENTATION) Slog.i(TAG,
+                "createSurface " + this + ": mDrawState=DRAW_PENDING");
+
+        resetDrawState();
+
+        mService.makeWindowFreezingScreenIfNeededLocked(w);
+
+        int flags = SurfaceControl.HIDDEN;
+        final WindowManager.LayoutParams attrs = w.mAttrs;
+
+        if (mService.isSecureLocked(w)) {
+            flags |= SurfaceControl.SECURE;
+        }
+
+        mTmpSize.set(w.mFrame.left + w.mXOffset, w.mFrame.top + w.mYOffset, 0, 0);
+        calculateSurfaceBounds(w, attrs);
+        final int width = mTmpSize.width();
+        final int height = mTmpSize.height();
+
+        if (DEBUG_VISIBILITY) {
+            Slog.v(TAG, "Creating surface in session "
+                    + mSession.mSurfaceSession + " window " + this
+                    + " w=" + width + " h=" + height
+                    + " x=" + mTmpSize.left + " y=" + mTmpSize.top
+                    + " format=" + attrs.format + " flags=" + flags);
+        }
+
+        // We may abort, so initialize to defaults.
+        mLastSystemDecorRect.set(0, 0, 0, 0);
+        mHasClipRect = false;
+        mClipRect.set(0, 0, 0, 0);
+        mLastClipRect.set(0, 0, 0, 0);
+
+        // Set up surface control with initial size.
+        try {
+
+            final boolean isHwAccelerated = (attrs.flags & FLAG_HARDWARE_ACCELERATED) != 0;
+            final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : attrs.format;
+            if (!PixelFormat.formatHasAlpha(attrs.format)
+                    // Don't make surface with surfaceInsets opaque as they display a
+                    // translucent shadow.
+                    && attrs.surfaceInsets.left == 0
+                    && attrs.surfaceInsets.top == 0
+                    && attrs.surfaceInsets.right == 0
+                    && attrs.surfaceInsets.bottom == 0
+                    // Don't make surface opaque when resizing to reduce the amount of
+                    // artifacts shown in areas the app isn't drawing content to.
+                    && !w.isDragResizing()) {
+                flags |= SurfaceControl.OPAQUE;
+            }
+
+            mSurfaceController = new WindowSurfaceController(mSession.mSurfaceSession,
+                    attrs.getTitle().toString(),
+                    width, height, format, flags, this, windowType, ownerUid);
+            mSurfaceFormat = format;
+
+            w.setHasSurface(true);
+
+            if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) {
+                Slog.i(TAG, "  CREATE SURFACE "
+                        + mSurfaceController + " IN SESSION "
+                        + mSession.mSurfaceSession
+                        + ": pid=" + mSession.mPid + " format="
+                        + attrs.format + " flags=0x"
+                        + Integer.toHexString(flags)
+                        + " / " + this);
+            }
+        } catch (OutOfResourcesException e) {
+            Slog.w(TAG, "OutOfResourcesException creating surface");
+            mService.mRoot.reclaimSomeSurfaceMemory(this, "create", true);
+            mDrawState = NO_SURFACE;
+            return null;
+        } catch (Exception e) {
+            Slog.e(TAG, "Exception creating surface", e);
+            mDrawState = NO_SURFACE;
+            return null;
+        }
+
+        if (WindowManagerService.localLOGV) Slog.v(TAG, "Got surface: " + mSurfaceController
+                + ", set left=" + w.mFrame.left + " top=" + w.mFrame.top
+                + ", animLayer=" + mAnimLayer);
+
+        if (SHOW_LIGHT_TRANSACTIONS) {
+            Slog.i(TAG, ">>> OPEN TRANSACTION createSurfaceLocked");
+            WindowManagerService.logSurface(w, "CREATE pos=("
+                    + w.mFrame.left + "," + w.mFrame.top + ") ("
+                    + width + "x" + height + "), layer=" + mAnimLayer + " HIDE", false);
+        }
+
+        // Start a new transaction and apply position & offset.
+
+        mService.openSurfaceTransaction();
+        try {
+            mSurfaceController.setPositionInTransaction(mTmpSize.left, mTmpSize.top, false);
+            mSurfaceController.setLayerStackInTransaction(getLayerStack());
+            mSurfaceController.setLayer(mAnimLayer);
+        } finally {
+            mService.closeSurfaceTransaction();
+        }
+
+        mLastHidden = true;
+
+        if (WindowManagerService.localLOGV) Slog.v(TAG, "Created surface " + this);
+        return mSurfaceController;
+    }
+
+    private void calculateSurfaceBounds(WindowState w, LayoutParams attrs) {
+        if ((attrs.flags & FLAG_SCALED) != 0) {
+            // For a scaled surface, we always want the requested size.
+            mTmpSize.right = mTmpSize.left + w.mRequestedWidth;
+            mTmpSize.bottom = mTmpSize.top + w.mRequestedHeight;
+        } else {
+            // When we're doing a drag-resizing, request a surface that's fullscreen size,
+            // so that we don't need to reallocate during the process. This also prevents
+            // buffer drops due to size mismatch.
+            if (w.isDragResizing()) {
+                if (w.getResizeMode() == DRAG_RESIZE_MODE_FREEFORM) {
+                    mTmpSize.left = 0;
+                    mTmpSize.top = 0;
+                }
+                final DisplayInfo displayInfo = w.getDisplayInfo();
+                mTmpSize.right = mTmpSize.left + displayInfo.logicalWidth;
+                mTmpSize.bottom = mTmpSize.top + displayInfo.logicalHeight;
+            } else {
+                mTmpSize.right = mTmpSize.left + w.mCompatFrame.width();
+                mTmpSize.bottom = mTmpSize.top + w.mCompatFrame.height();
+            }
+        }
+
+        // Something is wrong and SurfaceFlinger will not like this, try to revert to sane values.
+        // This doesn't necessarily mean that there is an error in the system. The sizes might be
+        // incorrect, because it is before the first layout or draw.
+        if (mTmpSize.width() < 1) {
+            mTmpSize.right = mTmpSize.left + 1;
+        }
+        if (mTmpSize.height() < 1) {
+            mTmpSize.bottom = mTmpSize.top + 1;
+        }
+
+        // Adjust for surface insets.
+        mTmpSize.left -= attrs.surfaceInsets.left;
+        mTmpSize.top -= attrs.surfaceInsets.top;
+        mTmpSize.right += attrs.surfaceInsets.right;
+        mTmpSize.bottom += attrs.surfaceInsets.bottom;
+    }
+
+    boolean hasSurface() {
+        return mSurfaceController != null && mSurfaceController.hasSurface();
+    }
+
+    void destroySurfaceLocked() {
+        final AppWindowToken wtoken = mWin.mAppToken;
+        if (wtoken != null) {
+            if (mWin == wtoken.startingWindow) {
+                wtoken.startingDisplayed = false;
+            }
+        }
+
+        if (mSurfaceController == null) {
+            return;
+        }
+
+        // When destroying a surface we want to make sure child windows are hidden. If we are
+        // preserving the surface until redraw though we intend to swap it out with another surface
+        // for resizing. In this case the window always remains visible to the user and the child
+        // windows should likewise remain visible.
+        if (!mDestroyPreservedSurfaceUponRedraw) {
+            mWin.mHidden = true;
+        }
+
+        try {
+            if (DEBUG_VISIBILITY) logWithStack(TAG, "Window " + this + " destroying surface "
+                    + mSurfaceController + ", session " + mSession);
+            if (mSurfaceDestroyDeferred) {
+                if (mSurfaceController != null && mPendingDestroySurface != mSurfaceController) {
+                    if (mPendingDestroySurface != null) {
+                        if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) {
+                            WindowManagerService.logSurface(mWin, "DESTROY PENDING", true);
+                        }
+                        mPendingDestroySurface.destroyInTransaction();
+                    }
+                    mPendingDestroySurface = mSurfaceController;
+                }
+            } else {
+                if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) {
+                    WindowManagerService.logSurface(mWin, "DESTROY", true);
+                }
+                destroySurface();
+            }
+            // Don't hide wallpaper if we're deferring the surface destroy
+            // because of a surface change.
+            if (!mDestroyPreservedSurfaceUponRedraw) {
+                mWallpaperControllerLocked.hideWallpapers(mWin);
+            }
+        } catch (RuntimeException e) {
+            Slog.w(TAG, "Exception thrown when destroying Window " + this
+                + " surface " + mSurfaceController + " session " + mSession + ": " + e.toString());
+        }
+
+        // Whether the surface was preserved (and copied to mPendingDestroySurface) or not, it
+        // needs to be cleared to match the WindowState.mHasSurface state. It is also necessary
+        // so it can be recreated successfully in mPendingDestroySurface case.
+        mWin.setHasSurface(false);
+        if (mSurfaceController != null) {
+            mSurfaceController.setShown(false);
+        }
+        mSurfaceController = null;
+        mDrawState = NO_SURFACE;
+    }
+
+    void destroyDeferredSurfaceLocked() {
+        try {
+            if (mPendingDestroySurface != null) {
+                if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) {
+                    WindowManagerService.logSurface(mWin, "DESTROY PENDING", true);
+                }
+                mPendingDestroySurface.destroyInTransaction();
+                // Don't hide wallpaper if we're destroying a deferred surface
+                // after a surface mode change.
+                if (!mDestroyPreservedSurfaceUponRedraw) {
+                    mWallpaperControllerLocked.hideWallpapers(mWin);
+                }
+            }
+        } catch (RuntimeException e) {
+            Slog.w(TAG, "Exception thrown when destroying Window "
+                    + this + " surface " + mPendingDestroySurface
+                    + " session " + mSession + ": " + e.toString());
+        }
+        mSurfaceDestroyDeferred = false;
+        mPendingDestroySurface = null;
+    }
+
+    void applyMagnificationSpec(MagnificationSpec spec, Matrix transform) {
+        final int surfaceInsetLeft = mWin.mAttrs.surfaceInsets.left;
+        final int surfaceInsetTop = mWin.mAttrs.surfaceInsets.top;
+
+        if (spec != null && !spec.isNop()) {
+            float scale = spec.scale;
+            transform.postScale(scale, scale);
+            transform.postTranslate(spec.offsetX, spec.offsetY);
+
+            // As we are scaling the whole surface, to keep the content
+            // in the same position we will also have to scale the surfaceInsets.
+            transform.postTranslate(-(surfaceInsetLeft*scale - surfaceInsetLeft),
+                    -(surfaceInsetTop*scale - surfaceInsetTop));
+        }
+    }
+
+    void computeShownFrameLocked() {
+        final boolean selfTransformation = mHasLocalTransformation;
+        Transformation attachedTransformation =
+                (mParentWinAnimator != null && mParentWinAnimator.mHasLocalTransformation)
+                ? mParentWinAnimator.mTransformation : null;
+        Transformation appTransformation = (mAppAnimator != null && mAppAnimator.hasTransformation)
+                ? mAppAnimator.transformation : null;
+
+        // Wallpapers are animated based on the "real" window they
+        // are currently targeting.
+        final WindowState wallpaperTarget = mWallpaperControllerLocked.getWallpaperTarget();
+        if (mIsWallpaper && wallpaperTarget != null && mService.mAnimateWallpaperWithTarget) {
+            final WindowStateAnimator wallpaperAnimator = wallpaperTarget.mWinAnimator;
+            if (wallpaperAnimator.mHasLocalTransformation &&
+                    wallpaperAnimator.mAnimation != null &&
+                    !wallpaperAnimator.mAnimation.getDetachWallpaper()) {
+                attachedTransformation = wallpaperAnimator.mTransformation;
+                if (DEBUG_WALLPAPER && attachedTransformation != null) {
+                    Slog.v(TAG, "WP target attached xform: " + attachedTransformation);
+                }
+            }
+            final AppWindowAnimator wpAppAnimator = wallpaperTarget.mAppToken == null ?
+                    null : wallpaperTarget.mAppToken.mAppAnimator;
+                if (wpAppAnimator != null && wpAppAnimator.hasTransformation
+                    && wpAppAnimator.animation != null
+                    && !wpAppAnimator.animation.getDetachWallpaper()) {
+                appTransformation = wpAppAnimator.transformation;
+                if (DEBUG_WALLPAPER && appTransformation != null) {
+                    Slog.v(TAG, "WP target app xform: " + appTransformation);
+                }
+            }
+        }
+
+        final int displayId = mWin.getDisplayId();
+        final ScreenRotationAnimation screenRotationAnimation =
+                mAnimator.getScreenRotationAnimationLocked(displayId);
+        final boolean screenAnimation =
+                screenRotationAnimation != null && screenRotationAnimation.isAnimating();
+
+        mHasClipRect = false;
+        if (selfTransformation || attachedTransformation != null
+                || appTransformation != null || screenAnimation) {
+            // cache often used attributes locally
+            final Rect frame = mWin.mFrame;
+            final float tmpFloats[] = mService.mTmpFloats;
+            final Matrix tmpMatrix = mWin.mTmpMatrix;
+
+            // Compute the desired transformation.
+            if (screenAnimation && screenRotationAnimation.isRotating()) {
+                // If we are doing a screen animation, the global rotation
+                // applied to windows can result in windows that are carefully
+                // aligned with each other to slightly separate, allowing you
+                // to see what is behind them.  An unsightly mess.  This...
+                // thing...  magically makes it call good: scale each window
+                // slightly (two pixels larger in each dimension, from the
+                // window's center).
+                final float w = frame.width();
+                final float h = frame.height();
+                if (w>=1 && h>=1) {
+                    tmpMatrix.setScale(1 + 2/w, 1 + 2/h, w/2, h/2);
+                } else {
+                    tmpMatrix.reset();
+                }
+            } else {
+                tmpMatrix.reset();
+            }
+            tmpMatrix.postScale(mWin.mGlobalScale, mWin.mGlobalScale);
+            if (selfTransformation) {
+                tmpMatrix.postConcat(mTransformation.getMatrix());
+            }
+            if (attachedTransformation != null) {
+                tmpMatrix.postConcat(attachedTransformation.getMatrix());
+            }
+            if (appTransformation != null) {
+                tmpMatrix.postConcat(appTransformation.getMatrix());
+            }
+
+            // The translation that applies the position of the window needs to be applied at the
+            // end in case that other translations include scaling. Otherwise the scaling will
+            // affect this translation. But it needs to be set before the screen rotation animation
+            // so the pivot point is at the center of the screen for all windows.
+            tmpMatrix.postTranslate(frame.left + mWin.mXOffset, frame.top + mWin.mYOffset);
+            if (screenAnimation) {
+                tmpMatrix.postConcat(screenRotationAnimation.getEnterTransformation().getMatrix());
+            }
+
+            MagnificationSpec spec = getMagnificationSpec();
+            if (spec != null) {
+                applyMagnificationSpec(spec, tmpMatrix);
+            }
+
+            // "convert" it into SurfaceFlinger's format
+            // (a 2x2 matrix + an offset)
+            // Here we must not transform the position of the surface
+            // since it is already included in the transformation.
+            //Slog.i(TAG_WM, "Transform: " + matrix);
+
+            mHaveMatrix = true;
+            tmpMatrix.getValues(tmpFloats);
+            mDsDx = tmpFloats[Matrix.MSCALE_X];
+            mDtDx = tmpFloats[Matrix.MSKEW_Y];
+            mDtDy = tmpFloats[Matrix.MSKEW_X];
+            mDsDy = tmpFloats[Matrix.MSCALE_Y];
+            float x = tmpFloats[Matrix.MTRANS_X];
+            float y = tmpFloats[Matrix.MTRANS_Y];
+            mWin.mShownPosition.set(Math.round(x), Math.round(y));
+
+            // Now set the alpha...  but because our current hardware
+            // can't do alpha transformation on a non-opaque surface,
+            // turn it off if we are running an animation that is also
+            // transforming since it is more important to have that
+            // animation be smooth.
+            mShownAlpha = mAlpha;
+            if (!mService.mLimitedAlphaCompositing
+                    || (!PixelFormat.formatHasAlpha(mWin.mAttrs.format)
+                    || (mWin.isIdentityMatrix(mDsDx, mDtDx, mDtDy, mDsDy)
+                            && x == frame.left && y == frame.top))) {
+                //Slog.i(TAG_WM, "Applying alpha transform");
+                if (selfTransformation) {
+                    mShownAlpha *= mTransformation.getAlpha();
+                }
+                if (attachedTransformation != null) {
+                    mShownAlpha *= attachedTransformation.getAlpha();
+                }
+                if (appTransformation != null) {
+                    mShownAlpha *= appTransformation.getAlpha();
+                    if (appTransformation.hasClipRect()) {
+                        mClipRect.set(appTransformation.getClipRect());
+                        mHasClipRect = true;
+                        // The app transformation clip will be in the coordinate space of the main
+                        // activity window, which the animation correctly assumes will be placed at
+                        // (0,0)+(insets) relative to the containing frame. This isn't necessarily
+                        // true for child windows though which can have an arbitrary frame position
+                        // relative to their containing frame. We need to offset the difference
+                        // between the containing frame as used to calculate the crop and our
+                        // bounds to compensate for this.
+                        if (mWin.layoutInParentFrame()) {
+                            mClipRect.offset( (mWin.mContainingFrame.left - mWin.mFrame.left),
+                                    mWin.mContainingFrame.top - mWin.mFrame.top );
+                        }
+                    }
+                }
+                if (screenAnimation) {
+                    mShownAlpha *= screenRotationAnimation.getEnterTransformation().getAlpha();
+                }
+            } else {
+                //Slog.i(TAG_WM, "Not applying alpha transform");
+            }
+
+            if ((DEBUG_SURFACE_TRACE || WindowManagerService.localLOGV)
+                    && (mShownAlpha == 1.0 || mShownAlpha == 0.0)) Slog.v(
+                    TAG, "computeShownFrameLocked: Animating " + this + " mAlpha=" + mAlpha
+                    + " self=" + (selfTransformation ? mTransformation.getAlpha() : "null")
+                    + " attached=" + (attachedTransformation == null ?
+                            "null" : attachedTransformation.getAlpha())
+                    + " app=" + (appTransformation == null ? "null" : appTransformation.getAlpha())
+                    + " screen=" + (screenAnimation ?
+                            screenRotationAnimation.getEnterTransformation().getAlpha() : "null"));
+            return;
+        } else if (mIsWallpaper && mService.mRoot.mWallpaperActionPending) {
+            return;
+        } else if (mWin.isDragResizeChanged()) {
+            // This window is awaiting a relayout because user just started (or ended)
+            // drag-resizing. The shown frame (which affects surface size and pos)
+            // should not be updated until we get next finished draw with the new surface.
+            // Otherwise one or two frames rendered with old settings would be displayed
+            // with new geometry.
+            return;
+        }
+
+        if (WindowManagerService.localLOGV) Slog.v(
+                TAG, "computeShownFrameLocked: " + this +
+                " not attached, mAlpha=" + mAlpha);
+
+        MagnificationSpec spec = getMagnificationSpec();
+        if (spec != null) {
+            final Rect frame = mWin.mFrame;
+            final float tmpFloats[] = mService.mTmpFloats;
+            final Matrix tmpMatrix = mWin.mTmpMatrix;
+
+            tmpMatrix.setScale(mWin.mGlobalScale, mWin.mGlobalScale);
+            tmpMatrix.postTranslate(frame.left + mWin.mXOffset, frame.top + mWin.mYOffset);
+
+            applyMagnificationSpec(spec, tmpMatrix);
+
+            tmpMatrix.getValues(tmpFloats);
+
+            mHaveMatrix = true;
+            mDsDx = tmpFloats[Matrix.MSCALE_X];
+            mDtDx = tmpFloats[Matrix.MSKEW_Y];
+            mDtDy = tmpFloats[Matrix.MSKEW_X];
+            mDsDy = tmpFloats[Matrix.MSCALE_Y];
+            float x = tmpFloats[Matrix.MTRANS_X];
+            float y = tmpFloats[Matrix.MTRANS_Y];
+            mWin.mShownPosition.set(Math.round(x), Math.round(y));
+
+            mShownAlpha = mAlpha;
+        } else {
+            mWin.mShownPosition.set(mWin.mFrame.left, mWin.mFrame.top);
+            if (mWin.mXOffset != 0 || mWin.mYOffset != 0) {
+                mWin.mShownPosition.offset(mWin.mXOffset, mWin.mYOffset);
+            }
+            mShownAlpha = mAlpha;
+            mHaveMatrix = false;
+            mDsDx = mWin.mGlobalScale;
+            mDtDx = 0;
+            mDtDy = 0;
+            mDsDy = mWin.mGlobalScale;
+        }
+    }
+
+    private MagnificationSpec getMagnificationSpec() {
+        //TODO (multidisplay): Magnification is supported only for the default display.
+        if (mService.mAccessibilityController != null && mWin.getDisplayId() == DEFAULT_DISPLAY) {
+            return mService.mAccessibilityController.getMagnificationSpecForWindowLocked(mWin);
+        }
+        return null;
+    }
+
+    /**
+     * In some scenarios we use a screen space clip rect (so called, final clip rect)
+     * to crop to stack bounds. Generally because it's easier to deal with while
+     * animating.
+     *
+     * @return True in scenarios where we use the final clip rect for stack clipping.
+     */
+    private boolean useFinalClipRect() {
+        return (isAnimationSet() && resolveStackClip() == STACK_CLIP_AFTER_ANIM)
+                || mDestroyPreservedSurfaceUponRedraw || mWin.inPinnedWorkspace();
+    }
+
+    /**
+     * Calculate the screen-space crop rect and fill finalClipRect.
+     * @return true if finalClipRect has been filled, otherwise,
+     * no screen space crop should be applied.
+     */
+    private boolean calculateFinalCrop(Rect finalClipRect) {
+        final WindowState w = mWin;
+        final DisplayContent displayContent = w.getDisplayContent();
+        finalClipRect.setEmpty();
+
+        if (displayContent == null) {
+            return false;
+        }
+
+        if (!shouldCropToStackBounds() || !useFinalClipRect()) {
+            return false;
+        }
+
+        // Task is non-null per shouldCropToStackBounds
+        final TaskStack stack = w.getTask().mStack;
+        stack.getDimBounds(finalClipRect);
+
+        if (stack.getWindowConfiguration().tasksAreFloating()) {
+            w.expandForSurfaceInsets(finalClipRect);
+        }
+
+        // We may be applying a magnification spec to all windows,
+        // simulating a transformation in screen space, in which case
+        // we need to transform all other screen space values...including
+        // the final crop. This is kind of messed up and we should look
+        // in to actually transforming screen-space via a parent-layer.
+        // b/38322835
+        MagnificationSpec spec = getMagnificationSpec();
+        if (spec != null && !spec.isNop()) {
+            Matrix transform = mWin.mTmpMatrix;
+            RectF finalCrop = mService.mTmpRectF;
+            transform.reset();
+            transform.postScale(spec.scale, spec.scale);
+            transform.postTranslate(-spec.offsetX, -spec.offsetY);
+            transform.mapRect(finalCrop);
+            finalClipRect.top = (int) finalCrop.top;
+            finalClipRect.left = (int) finalCrop.left;
+            finalClipRect.right = (int) finalCrop.right;
+            finalClipRect.bottom = (int) finalCrop.bottom;
+        }
+
+        return true;
+    }
+
+    /**
+     * Calculate the window-space crop rect and fill clipRect.
+     * @return true if clipRect has been filled otherwise, no window space crop should be applied.
+     */
+    private boolean calculateCrop(Rect clipRect) {
+        final WindowState w = mWin;
+        final DisplayContent displayContent = w.getDisplayContent();
+        clipRect.setEmpty();
+
+        if (displayContent == null) {
+            return false;
+        }
+
+        if (w.inPinnedWorkspace()) {
+            return false;
+        }
+
+        // If we're animating, the wallpaper should only
+        // be updated at the end of the animation.
+        if (w.mAttrs.type == TYPE_WALLPAPER) {
+            return false;
+        }
+
+        if (DEBUG_WINDOW_CROP) Slog.d(TAG,
+                "Updating crop win=" + w + " mLastCrop=" + mLastClipRect);
+
+        w.calculatePolicyCrop(mSystemDecorRect);
+
+        if (DEBUG_WINDOW_CROP) Slog.d(TAG, "Applying decor to crop win=" + w + " mDecorFrame="
+                + w.mDecorFrame + " mSystemDecorRect=" + mSystemDecorRect);
+
+        final Task task = w.getTask();
+        final boolean fullscreen = w.fillsDisplay() || (task != null && task.isFullscreen());
+        final boolean isFreeformResizing =
+                w.isDragResizing() && w.getResizeMode() == DRAG_RESIZE_MODE_FREEFORM;
+
+        // We use the clip rect as provided by the tranformation for non-fullscreen windows to
+        // avoid premature clipping with the system decor rect.
+        clipRect.set((mHasClipRect && !fullscreen) ? mClipRect : mSystemDecorRect);
+        if (DEBUG_WINDOW_CROP) Slog.d(TAG, "win=" + w + " Initial clip rect: " + clipRect
+                + " mHasClipRect=" + mHasClipRect + " fullscreen=" + fullscreen);
+
+        if (isFreeformResizing && !w.isChildWindow()) {
+            // For freeform resizing non child windows, we are using the big surface positioned
+            // at 0,0. Thus we must express the crop in that coordinate space.
+            clipRect.offset(w.mShownPosition.x, w.mShownPosition.y);
+        }
+
+        w.expandForSurfaceInsets(clipRect);
+
+        if (mHasClipRect && fullscreen) {
+            // We intersect the clip rect specified by the transformation with the expanded system
+            // decor rect to prevent artifacts from drawing during animation if the transformation
+            // clip rect extends outside the system decor rect.
+            clipRect.intersect(mClipRect);
+        }
+        // The clip rect was generated assuming (0,0) as the window origin,
+        // so we need to translate to match the actual surface coordinates.
+        clipRect.offset(w.mAttrs.surfaceInsets.left, w.mAttrs.surfaceInsets.top);
+
+        if (!useFinalClipRect()) {
+            adjustCropToStackBounds(clipRect, isFreeformResizing);
+        }
+        if (DEBUG_WINDOW_CROP) Slog.d(TAG,
+                "win=" + w + " Clip rect after stack adjustment=" + clipRect);
+
+        w.transformClipRectFromScreenToSurfaceSpace(clipRect);
+
+        return true;
+    }
+
+    private void applyCrop(Rect clipRect, Rect finalClipRect, boolean recoveringMemory) {
+        if (DEBUG_WINDOW_CROP) Slog.d(TAG, "applyCrop: win=" + mWin
+                + " clipRect=" + clipRect + " finalClipRect=" + finalClipRect);
+        if (clipRect != null) {
+            if (!clipRect.equals(mLastClipRect)) {
+                mLastClipRect.set(clipRect);
+                mSurfaceController.setCropInTransaction(clipRect, recoveringMemory);
+            }
+        } else {
+            mSurfaceController.clearCropInTransaction(recoveringMemory);
+        }
+
+        if (finalClipRect == null) {
+            finalClipRect = mService.mTmpRect;
+            finalClipRect.setEmpty();
+        }
+        if (!finalClipRect.equals(mLastFinalClipRect)) {
+            mLastFinalClipRect.set(finalClipRect);
+            mSurfaceController.setFinalCropInTransaction(finalClipRect);
+            if (mDestroyPreservedSurfaceUponRedraw && mPendingDestroySurface != null) {
+                mPendingDestroySurface.setFinalCropInTransaction(finalClipRect);
+            }
+        }
+    }
+
+    private int resolveStackClip() {
+        // App animation overrides window animation stack clip mode.
+        if (mAppAnimator != null && mAppAnimator.animation != null) {
+            return mAppAnimator.getStackClip();
+        } else {
+            return mStackClip;
+        }
+    }
+
+    private boolean shouldCropToStackBounds() {
+        final WindowState w = mWin;
+        final DisplayContent displayContent = w.getDisplayContent();
+        if (displayContent != null && !displayContent.isDefaultDisplay) {
+            // There are some windows that live on other displays while their app and main window
+            // live on the default display (e.g. casting...). We don't want to crop this windows
+            // to the stack bounds which is only currently supported on the default display.
+            // TODO(multi-display): Need to support cropping to stack bounds on other displays
+            // when we have stacks on other displays.
+            return false;
+        }
+
+        final Task task = w.getTask();
+        if (task == null || !task.cropWindowsToStackBounds()) {
+            return false;
+        }
+
+        final int stackClip = resolveStackClip();
+
+        // It's animating and we don't want to clip it to stack bounds during animation - abort.
+        if (isAnimationSet() && stackClip == STACK_CLIP_NONE) {
+            return false;
+        }
+        return true;
+    }
+
+    private void adjustCropToStackBounds(Rect clipRect,
+            boolean isFreeformResizing) {
+        final WindowState w = mWin;
+
+        if (!shouldCropToStackBounds()) {
+            return;
+        }
+
+        final TaskStack stack = w.getTask().mStack;
+        stack.getDimBounds(mTmpStackBounds);
+        final Rect surfaceInsets = w.getAttrs().surfaceInsets;
+        // 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
+        // hardcoding it, we use surface coordinates.
+        final int frameX = isFreeformResizing ? (int) mSurfaceController.getX() :
+                w.mFrame.left + mWin.mXOffset - surfaceInsets.left;
+        final int frameY = isFreeformResizing ? (int) mSurfaceController.getY() :
+                w.mFrame.top + mWin.mYOffset - surfaceInsets.top;
+
+        // We need to do some acrobatics with surface position, because their clip region is
+        // relative to the inside of the surface, but the stack bounds aren't.
+        final WindowConfiguration winConfig = w.getWindowConfiguration();
+        if (winConfig.hasWindowShadow() && !winConfig.canResizeTask()) {
+                // The windows in this stack display drop shadows and the fill the entire stack
+                // area. Adjust the stack bounds we will use to cropping take into account the
+                // offsets we use to display the drop shadow so it doesn't get cropped.
+                mTmpStackBounds.inset(-surfaceInsets.left, -surfaceInsets.top,
+                        -surfaceInsets.right, -surfaceInsets.bottom);
+        }
+
+        clipRect.left = Math.max(0,
+                Math.max(mTmpStackBounds.left, frameX + clipRect.left) - frameX);
+        clipRect.top = Math.max(0,
+                Math.max(mTmpStackBounds.top, frameY + clipRect.top) - frameY);
+        clipRect.right = Math.max(0,
+                Math.min(mTmpStackBounds.right, frameX + clipRect.right) - frameX);
+        clipRect.bottom = Math.max(0,
+                Math.min(mTmpStackBounds.bottom, frameY + clipRect.bottom) - frameY);
+    }
+
+    void setSurfaceBoundariesLocked(final boolean recoveringMemory) {
+        if (mSurfaceController == null) {
+            return;
+        }
+
+        final WindowState w = mWin;
+        final LayoutParams attrs = mWin.getAttrs();
+        final Task task = w.getTask();
+
+        // We got resized, so block all updates until we got the new surface.
+        if (w.isResizedWhileNotDragResizing() && !w.isGoneForLayoutLw()) {
+            return;
+        }
+
+        mTmpSize.set(w.mShownPosition.x, w.mShownPosition.y, 0, 0);
+        calculateSurfaceBounds(w, attrs);
+
+        mExtraHScale = (float) 1.0;
+        mExtraVScale = (float) 1.0;
+
+        boolean wasForceScaled = mForceScaleUntilResize;
+        boolean wasSeamlesslyRotated = w.mSeamlesslyRotated;
+
+        // Once relayout has been called at least once, we need to make sure
+        // we only resize the client surface during calls to relayout. For
+        // clients which use indeterminate measure specs (MATCH_PARENT),
+        // we may try and change their window size without a call to relayout.
+        // However, this would be unsafe, as the client may be in the middle
+        // of producing a frame at the old size, having just completed layout
+        // to find the surface size changed underneath it.
+        if (!w.mRelayoutCalled || w.mInRelayout) {
+            mSurfaceResized = mSurfaceController.setSizeInTransaction(
+                    mTmpSize.width(), mTmpSize.height(), recoveringMemory);
+        } else {
+            mSurfaceResized = false;
+        }
+        mForceScaleUntilResize = mForceScaleUntilResize && !mSurfaceResized;
+        // If we are undergoing seamless rotation, the surface has already
+        // been set up to persist at it's old location. We need to freeze
+        // updates until a resize occurs.
+        mService.markForSeamlessRotation(w, w.mSeamlesslyRotated && !mSurfaceResized);
+
+        Rect clipRect = null, finalClipRect = null;
+        if (calculateCrop(mTmpClipRect)) {
+            clipRect = mTmpClipRect;
+        }
+        if (calculateFinalCrop(mTmpFinalClipRect)) {
+            finalClipRect = mTmpFinalClipRect;
+        }
+
+        float surfaceWidth = mSurfaceController.getWidth();
+        float surfaceHeight = mSurfaceController.getHeight();
+
+        if (isForceScaled()) {
+            int hInsets = attrs.surfaceInsets.left + attrs.surfaceInsets.right;
+            int vInsets = attrs.surfaceInsets.top + attrs.surfaceInsets.bottom;
+            float surfaceContentWidth = surfaceWidth - hInsets;
+            float surfaceContentHeight = surfaceHeight - vInsets;
+            if (!mForceScaleUntilResize) {
+                mSurfaceController.forceScaleableInTransaction(true);
+            }
+
+            int posX = mTmpSize.left;
+            int posY = mTmpSize.top;
+            task.mStack.getDimBounds(mTmpStackBounds);
+
+            boolean allowStretching = false;
+            task.mStack.getFinalAnimationSourceHintBounds(mTmpSourceBounds);
+            // If we don't have source bounds, we can attempt to use the content insets
+            // in the following scenario:
+            //    1. We have content insets.
+            //    2. We are not transitioning to full screen
+            // We have to be careful to check "lastAnimatingBoundsWasToFullscreen" rather than
+            // the mBoundsAnimating state, as we may have already left it and only be here
+            // because of the force-scale until resize state.
+            if (mTmpSourceBounds.isEmpty() && (mWin.mLastRelayoutContentInsets.width() > 0
+                    || mWin.mLastRelayoutContentInsets.height() > 0)
+                        && !task.mStack.lastAnimatingBoundsWasToFullscreen()) {
+                mTmpSourceBounds.set(task.mStack.mPreAnimationBounds);
+                mTmpSourceBounds.inset(mWin.mLastRelayoutContentInsets);
+                allowStretching = true;
+            }
+            if (!mTmpSourceBounds.isEmpty()) {
+                // Get the final target stack bounds, if we are not animating, this is just the
+                // current stack bounds
+                task.mStack.getFinalAnimationBounds(mTmpAnimatingBounds);
+
+                // Calculate the current progress and interpolate the difference between the target
+                // and source bounds
+                float finalWidth = mTmpAnimatingBounds.width();
+                float initialWidth = mTmpSourceBounds.width();
+                float tw = (surfaceContentWidth - mTmpStackBounds.width())
+                        / (surfaceContentWidth - mTmpAnimatingBounds.width());
+                float th = tw;
+                mExtraHScale = (initialWidth + tw * (finalWidth - initialWidth)) / initialWidth;
+                if (allowStretching) {
+                    float finalHeight = mTmpAnimatingBounds.height();
+                    float initialHeight = mTmpSourceBounds.height();
+                    th = (surfaceContentHeight - mTmpStackBounds.height())
+                        / (surfaceContentHeight - mTmpAnimatingBounds.height());
+                    mExtraVScale = (initialHeight + tw * (finalHeight - initialHeight))
+                            / initialHeight;
+                } else {
+                    mExtraVScale = mExtraHScale;
+                }
+
+                // Adjust the position to account for the inset bounds
+                posX -= (int) (tw * mExtraHScale * mTmpSourceBounds.left);
+                posY -= (int) (th * mExtraVScale * mTmpSourceBounds.top);
+
+                // Always clip to the stack bounds since the surface can be larger with the current
+                // scale
+                clipRect = null;
+                finalClipRect = mTmpStackBounds;
+            } else {
+                // We want to calculate the scaling based on the content area, not based on
+                // the entire surface, so that we scale in sync with windows that don't have insets.
+                mExtraHScale = mTmpStackBounds.width() / surfaceContentWidth;
+                mExtraVScale = mTmpStackBounds.height() / surfaceContentHeight;
+
+                // Since we are scaled to fit in our previously desired crop, we can now
+                // expose the whole window in buffer space, and not risk extending
+                // past where the system would have cropped us
+                clipRect = null;
+                finalClipRect = null;
+            }
+
+            // In the case of ForceScaleToStack we scale entire tasks together,
+            // and so we need to scale our offsets relative to the task bounds
+            // or parent and child windows would fall out of alignment.
+            posX -= (int) (attrs.x * (1 - mExtraHScale));
+            posY -= (int) (attrs.y * (1 - mExtraVScale));
+
+            // Imagine we are scaling down. As we scale the buffer down, we decrease the
+            // distance between the surface top left, and the start of the surface contents
+            // (previously it was surfaceInsets.left pixels in screen space but now it
+            // will be surfaceInsets.left*mExtraHScale). This means in order to keep the
+            // non inset content at the same position, we have to shift the whole window
+            // forward. Likewise for scaling up, we've increased this distance, and we need
+            // to shift by a negative number to compensate.
+            posX += attrs.surfaceInsets.left * (1 - mExtraHScale);
+            posY += attrs.surfaceInsets.top * (1 - mExtraVScale);
+
+            mSurfaceController.setPositionInTransaction((float) Math.floor(posX),
+                    (float) Math.floor(posY), recoveringMemory);
+
+            // Various surfaces in the scaled stack may resize at different times.
+            // We need to ensure for each surface, that we disable transformation matrix
+            // scaling in the same transaction which we resize the surface in.
+            // As we are in SCALING_MODE_SCALE_TO_WINDOW, SurfaceFlinger will
+            // then take over the scaling until the new buffer arrives, and things
+            // will be seamless.
+            mForceScaleUntilResize = true;
+        } else {
+            if (!w.mSeamlesslyRotated) {
+                mSurfaceController.setPositionInTransaction(mTmpSize.left, mTmpSize.top,
+                        recoveringMemory);
+            }
+        }
+
+        // If we are ending the scaling mode. We switch to SCALING_MODE_FREEZE
+        // to prevent further updates until buffer latch.
+        // When ending both force scaling, and seamless rotation, we need to freeze
+        // the Surface geometry until a buffer comes in at the new size (normally position and crop
+        // are unfrozen). setGeometryAppliesWithResizeInTransaction accomplishes this for us.
+        if ((wasForceScaled && !mForceScaleUntilResize) ||
+                (wasSeamlesslyRotated && !w.mSeamlesslyRotated)) {
+            mSurfaceController.setGeometryAppliesWithResizeInTransaction(true);
+            mSurfaceController.forceScaleableInTransaction(false);
+        }
+
+        if (!w.mSeamlesslyRotated) {
+            applyCrop(clipRect, finalClipRect, recoveringMemory);
+            mSurfaceController.setMatrixInTransaction(mDsDx * w.mHScale * mExtraHScale,
+                    mDtDx * w.mVScale * mExtraVScale,
+                    mDtDy * w.mHScale * mExtraHScale,
+                    mDsDy * w.mVScale * mExtraVScale, recoveringMemory);
+        }
+
+        if (mSurfaceResized) {
+            mReportSurfaceResized = true;
+            mAnimator.setPendingLayoutChanges(w.getDisplayId(),
+                    WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
+            w.applyDimLayerIfNeeded();
+        }
+    }
+
+    /**
+     * Get rect of the task this window is currently in. If there is no task, rect will be set to
+     * empty.
+     */
+    void getContainerRect(Rect rect) {
+        final Task task = mWin.getTask();
+        if (task != null) {
+            task.getDimBounds(rect);
+        } else {
+            rect.left = rect.top = rect.right = rect.bottom = 0;
+        }
+    }
+
+    void prepareSurfaceLocked(final boolean recoveringMemory) {
+        final WindowState w = mWin;
+        if (!hasSurface()) {
+
+            // There is no need to wait for an animation change if our window is gone for layout
+            // already as we'll never be visible.
+            if (w.getOrientationChanging() && w.isGoneForLayoutLw()) {
+                if (DEBUG_ORIENTATION) {
+                    Slog.v(TAG, "Orientation change skips hidden " + w);
+                }
+                w.setOrientationChanging(false);
+            }
+            return;
+        }
+
+        // Do not change surface properties of opening apps if we are waiting for the
+        // transition to be ready. transitionGoodToGo could be not ready even after all
+        // opening apps are drawn. It's only waiting on isFetchingAppTransitionsSpecs()
+        // to get the animation spec. (For example, go into Recents and immediately open
+        // the same app again before the app's surface is destroyed or saved, the surface
+        // is always ready in the whole process.) If we go ahead here, the opening app
+        // will be shown with the full size before the correct animation spec arrives.
+        if (isWaitingForOpening()) {
+            return;
+        }
+
+        boolean displayed = false;
+
+        computeShownFrameLocked();
+
+        setSurfaceBoundariesLocked(recoveringMemory);
+
+        if (mIsWallpaper && !mWin.mWallpaperVisible) {
+            // Wallpaper is no longer visible and there is no wp target => hide it.
+            hide("prepareSurfaceLocked");
+        } else if (w.isParentWindowHidden() || !w.isOnScreen()) {
+            hide("prepareSurfaceLocked");
+            mWallpaperControllerLocked.hideWallpapers(w);
+
+            // If we are waiting for this window to handle an orientation change. If this window is
+            // really hidden (gone for layout), there is no point in still waiting for it.
+            // Note that this does introduce a potential glitch if the window becomes unhidden
+            // before it has drawn for the new orientation.
+            if (w.getOrientationChanging() && w.isGoneForLayoutLw()) {
+                w.setOrientationChanging(false);
+                if (DEBUG_ORIENTATION) Slog.v(TAG,
+                        "Orientation change skips hidden " + w);
+            }
+        } else if (mLastLayer != mAnimLayer
+                || mLastAlpha != mShownAlpha
+                || mLastDsDx != mDsDx
+                || mLastDtDx != mDtDx
+                || mLastDsDy != mDsDy
+                || mLastDtDy != mDtDy
+                || w.mLastHScale != w.mHScale
+                || w.mLastVScale != w.mVScale
+                || mLastHidden) {
+            displayed = true;
+            mLastAlpha = mShownAlpha;
+            mLastLayer = mAnimLayer;
+            mLastDsDx = mDsDx;
+            mLastDtDx = mDtDx;
+            mLastDsDy = mDsDy;
+            mLastDtDy = mDtDy;
+            w.mLastHScale = w.mHScale;
+            w.mLastVScale = w.mVScale;
+            if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(w,
+                    "controller=" + mSurfaceController +
+                    "alpha=" + mShownAlpha + " layer=" + mAnimLayer
+                    + " matrix=[" + mDsDx + "*" + w.mHScale
+                    + "," + mDtDx + "*" + w.mVScale
+                    + "][" + mDtDy + "*" + w.mHScale
+                    + "," + mDsDy + "*" + w.mVScale + "]", false);
+
+            boolean prepared =
+                mSurfaceController.prepareToShowInTransaction(mShownAlpha,
+                        mDsDx * w.mHScale * mExtraHScale,
+                        mDtDx * w.mVScale * mExtraVScale,
+                        mDtDy * w.mHScale * mExtraHScale,
+                        mDsDy * w.mVScale * mExtraVScale,
+                        recoveringMemory);
+            mSurfaceController.setLayer(mAnimLayer);
+
+            if (prepared && mDrawState == HAS_DRAWN) {
+                if (mLastHidden) {
+                    if (showSurfaceRobustlyLocked()) {
+                        markPreservedSurfaceForDestroy();
+                        mAnimator.requestRemovalOfReplacedWindows(w);
+                        mLastHidden = false;
+                        if (mIsWallpaper) {
+                            w.dispatchWallpaperVisibility(true);
+                        }
+                        // This draw means the difference between unique content and mirroring.
+                        // Run another pass through performLayout to set mHasContent in the
+                        // LogicalDisplay.
+                        mAnimator.setPendingLayoutChanges(w.getDisplayId(),
+                                WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM);
+                    } else {
+                        w.setOrientationChanging(false);
+                    }
+                }
+                // We process mTurnOnScreen even for windows which have already
+                // been shown, to handle cases where windows are not necessarily
+                // hidden while the screen is turning off.
+                // TODO(b/63773439): These cases should be eliminated, though we probably still
+                // want to process mTurnOnScreen in this way for clarity.
+                if (mWin.mTurnOnScreen &&
+                        (mWin.mAppToken == null || mWin.mAppToken.canTurnScreenOn())) {
+                    if (DEBUG_VISIBILITY) Slog.v(TAG, "Show surface turning screen on: " + mWin);
+                    mWin.mTurnOnScreen = false;
+
+                    // The window should only turn the screen on once per resume, but
+                    // prepareSurfaceLocked can be called multiple times. Set canTurnScreenOn to
+                    // false so the window doesn't turn the screen on again during this resume.
+                    if (mWin.mAppToken != null) {
+                        mWin.mAppToken.setCanTurnScreenOn(false);
+                    }
+
+                    // We do not add {@code SET_TURN_ON_SCREEN} when the screen is already
+                    // interactive as the value may persist until the next animation, which could
+                    // potentially occurring while turning off the screen. This would lead to the
+                    // screen incorrectly turning back on.
+                    if (!mService.mPowerManager.isInteractive()) {
+                        mAnimator.mBulkUpdateParams |= SET_TURN_ON_SCREEN;
+                    }
+                }
+            }
+            if (hasSurface()) {
+                w.mToken.hasVisible = true;
+            }
+        } else {
+            if (DEBUG_ANIM && isAnimationSet()) {
+                Slog.v(TAG, "prepareSurface: No changes in animation for " + this);
+            }
+            displayed = true;
+        }
+
+        if (w.getOrientationChanging()) {
+            if (!w.isDrawnLw()) {
+                mAnimator.mBulkUpdateParams &= ~SET_ORIENTATION_CHANGE_COMPLETE;
+                mAnimator.mLastWindowFreezeSource = w;
+                if (DEBUG_ORIENTATION) Slog.v(TAG,
+                        "Orientation continue waiting for draw in " + w);
+            } else {
+                w.setOrientationChanging(false);
+                if (DEBUG_ORIENTATION) Slog.v(TAG, "Orientation change complete in " + w);
+            }
+        }
+
+        if (displayed) {
+            w.mToken.hasVisible = true;
+        }
+    }
+
+    void setTransparentRegionHintLocked(final Region region) {
+        if (mSurfaceController == null) {
+            Slog.w(TAG, "setTransparentRegionHint: null mSurface after mHasSurface true");
+            return;
+        }
+        mSurfaceController.setTransparentRegionHint(region);
+    }
+
+    void setWallpaperOffset(Point shownPosition) {
+        final LayoutParams attrs = mWin.getAttrs();
+        final int left = shownPosition.x - attrs.surfaceInsets.left;
+        final int top = shownPosition.y - attrs.surfaceInsets.top;
+
+        try {
+            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setWallpaperOffset");
+            mService.openSurfaceTransaction();
+            mSurfaceController.setPositionInTransaction(mWin.mFrame.left + left,
+                    mWin.mFrame.top + top, false);
+            applyCrop(null, null, false);
+        } catch (RuntimeException e) {
+            Slog.w(TAG, "Error positioning surface of " + mWin
+                    + " pos=(" + left + "," + top + ")", e);
+        } finally {
+            mService.closeSurfaceTransaction();
+            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
+                    "<<< CLOSE TRANSACTION setWallpaperOffset");
+        }
+    }
+
+    /**
+     * Try to change the pixel format without recreating the surface. This
+     * will be common in the case of changing from PixelFormat.OPAQUE to
+     * PixelFormat.TRANSLUCENT in the hardware-accelerated case as both
+     * requested formats resolve to the same underlying SurfaceControl format
+     * @return True if format was succesfully changed, false otherwise
+     */
+    boolean tryChangeFormatInPlaceLocked() {
+        if (mSurfaceController == null) {
+            return false;
+        }
+        final LayoutParams attrs = mWin.getAttrs();
+        final boolean isHwAccelerated = (attrs.flags & FLAG_HARDWARE_ACCELERATED) != 0;
+        final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : attrs.format;
+        if (format == mSurfaceFormat) {
+            setOpaqueLocked(!PixelFormat.formatHasAlpha(attrs.format));
+            return true;
+        }
+        return false;
+    }
+
+    void setOpaqueLocked(boolean isOpaque) {
+        if (mSurfaceController == null) {
+            return;
+        }
+        mSurfaceController.setOpaque(isOpaque);
+    }
+
+    void setSecureLocked(boolean isSecure) {
+        if (mSurfaceController == null) {
+            return;
+        }
+        mSurfaceController.setSecure(isSecure);
+    }
+
+    /**
+     * Have the surface flinger show a surface, robustly dealing with
+     * error conditions.  In particular, if there is not enough memory
+     * to show the surface, then we will try to get rid of other surfaces
+     * in order to succeed.
+     *
+     * @return Returns true if the surface was successfully shown.
+     */
+    private boolean showSurfaceRobustlyLocked() {
+        if (mWin.getWindowConfiguration().windowsAreScaleable()) {
+            mSurfaceController.forceScaleableInTransaction(true);
+        }
+
+        boolean shown = mSurfaceController.showRobustlyInTransaction();
+        if (!shown)
+            return false;
+
+        return true;
+    }
+
+    void applyEnterAnimationLocked() {
+        // If we are the new part of a window replacement transition and we have requested
+        // not to animate, we instead want to make it seamless, so we don't want to apply
+        // an enter transition.
+        if (mWin.mSkipEnterAnimationForSeamlessReplacement) {
+            return;
+        }
+        final int transit;
+        if (mEnterAnimationPending) {
+            mEnterAnimationPending = false;
+            transit = WindowManagerPolicy.TRANSIT_ENTER;
+        } else {
+            transit = WindowManagerPolicy.TRANSIT_SHOW;
+        }
+        applyAnimationLocked(transit, true);
+        //TODO (multidisplay): Magnification is supported only for the default display.
+        if (mService.mAccessibilityController != null
+                && mWin.getDisplayId() == DEFAULT_DISPLAY) {
+            mService.mAccessibilityController.onWindowTransitionLocked(mWin, transit);
+        }
+    }
+
+    /**
+     * Choose the correct animation and set it to the passed WindowState.
+     * @param transit If AppTransition.TRANSIT_PREVIEW_DONE and the app window has been drawn
+     *      then the animation will be app_starting_exit. Any other value loads the animation from
+     *      the switch statement below.
+     * @param isEntrance The animation type the last time this was called. Used to keep from
+     *      loading the same animation twice.
+     * @return true if an animation has been loaded.
+     */
+    boolean applyAnimationLocked(int transit, boolean isEntrance) {
+        if (mLocalAnimating && mAnimationIsEntrance == isEntrance) {
+            // If we are trying to apply an animation, but already running
+            // an animation of the same type, then just leave that one alone.
+            return true;
+        }
+
+        // Only apply an animation if the display isn't frozen.  If it is
+        // frozen, there is no reason to animate and it can cause strange
+        // artifacts when we unfreeze the display if some different animation
+        // is running.
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WSA#applyAnimationLocked");
+        if (mWin.mToken.okToAnimate()) {
+            int anim = mPolicy.selectAnimationLw(mWin, transit);
+            int attr = -1;
+            Animation a = null;
+            if (anim != 0) {
+                a = anim != -1 ? AnimationUtils.loadAnimation(mContext, anim) : null;
+            } else {
+                switch (transit) {
+                    case WindowManagerPolicy.TRANSIT_ENTER:
+                        attr = com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation;
+                        break;
+                    case WindowManagerPolicy.TRANSIT_EXIT:
+                        attr = com.android.internal.R.styleable.WindowAnimation_windowExitAnimation;
+                        break;
+                    case WindowManagerPolicy.TRANSIT_SHOW:
+                        attr = com.android.internal.R.styleable.WindowAnimation_windowShowAnimation;
+                        break;
+                    case WindowManagerPolicy.TRANSIT_HIDE:
+                        attr = com.android.internal.R.styleable.WindowAnimation_windowHideAnimation;
+                        break;
+                }
+                if (attr >= 0) {
+                    a = mService.mAppTransition.loadAnimationAttr(mWin.mAttrs, attr);
+                }
+            }
+            if (DEBUG_ANIM) Slog.v(TAG,
+                    "applyAnimation: win=" + this
+                    + " anim=" + anim + " attr=0x" + Integer.toHexString(attr)
+                    + " a=" + a
+                    + " transit=" + transit
+                    + " isEntrance=" + isEntrance + " Callers " + Debug.getCallers(3));
+            if (a != null) {
+                if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + this);
+                setAnimation(a);
+                mAnimationIsEntrance = isEntrance;
+            }
+        } else {
+            clearAnimation();
+        }
+        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+
+        if (mWin.mAttrs.type == TYPE_INPUT_METHOD) {
+            mWin.getDisplayContent().adjustForImeIfNeeded();
+            if (isEntrance) {
+                mWin.setDisplayLayoutNeeded();
+                mService.mWindowPlacerLocked.requestTraversal();
+            }
+        }
+        return mAnimation != null;
+    }
+
+    private void applyFadeoutDuringKeyguardExitAnimation() {
+        long startTime = mAnimation.getStartTime();
+        long duration = mAnimation.getDuration();
+        long elapsed = mLastAnimationTime - startTime;
+        long fadeDuration = duration - elapsed;
+        if (fadeDuration <= 0) {
+            // Never mind, this would be no visible animation, so abort the animation change.
+            return;
+        }
+        AnimationSet newAnimation = new AnimationSet(false /* shareInterpolator */);
+        newAnimation.setDuration(duration);
+        newAnimation.setStartTime(startTime);
+        newAnimation.addAnimation(mAnimation);
+        Animation fadeOut = AnimationUtils.loadAnimation(
+                mContext, com.android.internal.R.anim.app_starting_exit);
+        fadeOut.setDuration(fadeDuration);
+        fadeOut.setStartOffset(elapsed);
+        newAnimation.addAnimation(fadeOut);
+        newAnimation.initialize(mWin.mFrame.width(), mWin.mFrame.height(), mAnimDx, mAnimDy);
+        mAnimation = newAnimation;
+    }
+
+    void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        mLastClipRect.writeToProto(proto, LAST_CLIP_RECT);
+        if (mSurfaceController != null) {
+            mSurfaceController.writeToProto(proto, SURFACE);
+        }
+        proto.end(token);
+    }
+
+    public void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+        if (mAnimating || mLocalAnimating || mAnimationIsEntrance
+                || mAnimation != null) {
+            pw.print(prefix); pw.print("mAnimating="); pw.print(mAnimating);
+                    pw.print(" mLocalAnimating="); pw.print(mLocalAnimating);
+                    pw.print(" mAnimationIsEntrance="); pw.print(mAnimationIsEntrance);
+                    pw.print(" mAnimation="); pw.print(mAnimation);
+                    pw.print(" mStackClip="); pw.println(mStackClip);
+        }
+        if (mHasTransformation || mHasLocalTransformation) {
+            pw.print(prefix); pw.print("XForm: has=");
+                    pw.print(mHasTransformation);
+                    pw.print(" hasLocal="); pw.print(mHasLocalTransformation);
+                    pw.print(" "); mTransformation.printShortString(pw);
+                    pw.println();
+        }
+        if (mSurfaceController != null) {
+            mSurfaceController.dump(pw, prefix, dumpAll);
+        }
+        if (dumpAll) {
+            pw.print(prefix); pw.print("mDrawState="); pw.print(drawStateToString());
+            pw.print(prefix); pw.print(" mLastHidden="); pw.println(mLastHidden);
+            pw.print(prefix); pw.print("mSystemDecorRect="); mSystemDecorRect.printShortString(pw);
+            pw.print(" last="); mLastSystemDecorRect.printShortString(pw);
+            pw.print(" mHasClipRect="); pw.print(mHasClipRect);
+            pw.print(" mLastClipRect="); mLastClipRect.printShortString(pw);
+
+            if (!mLastFinalClipRect.isEmpty()) {
+                pw.print(" mLastFinalClipRect="); mLastFinalClipRect.printShortString(pw);
+            }
+            pw.println();
+        }
+
+        if (mPendingDestroySurface != null) {
+            pw.print(prefix); pw.print("mPendingDestroySurface=");
+                    pw.println(mPendingDestroySurface);
+        }
+        if (mSurfaceResized || mSurfaceDestroyDeferred) {
+            pw.print(prefix); pw.print("mSurfaceResized="); pw.print(mSurfaceResized);
+                    pw.print(" mSurfaceDestroyDeferred="); pw.println(mSurfaceDestroyDeferred);
+        }
+        if (mShownAlpha != 1 || mAlpha != 1 || mLastAlpha != 1) {
+            pw.print(prefix); pw.print("mShownAlpha="); pw.print(mShownAlpha);
+                    pw.print(" mAlpha="); pw.print(mAlpha);
+                    pw.print(" mLastAlpha="); pw.println(mLastAlpha);
+        }
+        if (mHaveMatrix || mWin.mGlobalScale != 1) {
+            pw.print(prefix); pw.print("mGlobalScale="); pw.print(mWin.mGlobalScale);
+                    pw.print(" mDsDx="); pw.print(mDsDx);
+                    pw.print(" mDtDx="); pw.print(mDtDx);
+                    pw.print(" mDtDy="); pw.print(mDtDy);
+                    pw.print(" mDsDy="); pw.println(mDsDy);
+        }
+        if (mAnimationStartDelayed) {
+            pw.print(prefix); pw.print("mAnimationStartDelayed="); pw.print(mAnimationStartDelayed);
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer("WindowStateAnimator{");
+        sb.append(Integer.toHexString(System.identityHashCode(this)));
+        sb.append(' ');
+        sb.append(mWin.mAttrs.getTitle());
+        sb.append('}');
+        return sb.toString();
+    }
+
+    void reclaimSomeSurfaceMemory(String operation, boolean secure) {
+        mService.mRoot.reclaimSomeSurfaceMemory(this, operation, secure);
+    }
+
+    boolean getShown() {
+        if (mSurfaceController != null) {
+            return mSurfaceController.getShown();
+        }
+        return false;
+    }
+
+    void destroySurface() {
+        try {
+            if (mSurfaceController != null) {
+                mSurfaceController.destroyInTransaction();
+            }
+        } catch (RuntimeException e) {
+            Slog.w(TAG, "Exception thrown when destroying surface " + this
+                    + " surface " + mSurfaceController + " session " + mSession + ": " + e);
+        } finally {
+            mWin.setHasSurface(false);
+            mSurfaceController = null;
+            mDrawState = NO_SURFACE;
+        }
+    }
+
+    void setMoveAnimation(int left, int top) {
+        final Animation a = AnimationUtils.loadAnimation(mContext,
+                com.android.internal.R.anim.window_move_from_decor);
+        setAnimation(a);
+        mAnimDx = mWin.mLastFrame.left - left;
+        mAnimDy = mWin.mLastFrame.top - top;
+        mAnimateMove = true;
+    }
+
+    void deferTransactionUntilParentFrame(long frameNumber) {
+        if (!mWin.isChildWindow()) {
+            return;
+        }
+        mSurfaceController.deferTransactionUntil(
+                mWin.getParentWindow().mWinAnimator.mSurfaceController.getHandle(), frameNumber);
+    }
+
+    /**
+     * Sometimes we need to synchronize the first frame of animation with some external event.
+     * To achieve this, we prolong the start of the animation and keep producing the first frame of
+     * the animation.
+     */
+    private long getAnimationFrameTime(Animation animation, long currentTime) {
+        if (mAnimationStartDelayed) {
+            animation.setStartTime(currentTime);
+            return currentTime + 1;
+        }
+        return currentTime;
+    }
+
+    void startDelayingAnimationStart() {
+        mAnimationStartDelayed = true;
+    }
+
+    void endDelayingAnimationStart() {
+        mAnimationStartDelayed = false;
+    }
+
+    void seamlesslyRotateWindow(int oldRotation, int newRotation) {
+        final WindowState w = mWin;
+        if (!w.isVisibleNow() || w.mIsWallpaper) {
+            return;
+        }
+
+        final Rect cropRect = mService.mTmpRect;
+        final Rect displayRect = mService.mTmpRect2;
+        final RectF frameRect = mService.mTmpRectF;
+        final Matrix transform = mService.mTmpTransform;
+
+        final float x = w.mFrame.left;
+        final float y = w.mFrame.top;
+        final float width = w.mFrame.width();
+        final float height = w.mFrame.height();
+
+        mService.getDefaultDisplayContentLocked().getLogicalDisplayRect(displayRect);
+        final float displayWidth = displayRect.width();
+        final float displayHeight = displayRect.height();
+
+        // Compute a transform matrix to undo the coordinate space transformation,
+        // and present the window at the same physical position it previously occupied.
+        final int deltaRotation = DisplayContent.deltaRotation(newRotation, oldRotation);
+        DisplayContent.createRotationMatrix(deltaRotation, x, y, displayWidth, displayHeight,
+                transform);
+
+        // We just need to apply a rotation matrix to the window. For example
+        // if we have a portrait window and rotate to landscape, the window is still portrait
+        // and now extends off the bottom of the screen (and only halfway across). Essentially we
+        // apply a transform to display the current buffer at it's old position
+        // (in the new coordinate space). We then freeze layer updates until the resize
+        // occurs, at which point we undo, them.
+        mService.markForSeamlessRotation(w, true);
+        transform.getValues(mService.mTmpFloats);
+
+        float DsDx = mService.mTmpFloats[Matrix.MSCALE_X];
+        float DtDx = mService.mTmpFloats[Matrix.MSKEW_Y];
+        float DtDy = mService.mTmpFloats[Matrix.MSKEW_X];
+        float DsDy = mService.mTmpFloats[Matrix.MSCALE_Y];
+        float nx = mService.mTmpFloats[Matrix.MTRANS_X];
+        float ny = mService.mTmpFloats[Matrix.MTRANS_Y];
+        mSurfaceController.setPositionInTransaction(nx, ny, false);
+        mSurfaceController.setMatrixInTransaction(DsDx * w.mHScale,
+                DtDx * w.mVScale,
+                DtDy * w.mHScale,
+                DsDy * w.mVScale, false);
+    }
+
+    void enableSurfaceTrace(FileDescriptor fd) {
+        if (mSurfaceController != null) {
+            mSurfaceController.installRemoteTrace(fd);
+        }
+    }
+
+    void disableSurfaceTrace() {
+        if (mSurfaceController != null) {
+            try {
+                mSurfaceController.removeRemoteTrace();
+            } catch (ClassCastException e) {
+                Slog.e(TAG, "Disable surface trace for " + this + " but its not enabled");
+            }
+        }
+    }
+
+    /** The force-scaled state for a given window can persist past
+     * the state for it's stack as the windows complete resizing
+     * independently of one another.
+     */
+    boolean isForceScaled() {
+        final Task task = mWin.getTask();
+        if (task != null && task.mStack.isForceScaled()) {
+            return true;
+        }
+        return mForceScaleUntilResize;
+    }
+
+    void detachChildren() {
+        if (mSurfaceController != null) {
+            mSurfaceController.detachChildren();
+        }
+    }
+}
diff --git a/com/android/server/wm/WindowSurfaceController.java b/com/android/server/wm/WindowSurfaceController.java
new file mode 100644
index 0000000..2e1e3f7
--- /dev/null
+++ b/com/android/server/wm/WindowSurfaceController.java
@@ -0,0 +1,826 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.Surface.SCALING_MODE_SCALE_TO_WINDOW;
+
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.proto.WindowSurfaceControllerProto.LAYER;
+import static com.android.server.wm.proto.WindowSurfaceControllerProto.SHOWN;
+
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.IBinder;
+import android.os.Debug;
+import android.os.Trace;
+import android.util.proto.ProtoOutputStream;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.WindowContentFrameStats;
+import android.view.Surface.OutOfResourcesException;
+
+import android.util.Slog;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+class WindowSurfaceController {
+    static final String TAG = TAG_WITH_CLASS_NAME ? "WindowSurfaceController" : TAG_WM;
+
+    final WindowStateAnimator mAnimator;
+
+    private SurfaceControlWithBackground mSurfaceControl;
+
+    // Should only be set from within setShown().
+    private boolean mSurfaceShown = false;
+    private float mSurfaceX = 0;
+    private float mSurfaceY = 0;
+    private float mSurfaceW = 0;
+    private float mSurfaceH = 0;
+
+    // Initialize to the identity matrix.
+    private float mLastDsdx = 1;
+    private float mLastDtdx = 0;
+    private float mLastDsdy = 0;
+    private float mLastDtdy = 1;
+
+    private float mSurfaceAlpha = 0;
+
+    private int mSurfaceLayer = 0;
+
+    // Surface flinger doesn't support crop rectangles where width or height is non-positive.
+    // However, we need to somehow handle the situation where the cropping would completely hide
+    // the window. We achieve this by explicitly hiding the surface and not letting it be shown.
+    private boolean mHiddenForCrop = false;
+
+    // Initially a surface is hidden after just being created.
+    private boolean mHiddenForOtherReasons = true;
+    private final String title;
+
+    private final WindowManagerService mService;
+
+    private final int mWindowType;
+    private final Session mWindowSession;
+
+    public WindowSurfaceController(SurfaceSession s, String name, int w, int h, int format,
+            int flags, WindowStateAnimator animator, int windowType, int ownerUid) {
+        mAnimator = animator;
+
+        mSurfaceW = w;
+        mSurfaceH = h;
+
+        title = name;
+
+        mService = animator.mService;
+        final WindowState win = animator.mWin;
+        mWindowType = windowType;
+        mWindowSession = win.mSession;
+
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
+        mSurfaceControl = new SurfaceControlWithBackground(
+                s, name, w, h, format, flags, windowType, ownerUid, this);
+        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+
+        if (mService.mRoot.mSurfaceTraceEnabled) {
+            mSurfaceControl = new RemoteSurfaceTrace(
+                    mService.mRoot.mSurfaceTraceFd.getFileDescriptor(), mSurfaceControl, win);
+        }
+    }
+
+    void installRemoteTrace(FileDescriptor fd) {
+        mSurfaceControl = new RemoteSurfaceTrace(fd, mSurfaceControl, mAnimator.mWin);
+    }
+
+    void removeRemoteTrace() {
+        mSurfaceControl = new SurfaceControlWithBackground(mSurfaceControl);
+    }
+
+
+    private void logSurface(String msg, RuntimeException where) {
+        String str = "  SURFACE " + msg + ": " + title;
+        if (where != null) {
+            Slog.i(TAG, str, where);
+        } else {
+            Slog.i(TAG, str);
+        }
+    }
+
+    void reparentChildrenInTransaction(WindowSurfaceController other) {
+        if (SHOW_TRANSACTIONS) Slog.i(TAG, "REPARENT from: " + this + " to: " + other);
+        if ((mSurfaceControl != null) && (other.mSurfaceControl != null)) {
+            mSurfaceControl.reparentChildren(other.getHandle());
+        }
+    }
+
+    void detachChildren() {
+        if (SHOW_TRANSACTIONS) Slog.i(TAG, "SEVER CHILDREN");
+        if (mSurfaceControl != null) {
+            mSurfaceControl.detachChildren();
+        }
+    }
+
+    void hideInTransaction(String reason) {
+        if (SHOW_TRANSACTIONS) logSurface("HIDE ( " + reason + " )", null);
+        mHiddenForOtherReasons = true;
+
+        mAnimator.destroyPreservedSurfaceLocked();
+        updateVisibility();
+    }
+
+    private void hideSurface() {
+        if (mSurfaceControl == null) {
+            return;
+        }
+        setShown(false);
+        try {
+            mSurfaceControl.hide();
+        } catch (RuntimeException e) {
+            Slog.w(TAG, "Exception hiding surface in " + this);
+        }
+    }
+
+    void destroyInTransaction() {
+        if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) {
+            Slog.i(TAG, "Destroying surface " + this + " called by " + Debug.getCallers(8));
+        }
+        try {
+            if (mSurfaceControl != null) {
+                mSurfaceControl.destroy();
+            }
+        } catch (RuntimeException e) {
+            Slog.w(TAG, "Error destroying surface in: " + this, e);
+        } finally {
+            setShown(false);
+            mSurfaceControl = null;
+        }
+    }
+
+    void disconnectInTransaction() {
+        if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) {
+            Slog.i(TAG, "Disconnecting client: " + this);
+        }
+
+        try {
+            if (mSurfaceControl != null) {
+                mSurfaceControl.disconnect();
+            }
+        } catch (RuntimeException e) {
+            Slog.w(TAG, "Error disconnecting surface in: " + this, e);
+        }
+    }
+
+    void setCropInTransaction(Rect clipRect, boolean recoveringMemory) {
+        if (SHOW_TRANSACTIONS) logSurface(
+                "CROP " + clipRect.toShortString(), null);
+        try {
+            if (clipRect.width() > 0 && clipRect.height() > 0) {
+                mSurfaceControl.setWindowCrop(clipRect);
+                mHiddenForCrop = false;
+                updateVisibility();
+            } else {
+                mHiddenForCrop = true;
+                mAnimator.destroyPreservedSurfaceLocked();
+                updateVisibility();
+            }
+        } catch (RuntimeException e) {
+            Slog.w(TAG, "Error setting crop surface of " + this
+                    + " crop=" + clipRect.toShortString(), e);
+            if (!recoveringMemory) {
+                mAnimator.reclaimSomeSurfaceMemory("crop", true);
+            }
+        }
+    }
+
+    void clearCropInTransaction(boolean recoveringMemory) {
+        if (SHOW_TRANSACTIONS) logSurface(
+                "CLEAR CROP", null);
+        try {
+            Rect clipRect = new Rect(0, 0, -1, -1);
+            mSurfaceControl.setWindowCrop(clipRect);
+        } catch (RuntimeException e) {
+            Slog.w(TAG, "Error setting clearing crop of " + this, e);
+            if (!recoveringMemory) {
+                mAnimator.reclaimSomeSurfaceMemory("crop", true);
+            }
+        }
+    }
+
+    void setFinalCropInTransaction(Rect clipRect) {
+        if (SHOW_TRANSACTIONS) logSurface(
+                "FINAL CROP " + clipRect.toShortString(), null);
+        try {
+            mSurfaceControl.setFinalCrop(clipRect);
+        } catch (RuntimeException e) {
+            Slog.w(TAG, "Error disconnecting surface in: " + this, e);
+        }
+    }
+
+    void setLayer(int layer) {
+        if (mSurfaceControl != null) {
+            mService.openSurfaceTransaction();
+            try {
+                if (mAnimator.mWin.usesRelativeZOrdering()) {
+                    mSurfaceControl.setRelativeLayer(
+                            mAnimator.mWin.getParentWindow()
+                            .mWinAnimator.mSurfaceController.mSurfaceControl.getHandle(),
+                            -1);
+                } else {
+                    mSurfaceLayer = layer;
+                    mSurfaceControl.setLayer(layer);
+                }
+            } finally {
+                mService.closeSurfaceTransaction();
+            }
+        }
+    }
+
+    void setLayerStackInTransaction(int layerStack) {
+        if (mSurfaceControl != null) {
+            mSurfaceControl.setLayerStack(layerStack);
+        }
+    }
+
+    void setPositionInTransaction(float left, float top, boolean recoveringMemory) {
+        final boolean surfaceMoved = mSurfaceX != left || mSurfaceY != top;
+        if (surfaceMoved) {
+            mSurfaceX = left;
+            mSurfaceY = top;
+
+            try {
+                if (SHOW_TRANSACTIONS) logSurface(
+                        "POS (setPositionInTransaction) @ (" + left + "," + top + ")", null);
+
+                mSurfaceControl.setPosition(left, top);
+            } catch (RuntimeException e) {
+                Slog.w(TAG, "Error positioning surface of " + this
+                        + " pos=(" + left + "," + top + ")", e);
+                if (!recoveringMemory) {
+                    mAnimator.reclaimSomeSurfaceMemory("position", true);
+                }
+            }
+        }
+    }
+
+    void setGeometryAppliesWithResizeInTransaction(boolean recoveringMemory) {
+        mSurfaceControl.setGeometryAppliesWithResize();
+    }
+
+    void setMatrixInTransaction(float dsdx, float dtdx, float dtdy, float dsdy,
+            boolean recoveringMemory) {
+        final boolean matrixChanged = mLastDsdx != dsdx || mLastDtdx != dtdx ||
+                                      mLastDtdy != dtdy || mLastDsdy != dsdy;
+        if (!matrixChanged) {
+            return;
+        }
+
+        mLastDsdx = dsdx;
+        mLastDtdx = dtdx;
+        mLastDtdy = dtdy;
+        mLastDsdy = dsdy;
+
+        try {
+            if (SHOW_TRANSACTIONS) logSurface(
+                    "MATRIX [" + dsdx + "," + dtdx + "," + dtdy + "," + dsdy + "]", null);
+            mSurfaceControl.setMatrix(
+                    dsdx, dtdx, dtdy, dsdy);
+        } catch (RuntimeException e) {
+            // If something goes wrong with the surface (such
+            // as running out of memory), don't take down the
+            // entire system.
+            Slog.e(TAG, "Error setting matrix on surface surface" + title
+                    + " MATRIX [" + dsdx + "," + dtdx + "," + dtdy + "," + dsdy + "]", null);
+            if (!recoveringMemory) {
+                mAnimator.reclaimSomeSurfaceMemory("matrix", true);
+            }
+        }
+    }
+
+    boolean setSizeInTransaction(int width, int height, boolean recoveringMemory) {
+        final boolean surfaceResized = mSurfaceW != width || mSurfaceH != height;
+        if (surfaceResized) {
+            mSurfaceW = width;
+            mSurfaceH = height;
+
+            try {
+                if (SHOW_TRANSACTIONS) logSurface(
+                        "SIZE " + width + "x" + height, null);
+                mSurfaceControl.setSize(width, height);
+            } catch (RuntimeException e) {
+                // If something goes wrong with the surface (such
+                // as running out of memory), don't take down the
+                // entire system.
+                Slog.e(TAG, "Error resizing surface of " + title
+                        + " size=(" + width + "x" + height + ")", e);
+                if (!recoveringMemory) {
+                    mAnimator.reclaimSomeSurfaceMemory("size", true);
+                }
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    boolean prepareToShowInTransaction(float alpha,
+            float dsdx, float dtdx, float dsdy,
+            float dtdy, boolean recoveringMemory) {
+        if (mSurfaceControl != null) {
+            try {
+                mSurfaceAlpha = alpha;
+                mSurfaceControl.setAlpha(alpha);
+                mLastDsdx = dsdx;
+                mLastDtdx = dtdx;
+                mLastDsdy = dsdy;
+                mLastDtdy = dtdy;
+                mSurfaceControl.setMatrix(
+                        dsdx, dtdx, dsdy, dtdy);
+            } catch (RuntimeException e) {
+                Slog.w(TAG, "Error updating surface in " + title, e);
+                if (!recoveringMemory) {
+                    mAnimator.reclaimSomeSurfaceMemory("update", true);
+                }
+                return false;
+            }
+        }
+        return true;
+    }
+
+    void setTransparentRegionHint(final Region region) {
+        if (mSurfaceControl == null) {
+            Slog.w(TAG, "setTransparentRegionHint: null mSurface after mHasSurface true");
+            return;
+        }
+        if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setTransparentRegion");
+        mService.openSurfaceTransaction();
+        try {
+            mSurfaceControl.setTransparentRegionHint(region);
+        } finally {
+            mService.closeSurfaceTransaction();
+            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
+                    "<<< CLOSE TRANSACTION setTransparentRegion");
+        }
+    }
+
+    void setOpaque(boolean isOpaque) {
+        if (SHOW_TRANSACTIONS) logSurface("isOpaque=" + isOpaque,
+                null);
+
+        if (mSurfaceControl == null) {
+            return;
+        }
+        if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setOpaqueLocked");
+        mService.openSurfaceTransaction();
+        try {
+            mSurfaceControl.setOpaque(isOpaque);
+        } finally {
+            mService.closeSurfaceTransaction();
+            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION setOpaqueLocked");
+        }
+    }
+
+    void setSecure(boolean isSecure) {
+        if (SHOW_TRANSACTIONS) logSurface("isSecure=" + isSecure,
+                null);
+
+        if (mSurfaceControl == null) {
+            return;
+        }
+        if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setSecureLocked");
+        mService.openSurfaceTransaction();
+        try {
+            mSurfaceControl.setSecure(isSecure);
+        } finally {
+            mService.closeSurfaceTransaction();
+            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION setSecureLocked");
+        }
+    }
+
+    void getContainerRect(Rect rect) {
+        mAnimator.getContainerRect(rect);
+    }
+
+    boolean showRobustlyInTransaction() {
+        if (SHOW_TRANSACTIONS) logSurface(
+                "SHOW (performLayout)", null);
+        if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + this
+                + " during relayout");
+        mHiddenForOtherReasons = false;
+        return updateVisibility();
+    }
+
+    private boolean updateVisibility() {
+        if (mHiddenForCrop || mHiddenForOtherReasons) {
+            if (mSurfaceShown) {
+                hideSurface();
+            }
+            return false;
+        } else {
+            if (!mSurfaceShown) {
+                return showSurface();
+            } else {
+                return true;
+            }
+        }
+    }
+
+    private boolean showSurface() {
+        try {
+            setShown(true);
+            mSurfaceControl.show();
+            return true;
+        } catch (RuntimeException e) {
+            Slog.w(TAG, "Failure showing surface " + mSurfaceControl + " in " + this, e);
+        }
+
+        mAnimator.reclaimSomeSurfaceMemory("show", true);
+
+        return false;
+    }
+
+    void deferTransactionUntil(IBinder handle, long frame) {
+        // TODO: Logging
+        mSurfaceControl.deferTransactionUntil(handle, frame);
+    }
+
+    void forceScaleableInTransaction(boolean force) {
+        // -1 means we don't override the default or client specified
+        // scaling mode.
+        int scalingMode = force ? SCALING_MODE_SCALE_TO_WINDOW : -1;
+        mSurfaceControl.setOverrideScalingMode(scalingMode);
+    }
+
+    boolean clearWindowContentFrameStats() {
+        if (mSurfaceControl == null) {
+            return false;
+        }
+        return mSurfaceControl.clearContentFrameStats();
+    }
+
+    boolean getWindowContentFrameStats(WindowContentFrameStats outStats) {
+        if (mSurfaceControl == null) {
+            return false;
+        }
+        return mSurfaceControl.getContentFrameStats(outStats);
+    }
+
+
+    boolean hasSurface() {
+        return mSurfaceControl != null;
+    }
+
+    IBinder getHandle() {
+        if (mSurfaceControl == null) {
+            return null;
+        }
+        return mSurfaceControl.getHandle();
+    }
+
+    void getSurface(Surface outSurface) {
+        outSurface.copyFrom(mSurfaceControl);
+    }
+
+    int getLayer() {
+        return mSurfaceLayer;
+    }
+
+    boolean getShown() {
+        return mSurfaceShown;
+    }
+
+    void setShown(boolean surfaceShown) {
+        mSurfaceShown = surfaceShown;
+
+        mService.updateNonSystemOverlayWindowsVisibilityIfNeeded(mAnimator.mWin, surfaceShown);
+
+        if (mWindowSession != null) {
+            mWindowSession.onWindowSurfaceVisibilityChanged(this, mSurfaceShown, mWindowType);
+        }
+    }
+
+    float getX() {
+        return mSurfaceX;
+    }
+
+    float getY() {
+        return mSurfaceY;
+    }
+
+    float getWidth() {
+        return mSurfaceW;
+    }
+
+    float getHeight() {
+        return mSurfaceH;
+    }
+
+    void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(SHOWN, mSurfaceShown);
+        proto.write(LAYER, mSurfaceLayer);
+        proto.end(token);
+    }
+
+    public void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+        if (dumpAll) {
+            pw.print(prefix); pw.print("mSurface="); pw.println(mSurfaceControl);
+        }
+        pw.print(prefix); pw.print("Surface: shown="); pw.print(mSurfaceShown);
+        pw.print(" layer="); pw.print(mSurfaceLayer);
+        pw.print(" alpha="); pw.print(mSurfaceAlpha);
+        pw.print(" rect=("); pw.print(mSurfaceX);
+        pw.print(","); pw.print(mSurfaceY);
+        pw.print(") "); pw.print(mSurfaceW);
+        pw.print(" x "); pw.print(mSurfaceH);
+        pw.print(" transform=("); pw.print(mLastDsdx); pw.print(", ");
+        pw.print(mLastDtdx); pw.print(", "); pw.print(mLastDsdy);
+        pw.print(", "); pw.print(mLastDtdy); pw.println(")");
+    }
+
+    @Override
+    public String toString() {
+        return mSurfaceControl.toString();
+    }
+
+    static class SurfaceTrace extends SurfaceControl {
+        private final static String SURFACE_TAG = TAG_WITH_CLASS_NAME ? "SurfaceTrace" : TAG_WM;
+        private final static boolean LOG_SURFACE_TRACE = DEBUG_SURFACE_TRACE;
+        final static ArrayList<SurfaceTrace> sSurfaces = new ArrayList<SurfaceTrace>();
+
+        private float mSurfaceTraceAlpha = 0;
+        private int mLayer;
+        private final PointF mPosition = new PointF();
+        private final Point mSize = new Point();
+        private final Rect mWindowCrop = new Rect();
+        private final Rect mFinalCrop = new Rect();
+        private boolean mShown = false;
+        private int mLayerStack;
+        private boolean mIsOpaque;
+        private float mDsdx, mDtdx, mDsdy, mDtdy;
+        private final String mName;
+
+        public SurfaceTrace(SurfaceSession s, String name, int w, int h, int format, int flags,
+                        int windowType, int ownerUid)
+                    throws OutOfResourcesException {
+            super(s, name, w, h, format, flags, windowType, ownerUid);
+            mName = name != null ? name : "Not named";
+            mSize.set(w, h);
+            if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "ctor: " + this + ". Called by "
+                    + Debug.getCallers(3));
+            synchronized (sSurfaces) {
+                sSurfaces.add(0, this);
+            }
+        }
+
+        public SurfaceTrace(SurfaceSession s,
+                        String name, int w, int h, int format, int flags) {
+            super(s, name, w, h, format, flags);
+            mName = name != null ? name : "Not named";
+            mSize.set(w, h);
+            if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "ctor: " + this + ". Called by "
+                    + Debug.getCallers(3));
+            synchronized (sSurfaces) {
+                sSurfaces.add(0, this);
+            }
+        }
+
+        @Override
+        public void setAlpha(float alpha) {
+            if (mSurfaceTraceAlpha != alpha) {
+                if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setAlpha(" + alpha + "): OLD:" + this +
+                        ". Called by " + Debug.getCallers(3));
+                mSurfaceTraceAlpha = alpha;
+            }
+            super.setAlpha(alpha);
+        }
+
+        @Override
+        public void setLayer(int zorder) {
+            if (zorder != mLayer) {
+                if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setLayer(" + zorder + "): OLD:" + this
+                        + ". Called by " + Debug.getCallers(3));
+                mLayer = zorder;
+            }
+            super.setLayer(zorder);
+
+            synchronized (sSurfaces) {
+                sSurfaces.remove(this);
+                int i;
+                for (i = sSurfaces.size() - 1; i >= 0; i--) {
+                    SurfaceTrace s = sSurfaces.get(i);
+                    if (s.mLayer < zorder) {
+                        break;
+                    }
+                }
+                sSurfaces.add(i + 1, this);
+            }
+        }
+
+        @Override
+        public void setPosition(float x, float y) {
+            if (x != mPosition.x || y != mPosition.y) {
+                if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setPosition(" + x + "," + y + "): OLD:"
+                        + this + ". Called by " + Debug.getCallers(3));
+                mPosition.set(x, y);
+            }
+            super.setPosition(x, y);
+        }
+
+        @Override
+        public void setGeometryAppliesWithResize() {
+            if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setGeometryAppliesWithResize(): OLD: "
+                    + this + ". Called by" + Debug.getCallers(3));
+            super.setGeometryAppliesWithResize();
+        }
+
+        @Override
+        public void setSize(int w, int h) {
+            if (w != mSize.x || h != mSize.y) {
+                if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setSize(" + w + "," + h + "): OLD:"
+                        + this + ". Called by " + Debug.getCallers(3));
+                mSize.set(w, h);
+            }
+            super.setSize(w, h);
+        }
+
+        @Override
+        public void setWindowCrop(Rect crop) {
+            if (crop != null) {
+                if (!crop.equals(mWindowCrop)) {
+                    if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setWindowCrop("
+                            + crop.toShortString() + "): OLD:" + this + ". Called by "
+                            + Debug.getCallers(3));
+                    mWindowCrop.set(crop);
+                }
+            }
+            super.setWindowCrop(crop);
+        }
+
+        @Override
+        public void setFinalCrop(Rect crop) {
+            if (crop != null) {
+                if (!crop.equals(mFinalCrop)) {
+                    if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setFinalCrop("
+                            + crop.toShortString() + "): OLD:" + this + ". Called by "
+                            + Debug.getCallers(3));
+                    mFinalCrop.set(crop);
+                }
+            }
+            super.setFinalCrop(crop);
+        }
+
+        @Override
+        public void setLayerStack(int layerStack) {
+            if (layerStack != mLayerStack) {
+                if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setLayerStack(" + layerStack + "): OLD:"
+                        + this + ". Called by " + Debug.getCallers(3));
+                mLayerStack = layerStack;
+            }
+            super.setLayerStack(layerStack);
+        }
+
+        @Override
+        public void setOpaque(boolean isOpaque) {
+            if (isOpaque != mIsOpaque) {
+                if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setOpaque(" + isOpaque + "): OLD:"
+                        + this + ". Called by " + Debug.getCallers(3));
+                mIsOpaque = isOpaque;
+            }
+            super.setOpaque(isOpaque);
+        }
+
+        @Override
+        public void setSecure(boolean isSecure) {
+            super.setSecure(isSecure);
+        }
+
+        @Override
+        public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
+            if (dsdx != mDsdx || dtdx != mDtdx || dsdy != mDsdy || dtdy != mDtdy) {
+                if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setMatrix(" + dsdx + "," + dtdx + ","
+                        + dsdy + "," + dtdy + "): OLD:" + this + ". Called by "
+                        + Debug.getCallers(3));
+                mDsdx = dsdx;
+                mDtdx = dtdx;
+                mDsdy = dsdy;
+                mDtdy = dtdy;
+            }
+            super.setMatrix(dsdx, dtdx, dsdy, dtdy);
+        }
+
+        @Override
+        public void hide() {
+            if (mShown) {
+                if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "hide: OLD:" + this + ". Called by "
+                        + Debug.getCallers(3));
+                mShown = false;
+            }
+            super.hide();
+        }
+
+        @Override
+        public void show() {
+            if (!mShown) {
+                if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "show: OLD:" + this + ". Called by "
+                        + Debug.getCallers(3));
+                mShown = true;
+            }
+            super.show();
+        }
+
+        @Override
+        public void destroy() {
+            super.destroy();
+            if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "destroy: " + this + ". Called by "
+                    + Debug.getCallers(3));
+            synchronized (sSurfaces) {
+                sSurfaces.remove(this);
+            }
+        }
+
+        @Override
+        public void release() {
+            super.release();
+            if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "release: " + this + ". Called by "
+                    + Debug.getCallers(3));
+            synchronized (sSurfaces) {
+                sSurfaces.remove(this);
+            }
+        }
+
+        @Override
+        public void setTransparentRegionHint(Region region) {
+            if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setTransparentRegionHint(" + region
+                    + "): OLD: " + this + " . Called by " + Debug.getCallers(3));
+            super.setTransparentRegionHint(region);
+        }
+
+        static void dumpAllSurfaces(PrintWriter pw, String header) {
+            synchronized (sSurfaces) {
+                final int N = sSurfaces.size();
+                if (N <= 0) {
+                    return;
+                }
+                if (header != null) {
+                    pw.println(header);
+                }
+                pw.println("WINDOW MANAGER SURFACES (dumpsys window surfaces)");
+                for (int i = 0; i < N; i++) {
+                    SurfaceTrace s = sSurfaces.get(i);
+                    pw.print("  Surface #"); pw.print(i); pw.print(": #");
+                            pw.print(Integer.toHexString(System.identityHashCode(s)));
+                            pw.print(" "); pw.println(s.mName);
+                    pw.print("    mLayerStack="); pw.print(s.mLayerStack);
+                            pw.print(" mLayer="); pw.println(s.mLayer);
+                    pw.print("    mShown="); pw.print(s.mShown); pw.print(" mAlpha=");
+                            pw.print(s.mSurfaceTraceAlpha); pw.print(" mIsOpaque=");
+                            pw.println(s.mIsOpaque);
+                    pw.print("    mPosition="); pw.print(s.mPosition.x); pw.print(",");
+                            pw.print(s.mPosition.y);
+                            pw.print(" mSize="); pw.print(s.mSize.x); pw.print("x");
+                            pw.println(s.mSize.y);
+                    pw.print("    mCrop="); s.mWindowCrop.printShortString(pw); pw.println();
+                    pw.print("    mFinalCrop="); s.mFinalCrop.printShortString(pw); pw.println();
+                    pw.print("    Transform: ("); pw.print(s.mDsdx); pw.print(", ");
+                            pw.print(s.mDtdx); pw.print(", "); pw.print(s.mDsdy);
+                            pw.print(", "); pw.print(s.mDtdy); pw.println(")");
+                }
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "Surface " + Integer.toHexString(System.identityHashCode(this)) + " "
+                    + mName + " (" + mLayerStack + "): shown=" + mShown + " layer=" + mLayer
+                    + " alpha=" + mSurfaceTraceAlpha + " " + mPosition.x + "," + mPosition.y
+                    + " " + mSize.x + "x" + mSize.y
+                    + " crop=" + mWindowCrop.toShortString()
+                    + " opaque=" + mIsOpaque
+                    + " (" + mDsdx + "," + mDtdx + "," + mDsdy + "," + mDtdy + ")";
+        }
+    }
+}
diff --git a/com/android/server/wm/WindowSurfacePlacer.java b/com/android/server/wm/WindowSurfacePlacer.java
new file mode 100644
index 0000000..88625d3
--- /dev/null
+++ b/com/android/server/wm/WindowSurfacePlacer.java
@@ -0,0 +1,779 @@
+
+package com.android.server.wm;
+
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.ActivityManagerInternal.APP_TRANSITION_SNAPSHOT;
+import static android.app.ActivityManagerInternal.APP_TRANSITION_SPLASH_SCREEN;
+import static android.app.ActivityManagerInternal.APP_TRANSITION_WINDOWS_DRAWN;
+import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
+import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
+import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_CLOSE;
+import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN;
+import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
+import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
+import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
+import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_GOING_AWAY;
+import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
+import static com.android.server.wm.AppTransition.TRANSIT_NONE;
+import static com.android.server.wm.AppTransition.TRANSIT_TASK_CLOSE;
+import static com.android.server.wm.AppTransition.TRANSIT_TASK_IN_PLACE;
+import static com.android.server.wm.AppTransition.TRANSIT_TASK_OPEN;
+import static com.android.server.wm.AppTransition.TRANSIT_TASK_TO_BACK;
+import static com.android.server.wm.AppTransition.TRANSIT_TASK_TO_FRONT;
+import static com.android.server.wm.AppTransition.TRANSIT_WALLPAPER_CLOSE;
+import static com.android.server.wm.AppTransition.TRANSIT_WALLPAPER_INTRA_CLOSE;
+import static com.android.server.wm.AppTransition.TRANSIT_WALLPAPER_INTRA_OPEN;
+import static com.android.server.wm.AppTransition.TRANSIT_WALLPAPER_OPEN;
+import static com.android.server.wm.AppTransition.isKeyguardGoingAwayTransit;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.H.NOTIFY_APP_TRANSITION_STARTING;
+import static com.android.server.wm.WindowManagerService.H.REPORT_WINDOWS_CHANGE;
+import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD;
+import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
+import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES;
+
+import android.content.res.Configuration;
+import android.graphics.GraphicBuffer;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.Debug;
+import android.os.Trace;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseIntArray;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.WindowManager.LayoutParams;
+import android.view.animation.Animation;
+
+import com.android.server.wm.WindowManagerService.H;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Positions windows and their surfaces.
+ *
+ * It sets positions of windows by calculating their frames and then applies this by positioning
+ * surfaces according to these frames. Z layer is still assigned withing WindowManagerService.
+ */
+class WindowSurfacePlacer {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowSurfacePlacer" : TAG_WM;
+    private final WindowManagerService mService;
+    private final WallpaperController mWallpaperControllerLocked;
+
+    private boolean mInLayout = false;
+
+    /** Only do a maximum of 6 repeated layouts. After that quit */
+    private int mLayoutRepeatCount;
+
+    static final int SET_UPDATE_ROTATION                = 1 << 0;
+    static final int SET_WALLPAPER_MAY_CHANGE           = 1 << 1;
+    static final int SET_FORCE_HIDING_CHANGED           = 1 << 2;
+    static final int SET_ORIENTATION_CHANGE_COMPLETE    = 1 << 3;
+    static final int SET_TURN_ON_SCREEN                 = 1 << 4;
+    static final int SET_WALLPAPER_ACTION_PENDING       = 1 << 5;
+
+    private final Rect mTmpStartRect = new Rect();
+    private final Rect mTmpContentRect = new Rect();
+
+    private boolean mTraversalScheduled;
+    private int mDeferDepth = 0;
+
+    private static final class LayerAndToken {
+        public int layer;
+        public AppWindowToken token;
+    }
+    private final LayerAndToken mTmpLayerAndToken = new LayerAndToken();
+
+    private final ArrayList<SurfaceControl> mPendingDestroyingSurfaces = new ArrayList<>();
+    private final SparseIntArray mTempTransitionReasons = new SparseIntArray();
+
+    private final Runnable mPerformSurfacePlacement;
+
+    public WindowSurfacePlacer(WindowManagerService service) {
+        mService = service;
+        mWallpaperControllerLocked = mService.mRoot.mWallpaperController;
+        mPerformSurfacePlacement = () -> {
+            synchronized (mService.mWindowMap) {
+                performSurfacePlacement();
+            }
+        };
+    }
+
+    /**
+     * See {@link WindowManagerService#deferSurfaceLayout()}
+     */
+    void deferLayout() {
+        mDeferDepth++;
+    }
+
+    /**
+     * See {@link WindowManagerService#continueSurfaceLayout()}
+     */
+    void continueLayout() {
+        mDeferDepth--;
+        if (mDeferDepth <= 0) {
+            performSurfacePlacement();
+        }
+    }
+
+    boolean isLayoutDeferred() {
+        return mDeferDepth > 0;
+    }
+
+    final void performSurfacePlacement() {
+        performSurfacePlacement(false /* force */);
+    }
+
+    final void performSurfacePlacement(boolean force) {
+        if (mDeferDepth > 0 && !force) {
+            return;
+        }
+        int loopCount = 6;
+        do {
+            mTraversalScheduled = false;
+            performSurfacePlacementLoop();
+            mService.mAnimationHandler.removeCallbacks(mPerformSurfacePlacement);
+            loopCount--;
+        } while (mTraversalScheduled && loopCount > 0);
+        mService.mRoot.mWallpaperActionPending = false;
+    }
+
+    private void performSurfacePlacementLoop() {
+        if (mInLayout) {
+            if (DEBUG) {
+                throw new RuntimeException("Recursive call!");
+            }
+            Slog.w(TAG, "performLayoutAndPlaceSurfacesLocked called while in layout. Callers="
+                    + Debug.getCallers(3));
+            return;
+        }
+
+        if (mService.mWaitingForConfig) {
+            // Our configuration has changed (most likely rotation), but we
+            // don't yet have the complete configuration to report to
+            // applications.  Don't do any window layout until we have it.
+            return;
+        }
+
+        if (!mService.mDisplayReady) {
+            // Not yet initialized, nothing to do.
+            return;
+        }
+
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "wmLayout");
+        mInLayout = true;
+
+        boolean recoveringMemory = false;
+        if (!mService.mForceRemoves.isEmpty()) {
+            recoveringMemory = true;
+            // Wait a little bit for things to settle down, and off we go.
+            while (!mService.mForceRemoves.isEmpty()) {
+                final WindowState ws = mService.mForceRemoves.remove(0);
+                Slog.i(TAG, "Force removing: " + ws);
+                ws.removeImmediately();
+            }
+            Slog.w(TAG, "Due to memory failure, waiting a bit for next layout");
+            Object tmp = new Object();
+            synchronized (tmp) {
+                try {
+                    tmp.wait(250);
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+
+        try {
+            mService.mRoot.performSurfacePlacement(recoveringMemory);
+
+            mInLayout = false;
+
+            if (mService.mRoot.isLayoutNeeded()) {
+                if (++mLayoutRepeatCount < 6) {
+                    requestTraversal();
+                } else {
+                    Slog.e(TAG, "Performed 6 layouts in a row. Skipping");
+                    mLayoutRepeatCount = 0;
+                }
+            } else {
+                mLayoutRepeatCount = 0;
+            }
+
+            if (mService.mWindowsChanged && !mService.mWindowChangeListeners.isEmpty()) {
+                mService.mH.removeMessages(REPORT_WINDOWS_CHANGE);
+                mService.mH.sendEmptyMessage(REPORT_WINDOWS_CHANGE);
+            }
+        } catch (RuntimeException e) {
+            mInLayout = false;
+            Slog.wtf(TAG, "Unhandled exception while laying out windows", e);
+        }
+
+        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+    }
+
+    void debugLayoutRepeats(final String msg, int pendingLayoutChanges) {
+        if (mLayoutRepeatCount >= LAYOUT_REPEAT_THRESHOLD) {
+            Slog.v(TAG, "Layouts looping: " + msg +
+                    ", mPendingLayoutChanges = 0x" + Integer.toHexString(pendingLayoutChanges));
+        }
+    }
+
+    boolean isInLayout() {
+        return mInLayout;
+    }
+
+    /**
+     * @return bitmap indicating if another pass through layout must be made.
+     */
+    int handleAppTransitionReadyLocked() {
+        int appsCount = mService.mOpeningApps.size();
+        if (!transitionGoodToGo(appsCount, mTempTransitionReasons)) {
+            return 0;
+        }
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");
+
+        if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO");
+        int transit = mService.mAppTransition.getAppTransition();
+        if (mService.mSkipAppTransitionAnimation && !isKeyguardGoingAwayTransit(transit)) {
+            transit = AppTransition.TRANSIT_UNSET;
+        }
+        mService.mSkipAppTransitionAnimation = false;
+        mService.mNoAnimationNotifyOnTransitionFinished.clear();
+
+        mService.mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
+
+        final DisplayContent displayContent = mService.getDefaultDisplayContentLocked();
+        // TODO: Don't believe this is really needed...
+        //mService.mWindowsChanged = true;
+
+        mService.mRoot.mWallpaperMayChange = false;
+
+        // The top-most window will supply the layout params, and we will determine it below.
+        LayoutParams animLp = null;
+        int bestAnimLayer = -1;
+        boolean fullscreenAnim = false;
+        boolean voiceInteraction = false;
+
+        int i;
+        for (i = 0; i < appsCount; i++) {
+            final AppWindowToken wtoken = mService.mOpeningApps.valueAt(i);
+            // Clearing the mAnimatingExit flag before entering animation. It's set to true if app
+            // window is removed, or window relayout to invisible. This also affects window
+            // visibility. We need to clear it *before* maybeUpdateTransitToWallpaper() as the
+            // transition selection depends on wallpaper target visibility.
+            wtoken.clearAnimatingFlags();
+
+        }
+
+        // Adjust wallpaper before we pull the lower/upper target, since pending changes
+        // (like the clearAnimatingFlags() above) might affect wallpaper target result.
+        // Or, the opening app window should be a wallpaper target.
+        mWallpaperControllerLocked.adjustWallpaperWindowsForAppTransitionIfNeeded(displayContent,
+                mService.mOpeningApps);
+
+        final WindowState wallpaperTarget = mWallpaperControllerLocked.getWallpaperTarget();
+        boolean openingAppHasWallpaper = false;
+        boolean closingAppHasWallpaper = false;
+
+        // Do a first pass through the tokens for two things:
+        // (1) Determine if both the closing and opening app token sets are wallpaper targets, in
+        // which case special animations are needed (since the wallpaper needs to stay static behind
+        // them).
+        // (2) Find the layout params of the top-most application window in the tokens, which is
+        // what will control the animation theme.
+        final int closingAppsCount = mService.mClosingApps.size();
+        appsCount = closingAppsCount + mService.mOpeningApps.size();
+        for (i = 0; i < appsCount; i++) {
+            final AppWindowToken wtoken;
+            if (i < closingAppsCount) {
+                wtoken = mService.mClosingApps.valueAt(i);
+                if (wallpaperTarget != null && wtoken.windowsCanBeWallpaperTarget()) {
+                    closingAppHasWallpaper = true;
+                }
+            } else {
+                wtoken = mService.mOpeningApps.valueAt(i - closingAppsCount);
+                if (wallpaperTarget != null && wtoken.windowsCanBeWallpaperTarget()) {
+                    openingAppHasWallpaper = true;
+                }
+            }
+
+            voiceInteraction |= wtoken.mVoiceInteraction;
+
+            if (wtoken.fillsParent()) {
+                final WindowState ws = wtoken.findMainWindow();
+                if (ws != null) {
+                    animLp = ws.mAttrs;
+                    bestAnimLayer = ws.mLayer;
+                    fullscreenAnim = true;
+                }
+            } else if (!fullscreenAnim) {
+                final WindowState ws = wtoken.findMainWindow();
+                if (ws != null) {
+                    if (ws.mLayer > bestAnimLayer) {
+                        animLp = ws.mAttrs;
+                        bestAnimLayer = ws.mLayer;
+                    }
+                }
+            }
+        }
+
+        transit = maybeUpdateTransitToWallpaper(transit, openingAppHasWallpaper,
+                closingAppHasWallpaper);
+
+        // If all closing windows are obscured, then there is no need to do an animation. This is
+        // the case, for example, when this transition is being done behind the lock screen.
+        if (!mService.mPolicy.allowAppAnimationsLw()) {
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
+                    "Animations disallowed by keyguard or dream.");
+            animLp = null;
+        }
+
+        processApplicationsAnimatingInPlace(transit);
+
+        mTmpLayerAndToken.token = null;
+        handleClosingApps(transit, animLp, voiceInteraction, mTmpLayerAndToken);
+        final AppWindowToken topClosingApp = mTmpLayerAndToken.token;
+        final int topClosingLayer = mTmpLayerAndToken.layer;
+
+        final AppWindowToken topOpeningApp = handleOpeningApps(transit,
+                animLp, voiceInteraction, topClosingLayer);
+
+        mService.mAppTransition.setLastAppTransition(transit, topOpeningApp, topClosingApp);
+
+        final AppWindowAnimator openingAppAnimator = (topOpeningApp == null) ?  null :
+                topOpeningApp.mAppAnimator;
+        final AppWindowAnimator closingAppAnimator = (topClosingApp == null) ? null :
+                topClosingApp.mAppAnimator;
+
+        final int flags = mService.mAppTransition.getTransitFlags();
+        int layoutRedo = mService.mAppTransition.goodToGo(transit, openingAppAnimator,
+                closingAppAnimator, mService.mOpeningApps, mService.mClosingApps);
+        handleNonAppWindowsInTransition(transit, flags);
+        mService.mAppTransition.postAnimationCallback();
+        mService.mAppTransition.clear();
+
+        mService.mTaskSnapshotController.onTransitionStarting();
+
+        mService.mOpeningApps.clear();
+        mService.mClosingApps.clear();
+        mService.mUnknownAppVisibilityController.clear();
+
+        // This has changed the visibility of windows, so perform
+        // a new layout to get them all up-to-date.
+        displayContent.setLayoutNeeded();
+
+        // TODO(multidisplay): IMEs are only supported on the default display.
+        final DisplayContent dc = mService.getDefaultDisplayContentLocked();
+        dc.computeImeTarget(true /* updateImeTarget */);
+        mService.updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES,
+                true /*updateInputWindows*/);
+        mService.mFocusMayChange = false;
+
+        mService.mH.obtainMessage(NOTIFY_APP_TRANSITION_STARTING,
+                mTempTransitionReasons.clone()).sendToTarget();
+
+        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+
+        return layoutRedo | FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_CONFIG;
+    }
+
+    private AppWindowToken handleOpeningApps(int transit, LayoutParams animLp,
+            boolean voiceInteraction, int topClosingLayer) {
+        AppWindowToken topOpeningApp = null;
+        final int appsCount = mService.mOpeningApps.size();
+        for (int i = 0; i < appsCount; i++) {
+            AppWindowToken wtoken = mService.mOpeningApps.valueAt(i);
+            final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now opening app" + wtoken);
+
+            if (!appAnimator.usingTransferredAnimation) {
+                appAnimator.clearThumbnail();
+                appAnimator.setNullAnimation();
+            }
+
+            if (!wtoken.setVisibility(animLp, true, transit, false, voiceInteraction)){
+                // This token isn't going to be animating. Add it to the list of tokens to
+                // be notified of app transition complete since the notification will not be
+                // sent be the app window animator.
+                mService.mNoAnimationNotifyOnTransitionFinished.add(wtoken.token);
+            }
+            wtoken.updateReportedVisibilityLocked();
+            wtoken.waitingToShow = false;
+            wtoken.setAllAppWinAnimators();
+
+            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
+                    ">>> OPEN TRANSACTION handleAppTransitionReadyLocked()");
+            mService.openSurfaceTransaction();
+            try {
+                mService.mAnimator.orAnimating(appAnimator.showAllWindowsLocked());
+            } finally {
+                mService.closeSurfaceTransaction();
+                if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
+                        "<<< CLOSE TRANSACTION handleAppTransitionReadyLocked()");
+            }
+            mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
+
+            int topOpeningLayer = 0;
+            if (animLp != null) {
+                final int layer = wtoken.getHighestAnimLayer();
+                if (topOpeningApp == null || layer > topOpeningLayer) {
+                    topOpeningApp = wtoken;
+                    topOpeningLayer = layer;
+                }
+            }
+            if (mService.mAppTransition.isNextAppTransitionThumbnailUp()) {
+                createThumbnailAppAnimator(transit, wtoken, topOpeningLayer, topClosingLayer);
+            }
+        }
+        return topOpeningApp;
+    }
+
+    private void handleClosingApps(int transit, LayoutParams animLp, boolean voiceInteraction,
+            LayerAndToken layerAndToken) {
+        final int appsCount;
+        appsCount = mService.mClosingApps.size();
+        for (int i = 0; i < appsCount; i++) {
+            AppWindowToken wtoken = mService.mClosingApps.valueAt(i);
+
+            final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now closing app " + wtoken);
+            appAnimator.clearThumbnail();
+            appAnimator.setNullAnimation();
+            // TODO: Do we need to add to mNoAnimationNotifyOnTransitionFinished like above if not
+            //       animating?
+            wtoken.setVisibility(animLp, false, transit, false, voiceInteraction);
+            wtoken.updateReportedVisibilityLocked();
+            // Force the allDrawn flag, because we want to start
+            // this guy's animations regardless of whether it's
+            // gotten drawn.
+            wtoken.allDrawn = true;
+            wtoken.deferClearAllDrawn = false;
+            // Ensure that apps that are mid-starting are also scheduled to have their
+            // starting windows removed after the animation is complete
+            if (wtoken.startingWindow != null && !wtoken.startingWindow.mAnimatingExit
+                    && wtoken.getController() != null) {
+                wtoken.getController().removeStartingWindow();
+            }
+            mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
+
+            if (animLp != null) {
+                int layer = wtoken.getHighestAnimLayer();
+                if (layerAndToken.token == null || layer > layerAndToken.layer) {
+                    layerAndToken.token = wtoken;
+                    layerAndToken.layer = layer;
+                }
+            }
+            if (mService.mAppTransition.isNextAppTransitionThumbnailDown()) {
+                createThumbnailAppAnimator(transit, wtoken, 0, layerAndToken.layer);
+            }
+        }
+    }
+
+    private void handleNonAppWindowsInTransition(int transit, int flags) {
+        if (transit == TRANSIT_KEYGUARD_GOING_AWAY) {
+            if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0
+                    && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0) {
+                Animation anim = mService.mPolicy.createKeyguardWallpaperExit(
+                        (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0);
+                if (anim != null) {
+                    mService.getDefaultDisplayContentLocked().mWallpaperController
+                            .startWallpaperAnimation(anim);
+                }
+            }
+        }
+        if (transit == TRANSIT_KEYGUARD_GOING_AWAY
+                || transit == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER) {
+            mService.getDefaultDisplayContentLocked().startKeyguardExitOnNonAppWindows(
+                    transit == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
+                    (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0);
+        }
+    }
+
+    private boolean transitionGoodToGo(int appsCount, SparseIntArray outReasons) {
+        if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
+                "Checking " + appsCount + " opening apps (frozen="
+                        + mService.mDisplayFrozen + " timeout="
+                        + mService.mAppTransition.isTimeout() + ")...");
+        final ScreenRotationAnimation screenRotationAnimation =
+            mService.mAnimator.getScreenRotationAnimationLocked(
+                    Display.DEFAULT_DISPLAY);
+
+        outReasons.clear();
+        if (!mService.mAppTransition.isTimeout()) {
+            // Imagine the case where we are changing orientation due to an app transition, but a previous
+            // orientation change is still in progress. We won't process the orientation change
+            // for our transition because we need to wait for the rotation animation to finish.
+            // If we start the app transition at this point, we will interrupt it halfway with a new rotation
+            // animation after the old one finally finishes. It's better to defer the
+            // app transition.
+            if (screenRotationAnimation != null && screenRotationAnimation.isAnimating() &&
+                    mService.rotationNeedsUpdateLocked()) {
+                if (DEBUG_APP_TRANSITIONS) {
+                    Slog.v(TAG, "Delaying app transition for screen rotation animation to finish");
+                }
+                return false;
+            }
+            for (int i = 0; i < appsCount; i++) {
+                AppWindowToken wtoken = mService.mOpeningApps.valueAt(i);
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
+                        "Check opening app=" + wtoken + ": allDrawn="
+                        + wtoken.allDrawn + " startingDisplayed="
+                        + wtoken.startingDisplayed + " startingMoved="
+                        + wtoken.startingMoved + " isRelaunching()="
+                        + wtoken.isRelaunching());
+
+
+                final boolean allDrawn = wtoken.allDrawn && !wtoken.isRelaunching();
+                if (!allDrawn && !wtoken.startingDisplayed && !wtoken.startingMoved) {
+                    return false;
+                }
+                final TaskStack stack = wtoken.getStack();
+                final int stackId = stack != null ? stack.mStackId : INVALID_STACK_ID;
+                if (allDrawn) {
+                    outReasons.put(stackId,  APP_TRANSITION_WINDOWS_DRAWN);
+                } else {
+                    outReasons.put(stackId, wtoken.startingData instanceof SplashScreenStartingData
+                            ? APP_TRANSITION_SPLASH_SCREEN
+                            : APP_TRANSITION_SNAPSHOT);
+                }
+            }
+
+            // We also need to wait for the specs to be fetched, if needed.
+            if (mService.mAppTransition.isFetchingAppTransitionsSpecs()) {
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "isFetchingAppTransitionSpecs=true");
+                return false;
+            }
+
+            if (!mService.mUnknownAppVisibilityController.allResolved()) {
+                if (DEBUG_APP_TRANSITIONS) {
+                    Slog.v(TAG, "unknownApps is not empty: "
+                            + mService.mUnknownAppVisibilityController.getDebugMessage());
+                }
+                return false;
+            }
+
+            // If the wallpaper is visible, we need to check it's ready too.
+            boolean wallpaperReady = !mWallpaperControllerLocked.isWallpaperVisible() ||
+                    mWallpaperControllerLocked.wallpaperTransitionReady();
+            if (wallpaperReady) {
+                return true;
+            }
+            return false;
+        }
+        return true;
+    }
+
+    private int maybeUpdateTransitToWallpaper(int transit, boolean openingAppHasWallpaper,
+            boolean closingAppHasWallpaper) {
+        // Given no app transition pass it through instead of a wallpaper transition
+        if (transit == TRANSIT_NONE) {
+            return TRANSIT_NONE;
+        }
+
+        // if wallpaper is animating in or out set oldWallpaper to null else to wallpaper
+        final WindowState wallpaperTarget = mWallpaperControllerLocked.getWallpaperTarget();
+        final WindowState oldWallpaper = mWallpaperControllerLocked.isWallpaperTargetAnimating()
+                ? null : wallpaperTarget;
+        final ArraySet<AppWindowToken> openingApps = mService.mOpeningApps;
+        final ArraySet<AppWindowToken> closingApps = mService.mClosingApps;
+        boolean openingCanBeWallpaperTarget = canBeWallpaperTarget(openingApps);
+        if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
+                "New wallpaper target=" + wallpaperTarget
+                        + ", oldWallpaper=" + oldWallpaper
+                        + ", openingApps=" + openingApps
+                        + ", closingApps=" + closingApps);
+        mService.mAnimateWallpaperWithTarget = false;
+        if (openingCanBeWallpaperTarget && transit == TRANSIT_KEYGUARD_GOING_AWAY) {
+            transit = TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
+                    "New transit: " + AppTransition.appTransitionToString(transit));
+        }
+        // We never want to change from a Keyguard transit to a non-Keyguard transit, as our logic
+        // relies on the fact that we always execute a Keyguard transition after preparing one.
+        else if (!isKeyguardGoingAwayTransit(transit)) {
+            if (closingAppHasWallpaper && openingAppHasWallpaper) {
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Wallpaper animation!");
+                switch (transit) {
+                    case TRANSIT_ACTIVITY_OPEN:
+                    case TRANSIT_TASK_OPEN:
+                    case TRANSIT_TASK_TO_FRONT:
+                        transit = TRANSIT_WALLPAPER_INTRA_OPEN;
+                        break;
+                    case TRANSIT_ACTIVITY_CLOSE:
+                    case TRANSIT_TASK_CLOSE:
+                    case TRANSIT_TASK_TO_BACK:
+                        transit = TRANSIT_WALLPAPER_INTRA_CLOSE;
+                        break;
+                }
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
+                        "New transit: " + AppTransition.appTransitionToString(transit));
+            } else if (oldWallpaper != null && !mService.mOpeningApps.isEmpty()
+                    && !openingApps.contains(oldWallpaper.mAppToken)
+                    && closingApps.contains(oldWallpaper.mAppToken)) {
+                // We are transitioning from an activity with a wallpaper to one without.
+                transit = TRANSIT_WALLPAPER_CLOSE;
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New transit away from wallpaper: "
+                        + AppTransition.appTransitionToString(transit));
+            } else if (wallpaperTarget != null && wallpaperTarget.isVisibleLw() &&
+                    openingApps.contains(wallpaperTarget.mAppToken)) {
+                // We are transitioning from an activity without
+                // a wallpaper to now showing the wallpaper
+                transit = TRANSIT_WALLPAPER_OPEN;
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New transit into wallpaper: "
+                        + AppTransition.appTransitionToString(transit));
+            } else {
+                mService.mAnimateWallpaperWithTarget = true;
+            }
+        }
+        return transit;
+    }
+
+    private boolean canBeWallpaperTarget(ArraySet<AppWindowToken> apps) {
+        for (int i = apps.size() - 1; i >= 0; i--) {
+            if (apps.valueAt(i).windowsCanBeWallpaperTarget()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void processApplicationsAnimatingInPlace(int transit) {
+        if (transit == TRANSIT_TASK_IN_PLACE) {
+            // Find the focused window
+            final WindowState win = mService.getDefaultDisplayContentLocked().findFocusedWindow();
+            if (win != null) {
+                final AppWindowToken wtoken = win.mAppToken;
+                final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
+                if (DEBUG_APP_TRANSITIONS)
+                    Slog.v(TAG, "Now animating app in place " + wtoken);
+                appAnimator.clearThumbnail();
+                appAnimator.setNullAnimation();
+                mService.updateTokenInPlaceLocked(wtoken, transit);
+                wtoken.updateReportedVisibilityLocked();
+                wtoken.setAllAppWinAnimators();
+                mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
+                mService.mAnimator.orAnimating(appAnimator.showAllWindowsLocked());
+            }
+        }
+    }
+
+    private void createThumbnailAppAnimator(int transit, AppWindowToken appToken,
+            int openingLayer, int closingLayer) {
+        AppWindowAnimator openingAppAnimator = (appToken == null) ? null : appToken.mAppAnimator;
+        if (openingAppAnimator == null || openingAppAnimator.animation == null) {
+            return;
+        }
+        final int taskId = appToken.getTask().mTaskId;
+        final GraphicBuffer thumbnailHeader =
+                mService.mAppTransition.getAppTransitionThumbnailHeader(taskId);
+        if (thumbnailHeader == null) {
+            if (DEBUG_APP_TRANSITIONS) Slog.d(TAG, "No thumbnail header bitmap for: " + taskId);
+            return;
+        }
+        // This thumbnail animation is very special, we need to have
+        // an extra surface with the thumbnail included with the animation.
+        Rect dirty = new Rect(0, 0, thumbnailHeader.getWidth(), thumbnailHeader.getHeight());
+        try {
+            // TODO(multi-display): support other displays
+            final DisplayContent displayContent = mService.getDefaultDisplayContentLocked();
+            final Display display = displayContent.getDisplay();
+            final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+
+            // Create a new surface for the thumbnail
+            WindowState window = appToken.findMainWindow();
+            SurfaceControl surfaceControl = new SurfaceControl(mService.mFxSession,
+                    "thumbnail anim", dirty.width(), dirty.height(),
+                    PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN,
+                    appToken.windowType,
+                    window != null ? window.mOwnerUid : Binder.getCallingUid());
+            surfaceControl.setLayerStack(display.getLayerStack());
+            if (SHOW_TRANSACTIONS) {
+                Slog.i(TAG, "  THUMBNAIL " + surfaceControl + ": CREATE");
+            }
+
+            // Transfer the thumbnail to the surface
+            Surface drawSurface = new Surface();
+            drawSurface.copyFrom(surfaceControl);
+            drawSurface.attachAndQueueBuffer(thumbnailHeader);
+            drawSurface.release();
+
+            // Get the thumbnail animation
+            Animation anim;
+            if (mService.mAppTransition.isNextThumbnailTransitionAspectScaled()) {
+                // If this is a multi-window scenario, we use the windows frame as
+                // destination of the thumbnail header animation. If this is a full screen
+                // window scenario, we use the whole display as the target.
+                WindowState win = appToken.findMainWindow();
+                Rect appRect = win != null ? win.getContentFrameLw() :
+                        new Rect(0, 0, displayInfo.appWidth, displayInfo.appHeight);
+                Rect insets = win != null ? win.mContentInsets : null;
+                final Configuration displayConfig = displayContent.getConfiguration();
+                // For the new aspect-scaled transition, we want it to always show
+                // above the animating opening/closing window, and we want to
+                // synchronize its thumbnail surface with the surface for the
+                // open/close animation (only on the way down)
+                anim = mService.mAppTransition.createThumbnailAspectScaleAnimationLocked(appRect,
+                        insets, thumbnailHeader, taskId, displayConfig.uiMode,
+                        displayConfig.orientation);
+                openingAppAnimator.thumbnailForceAboveLayer = Math.max(openingLayer, closingLayer);
+                openingAppAnimator.deferThumbnailDestruction =
+                        !mService.mAppTransition.isNextThumbnailTransitionScaleUp();
+            } else {
+                anim = mService.mAppTransition.createThumbnailScaleAnimationLocked(
+                        displayInfo.appWidth, displayInfo.appHeight, transit, thumbnailHeader);
+            }
+            anim.restrictDuration(MAX_ANIMATION_DURATION);
+            anim.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked());
+
+            openingAppAnimator.thumbnail = surfaceControl;
+            openingAppAnimator.thumbnailLayer = openingLayer;
+            openingAppAnimator.thumbnailAnimation = anim;
+            mService.mAppTransition.getNextAppTransitionStartRect(taskId, mTmpStartRect);
+        } catch (Surface.OutOfResourcesException e) {
+            Slog.e(TAG, "Can't allocate thumbnail/Canvas surface w="
+                    + dirty.width() + " h=" + dirty.height(), e);
+            openingAppAnimator.clearThumbnail();
+        }
+    }
+
+    void requestTraversal() {
+        if (!mTraversalScheduled) {
+            mTraversalScheduled = true;
+            mService.mAnimationHandler.post(mPerformSurfacePlacement);
+        }
+    }
+
+    /**
+     * Puts the {@param surface} into a pending list to be destroyed after the current transaction
+     * has been committed.
+     */
+    void destroyAfterTransaction(SurfaceControl surface) {
+        mPendingDestroyingSurfaces.add(surface);
+    }
+
+    /**
+     * Destroys any surfaces that have been put into the pending list with
+     * {@link #destroyAfterTransaction}.
+     */
+    void destroyPendingSurfaces() {
+        for (int i = mPendingDestroyingSurfaces.size() - 1; i >= 0; i--) {
+            mPendingDestroyingSurfaces.get(i).destroy();
+        }
+        mPendingDestroyingSurfaces.clear();
+    }
+
+    public void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "mTraversalScheduled=" + mTraversalScheduled);
+        pw.println(prefix + "mHoldScreenWindow=" + mService.mRoot.mHoldScreenWindow);
+        pw.println(prefix + "mObscuringWindow=" + mService.mRoot.mObscuringWindow);
+    }
+}
diff --git a/com/android/server/wm/WindowToken.java b/com/android/server/wm/WindowToken.java
new file mode 100644
index 0000000..422615b
--- /dev/null
+++ b/com/android/server/wm/WindowToken.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.util.proto.ProtoOutputStream;
+import java.util.Comparator;
+import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEMENT;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
+import static com.android.server.wm.proto.WindowTokenProto.HASH_CODE;
+import static com.android.server.wm.proto.WindowTokenProto.WINDOWS;
+
+import android.os.Debug;
+import android.os.IBinder;
+import android.util.Slog;
+import android.view.SurfaceControl;
+
+import java.io.PrintWriter;
+
+/**
+ * Container of a set of related windows in the window manager. Often this is an AppWindowToken,
+ * which is the handle for an Activity that it uses to display windows. For nested windows, there is
+ * a WindowToken created for the parent window to manage its children.
+ */
+class WindowToken extends WindowContainer<WindowState> {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowToken" : TAG_WM;
+
+    // The window manager!
+    protected final WindowManagerService mService;
+
+    // The actual token.
+    final IBinder token;
+
+    // The type of window this token is for, as per WindowManager.LayoutParams.
+    final int windowType;
+
+    // Set if this token was explicitly added by a client, so should
+    // persist (not be removed) when all windows are removed.
+    boolean mPersistOnEmpty;
+
+    // For printing.
+    String stringName;
+
+    // Is key dispatching paused for this token?
+    boolean paused = false;
+
+    // Should this token's windows be hidden?
+    boolean hidden;
+
+    // Temporary for finding which tokens no longer have visible windows.
+    boolean hasVisible;
+
+    // Set to true when this token is in a pending transaction where it
+    // will be shown.
+    boolean waitingToShow;
+
+    // Set to true when this token is in a pending transaction where its
+    // windows will be put to the bottom of the list.
+    boolean sendingToBottom;
+
+    // The display this token is on.
+    protected DisplayContent mDisplayContent;
+
+    /** The owner has {@link android.Manifest.permission#MANAGE_APP_TOKENS} */
+    final boolean mOwnerCanManageAppTokens;
+
+    /**
+     * Compares two child window of this token and returns -1 if the first is lesser than the
+     * second in terms of z-order and 1 otherwise.
+     */
+    private final Comparator<WindowState> mWindowComparator =
+            (WindowState newWindow, WindowState existingWindow) -> {
+        final WindowToken token = WindowToken.this;
+        if (newWindow.mToken != token) {
+            throw new IllegalArgumentException("newWindow=" + newWindow
+                    + " is not a child of token=" + token);
+        }
+
+        if (existingWindow.mToken != token) {
+            throw new IllegalArgumentException("existingWindow=" + existingWindow
+                    + " is not a child of token=" + token);
+        }
+
+        return isFirstChildWindowGreaterThanSecond(newWindow, existingWindow) ? 1 : -1;
+    };
+
+    WindowToken(WindowManagerService service, IBinder _token, int type, boolean persistOnEmpty,
+            DisplayContent dc, boolean ownerCanManageAppTokens) {
+        mService = service;
+        token = _token;
+        windowType = type;
+        mPersistOnEmpty = persistOnEmpty;
+        mOwnerCanManageAppTokens = ownerCanManageAppTokens;
+        onDisplayChanged(dc);
+    }
+
+    void removeAllWindowsIfPossible() {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowState win = mChildren.get(i);
+            if (DEBUG_WINDOW_MOVEMENT) Slog.w(TAG_WM,
+                    "removeAllWindowsIfPossible: removing win=" + win);
+            win.removeIfPossible();
+        }
+    }
+
+    void setExiting() {
+        // This token is exiting, so allow it to be removed when it no longer contains any windows.
+        mPersistOnEmpty = false;
+
+        if (hidden) {
+            return;
+        }
+
+        final int count = mChildren.size();
+        boolean changed = false;
+        boolean delayed = false;
+
+        for (int i = 0; i < count; i++) {
+            final WindowState win = mChildren.get(i);
+            if (win.mWinAnimator.isAnimationSet()) {
+                delayed = true;
+            }
+            changed |= win.onSetAppExiting();
+        }
+
+        hidden = true;
+
+        if (changed) {
+            mService.mWindowPlacerLocked.performSurfacePlacement();
+            mService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, false /*updateInputWindows*/);
+        }
+
+        if (delayed) {
+            mDisplayContent.mExitingTokens.add(this);
+        }
+    }
+
+    /**
+     * Returns true if the new window is considered greater than the existing window in terms of
+     * z-order.
+     */
+    protected boolean isFirstChildWindowGreaterThanSecond(WindowState newWindow,
+            WindowState existingWindow) {
+        // New window is considered greater if it has a higher or equal base layer.
+        return newWindow.mBaseLayer >= existingWindow.mBaseLayer;
+    }
+
+    void addWindow(final WindowState win) {
+        if (DEBUG_FOCUS) Slog.d(TAG_WM,
+                "addWindow: win=" + win + " Callers=" + Debug.getCallers(5));
+
+        if (win.isChildWindow()) {
+            // Child windows are added to their parent windows.
+            return;
+        }
+        if (!mChildren.contains(win)) {
+            if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Adding " + win + " to " + this);
+            addChild(win, mWindowComparator);
+            mService.mWindowsChanged = true;
+            // TODO: Should we also be setting layout needed here and other places?
+        }
+    }
+
+    /** Returns true if the token windows list is empty. */
+    boolean isEmpty() {
+        return mChildren.isEmpty();
+    }
+
+    // Used by AppWindowToken.
+    int getAnimLayerAdjustment() {
+        return 0;
+    }
+
+    WindowState getReplacingWindow() {
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final WindowState win = mChildren.get(i);
+            final WindowState replacing = win.getReplacingWindow();
+            if (replacing != null) {
+                return replacing;
+            }
+        }
+        return null;
+    }
+
+    /** Return true if this token has a window that wants the wallpaper displayed behind it. */
+    boolean windowsCanBeWallpaperTarget() {
+        for (int j = mChildren.size() - 1; j >= 0; j--) {
+            final WindowState w = mChildren.get(j);
+            if ((w.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    int getHighestAnimLayer() {
+        int highest = -1;
+        for (int j = 0; j < mChildren.size(); j++) {
+            final WindowState w = mChildren.get(j);
+            final int wLayer = w.getHighestAnimLayer();
+            if (wLayer > highest) {
+                highest = wLayer;
+            }
+        }
+        return highest;
+    }
+
+    AppWindowToken asAppWindowToken() {
+        // TODO: Not sure if this is the best way to handle this vs. using instanceof and casting.
+        // I am not an app window token!
+        return null;
+    }
+
+    DisplayContent getDisplayContent() {
+        return mDisplayContent;
+    }
+
+    @Override
+    void removeImmediately() {
+        if (mDisplayContent != null) {
+            mDisplayContent.removeWindowToken(token);
+        }
+        // Needs to occur after the token is removed from the display above to avoid attempt at
+        // duplicate removal of this window container from it's parent.
+        super.removeImmediately();
+    }
+
+    void onDisplayChanged(DisplayContent dc) {
+        dc.reParentWindowToken(this);
+        mDisplayContent = dc;
+
+        // TODO(b/36740756): One day this should perhaps be hooked
+        // up with goodToGo, so we don't move a window
+        // to another display before the window behind
+        // it is ready.
+        SurfaceControl.openTransaction();
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final WindowState win = mChildren.get(i);
+            win.mWinAnimator.updateLayerStackInTransaction();
+        }
+        SurfaceControl.closeTransaction();
+
+        super.onDisplayChanged(dc);
+    }
+
+    void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(HASH_CODE, System.identityHashCode(this));
+        for (int i = 0; i < mChildren.size(); i++) {
+            final WindowState w = mChildren.get(i);
+            w.writeToProto(proto, WINDOWS);
+        }
+        proto.end(token);
+    }
+
+    void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix); pw.print("windows="); pw.println(mChildren);
+        pw.print(prefix); pw.print("windowType="); pw.print(windowType);
+                pw.print(" hidden="); pw.print(hidden);
+                pw.print(" hasVisible="); pw.println(hasVisible);
+        if (waitingToShow || sendingToBottom) {
+            pw.print(prefix); pw.print("waitingToShow="); pw.print(waitingToShow);
+                    pw.print(" sendingToBottom="); pw.print(sendingToBottom);
+        }
+    }
+
+    @Override
+    public String toString() {
+        if (stringName == null) {
+            StringBuilder sb = new StringBuilder();
+            sb.append("WindowToken{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(" "); sb.append(token); sb.append('}');
+            stringName = sb.toString();
+        }
+        return stringName;
+    }
+
+    @Override
+    String getName() {
+        return toString();
+    }
+
+    boolean okToDisplay() {
+        return mDisplayContent != null && mDisplayContent.okToDisplay();
+    }
+
+    boolean okToAnimate() {
+        return mDisplayContent != null && mDisplayContent.okToAnimate();
+    }
+}
diff --git a/com/android/server/wm/animation/ClipRectLRAnimation.java b/com/android/server/wm/animation/ClipRectLRAnimation.java
new file mode 100644
index 0000000..0db4c70
--- /dev/null
+++ b/com/android/server/wm/animation/ClipRectLRAnimation.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm.animation;
+
+import android.graphics.Rect;
+import android.view.animation.ClipRectAnimation;
+import android.view.animation.Transformation;
+
+/**
+ * Special case of ClipRectAnimation that animates only the left/right
+ * dimensions of the clip, picking up the other dimensions from whatever is
+ * set on the transform already.
+ *
+ * @hide
+ */
+public class ClipRectLRAnimation extends ClipRectAnimation {
+
+    /**
+     * Constructor. Passes in 0 for Top/Bottom parameters of ClipRectAnimation
+     */
+    public ClipRectLRAnimation(int fromL, int fromR, int toL, int toR) {
+        super(fromL, 0, fromR, 0, toL, 0, toR, 0);
+    }
+
+    /**
+     * Calculates and sets clip rect on given transformation. It uses existing values
+     * on the Transformation for Top/Bottom clip parameters.
+     */
+    @Override
+    protected void applyTransformation(float it, Transformation tr) {
+        Rect oldClipRect = tr.getClipRect();
+        tr.setClipRect(mFromRect.left + (int) ((mToRect.left - mFromRect.left) * it),
+                oldClipRect.top,
+                mFromRect.right + (int) ((mToRect.right - mFromRect.right) * it),
+                oldClipRect.bottom);
+    }
+}
diff --git a/com/android/server/wm/animation/ClipRectTBAnimation.java b/com/android/server/wm/animation/ClipRectTBAnimation.java
new file mode 100644
index 0000000..1f5b1a3
--- /dev/null
+++ b/com/android/server/wm/animation/ClipRectTBAnimation.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm.animation;
+
+import android.graphics.Rect;
+import android.view.animation.ClipRectAnimation;
+import android.view.animation.Interpolator;
+import android.view.animation.Transformation;
+import android.view.animation.TranslateAnimation;
+
+/**
+ * Special case of ClipRectAnimation that animates only the top/bottom
+ * dimensions of the clip, picking up the other dimensions from whatever is
+ * set on the transform already. In addition to that, information about a vertical translation
+ * animation can be specified so this animation simulates as the clip would be applied after instead
+ * of before applying the translation.
+ */
+public class ClipRectTBAnimation extends ClipRectAnimation {
+
+    private final int mFromTranslateY;
+    private final int mToTranslateY;
+    private final Interpolator mTranslateInterpolator;
+    private float mNormalizedTime;
+
+    /**
+     * Constructor. Passes in 0 for Left/Right parameters of ClipRectAnimation
+     */
+    public ClipRectTBAnimation(int fromT, int fromB, int toT, int toB,
+            int fromTranslateY, int toTranslateY, Interpolator translateInterpolator) {
+        super(0, fromT, 0, fromB, 0, toT, 0, toB);
+        mFromTranslateY = fromTranslateY;
+        mToTranslateY = toTranslateY;
+        mTranslateInterpolator = translateInterpolator;
+    }
+
+    @Override
+    public boolean getTransformation(long currentTime, Transformation outTransformation) {
+
+        // Hack: Because translation animation has a different interpolator, we need to duplicate
+        // code from Animation here and use it to calculate/store the uninterpolated normalized
+        // time.
+        final long startOffset = getStartOffset();
+        final long duration = getDuration();
+        float normalizedTime;
+        if (duration != 0) {
+            normalizedTime = ((float) (currentTime - (getStartTime() + startOffset))) /
+                    (float) duration;
+        } else {
+            // time is a step-change with a zero duration
+            normalizedTime = currentTime < getStartTime() ? 0.0f : 1.0f;
+        }
+        mNormalizedTime = normalizedTime;
+        return super.getTransformation(currentTime, outTransformation);
+    }
+
+    /**
+     * Calculates and sets clip rect on given transformation. It uses existing values
+     * on the Transformation for Left/Right clip parameters.
+     */
+    @Override
+    protected void applyTransformation(float it, Transformation tr) {
+        float translationT = mTranslateInterpolator.getInterpolation(mNormalizedTime);
+        int translation =
+                (int) (mFromTranslateY + (mToTranslateY - mFromTranslateY) * translationT);
+        Rect oldClipRect = tr.getClipRect();
+        tr.setClipRect(oldClipRect.left,
+                mFromRect.top - translation + (int) ((mToRect.top - mFromRect.top) * it),
+                oldClipRect.right,
+                mFromRect.bottom - translation + (int) ((mToRect.bottom - mFromRect.bottom) * it));
+    }
+
+}
diff --git a/com/android/server/wm/animation/CurvedTranslateAnimation.java b/com/android/server/wm/animation/CurvedTranslateAnimation.java
new file mode 100644
index 0000000..33ac2ff
--- /dev/null
+++ b/com/android/server/wm/animation/CurvedTranslateAnimation.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm.animation;
+
+import android.animation.KeyframeSet;
+import android.animation.PathKeyframes;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.view.animation.Animation;
+import android.view.animation.Transformation;
+
+/**
+ * Translate animation which follows a curved path.
+ */
+public class CurvedTranslateAnimation extends Animation {
+
+    private final PathKeyframes mKeyframes;
+
+    public CurvedTranslateAnimation(Path path) {
+        mKeyframes = KeyframeSet.ofPath(path);
+    }
+
+    @Override
+    protected void applyTransformation(float interpolatedTime, Transformation t) {
+        PointF location = (PointF) mKeyframes.getValue(interpolatedTime);
+        t.getMatrix().setTranslate(location.x, location.y);
+    }
+}