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

Commit 31d25e9e authored by Mariia Sandrikova's avatar Mariia Sandrikova
Browse files

Letterbox positioning (1/4): Consolidate activity and task letterboxing

Now letterboxing can happen on both task (fixed orientation) and activity (size compat or aspect ratio restrictions) levels. This change consolidates them to prevent future changes for letterbox positioning from being split between Task and ActivityRecord.

Main changes:
- Move Task#computeLetterboxBounds to ActivityRecord#resolveFixedOrientationConfiguration that is called from ActivityRecord#resolveOverrideConfiguration.
- Since fixed orientation letterboxing is no longer included in parent (task) bounds of the activity, account for fixed orientation letterbox bounds in size compat methods that relies on that.
- Replace all calls of ActivityRecord#updateCompatDisplayInsets (renamed updateSizeCompatMode) with just one call in ActivityRecord#resolveOverrideConfiguration.
- Refactor ActivityRecord#inSizeCompatMode to use mInSizeCompatModeForBounds pre-calculated in ActivityRecord#resolveSizeCompatModeConfiguration.

Changes planned in next CLs:
(2/4) Consolidate positioning logic in ActivityRecord#positionLetterboxedBounds(...) called from ActivityRecord#resolveOverrideConfiguration after bounds dimensions are computed.
(3/4) Position bounds using gravity specified in config or via ADB commands.
(4/4) Rename WindowManagerService#getTaskLetterboxAspectRatio and all related methods since fixed orientation letterboxing now happens on activity level.

Test: atest WmTests
Bug: 175212232

Change-Id: I8b1f7ac178bef2105c46940ddd3a493c547f0be9
parent e9c691d3
Loading
Loading
Loading
Loading
+262 −118

File changed.

Preview size limit exceeded, changes collapsed.

+2 −127
Original line number Diff line number Diff line
@@ -35,7 +35,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMAR
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.activityTypeToString;
import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
import static android.app.WindowConfiguration.windowingModeToString;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -148,7 +147,6 @@ import static com.android.server.wm.WindowContainerChildProto.TASK;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ROOT_TASK;
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.MIN_TASK_LETTERBOX_ASPECT_RATIO;
import static com.android.server.wm.WindowManagerService.dipToPixel;
import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_BEFORE_ANIM;

@@ -597,10 +595,6 @@ class Task extends WindowContainer<WindowContainer> {
    @Nullable
    private ActivityRecord mResumedActivity = null;

    /** Last activity that is used to compute the Task bounds. */
    @Nullable
    private ActivityRecord mLastTaskBoundsComputeActivity;

    private boolean mForceShowForAllUsers;

    /** When set, will force the task to report as invisible. */
@@ -1496,11 +1490,6 @@ class Task extends WindowContainer<WindowContainer> {
    }

    void cleanUpActivityReferences(ActivityRecord r) {
        // mLastTaskBoundsComputeActivity is set at leaf Task
        if (mLastTaskBoundsComputeActivity == r) {
            mLastTaskBoundsComputeActivity = null;
        }

        // mPausingActivity is set at leaf task
        if (mPausingActivity != null && mPausingActivity == r) {
            mPausingActivity = null;
@@ -2865,7 +2854,6 @@ class Task extends WindowContainer<WindowContainer> {

    private void resolveLeafOnlyOverrideConfigs(Configuration newParentConfig,
            Rect previousBounds) {
        mLastTaskBoundsComputeActivity = getTopNonFinishingActivity(false /* includeOverlays */);

        int windowingMode =
                getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode();
@@ -2879,7 +2867,8 @@ class Task extends WindowContainer<WindowContainer> {
                getResolvedOverrideConfiguration().windowConfiguration.getBounds();

        if (windowingMode == WINDOWING_MODE_FULLSCREEN) {
            computeFullscreenBounds(outOverrideBounds, newParentConfig);
            // Use empty bounds to indicate "fill parent".
            outOverrideBounds.setEmpty();
            // The bounds for fullscreen mode shouldn't be adjusted by minimal size. Otherwise if
            // the parent or display is smaller than the size, the content may be cropped.
            return;
@@ -2890,21 +2879,6 @@ class Task extends WindowContainer<WindowContainer> {
            computeFreeformBounds(outOverrideBounds, newParentConfig);
            return;
        }

        if (isSplitScreenWindowingMode(windowingMode)
                || windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
            // This is to compute whether the task should be letterboxed to handle non-resizable app
            // in multi window. There is no split screen only logic.
            computeLetterboxBounds(outOverrideBounds, newParentConfig);
        }
    }

    /** Computes bounds for {@link WindowConfiguration#WINDOWING_MODE_FULLSCREEN}. */
    @VisibleForTesting
    void computeFullscreenBounds(@NonNull Rect outBounds, @NonNull Configuration newParentConfig) {
        // In FULLSCREEN mode, always start with empty bounds to indicate "fill parent".
        outBounds.setEmpty();
        computeLetterboxBounds(outBounds, newParentConfig);
    }

    /** Computes bounds for {@link WindowConfiguration#WINDOWING_MODE_FREEFORM}. */
@@ -2936,94 +2910,6 @@ class Task extends WindowContainer<WindowContainer> {
        }
    }

    /**
     * Computes bounds (letterbox or pillarbox) when the parent doesn't handle the orientation
     * change and the requested orientation is different from the parent.
     */
    private void computeLetterboxBounds(@NonNull Rect outBounds,
            @NonNull Configuration newParentConfig) {
        if (handlesOrientationChangeFromDescendant()) {
            // No need to letterbox at task level. Display will handle fixed-orientation requests.
            return;
        }

        final int parentOrientation = newParentConfig.orientation;
        // Use the top activity as the reference of orientation. Don't include overlays because
        // it is usually not the actual content or just temporarily shown.
        // E.g. ForcedResizableInfoActivity.
        final ActivityRecord refActivity = getTopNonFinishingActivity(false /* includeOverlays */);

        // If the task or the reference activity requires a different orientation (either by
        // override or activityInfo), make it fit the available bounds by scaling down its bounds.
        final int overrideOrientation = getRequestedOverrideConfiguration().orientation;
        final int forcedOrientation =
                (overrideOrientation != ORIENTATION_UNDEFINED || refActivity == null)
                        ? overrideOrientation : refActivity.getRequestedConfigurationOrientation();
        if (forcedOrientation == ORIENTATION_UNDEFINED || forcedOrientation == parentOrientation) {
            return;
        }

        final ActivityRecord.CompatDisplayInsets compatDisplayInsets =
                refActivity == null ? null : refActivity.getCompatDisplayInsets();
        if (compatDisplayInsets != null && !compatDisplayInsets.mIsTaskLetterboxed) {
            // App prefers to keep its original size.
            // If the size compat is from previous task letterboxing, we may want to have task
            // letterbox again, otherwise it will show the size compat restart button even if the
            // restart bounds will be the same.
            return;
        }

        final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
        final int parentWidth = parentBounds.width();
        final int parentHeight = parentBounds.height();
        float aspect = Math.max(parentWidth, parentHeight)
                / (float) Math.min(parentWidth, parentHeight);

        // Adjust the Task letterbox bounds to fit the app request aspect ratio in order to use the
        // extra available space.
        if (refActivity != null) {
            final float maxAspectRatio = refActivity.info.maxAspectRatio;
            final float minAspectRatio = refActivity.info.minAspectRatio;
            if (aspect > maxAspectRatio && maxAspectRatio != 0) {
                aspect = maxAspectRatio;
            } else if (aspect < minAspectRatio) {
                aspect = minAspectRatio;
            }
        }

        // Override from config_letterboxAspectRatio or via ADB with set-letterbox-aspect-ratio.
        final float letterboxAspectRatioOverride = mWmService.getTaskLetterboxAspectRatio();
        // Activity min/max aspect ratio restrictions will be respected by the activity-level
        // letterboxing (size-compat mode). Therefore this override can control the maximum screen
        // area that can be occupied by the app in the letterbox mode.
        aspect = letterboxAspectRatioOverride > MIN_TASK_LETTERBOX_ASPECT_RATIO
                ? letterboxAspectRatioOverride : aspect;

        // Store the current bounds to be able to revert to size compat mode values below if needed.
        mTmpFullBounds.set(outBounds);
        if (forcedOrientation == ORIENTATION_LANDSCAPE) {
            final int height = (int) Math.rint(parentWidth / aspect);
            final int top = parentBounds.centerY() - height / 2;
            outBounds.set(parentBounds.left, top, parentBounds.right, top + height);
        } else {
            final int width = (int) Math.rint(parentHeight / aspect);
            final int left = parentBounds.centerX() - width / 2;
            outBounds.set(left, parentBounds.top, left + width, parentBounds.bottom);
        }

        if (compatDisplayInsets != null) {
            compatDisplayInsets.getBoundsByRotation(
                    mTmpBounds, newParentConfig.windowConfiguration.getRotation());
            if (outBounds.width() != mTmpBounds.width()
                    || outBounds.height() != mTmpBounds.height()) {
                // The app shouldn't be resized, we only do task letterboxing if the compat bounds
                // is also from the same task letterbox. Otherwise, clear the task bounds to show
                // app in size compat mode.
                outBounds.set(mTmpFullBounds);
            }
        }
    }

    Rect updateOverrideConfigurationFromLaunchBounds() {
        // If the task is controlled by another organized task, do not set override
        // configurations and let its parent (organized task) to control it;
@@ -3038,11 +2924,6 @@ class Task extends WindowContainer<WindowContainer> {
        return bounds;
    }

    @Nullable
    ActivityRecord getLastTaskBoundsComputeActivity() {
        return mLastTaskBoundsComputeActivity;
    }

    /** Updates the task's bounds and override configuration to match what is expected for the
     * input root task. */
    void updateOverrideConfigurationForRootTask(Task inRootTask) {
@@ -3941,12 +3822,6 @@ class Task extends WindowContainer<WindowContainer> {
                || activityType == ACTIVITY_TYPE_ASSISTANT;
    }

    boolean isTaskLetterboxed() {
        // No letterbox for multi window root task
        return !matchParentBounds()
                && (getWindowingMode() == WINDOWING_MODE_FULLSCREEN || !isRootTask());
    }

    @Override
    boolean fillsParent() {
        // From the perspective of policy, we still want to report that this task fills parent
+15 −7
Original line number Diff line number Diff line
@@ -3821,13 +3821,21 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
    /** @return true when the window should be letterboxed. */
    boolean isLetterboxedAppWindow() {
        // Fullscreen mode but doesn't fill display area.
        return (!inMultiWindowMode() && !matchesDisplayAreaBounds())
        if (!inMultiWindowMode() && !matchesDisplayAreaBounds()) {
            return true;
        }
        if (mActivityRecord != null) {
            // Activity in size compat.
                || (mActivityRecord != null && mActivityRecord.inSizeCompatMode())
                // Task letterboxed.
                || (getTask() != null && getTask().isTaskLetterboxed())
            if (mActivityRecord.inSizeCompatMode()) {
                return true;
            }
            // Letterbox for fixed orientation.
            if (mActivityRecord.isLetterboxedForFixedOrientationAndAspectRatio()) {
                return true;
            }
        }
        // Letterboxed for display cutout.
                || isLetterboxedForDisplayCutout();
        return isLetterboxedForDisplayCutout();
    }

    /** Returns {@code true} if the window is letterboxed for the display cutout. */
+3 −2
Original line number Diff line number Diff line
@@ -555,9 +555,10 @@ public class ActivityRecordTests extends WindowTestsBase {
        activity.setRequestedOrientation(
                isScreenPortrait ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE);

        // Asserts it has orientation derived from bounds.
        assertEquals(isScreenPortrait ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT,
        // Asserts it has orientation derived requested orientation (fixed orientation letterbox).
        assertEquals(isScreenPortrait ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE,
                activity.getConfiguration().orientation);
        assertTrue(activity.isLetterboxedForFixedOrientationAndAspectRatio());
    }

    @Test
+17 −17
Original line number Diff line number Diff line
@@ -171,7 +171,7 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase {
        final Rect activityBounds = new Rect(mFirstActivity.getBounds());

        // DAG is portrait (860x1200), so Task and Activity fill DAG.
        assertThat(mFirstTask.isTaskLetterboxed()).isFalse();
        assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
        assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
        assertThat(taskBounds).isEqualTo(dagBounds);
        assertThat(activityBounds).isEqualTo(taskBounds);
@@ -194,8 +194,8 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase {
        final Rect activityConfigBounds =
                new Rect(mFirstActivity.getConfiguration().windowConfiguration.getBounds());

        // DAG is landscape (1200x860), Task fills parent
        assertThat(mFirstTask.isTaskLetterboxed()).isFalse();
        // DAG is landscape (1200x860), no fixed orientation letterbox
        assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
        assertThat(mFirstActivity.inSizeCompatMode()).isTrue();
        assertThat(newDagBounds.width()).isEqualTo(dagBounds.height());
        assertThat(newDagBounds.height()).isEqualTo(dagBounds.width());
@@ -211,7 +211,7 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase {
    }

    @Test
    public void testLaunchLandscapeApp_taskIsLetterboxInDisplayAreaGroup() {
    public void testLaunchLandscapeApp_activityIsLetterboxForFixedOrientationInDisplayAreaGroup() {
        mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
@@ -221,17 +221,18 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase {
        final Rect taskBounds = new Rect(mFirstTask.getBounds());
        final Rect activityBounds = new Rect(mFirstActivity.getBounds());

        // DAG is portrait (860x1200), so Task is letterbox (860x[860x860/1200=616])
        assertThat(mFirstTask.isTaskLetterboxed()).isTrue();
        // DAG is portrait (860x1200), and activity is letterboxed for fixed orientation
        // (860x[860x860/1200=616]). Task fills DAG.
        assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isTrue();
        assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
        assertThat(taskBounds.width()).isEqualTo(dagBounds.width());
        assertThat(taskBounds.height())
        assertThat(taskBounds).isEqualTo(dagBounds);
        assertThat(activityBounds.width()).isEqualTo(dagBounds.width());
        assertThat(activityBounds.height())
                .isEqualTo(dagBounds.width() * dagBounds.width() / dagBounds.height());
        assertThat(activityBounds).isEqualTo(taskBounds);
    }

    @Test
    public void testLaunchLandscapeApp_taskLetterboxBecomesActivityLetterboxAfterRotation() {
    public void testLaunchLandscapeApp_fixedOrientationLetterboxBecomesSizeCompatAfterRotation() {
        mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
@@ -245,9 +246,8 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase {
        final Rect newTaskBounds = new Rect(mFirstTask.getBounds());
        final Rect newActivityBounds = new Rect(mFirstActivity.getBounds());

        // DAG is landscape (1200x860), Task fills parent
        // Task letterbox size
        assertThat(mFirstTask.isTaskLetterboxed()).isFalse();
        // DAG is landscape (1200x860), no fixed orientation letterbox
        assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
        assertThat(mFirstActivity.inSizeCompatMode()).isTrue();
        assertThat(newDagBounds.width()).isEqualTo(dagBounds.height());
        assertThat(newDagBounds.height()).isEqualTo(dagBounds.width());
@@ -311,7 +311,7 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase {
    }

    @Test
    public void testResizableFixedOrientationApp_taskLevelLetterboxing() {
    public void testResizableFixedOrientationApp_fixedOrientationLetterboxing() {
        mFirstRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */);
        mSecondRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */);

@@ -324,7 +324,7 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase {
        assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE);
        assertThat(mFirstRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT);
        assertThat(mFirstActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT);
        assertThat(mFirstTask.isTaskLetterboxed()).isFalse();
        assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
        assertThat(mFirstActivity.inSizeCompatMode()).isFalse();

        // Launch portrait on second DAG
@@ -336,13 +336,13 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase {
        assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_PORTRAIT);
        assertThat(mSecondRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE);
        assertThat(mSecondActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE);
        assertThat(mSecondTask.isTaskLetterboxed()).isFalse();
        assertThat(mSecondActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
        assertThat(mSecondActivity.inSizeCompatMode()).isFalse();

        // First activity is letterboxed in portrait as requested.
        assertThat(mFirstRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE);
        assertThat(mFirstActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT);
        assertThat(mFirstTask.isTaskLetterboxed()).isTrue();
        assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isTrue();
        assertThat(mFirstActivity.inSizeCompatMode()).isFalse();

    }
Loading