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);
+ }
+}