Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ac85cb80 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add a DisplayLayout utility"

parents c8ba6307 cfa31de6
Loading
Loading
Loading
Loading
+474 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.systemui.wm;

import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
import static android.os.Process.SYSTEM_UID;
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
import static android.view.Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;

import android.annotation.NonNull;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.os.SystemProperties;
import android.provider.Settings;
import android.util.Size;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.Surface;

import com.android.internal.R;

import java.util.List;

/**
 * Contains information about the layout-properties of a display. This refers to internal layout
 * like insets/cutout/rotation. In general, this can be thought of as the System-UI analog to
 * DisplayPolicy.
 */
public class DisplayLayout {
    // Navigation bar position values
    private static final int NAV_BAR_LEFT = 1 << 0;
    private static final int NAV_BAR_RIGHT = 1 << 1;
    private static final int NAV_BAR_BOTTOM = 1 << 2;

    private int mUiMode;
    private int mWidth;
    private int mHeight;
    private DisplayCutout mCutout;
    private int mRotation;
    private int mDensityDpi;
    private final Rect mNonDecorInsets = new Rect();
    private final Rect mStableInsets = new Rect();
    private boolean mHasNavigationBar = false;
    private boolean mHasStatusBar = false;

    /**
     * Create empty layout.
     */
    public DisplayLayout() {
    }

    /**
     * Construct a custom display layout using a DisplayInfo.
     * @param info
     * @param res
     */
    public DisplayLayout(DisplayInfo info, Resources res, boolean hasNavigationBar,
            boolean hasStatusBar) {
        init(info, res, hasNavigationBar, hasStatusBar);
    }

    /**
     * Construct a display layout based on a live display.
     * @param context Used for resources.
     */
    public DisplayLayout(@NonNull Context context, @NonNull Display rawDisplay) {
        final int displayId = rawDisplay.getDisplayId();
        DisplayInfo info = new DisplayInfo();
        rawDisplay.getDisplayInfo(info);
        init(info, context.getResources(), hasNavigationBar(info, context, displayId),
                hasStatusBar(displayId));
    }

    public DisplayLayout(DisplayLayout dl) {
        set(dl);
    }

    /** sets this DisplayLayout to a copy of another on. */
    public void set(DisplayLayout dl) {
        mUiMode = dl.mUiMode;
        mWidth = dl.mWidth;
        mHeight = dl.mHeight;
        mCutout = dl.mCutout;
        mRotation = dl.mRotation;
        mDensityDpi = dl.mDensityDpi;
        mHasNavigationBar = dl.mHasNavigationBar;
        mHasStatusBar = dl.mHasStatusBar;
        mNonDecorInsets.set(dl.mNonDecorInsets);
        mStableInsets.set(dl.mStableInsets);
    }

    private void init(DisplayInfo info, Resources res, boolean hasNavigationBar,
            boolean hasStatusBar) {
        mUiMode = res.getConfiguration().uiMode;
        mWidth = info.logicalWidth;
        mHeight = info.logicalHeight;
        mRotation = info.rotation;
        mCutout = info.displayCutout;
        mDensityDpi = info.logicalDensityDpi;
        mHasNavigationBar = hasNavigationBar;
        mHasStatusBar = hasStatusBar;
        recalcInsets(res);
    }

    private void recalcInsets(Resources res) {
        computeNonDecorInsets(res, mRotation, mWidth, mHeight, mCutout, mUiMode, mNonDecorInsets,
                mHasNavigationBar);
        mStableInsets.set(mNonDecorInsets);
        if (mHasStatusBar) {
            convertNonDecorInsetsToStableInsets(res, mStableInsets, mWidth, mHeight, mHasStatusBar);
        }
    }

    /**
     * Apply a rotation to this layout and its parameters.
     * @param res
     * @param targetRotation
     */
    public void rotateTo(Resources res, @Surface.Rotation int targetRotation) {
        final int rotationDelta = (targetRotation - mRotation + 4) % 4;
        final boolean changeOrient = (rotationDelta % 2) != 0;

        final int origWidth = mWidth;
        final int origHeight = mHeight;

        mRotation = targetRotation;
        if (changeOrient) {
            mWidth = origHeight;
            mHeight = origWidth;
        }

        if (mCutout != null && !mCutout.isEmpty()) {
            mCutout = calculateDisplayCutoutForRotation(mCutout, rotationDelta, origWidth,
                    origHeight);
        }

        recalcInsets(res);
    }

    /** Get this layout's non-decor insets. */
    public Rect nonDecorInsets() {
        return mNonDecorInsets;
    }

    /** Get this layout's stable insets. */
    public Rect stableInsets() {
        return mStableInsets;
    }

    /** Get this layout's width. */
    public int width() {
        return mWidth;
    }

    /** Get this layout's height. */
    public int height() {
        return mHeight;
    }

    /** Get this layout's display rotation. */
    public int rotation() {
        return mRotation;
    }

    /** Get this layout's display density. */
    public int densityDpi() {
        return mDensityDpi;
    }

    /** Get whether this layout is landscape. */
    public boolean isLandscape() {
        return mWidth > mHeight;
    }

    /** Gets the orientation of this layout */
    public int getOrientation() {
        return (mWidth > mHeight) ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
    }

    /** Gets the calculated stable-bounds for this layout */
    public void getStableBounds(Rect outBounds) {
        outBounds.set(0, 0, mWidth, mHeight);
        outBounds.inset(mStableInsets);
    }

    /**
     * Rotates bounds as if parentBounds and bounds are a group. The group is rotated by `delta`
     * 90-degree counter-clockwise increments. This assumes that parentBounds is at 0,0 and
     * remains at 0,0 after rotation.
     *
     * Only 'bounds' is mutated.
     */
    public static void rotateBounds(Rect inOutBounds, Rect parentBounds, int delta) {
        int rdelta = ((delta % 4) + 4) % 4;
        int origLeft = inOutBounds.left;
        switch (rdelta) {
            case 0:
                return;
            case 1:
                inOutBounds.left = inOutBounds.top;
                inOutBounds.top = parentBounds.right - inOutBounds.right;
                inOutBounds.right = inOutBounds.bottom;
                inOutBounds.bottom = parentBounds.right - origLeft;
                return;
            case 2:
                inOutBounds.left = parentBounds.right - inOutBounds.right;
                inOutBounds.right = parentBounds.right - origLeft;
                return;
            case 3:
                inOutBounds.left = parentBounds.bottom - inOutBounds.bottom;
                inOutBounds.bottom = inOutBounds.right;
                inOutBounds.right = parentBounds.bottom - inOutBounds.top;
                inOutBounds.top = origLeft;
                return;
        }
    }

    /**
     * Calculates the stable insets if we already have the non-decor insets.
     */
    private static void convertNonDecorInsetsToStableInsets(Resources res, Rect inOutInsets,
            int displayWidth, int displayHeight, boolean hasStatusBar) {
        if (!hasStatusBar) {
            return;
        }
        int statusBarHeight = getStatusBarHeight(displayWidth > displayHeight, res);
        inOutInsets.top = Math.max(inOutInsets.top, statusBarHeight);
    }

    /**
     * Calculates the insets for the areas that could never be removed in Honeycomb, i.e. system
     * bar or button bar.
     *
     * @param displayRotation the current display rotation
     * @param displayWidth the current display width
     * @param displayHeight the current display height
     * @param displayCutout the current display cutout
     * @param outInsets the insets to return
     */
    static void computeNonDecorInsets(Resources res, int displayRotation, int displayWidth,
            int displayHeight, DisplayCutout displayCutout, int uiMode, Rect outInsets,
            boolean hasNavigationBar) {
        outInsets.setEmpty();

        // Only navigation bar
        if (hasNavigationBar) {
            int position = navigationBarPosition(res, displayWidth, displayHeight, displayRotation);
            int navBarSize =
                    getNavigationBarSize(res, position, displayWidth > displayHeight, uiMode);
            if (position == NAV_BAR_BOTTOM) {
                outInsets.bottom = navBarSize;
            } else if (position == NAV_BAR_RIGHT) {
                outInsets.right = navBarSize;
            } else if (position == NAV_BAR_LEFT) {
                outInsets.left = navBarSize;
            }
        }

        if (displayCutout != null) {
            outInsets.left += displayCutout.getSafeInsetLeft();
            outInsets.top += displayCutout.getSafeInsetTop();
            outInsets.right += displayCutout.getSafeInsetRight();
            outInsets.bottom += displayCutout.getSafeInsetBottom();
        }
    }

    /**
     * Calculates the stable insets without running a layout.
     *
     * @param displayRotation the current display rotation
     * @param displayWidth the current display width
     * @param displayHeight the current display height
     * @param displayCutout the current display cutout
     * @param outInsets the insets to return
     */
    static void computeStableInsets(Resources res, int displayRotation, int displayWidth,
            int displayHeight, DisplayCutout displayCutout, int uiMode, Rect outInsets,
            boolean hasNavigationBar, boolean hasStatusBar) {
        outInsets.setEmpty();

        // Navigation bar and status bar.
        computeNonDecorInsets(res, displayRotation, displayWidth, displayHeight, displayCutout,
                uiMode, outInsets, hasNavigationBar);
        convertNonDecorInsetsToStableInsets(res, outInsets, displayWidth, displayHeight,
                hasStatusBar);
    }

    /** Retrieve the statusbar height from resources. */
    static int getStatusBarHeight(boolean landscape, Resources res) {
        return landscape ? res.getDimensionPixelSize(
                    com.android.internal.R.dimen.status_bar_height_landscape)
                    : res.getDimensionPixelSize(
                            com.android.internal.R.dimen.status_bar_height_portrait);
    }

    /** Calculate the DisplayCutout for a particular display size/rotation. */
    public static DisplayCutout calculateDisplayCutoutForRotation(
            DisplayCutout cutout, int rotation, int displayWidth, int displayHeight) {
        if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) {
            return null;
        }
        if (rotation == ROTATION_0) {
            return computeSafeInsets(
                    cutout, displayWidth, displayHeight);
        }
        final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
        Rect[] cutoutRects = computeSafeInsets(cutout, displayWidth, displayHeight)
                        .getBoundingRectsAll();
        final Rect[] newBounds = new Rect[cutoutRects.length];
        final Rect displayBounds = new Rect(0, 0, displayWidth, displayHeight);
        for (int i = 0; i < cutoutRects.length; ++i) {
            newBounds[i] = new Rect(cutoutRects[i]);
            rotateBounds(newBounds[i], displayBounds, rotation);
        }
        return computeSafeInsets(DisplayCutout.fromBounds(newBounds),
                rotated ? displayHeight : displayWidth,
                rotated ? displayWidth : displayHeight);
    }

    /** Calculate safe insets. */
    public static DisplayCutout computeSafeInsets(DisplayCutout inner,
            int displayWidth, int displayHeight) {
        if (inner == DisplayCutout.NO_CUTOUT || inner.isBoundsEmpty()) {
            return null;
        }

        final Size displaySize = new Size(displayWidth, displayHeight);
        final Rect safeInsets = computeSafeInsets(displaySize, inner);
        return inner.replaceSafeInsets(safeInsets);
    }

    private static Rect computeSafeInsets(Size displaySize, DisplayCutout cutout) {
        if (displaySize.getWidth() < displaySize.getHeight()) {
            final List<Rect> boundingRects = cutout.replaceSafeInsets(
                    new Rect(0, displaySize.getHeight() / 2, 0, displaySize.getHeight() / 2))
                    .getBoundingRects();
            int topInset = findInsetForSide(displaySize, boundingRects, Gravity.TOP);
            int bottomInset = findInsetForSide(displaySize, boundingRects, Gravity.BOTTOM);
            return new Rect(0, topInset, 0, bottomInset);
        } else if (displaySize.getWidth() > displaySize.getHeight()) {
            final List<Rect> boundingRects = cutout.replaceSafeInsets(
                    new Rect(displaySize.getWidth() / 2, 0, displaySize.getWidth() / 2, 0))
                    .getBoundingRects();
            int leftInset = findInsetForSide(displaySize, boundingRects, Gravity.LEFT);
            int right = findInsetForSide(displaySize, boundingRects, Gravity.RIGHT);
            return new Rect(leftInset, 0, right, 0);
        } else {
            throw new UnsupportedOperationException("not implemented: display=" + displaySize
                    + " cutout=" + cutout);
        }
    }

    private static int findInsetForSide(Size display, List<Rect> boundingRects, int gravity) {
        int inset = 0;
        final int size = boundingRects.size();
        for (int i = 0; i < size; i++) {
            Rect boundingRect = boundingRects.get(i);
            switch (gravity) {
                case Gravity.TOP:
                    if (boundingRect.top == 0) {
                        inset = Math.max(inset, boundingRect.bottom);
                    }
                    break;
                case Gravity.BOTTOM:
                    if (boundingRect.bottom == display.getHeight()) {
                        inset = Math.max(inset, display.getHeight() - boundingRect.top);
                    }
                    break;
                case Gravity.LEFT:
                    if (boundingRect.left == 0) {
                        inset = Math.max(inset, boundingRect.right);
                    }
                    break;
                case Gravity.RIGHT:
                    if (boundingRect.right == display.getWidth()) {
                        inset = Math.max(inset, display.getWidth() - boundingRect.left);
                    }
                    break;
                default:
                    throw new IllegalArgumentException("unknown gravity: " + gravity);
            }
        }
        return inset;
    }

    static boolean hasNavigationBar(DisplayInfo info, Context context, int displayId) {
        if (displayId == Display.DEFAULT_DISPLAY) {
            // Allow a system property to override this. Used by the emulator.
            final String navBarOverride = SystemProperties.get("qemu.hw.mainkeys");
            if ("1".equals(navBarOverride)) {
                return false;
            } else if ("0".equals(navBarOverride)) {
                return true;
            }
            return context.getResources().getBoolean(R.bool.config_showNavigationBar);
        } else {
            boolean isUntrustedVirtualDisplay = info.type == Display.TYPE_VIRTUAL
                    && info.ownerUid != SYSTEM_UID;
            final ContentResolver resolver = context.getContentResolver();
            boolean forceDesktopOnExternal = Settings.Global.getInt(resolver,
                    DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0) != 0;

            return ((info.flags & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0
                    || (forceDesktopOnExternal && !isUntrustedVirtualDisplay));
            // TODO(b/142569966): make sure VR2D and DisplayWindowSettings are moved here somehow.
        }
    }

    static boolean hasStatusBar(int displayId) {
        return displayId == Display.DEFAULT_DISPLAY;
    }

    /** Retrieve navigation bar position from resources based on rotation and size. */
    public static int navigationBarPosition(Resources res, int displayWidth, int displayHeight,
            int rotation) {
        boolean navBarCanMove = displayWidth != displayHeight && res.getBoolean(
                com.android.internal.R.bool.config_navBarCanMove);
        if (navBarCanMove && displayWidth > displayHeight) {
            if (rotation == Surface.ROTATION_90) {
                return NAV_BAR_RIGHT;
            } else {
                return NAV_BAR_LEFT;
            }
        }
        return NAV_BAR_BOTTOM;
    }

    /** Retrieve navigation bar size from resources based on side/orientation/ui-mode */
    public static int getNavigationBarSize(Resources res, int navBarSide, boolean landscape,
            int uiMode) {
        final boolean carMode = (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR;
        if (carMode) {
            if (navBarSide == NAV_BAR_BOTTOM) {
                return res.getDimensionPixelSize(landscape
                        ? R.dimen.navigation_bar_height_landscape_car_mode
                        : R.dimen.navigation_bar_height_car_mode);
            } else {
                return res.getDimensionPixelSize(R.dimen.navigation_bar_width_car_mode);
            }
        } else {
            if (navBarSide == NAV_BAR_BOTTOM) {
                return res.getDimensionPixelSize(landscape
                        ? R.dimen.navigation_bar_height_landscape
                        : R.dimen.navigation_bar_height);
            } else {
                return res.getDimensionPixelSize(R.dimen.navigation_bar_width);
            }
        }
    }
}
+50 −6
Original line number Diff line number Diff line
@@ -16,16 +16,20 @@

package com.android.systemui.wm;

import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.IDisplayWindowListener;
import android.view.IDisplayWindowRotationCallback;
import android.view.IDisplayWindowRotationController;
import android.view.IWindowManager;
import android.view.WindowContainerTransaction;
import android.view.WindowManagerGlobal;

import com.android.systemui.dagger.qualifiers.MainHandler;

@@ -45,6 +49,8 @@ public class DisplayWindowController {
    private static final String TAG = "DisplayWindowController";

    private final Handler mHandler;
    private final Context mContext;
    private final IWindowManager mWmService;

    private final ArrayList<OnDisplayWindowRotationController> mRotationControllers =
            new ArrayList<>();
@@ -76,6 +82,14 @@ public class DisplayWindowController {
                }
            };

    /**
     * Get's a display by id from DisplayManager.
     */
    public Display getDisplay(int displayId) {
        final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
        return displayManager.getDisplay(displayId);
    }

    private final IDisplayWindowListener mDisplayContainerListener =
            new IDisplayWindowListener.Stub() {
                @Override
@@ -87,6 +101,10 @@ public class DisplayWindowController {
                            }
                            DisplayRecord record = new DisplayRecord();
                            record.mDisplayId = displayId;
                            Display display = getDisplay(displayId);
                            record.mContext = (displayId == Display.DEFAULT_DISPLAY) ? mContext
                                    : mContext.createDisplayContext(display);
                            record.mDisplayLayout = new DisplayLayout(record.mContext, display);
                            mDisplays.put(displayId, record);
                            for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
                                mDisplayChangedListeners.get(i).onDisplayAdded(displayId);
@@ -105,6 +123,13 @@ public class DisplayWindowController {
                                        + " display.");
                                return;
                            }
                            Display display = getDisplay(displayId);
                            Context perDisplayContext = mContext;
                            if (displayId != Display.DEFAULT_DISPLAY) {
                                perDisplayContext = mContext.createDisplayContext(display);
                            }
                            dr.mContext = perDisplayContext.createConfigurationContext(newConfig);
                            dr.mDisplayLayout = new DisplayLayout(dr.mContext, display);
                            for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
                                mDisplayChangedListeners.get(i).onDisplayConfigurationChanged(
                                        displayId, newConfig);
@@ -127,18 +152,35 @@ public class DisplayWindowController {
            };

    @Inject
    public DisplayWindowController(@MainHandler Handler mainHandler) {
    public DisplayWindowController(Context context, @MainHandler Handler mainHandler,
            IWindowManager wmService) {
        mHandler = mainHandler;
        mContext = context;
        mWmService = wmService;
        try {
            WindowManagerGlobal.getWindowManagerService().registerDisplayWindowListener(
                    mDisplayContainerListener);
            WindowManagerGlobal.getWindowManagerService().setDisplayWindowRotationController(
                    mDisplayRotationController);
            mWmService.registerDisplayWindowListener(mDisplayContainerListener);
            mWmService.setDisplayWindowRotationController(mDisplayRotationController);
        } catch (RemoteException e) {
            throw new RuntimeException("Unable to register hierarchy listener");
        }
    }

    /**
     * Gets the DisplayLayout associated with a display.
     */
    public @Nullable DisplayLayout getDisplayLayout(int displayId) {
        final DisplayRecord r = mDisplays.get(displayId);
        return r != null ? r.mDisplayLayout : null;
    }

    /**
     * Gets a display-specific context for a display.
     */
    public @Nullable Context getDisplayContext(int displayId) {
        final DisplayRecord r = mDisplays.get(displayId);
        return r != null ? r.mContext : null;
    }

    /**
     * Add a display window-container listener. It will get notified whenever a display's
     * configuration changes or when displays are added/removed from the WM hierarchy.
@@ -184,6 +226,8 @@ public class DisplayWindowController {

    private static class DisplayRecord {
        int mDisplayId;
        Context mContext;
        DisplayLayout mDisplayLayout;
    }

    /**
+141 −0

File added.

Preview size limit exceeded, changes collapsed.