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

Commit de03a722 authored by Massimo Carli's avatar Massimo Carli
Browse files

Fix scale for translucent activities

The transparent activity needs to use the compatScale
from the opaque one beneath in order to resize properly
after rotation or when in multi-window mode.

Bug: 261721427
Test: run `atest SizeCompatTests`

Change-Id: Id856eae6786cc7c58041becf89ab4603f7fe6f51
parent a100480a
Loading
Loading
Loading
Loading
+56 −38
Original line number Diff line number Diff line
@@ -7879,6 +7879,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
        return mCompatDisplayInsets;
    }

    /**
     * @return The {@code true} if the current instance has {@link mCompatDisplayInsets} without
     * considering the inheritance implemented in {@link #getCompatDisplayInsets()}
     */
    boolean hasCompatDisplayInsetsWithoutInheritance() {
        return mCompatDisplayInsets != null;
    }

    /**
     * @return {@code true} if this activity is in size compatibility mode that uses the different
     *         density than its parent or its bounds don't fit in parent naturally.
@@ -7887,7 +7895,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
        if (mInSizeCompatModeForBounds) {
            return true;
        }
        if (mCompatDisplayInsets == null || !shouldCreateCompatDisplayInsets()
        if (getCompatDisplayInsets() == null || !shouldCreateCompatDisplayInsets()
                // The orientation is different from parent when transforming.
                || isFixedRotationTransforming()) {
            return false;
@@ -7958,11 +7966,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A

    // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
    private void updateCompatDisplayInsets() {
        if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
            mCompatDisplayInsets =  mLetterboxUiController.getInheritedCompatDisplayInsets();
            return;
        }
        if (mCompatDisplayInsets != null || !shouldCreateCompatDisplayInsets()) {
        if (getCompatDisplayInsets() != null || !shouldCreateCompatDisplayInsets()) {
            // The override configuration is set only once in size compatibility mode.
            return;
        }
@@ -8025,9 +8029,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A

    @Override
    float getCompatScale() {
        if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
            return mLetterboxUiController.getInheritedSizeCompatScale();
        }
        return hasSizeCompatBounds() ? mSizeCompatScale : super.getCompatScale();
    }

@@ -8071,7 +8072,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
            resolveFixedOrientationConfiguration(newParentConfiguration);
        }

        if (mCompatDisplayInsets != null) {
        if (getCompatDisplayInsets() != null) {
            resolveSizeCompatModeConfiguration(newParentConfiguration);
        } else if (inMultiWindowMode() && !isFixedOrientationLetterboxAllowed) {
            // We ignore activities' requested orientation in multi-window modes. They may be
@@ -8089,7 +8090,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
            resolveAspectRatioRestriction(newParentConfiguration);
        }

        if (isFixedOrientationLetterboxAllowed || mCompatDisplayInsets != null
        if (isFixedOrientationLetterboxAllowed || getCompatDisplayInsets() != null
                // In fullscreen, can be letterboxed for aspect ratio.
                || !inMultiWindowMode()) {
            updateResolvedBoundsPosition(newParentConfiguration);
@@ -8097,7 +8098,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A

        boolean isIgnoreOrientationRequest = mDisplayContent != null
                && mDisplayContent.getIgnoreOrientationRequest();
        if (mCompatDisplayInsets == null // for size compat mode set in updateCompatDisplayInsets
        if (getCompatDisplayInsets() == null
                // for size compat mode set in updateCompatDisplayInsets
                // Fixed orientation letterboxing is possible on both large screen devices
                // with ignoreOrientationRequest enabled and on phones in split screen even with
                // ignoreOrientationRequest disabled.
@@ -8143,7 +8145,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
                        info.neverSandboxDisplayApis(sConstrainDisplayApisConfig),
                        info.alwaysSandboxDisplayApis(sConstrainDisplayApisConfig),
                        !matchParentBounds(),
                        mCompatDisplayInsets != null,
                        getCompatDisplayInsets() != null,
                        shouldCreateCompatDisplayInsets());
            }
            resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds);
@@ -8442,8 +8444,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
                || orientationRespectedWithInsets)) {
            return;
        }
        final CompatDisplayInsets compatDisplayInsets = getCompatDisplayInsets();

        if (mCompatDisplayInsets != null && !mCompatDisplayInsets.mIsInFixedOrientationLetterbox) {
        if (compatDisplayInsets != null && !compatDisplayInsets.mIsInFixedOrientationLetterbox) {
            // App prefers to keep its original size.
            // If the size compat is from previous fixed orientation letterboxing, we may want to
            // have fixed orientation letterbox again, otherwise it will show the size compat
@@ -8498,9 +8501,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
        mIsAspectRatioApplied = applyAspectRatio(resolvedBounds, containingBoundsWithInsets,
                containingBounds, desiredAspectRatio);

        if (mCompatDisplayInsets != null) {
            mCompatDisplayInsets.getBoundsByRotation(
                    mTmpBounds, newParentConfig.windowConfiguration.getRotation());
        if (compatDisplayInsets != null) {
            compatDisplayInsets.getBoundsByRotation(mTmpBounds,
                    newParentConfig.windowConfiguration.getRotation());
            if (resolvedBounds.width() != mTmpBounds.width()
                    || resolvedBounds.height() != mTmpBounds.height()) {
                // The app shouldn't be resized, we only do fixed orientation letterboxing if the
@@ -8514,7 +8517,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
        // Calculate app bounds using fixed orientation bounds because they will be needed later
        // for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}.
        getTaskFragment().computeConfigResourceOverrides(getResolvedOverrideConfiguration(),
                newParentConfig, mCompatDisplayInsets);
                newParentConfig, compatDisplayInsets);
        mLetterboxBoundsForFixedOrientationAndAspectRatio = new Rect(resolvedBounds);
    }

@@ -8571,13 +8574,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
                ? requestedOrientation
                // We should use the original orientation of the activity when possible to avoid
                // forcing the activity in the opposite orientation.
                : mCompatDisplayInsets.mOriginalRequestedOrientation != ORIENTATION_UNDEFINED
                        ? mCompatDisplayInsets.mOriginalRequestedOrientation
                : getCompatDisplayInsets().mOriginalRequestedOrientation != ORIENTATION_UNDEFINED
                        ? getCompatDisplayInsets().mOriginalRequestedOrientation
                        : newParentConfiguration.orientation;
        int rotation = newParentConfiguration.windowConfiguration.getRotation();
        final boolean isFixedToUserRotation = mDisplayContent == null
                || mDisplayContent.getDisplayRotation().isFixedToUserRotation();
        if (!isFixedToUserRotation && !mCompatDisplayInsets.mIsFloating) {
        if (!isFixedToUserRotation && !getCompatDisplayInsets().mIsFloating) {
            // Use parent rotation because the original display can be rotated.
            resolvedConfig.windowConfiguration.setRotation(rotation);
        } else {
@@ -8593,11 +8596,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
        // rely on them to contain the original and unchanging width and height of the app.
        final Rect containingAppBounds = new Rect();
        final Rect containingBounds = mTmpBounds;
        mCompatDisplayInsets.getContainerBounds(containingAppBounds, containingBounds, rotation,
        getCompatDisplayInsets().getContainerBounds(containingAppBounds, containingBounds, rotation,
                orientation, orientationRequested, isFixedToUserRotation);
        resolvedBounds.set(containingBounds);
        // The size of floating task is fixed (only swap), so the aspect ratio is already correct.
        if (!mCompatDisplayInsets.mIsFloating) {
        if (!getCompatDisplayInsets().mIsFloating) {
            mIsAspectRatioApplied =
                    applyAspectRatio(resolvedBounds, containingAppBounds, containingBounds);
        }
@@ -8606,7 +8609,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
        // are calculated in compat container space. The actual position on screen will be applied
        // later, so the calculation is simpler that doesn't need to involve offset from parent.
        getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
                mCompatDisplayInsets);
                getCompatDisplayInsets());
        // Use current screen layout as source because the size of app is independent to parent.
        resolvedConfig.screenLayout = TaskFragment.computeScreenLayoutOverride(
                getConfiguration().screenLayout, resolvedConfig.screenWidthDp,
@@ -8641,14 +8644,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A

        // Calculates the scale the size compatibility bounds into the region which is available
        // to application.
        final int contentW = resolvedAppBounds.width();
        final int contentH = resolvedAppBounds.height();
        final int viewportW = containerAppBounds.width();
        final int viewportH = containerAppBounds.height();
        final float lastSizeCompatScale = mSizeCompatScale;
        // Only allow to scale down.
        mSizeCompatScale = (contentW <= viewportW && contentH <= viewportH)
                ? 1f : Math.min((float) viewportW / contentW, (float) viewportH / contentH);
        updateSizeCompatScale(resolvedAppBounds, containerAppBounds);

        final int containerTopInset = containerAppBounds.top - containerBounds.top;
        final boolean topNotAligned =
                containerTopInset != resolvedAppBounds.top - resolvedBounds.top;
@@ -8688,6 +8686,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
                isInSizeCompatModeForBounds(resolvedAppBounds, containerAppBounds);
    }

    void updateSizeCompatScale(Rect resolvedAppBounds, Rect containerAppBounds) {
        // Only allow to scale down.
        mSizeCompatScale = mLetterboxUiController.findOpaqueNotFinishingActivityBelow()
                .map(activityRecord -> activityRecord.mSizeCompatScale)
                .orElseGet(() -> {
                    final int contentW = resolvedAppBounds.width();
                    final int contentH = resolvedAppBounds.height();
                    final int viewportW = containerAppBounds.width();
                    final int viewportH = containerAppBounds.height();
                    return (contentW <= viewportW && contentH <= viewportH) ? 1f : Math.min(
                            (float) viewportW / contentW, (float) viewportH / contentH);
                });
    }

    private boolean isInSizeCompatModeForBounds(final Rect appBounds, final Rect containerBounds) {
        if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
            // To avoid wrong app behaviour, we decided to disable SCM when a translucent activity
@@ -8750,10 +8762,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A

    @Override
    public Rect getBounds() {
        // TODO(b/268458693): Refactor configuration inheritance in case of translucent activities
        final Rect superBounds = super.getBounds();
        return mLetterboxUiController.findOpaqueNotFinishingActivityBelow()
                .map(ActivityRecord::getBounds)
                .orElseGet(() -> {
                    if (mSizeCompatBounds != null) {
                        return mSizeCompatBounds;
                    }
        return super.getBounds();
                    return superBounds;
                });
    }

    @Override
@@ -8778,7 +8796,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
        // Max bounds should be sandboxed when an activity should have compatDisplayInsets, and it
        // will keep the same bounds and screen configuration when it was first launched regardless
        // how its parent window changes, so that the sandbox API will provide a consistent result.
        if (mCompatDisplayInsets != null || shouldCreateCompatDisplayInsets()) {
        if (getCompatDisplayInsets() != null || shouldCreateCompatDisplayInsets()) {
            return true;
        }

@@ -8820,7 +8838,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
                mTransitionController.collect(this);
            }
        }
        if (mCompatDisplayInsets != null) {
        if (getCompatDisplayInsets() != null) {
            Configuration overrideConfig = getRequestedOverrideConfiguration();
            // Adapt to changes in orientation locking. The app is still non-resizable, but
            // it can change which orientation is fixed. If the fixed orientation changes,
@@ -8896,7 +8914,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
        if (mVisibleRequested) {
            // It may toggle the UI for user to restart the size compatibility mode activity.
            display.handleActivitySizeCompatModeIfNeeded(this);
        } else if (mCompatDisplayInsets != null && !visibleIgnoringKeyguard
        } else if (getCompatDisplayInsets() != null && !visibleIgnoringKeyguard
                && (app == null || !app.hasVisibleActivities())) {
            // visibleIgnoringKeyguard is checked to avoid clearing mCompatDisplayInsets during
            // displays change. Displays are turned off during the change so mVisibleRequested
+20 −20
Original line number Diff line number Diff line
@@ -193,12 +193,6 @@ final class LetterboxUiController {
    // The app compat state for the opaque activity if any
    private int mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN;

    // If true it means that the opaque activity beneath a translucent one is in SizeCompatMode.
    private boolean mIsInheritedInSizeCompatMode;

    // This is the SizeCompatScale of the opaque activity beneath a translucent one
    private float mInheritedSizeCompatScale;

    // The CompatDisplayInsets of the opaque activity beneath the translucent one.
    private ActivityRecord.CompatDisplayInsets mInheritedCompatDisplayInsets;

@@ -735,8 +729,21 @@ final class LetterboxUiController {
                    : mActivityRecord.inMultiWindowMode()
                            ? mActivityRecord.getTask().getBounds()
                            : mActivityRecord.getRootTask().getParent().getBounds();
            // In case of translucent activities an option is to use the WindowState#getFrame() of
            // the first opaque activity beneath. In some cases (e.g. an opaque activity is using
            // non MATCH_PARENT layouts or a Dialog theme) this might not provide the correct
            // information and in particular it might provide a value for a smaller area making
            // the letterbox overlap with the translucent activity's frame.
            // If we use WindowState#getFrame() for the translucent activity's letterbox inner
            // frame, the letterbox will then be overlapped with the translucent activity's frame.
            // Because the surface layer of letterbox is lower than an activity window, this
            // won't crop the content, but it may affect other features that rely on values stored
            // in mLetterbox, e.g. transitions, a status bar scrim and recents preview in Launcher
            // For this reason we use ActivityRecord#getBounds() that the translucent activity
            // inherits from the first opaque activity beneath and also takes care of the scaling
            // in case of activities in size compat mode.
            final Rect innerFrame = hasInheritedLetterboxBehavior()
                    ? mActivityRecord.getWindowConfiguration().getBounds() : w.getFrame();
                    ? mActivityRecord.getBounds() : w.getFrame();
            mLetterbox.layout(spaceToFill, innerFrame, mTmpPoint);
        } else if (mLetterbox != null) {
            mLetterbox.hide();
@@ -1386,10 +1393,10 @@ final class LetterboxUiController {
            mLetterboxConfigListener.onRemoved();
            clearInheritedConfig();
        }
        // In case mActivityRecord.getCompatDisplayInsets() is not null we don't apply the
        // In case mActivityRecord.hasCompatDisplayInsetsWithoutOverride() we don't apply the
        // opaque activity constraints because we're expecting the activity is already letterboxed.
        if (mActivityRecord.getTask() == null || mActivityRecord.getCompatDisplayInsets() != null
                || mActivityRecord.fillsParent()) {
        if (mActivityRecord.getTask() == null || mActivityRecord.fillsParent()
                || mActivityRecord.hasCompatDisplayInsetsWithoutInheritance()) {
            return;
        }
        final ActivityRecord firstOpaqueActivityBeneath = mActivityRecord.getTask().getActivity(
@@ -1417,6 +1424,7 @@ final class LetterboxUiController {
                    // We need to initialize appBounds to avoid NPE. The actual value will
                    // be set ahead when resolving the Configuration for the activity.
                    mutatedConfiguration.windowConfiguration.setAppBounds(new Rect());
                    inheritConfiguration(firstOpaqueActivityBeneath);
                    return mutatedConfiguration;
                });
    }
@@ -1457,16 +1465,12 @@ final class LetterboxUiController {
        return mInheritedAppCompatState;
    }

    float getInheritedSizeCompatScale() {
        return mInheritedSizeCompatScale;
    }

    @Configuration.Orientation
    int getInheritedOrientation() {
        return mInheritedOrientation;
    }

    public ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() {
    ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() {
        return mInheritedCompatDisplayInsets;
    }

@@ -1486,7 +1490,7 @@ final class LetterboxUiController {
     * @return The first not finishing opaque activity beneath the current translucent activity
     * if it exists and the strategy is enabled.
     */
    private Optional<ActivityRecord> findOpaqueNotFinishingActivityBelow() {
    Optional<ActivityRecord> findOpaqueNotFinishingActivityBelow() {
        if (!hasInheritedLetterboxBehavior() || mActivityRecord.getTask() == null) {
            return Optional.empty();
        }
@@ -1508,8 +1512,6 @@ final class LetterboxUiController {
        }
        mInheritedOrientation = firstOpaque.getRequestedConfigurationOrientation();
        mInheritedAppCompatState = firstOpaque.getAppCompatState();
        mIsInheritedInSizeCompatMode = firstOpaque.inSizeCompatMode();
        mInheritedSizeCompatScale = firstOpaque.getCompatScale();
        mInheritedCompatDisplayInsets = firstOpaque.getCompatDisplayInsets();
    }

@@ -1519,8 +1521,6 @@ final class LetterboxUiController {
        mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO;
        mInheritedOrientation = Configuration.ORIENTATION_UNDEFINED;
        mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN;
        mIsInheritedInSizeCompatMode = false;
        mInheritedSizeCompatScale = 1f;
        mInheritedCompatDisplayInsets = null;
    }
}
+5 −0
Original line number Diff line number Diff line
@@ -3455,6 +3455,11 @@ class Task extends TaskFragment {
                && top.getOrganizedTask() == this && top.isState(RESUMED);
        // Whether the direct top activity is in size compat mode on foreground.
        info.topActivityInSizeCompat = isTopActivityResumed && top.inSizeCompatMode();
        if (info.topActivityInSizeCompat
                && mWmService.mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) {
            // We hide the restart button in case of transparent activities.
            info.topActivityInSizeCompat = top.fillsParent();
        }
        // Whether the direct top activity is eligible for letterbox education.
        info.topActivityEligibleForLetterboxEducation = isTopActivityResumed
                && top.isEligibleForLetterboxEducation();
+9 −3
Original line number Diff line number Diff line
@@ -274,7 +274,8 @@ public class SizeCompatTests extends WindowTestsBase {
    public void testTranslucentActivitiesWhenUnfolding() {
        mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
        setUpDisplaySizeWithApp(2800, 1400);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(
                true /* ignoreOrientationRequest */);
        mActivity.mWmService.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(
                1.0f /*letterboxVerticalPositionMultiplier*/);
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
@@ -285,18 +286,23 @@ public class SizeCompatTests extends WindowTestsBase {
                .build();
        doReturn(false).when(translucentActivity).fillsParent();
        mTask.addChild(translucentActivity);
        assertEquals(translucentActivity.getBounds(), mActivity.getBounds());

        mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
        spyOn(mActivity);

        // Halffold
        setFoldablePosture(translucentActivity, true /* isHalfFolded */, false /* isTabletop */);
        setFoldablePosture(translucentActivity, true /* isHalfFolded */,
                false /* isTabletop */);
        verify(mActivity).recomputeConfiguration();
        assertEquals(translucentActivity.getBounds(), mActivity.getBounds());
        clearInvocations(mActivity);

        // Unfold
        setFoldablePosture(translucentActivity, false /* isHalfFolded */, false /* isTabletop */);
        setFoldablePosture(translucentActivity, false /* isHalfFolded */,
                false /* isTabletop */);
        verify(mActivity).recomputeConfiguration();
        assertEquals(translucentActivity.getBounds(), mActivity.getBounds());
    }

    @Test