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

Commit faafea6e authored by Kazuki Takise's avatar Kazuki Takise
Browse files

Upscale SCM for display and windowing mode changes

Currently upscaling is generally not allowed in fullscreen, but
with connected displays, we've got a few cases where upscaling
should be enabled in fullscreen:

1. An app moves between displays. Especially after an SCM app
moves from external to phone screen, the app content can become
very small with a big letterbox.
2. An app becomes fullscreen from freeform.

As an exception, ignore-orientation-request internal displays
are explicitly excluded from this treatment because there are
already some scenarios SCM apps are not upscaled on those
displays such as orientation changes, and introducing this
treatment could make the scaling logic incnosistent and
confusing.

For now, we allow upscaling only to these two cases (besides the
existing conditions), but these heuristics will be revisited
later to enable upscaling in more cases such as desktop-first.

Flag: com.android.window.flags.enable_upscaling_size_compat_on_exiting_desktop_bugfix
Bug: 432329483
Test: SizeCompatTests#testUpscaling_boundsUpscaledWithWindowingModeChange
Change-Id: I1e64317d10f0ef1c0b6a5f3e6888e8ec9606e0ea
parent b238d2d1
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -53,6 +53,9 @@ class AppCompatDisplayCompatModePolicy {
    @NonNull
    private final ActivityRecord mActivityRecord;

    /**
     * {@code true} if the activity has moved to a different display and has not been restarted yet.
     */
    private boolean mDisplayChangedWithoutRestart;

    AppCompatDisplayCompatModePolicy(@NonNull ActivityRecord activityRecord) {
@@ -107,6 +110,14 @@ class AppCompatDisplayCompatModePolicy {
        }
    }

    /**
     * Returns {@code true} if the activity has moved to a different display and has not been
     * restarted yet.
     */
    boolean getDisplayChangedWithoutRestart() {
        return mDisplayChangedWithoutRestart;
    }

    /**
     * Called when the activity's process is restarted.
     */
+8 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;

import static com.android.server.wm.AppCompatUtils.computeAspectRatio;
import static com.android.server.wm.AppCompatUtils.isInDesktopMode;

import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -64,6 +65,11 @@ class AppCompatDisplayInsets {
     * {@link Configuration#screenHeightDp}.
     */
    final Rect[] mStableInsets = new Rect[4];
    /**
     * Whether the activity was originally launched in desktop mode or not. Used for the heuristic
     * to determine whether the app should be upscaled or not.
     */
    final boolean mInDesktopMode;

    /** Constructs the environment to simulate the bounds behavior of the given container. */
    AppCompatDisplayInsets(@NonNull DisplayContent display, @NonNull ActivityRecord container,
@@ -71,6 +77,8 @@ class AppCompatDisplayInsets {
        mOriginalRotation = display.getRotation();
        mIsFloating = container.getWindowConfiguration().tasksAreFloating();
        mOriginalRequestedOrientation = container.getRequestedConfigurationOrientation();
        mInDesktopMode = isInDesktopMode(container.mAtmService.mContext,
                container.getWindowConfiguration().getWindowingMode());
        if (mIsFloating) {
            final Rect containerBounds = container.getWindowConfiguration().getBounds();
            mWidth = containerBounds.width();
+38 −4
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import static android.content.pm.ActivityInfo.SIZE_CHANGES_UNSUPPORTED_OVERRIDE;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.internal.perfetto.protos.Windowmanagerservice.ActivityRecordProto.IN_SIZE_COMPAT_MODE;
import static android.window.DesktopExperienceFlags.ENABLE_SIZE_COMPAT_MODE_IMPROVEMENTS_FOR_CONNECTED_DISPLAYS;
import static android.window.DesktopExperienceFlags.ENABLE_UPSCALING_SIZE_COMPAT_ON_EXITING_DESKTOP_BUGFIX;

import static com.android.server.wm.AppCompatUtils.isInDesktopMode;

@@ -33,6 +34,7 @@ import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.util.proto.ProtoOutputStream;
import android.view.Display;

import java.io.PrintWriter;
import java.util.function.DoubleSupplier;
@@ -572,12 +574,44 @@ class AppCompatSizeCompatModePolicy {
        final int contentH = resolvedAppBounds.height();
        final int viewportW = containerAppBounds.width();
        final int viewportH = containerAppBounds.height();
        // Allow an application to be up-scaled if its window is smaller than its
        // original container or if it's a freeform window in desktop mode.
        boolean shouldAllowUpscaling = !(contentW <= viewportW && contentH <= viewportH)
                || isInDesktopMode(mActivityRecord.mAtmService.mContext,
        final boolean isInDesktopMode = isInDesktopMode(mActivityRecord.mAtmService.mContext,
                newParentConfig.windowConfiguration.getWindowingMode());
        // Allow an application to be up-scaled if its window is smaller than its
        // original container, if it's a freeform window in desktop mode, or if display or windowing
        // mode has changed in some special conditions.
        final boolean shouldAllowUpscaling = !(contentW <= viewportW && contentH <= viewportH)
                || isInDesktopMode
                || shouldAllowUpscalingForDisplayOrWindowingModeChange(isInDesktopMode);
        return shouldAllowUpscaling ? Math.min(
                (float) viewportW / contentW, (float) viewportH / contentH) : 1f;
    }

    /**
     * Returns whether the activity should be upscaled due to a change in display or windowing
     * mode. Upscaling is generally disabled in fullscreen to avoid pixelation etc. However, it is
     * enabled in specific scenarios to prevent the app from becoming too small in a parent window,
     * such as when:
     * - Moving from an external display to a smaller phone screen.
     * - Transitioning from desktop mode to fullscreen.
     * This treatment is not applied to internal displays that ignore orientation requests to
     * maintain consistent scaling behavior with orientation changes on those displays.
     */
    private boolean shouldAllowUpscalingForDisplayOrWindowingModeChange(boolean isInDesktopMode) {
        final boolean launchedInAndExitedFromDesktop  = getAppCompatDisplayInsets() != null
                && getAppCompatDisplayInsets().mInDesktopMode && !isInDesktopMode;
        final boolean hasMovedBetweenDisplays = mActivityRecord.mAppCompatController
                .getDisplayCompatModePolicy().getDisplayChangedWithoutRestart();
        final boolean isOnIgnoreOrientationRequestInternalDisplay = isOnInternalDisplay()
                && mActivityRecord.getDisplayContent().getIgnoreOrientationRequest();

        // TODO(b/432329483): Polish the policy for desktop-first devices.
        return ENABLE_UPSCALING_SIZE_COMPAT_ON_EXITING_DESKTOP_BUGFIX.isTrue()
                && (launchedInAndExitedFromDesktop || hasMovedBetweenDisplays)
                && !isOnIgnoreOrientationRequestInternalDisplay;
    }

    /** Returns whether the activity is on an internal display. */
    private boolean isOnInternalDisplay() {
        return mActivityRecord.getDisplayContent().getDisplay().getType() == Display.TYPE_INTERNAL;
    }
}
+58 −2
Original line number Diff line number Diff line
@@ -34,6 +34,9 @@ import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCRE
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.TYPE_EXTERNAL;
import static android.view.Display.TYPE_INTERNAL;
import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_180;
@@ -67,7 +70,6 @@ import static com.android.server.wm.ActivityRecord.State.STOPPED;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_POSITION_MULTIPLIER_CENTER;
import static com.android.server.wm.AppCompatUtils.computeAspectRatio;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.window.flags.Flags.FLAG_ENABLE_SIZE_COMPAT_MODE_IMPROVEMENTS_FOR_CONNECTED_DISPLAYS;

import static com.google.common.truth.Truth.assertThat;

@@ -564,7 +566,7 @@ public class SizeCompatTests extends WindowTestsBase {
        assertDownScaled();
    }

    @EnableFlags(FLAG_ENABLE_SIZE_COMPAT_MODE_IMPROVEMENTS_FOR_CONNECTED_DISPLAYS)
    @EnableFlags(Flags.FLAG_ENABLE_SIZE_COMPAT_MODE_IMPROVEMENTS_FOR_CONNECTED_DISPLAYS)
    @Test
    public void testFixedMiscConfigurationWhenMovingToDisplay() {
        setUpDisplaySizeWithApp(1000, 2500);
@@ -4594,6 +4596,60 @@ public class SizeCompatTests extends WindowTestsBase {
        assertEquals(notchHeight, appBounds.top - bounds.top);
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_UPSCALING_SIZE_COMPAT_ON_EXITING_DESKTOP_BUGFIX)
    public void testUpscaling_boundsUpscaledWithWindowingModeChange() {
        allowDesktopMode();

        // Launch an SCM app in freeform on an external display.
        final int dw = 2000;
        final int dh = 1600;
        final DisplayInfo displayInfo = new DisplayInfo();
        displayInfo.copyFrom(mDisplayInfo);
        displayInfo.type = TYPE_EXTERNAL;
        displayInfo.displayId = DEFAULT_DISPLAY + 1;
        displayInfo.logicalWidth = dw;
        displayInfo.logicalHeight = dh;
        final DisplayContent display = new TestDisplayContent.Builder(mAtm, displayInfo).build();
        display.getDefaultTaskDisplayArea()
                .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
        final TaskBuilder taskBuilder =
                new TaskBuilder(mSupervisor).setWindowingMode(WINDOWING_MODE_FREEFORM);
        setUpApp(display, null /* appBuilder */, taskBuilder);
        Rect bounds = new Rect(0, 0, 600, 800);
        mTask.setBounds(bounds);
        mTask.onConfigurationChanged(mTask.getDisplayArea().getConfiguration());
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
        assertEquals(WindowConfiguration.WINDOWING_MODE_FREEFORM, mTask.getWindowingMode());
        assertFitted();

        // Exit freeform into fullscreen.
        display.getDefaultTaskDisplayArea()
                .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
        mTask.setBounds(null);
        mActivity.onConfigurationChanged(mTask.getConfiguration());
        assertUpScaled();

        // Move to a fullscreen, ignore-orientation-request, internal display.
        final DisplayInfo internalDisplayInfo = new DisplayInfo();
        internalDisplayInfo.copyFrom(display.getDisplayInfo());
        internalDisplayInfo.type = TYPE_INTERNAL;
        internalDisplayInfo.displayId = display.getDisplayInfo().displayId + 1;
        final DisplayContent internalDisplay =
                new TestDisplayContent.Builder(mAtm, internalDisplayInfo).build();
        mTask.mWmService.mRoot.moveRootTaskToDisplay(mTask.mTaskId, internalDisplay.mDisplayId,
                true /* onTop */);
        internalDisplay.setIgnoreOrientationRequest(true);
        mActivity.onConfigurationChanged(mTask.getConfiguration());
        assertTrue(mActivity.inSizeCompatMode());
        assertEquals(1f, mActivity.getCompatScale(), 1e7);

        // Make the display not ignore-orientation-request.
        internalDisplay.setIgnoreOrientationRequest(false);
        mActivity.onConfigurationChanged(mTask.getConfiguration());
        assertUpScaled();
    }


    @Test
    @EnableFlags(Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES)