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

Commit 2abc7d40 authored by Eghosa Ewansiha-Vlachavas's avatar Eghosa Ewansiha-Vlachavas
Browse files

Respect bounds and size constraints when modifying launch params for dw

Ensure the `DesktopModeLaunchParamsModifier` does not override any
bounds set in the activities options and respect any size contraints
set through the layout.

Layout contraints can include:
- width/ height
- fraction width/ height
- gravity

Any bounds set must also fall within the default bounds (the display
area bounds). In the event the bounds fall outside this are, the
default bounds will be used.

Fixes: 340460999
Fixes: 340172101
Fixes: 340172030
Test: atest WmTests:DesktopModeLaunchParamsModifierTests
Test: atest WmTests:TaskLaunchParamsModifierTests

Change-Id: I86bc7e366c7d10b31aea3fe6398bc1db934a3a50
parent 6d223f0c
Loading
Loading
Loading
Loading
+45 −10
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package com.android.server.wm;

import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.LaunchParamsModifierUtils.applyLayoutGravity;
import static com.android.server.wm.LaunchParamsModifierUtils.calculateLayoutBounds;

import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -26,7 +28,9 @@ import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
import android.os.SystemProperties;
import android.util.Size;
import android.util.Slog;
import android.view.Gravity;

import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
@@ -119,10 +123,37 @@ public class DesktopModeLaunchParamsModifier implements LaunchParamsModifier {
            return RESULT_SKIP;
        }

        // Use stable frame instead of raw frame to avoid launching freeform windows on top of
        // stable insets, which usually are system widgets such as sysbar & navbar.
        final Rect stableBounds = new Rect();
        task.getDisplayArea().getStableRect(stableBounds);
        final int desiredWidth = (int) (stableBounds.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
        final int desiredHeight = (int) (stableBounds.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);

        if (options != null && options.getLaunchBounds() != null) {
            outParams.mBounds.set(options.getLaunchBounds());
            appendLog("inherit-from-options=" + outParams.mBounds);
        } else if (layout != null) {
            final int verticalGravity = layout.gravity & Gravity.VERTICAL_GRAVITY_MASK;
            final int horizontalGravity = layout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
            if (layout.hasSpecifiedSize()) {
                calculateLayoutBounds(stableBounds, layout, outParams.mBounds,
                        new Size(desiredWidth, desiredHeight));
                applyLayoutGravity(verticalGravity, horizontalGravity, outParams.mBounds,
                        stableBounds);
                appendLog("layout specifies sizes, inheriting size and applying gravity");
            } else if (verticalGravity > 0 || horizontalGravity > 0) {
                calculateAndCentreInitialBounds(task, outParams);
                applyLayoutGravity(verticalGravity, horizontalGravity, outParams.mBounds,
                        stableBounds);
                appendLog("layout specifies gravity, applying desired bounds and gravity");
            }
        } else {
            calculateAndCentreInitialBounds(task, outParams);
            appendLog("layout not specified, applying desired bounds");
        }

        appendLog("setting desktop mode task bounds to %s", outParams.mBounds);

        appendLog("final desktop mode task bounds set to %s", outParams.mBounds);
        return RESULT_CONTINUE;
    }

@@ -133,13 +164,17 @@ public class DesktopModeLaunchParamsModifier implements LaunchParamsModifier {
    private void calculateAndCentreInitialBounds(Task task,
            LaunchParamsController.LaunchParams outParams) {
        // TODO(b/319819547): Account for app constraints so apps do not become letterboxed
        final Rect windowBounds = task.getDisplayArea().getBounds();
        final int width = (int) (windowBounds.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
        final int height = (int) (windowBounds.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
        outParams.mBounds.right = width;
        outParams.mBounds.bottom = height;
        outParams.mBounds.offset(windowBounds.centerX() - outParams.mBounds.centerX(),
                windowBounds.centerY() - outParams.mBounds.centerY());
        final Rect stableBounds = new Rect();
        task.getDisplayArea().getStableRect(stableBounds);
        // The desired dimensions that a fully resizable window should take when initially entering
        // desktop mode. Calculated as a percentage of the available display area as defined by the
        // DESKTOP_MODE_INITIAL_BOUNDS_SCALE.
        final int desiredWidth = (int) (stableBounds.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
        final int desiredHeight = (int) (stableBounds.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
        outParams.mBounds.right = desiredWidth;
        outParams.mBounds.bottom = desiredHeight;
        outParams.mBounds.offset(stableBounds.centerX() - outParams.mBounds.centerX(),
                stableBounds.centerY() - outParams.mBounds.centerY());
    }

    private void initLogBuilder(Task task, ActivityRecord activity) {
+102 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.NonNull;
import android.annotation.Nullable;
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
import android.util.Size;
import android.view.Gravity;

class LaunchParamsModifierUtils {

    /**
     * Calculates bounds based on window layout size manifest values. These can include width,
     * height, width fraction and height fraction. In the event only one dimension of values are
     * specified in the manifest (e.g. width but no height value), the corresponding display area
     * dimension will be used as the default value unless some desired sizes have been specified.
     */
    static void calculateLayoutBounds(@NonNull Rect stableBounds,
            @NonNull ActivityInfo.WindowLayout windowLayout, @NonNull Rect inOutBounds,
            @Nullable Size desiredSize) {
        final int defaultWidth = stableBounds.width();
        final int defaultHeight = stableBounds.height();
        int width;
        int height;

        if (desiredSize == null) {
            // If desired bounds have not been specified, use the exiting default bounds as the
            // desired.
            desiredSize = new Size(stableBounds.width(), stableBounds.height());
        }

        width = desiredSize.getWidth();
        if (windowLayout.width > 0 && windowLayout.width < defaultWidth) {
            width = windowLayout.width;
        } else if (windowLayout.widthFraction > 0 && windowLayout.widthFraction < 1.0f) {
            width = (int) (defaultWidth * windowLayout.widthFraction);
        }

        height = desiredSize.getHeight();
        if (windowLayout.height > 0 && windowLayout.height < defaultHeight) {
            height = windowLayout.height;
        } else if (windowLayout.heightFraction > 0 && windowLayout.heightFraction < 1.0f) {
            height = (int) (defaultHeight * windowLayout.heightFraction);
        }

        inOutBounds.set(0, 0, width, height);
    }

    /**
     * Applies a vertical and horizontal gravity on the inOutBounds in relation to the stableBounds.
     */
    static void applyLayoutGravity(int verticalGravity, int horizontalGravity,
            @NonNull Rect inOutBounds, @NonNull Rect stableBounds) {
        final int width = inOutBounds.width();
        final int height = inOutBounds.height();

        final float fractionOfHorizontalOffset;
        switch (horizontalGravity) {
            case Gravity.LEFT:
                fractionOfHorizontalOffset = 0f;
                break;
            case Gravity.RIGHT:
                fractionOfHorizontalOffset = 1f;
                break;
            default:
                fractionOfHorizontalOffset = 0.5f;
        }

        final float fractionOfVerticalOffset;
        switch (verticalGravity) {
            case Gravity.TOP:
                fractionOfVerticalOffset = 0f;
                break;
            case Gravity.BOTTOM:
                fractionOfVerticalOffset = 1f;
                break;
            default:
                fractionOfVerticalOffset = 0.5f;
        }

        inOutBounds.offsetTo(stableBounds.left, stableBounds.top);
        final int xOffset = (int) (fractionOfHorizontalOffset * (stableBounds.width() - width));
        final int yOffset = (int) (fractionOfVerticalOffset * (stableBounds.height() - height));
        inOutBounds.offset(xOffset, yOffset);
    }
}
+10 −60
Original line number Diff line number Diff line
@@ -40,6 +40,8 @@ import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
import static com.android.server.wm.ActivityStarter.Request;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.LaunchParamsModifierUtils.applyLayoutGravity;
import static com.android.server.wm.LaunchParamsModifierUtils.calculateLayoutBounds;

import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -640,68 +642,16 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
        // stable insets, which usually are system widgets such as sysbar & navbar.
        final Rect stableBounds = mTmpStableBounds;
        displayArea.getStableRect(stableBounds);
        final int defaultWidth = stableBounds.width();
        final int defaultHeight = stableBounds.height();

        int width;
        int height;
        if (!windowLayout.hasSpecifiedSize()) {
            if (!inOutBounds.isEmpty()) {
                // If the bounds is resolved already and WindowLayout doesn't have any opinion on
                // its size, use the already resolved size and apply the gravity to it.
                width = inOutBounds.width();
                height = inOutBounds.height();
            } else {

        if (windowLayout.hasSpecifiedSize()) {
            calculateLayoutBounds(stableBounds, windowLayout, inOutBounds,
                    /* desiredBounds */ null);
        } else if (inOutBounds.isEmpty()) {
            getTaskBounds(root, displayArea, windowLayout, WINDOWING_MODE_FREEFORM,
                    /* hasInitialBounds */ false, inOutBounds);
                width = inOutBounds.width();
                height = inOutBounds.height();
            }
        } else {
            width = defaultWidth;
            if (windowLayout.width > 0 && windowLayout.width < defaultWidth) {
                width = windowLayout.width;
            } else if (windowLayout.widthFraction > 0 && windowLayout.widthFraction < 1.0f) {
                width = (int) (width * windowLayout.widthFraction);
            }

            height = defaultHeight;
            if (windowLayout.height > 0 && windowLayout.height < defaultHeight) {
                height = windowLayout.height;
            } else if (windowLayout.heightFraction > 0 && windowLayout.heightFraction < 1.0f) {
                height = (int) (height * windowLayout.heightFraction);
            }
        }

        final float fractionOfHorizontalOffset;
        switch (horizontalGravity) {
            case Gravity.LEFT:
                fractionOfHorizontalOffset = 0f;
                break;
            case Gravity.RIGHT:
                fractionOfHorizontalOffset = 1f;
                break;
            default:
                fractionOfHorizontalOffset = 0.5f;
        }

        final float fractionOfVerticalOffset;
        switch (verticalGravity) {
            case Gravity.TOP:
                fractionOfVerticalOffset = 0f;
                break;
            case Gravity.BOTTOM:
                fractionOfVerticalOffset = 1f;
                break;
            default:
                fractionOfVerticalOffset = 0.5f;
        }

        inOutBounds.set(0, 0, width, height);
        inOutBounds.offset(stableBounds.left, stableBounds.top);
        final int xOffset = (int) (fractionOfHorizontalOffset * (defaultWidth - width));
        final int yOffset = (int) (fractionOfVerticalOffset * (defaultHeight - height));
        inOutBounds.offset(xOffset, yOffset);
        applyLayoutGravity(verticalGravity, horizontalGravity, inOutBounds,
                stableBounds);
    }

    private boolean shouldLaunchUnresizableAppInFreeform(ActivityRecord activity,
+245 −46

File changed.

Preview size limit exceeded, changes collapsed.

+206 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.util.DisplayMetrics.DENSITY_DEFAULT;

import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;

import android.app.ActivityOptions;
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
import android.view.Gravity;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.WindowInsets;

import com.android.server.wm.LaunchParamsController.LaunchParams;
import com.android.server.wm.LaunchParamsController.LaunchParamsModifier;

/** Common base class for launch param modifier unit test classes. */
public class LaunchParamsModifierTestsBase extends WindowTestsBase{

    static final Rect DISPLAY_BOUNDS = new Rect(/* left */ 0, /* top */ 0,
            /* right */ 1920, /* bottom */ 1080);
    static final Rect DISPLAY_STABLE_BOUNDS = new Rect(/* left */ 100,
            /* top */ 200, /* right */ 1620, /* bottom */ 680);

    ActivityRecord mActivity;

    LaunchParamsModifier mTarget;

    LaunchParams mCurrent;
    LaunchParams mResult;


    TestDisplayContent createNewDisplayContent(int windowingMode) {
        return createNewDisplayContent(windowingMode, DISPLAY_BOUNDS, DISPLAY_STABLE_BOUNDS);
    }

    TestDisplayContent createNewDisplayContent(int windowingMode, Rect displayBounds,
            Rect displayStableBounds) {
        final TestDisplayContent display = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
        display.getDefaultTaskDisplayArea().setWindowingMode(windowingMode);
        display.setBounds(displayBounds);
        display.getConfiguration().densityDpi = DENSITY_DEFAULT;
        display.getConfiguration().orientation = ORIENTATION_LANDSCAPE;
        configInsetsState(display.getInsetsStateController().getRawInsetsState(), display,
                displayStableBounds);
        return display;
    }

    /**
     * Creates insets sources so that we can get the expected stable frame.
     */
    static void configInsetsState(InsetsState state, DisplayContent display,
            Rect stableFrame) {
        final Rect displayFrame = display.getBounds();
        final int dl = displayFrame.left;
        final int dt = displayFrame.top;
        final int dr = displayFrame.right;
        final int db = displayFrame.bottom;
        final int sl = stableFrame.left;
        final int st = stableFrame.top;
        final int sr = stableFrame.right;
        final int sb = stableFrame.bottom;
        final @WindowInsets.Type.InsetsType int statusBarType = WindowInsets.Type.statusBars();
        final @WindowInsets.Type.InsetsType int navBarType = WindowInsets.Type.navigationBars();

        state.setDisplayFrame(displayFrame);
        if (sl > dl) {
            state.getOrCreateSource(InsetsSource.createId(null, 0, statusBarType), statusBarType)
                    .setFrame(dl, dt, sl, db);
        }
        if (st > dt) {
            state.getOrCreateSource(InsetsSource.createId(null, 1, statusBarType), statusBarType)
                    .setFrame(dl, dt, dr, st);
        }
        if (sr < dr) {
            state.getOrCreateSource(InsetsSource.createId(null, 0, navBarType), navBarType)
                    .setFrame(sr, dt, dr, db);
        }
        if (sb < db) {
            state.getOrCreateSource(InsetsSource.createId(null, 1, navBarType), navBarType)
                    .setFrame(dl, sb, dr, db);
        }
        // Recompute config and push to children.
        display.onRequestedOverrideConfigurationChanged(display.getConfiguration());
    }

    class CalculateRequestBuilder {
        private Task mTask;
        private ActivityInfo.WindowLayout mLayout;
        private ActivityRecord mActivity = LaunchParamsModifierTestsBase.this.mActivity;
        private ActivityRecord mSource;
        private ActivityOptions mOptions;
        private ActivityStarter.Request mRequest;
        private int mPhase = PHASE_BOUNDS;
        private LaunchParams mCurrentParams = mCurrent;
        private LaunchParams mOutParams = mResult;

        CalculateRequestBuilder setTask(Task task) {
            mTask = task;
            return this;
        }

        CalculateRequestBuilder setLayout(ActivityInfo.WindowLayout layout) {
            mLayout = layout;
            return this;
        }

        CalculateRequestBuilder setPhase(int phase) {
            mPhase = phase;
            return this;
        }

        CalculateRequestBuilder setActivity(ActivityRecord activity) {
            mActivity = activity;
            return this;
        }

        CalculateRequestBuilder setSource(ActivityRecord source) {
            mSource = source;
            return this;
        }

        CalculateRequestBuilder setOptions(ActivityOptions options) {
            mOptions = options;
            return this;
        }

        CalculateRequestBuilder setRequest(ActivityStarter.Request request) {
            mRequest = request;
            return this;
        }

        @LaunchParamsController.LaunchParamsModifier.Result int calculate() {
            return mTarget.onCalculate(mTask, mLayout, mActivity, mSource, mOptions, mRequest,
                    mPhase, mCurrentParams, mOutParams);
        }
    }

    static class WindowLayoutBuilder {
        private int mWidth = -1;
        private int mHeight = -1;
        private float mWidthFraction = -1f;
        private float mHeightFraction = -1f;
        private int mGravity = Gravity.NO_GRAVITY;
        private int mMinWidth = -1;
        private int mMinHeight = -1;

        WindowLayoutBuilder setWidth(int width) {
            mWidth = width;
            return this;
        }

        WindowLayoutBuilder setHeight(int height) {
            mHeight = height;
            return this;
        }

        WindowLayoutBuilder setWidthFraction(float widthFraction) {
            mWidthFraction = widthFraction;
            return this;
        }

        WindowLayoutBuilder setHeightFraction(float heightFraction) {
            mHeightFraction = heightFraction;
            return this;
        }

        WindowLayoutBuilder setGravity(int gravity) {
            mGravity = gravity;
            return this;
        }

        WindowLayoutBuilder setMinWidth(int minWidth) {
            mMinWidth = minWidth;
            return this;
        }

        WindowLayoutBuilder setMinHeight(int minHeight) {
            mMinHeight = minHeight;
            return this;
        }

        ActivityInfo.WindowLayout build() {
            return new ActivityInfo.WindowLayout(mWidth, mWidthFraction, mHeight, mHeightFraction,
                    mGravity, mMinWidth, mMinHeight);
        }
    }
}
Loading