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

Commit 0177507b authored by Evan Rosky's avatar Evan Rosky
Browse files

Separate and add more SizeCompat tests; add Display Builder

The number of SizeCompat tests is growing so this
separates them out into their own class. Additionally,
it streamlines display and activity creation for the
variety of scenarios that need testing and facilitates
consistent display creation for tests (previous code
would create displays based on the device display which
meant the tests would potentially test different things
on different devices).

Also added a Builder to TestActivityDisplay. This
makes it possible to create displays with/without notch
and custom windowingmode/rotatibility/density/etc.

Also fixed a small bug where aspect-ratio was calculated
incorrectly.

Some SizeCompat tests were added too:
 testFixedAspOrientChangeOrient to verify that a sizecompat
   app can still change fixed orientation properly
 testMoveToDifferentOrientDisplay to verify that a sizecompat
   app properly moves between different non-rotateble displays
 testLetterboxFullscreenBounds to sanity check that sizecompat
   apps start properly when letterboxed by policy
 testFixedOrientRotateCutoutDisplay to verify that a sizecompat
   app stays fixed when on a rotating display with cutout.

Bug: 138594779
Test: this
Change-Id: Ic78374ae9e58297c609349c382efc6738521872c
parent 822b2f4d
Loading
Loading
Loading
Loading
+1 −2
Original line number Diff line number Diff line
@@ -6171,8 +6171,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
        // The rest of the condition is that only one side is smaller than the parent, but it still
        // needs to exclude the cases where the size is limited by the fixed aspect ratio.
        if (info.maxAspectRatio > 0) {
            final float aspectRatio = (0.5f + Math.max(appWidth, appHeight))
                    / Math.min(appWidth, appHeight);
            final float aspectRatio = Math.max(appWidth, appHeight) / Math.min(appWidth, appHeight);
            if (aspectRatio >= info.maxAspectRatio) {
                // The current size has reached the max aspect ratio.
                return false;
+0 −55
Original line number Diff line number Diff line
@@ -38,12 +38,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.when;

import android.app.TaskStackListener;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;

import androidx.test.filters.SmallTest;
@@ -51,10 +46,6 @@ import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
 * Tests for the {@link ActivityDisplay} class.
 *
@@ -331,50 +322,4 @@ public class ActivityDisplayTests extends ActivityTestsBase {
        verify(mSupervisor).removeTaskByIdLocked(eq(task1.mTaskId), anyBoolean(), anyBoolean(),
                any());
    }

    /**
     * Ensures that {@link TaskStackListener} can receive callback about the activity in size
     * compatibility mode.
     */
    @Test
    public void testHandleActivitySizeCompatMode() throws Exception {
        final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
        final ActivityRecord activity = createFullscreenStackWithSimpleActivityAt(
                display).topRunningActivityLocked();
        activity.setState(ActivityStack.ActivityState.RESUMED, "testHandleActivitySizeCompatMode");
        when(activity.getRequestedOrientation()).thenReturn(
                ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        activity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
        activity.info.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
        activity.visible = true;
        activity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);

        final ArrayList<CompletableFuture<IBinder>> resultWrapper = new ArrayList<>();
        mService.getTaskChangeNotificationController().registerTaskStackListener(
                new TaskStackListener() {
                    @Override
                    public void onSizeCompatModeActivityChanged(int displayId,
                            IBinder activityToken) {
                        resultWrapper.get(0).complete(activityToken);
                    }
                });

        resultWrapper.add(new CompletableFuture<>());

        // resize the display to exercise size-compat mode
        final DisplayContent displayContent = display.mDisplayContent;
        displayContent.mBaseDisplayHeight = (int) (0.8f * displayContent.mBaseDisplayHeight);
        Configuration c = new Configuration();
        displayContent.computeScreenConfiguration(c);
        display.onRequestedOverrideConfigurationChanged(c);

        assertEquals(activity.appToken, resultWrapper.get(0).get(2, TimeUnit.SECONDS));

        // Expect null token when switching to non-size-compat mode activity.
        activity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
        resultWrapper.set(0, new CompletableFuture<>());
        display.handleActivitySizeCompatModeIfNeeded(activity);

        assertNull(resultWrapper.get(0).get(2, TimeUnit.SECONDS));
    }
}
+0 −267
Original line number Diff line number Diff line
@@ -18,24 +18,18 @@ package com.android.server.wm;

import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.os.Process.NOBODY_UID;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_90;
import static android.view.WindowManager.TRANSIT_TASK_CLOSE;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
@@ -61,18 +55,13 @@ import static com.google.common.truth.Truth.assertThat;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.when;

import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
import android.app.WindowConfiguration;
import android.app.servertransaction.ActivityConfigurationChangeItem;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.PauseActivityItem;
@@ -80,13 +69,11 @@ import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.platform.test.annotations.Presubmit;
import android.util.MergedConfiguration;
import android.util.MutableBoolean;
import android.view.DisplayInfo;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner.Stub;
import android.view.RemoteAnimationAdapter;
@@ -218,23 +205,6 @@ public class ActivityRecordTests extends ActivityTestsBase {
        assertFalse(activity.canBeLaunchedOnDisplay(DEFAULT_DISPLAY + 1));
    }

    @Test
    public void testRestartProcessIfVisible() {
        doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity);
        mActivity.visible = true;
        mActivity.setSavedState(null /* savedState */);
        mActivity.setState(ActivityStack.ActivityState.RESUMED, "testRestart");
        prepareFixedAspectRatioUnresizableActivity();

        final Rect originalOverrideBounds = new Rect(mActivity.getBounds());
        setupDisplayAndParentSize(600, 1200);
        // The visible activity should recompute configuration according to the last parent bounds.
        mService.restartActivityProcessIfVisible(mActivity.appToken);

        assertEquals(ActivityStack.ActivityState.RESTARTING_PROCESS, mActivity.getState());
        assertNotEquals(originalOverrideBounds, mActivity.getBounds());
    }

    @Test
    public void testsApplyOptionsLocked() {
        ActivityOptions activityOptions = ActivityOptions.makeBasic();
@@ -483,214 +453,6 @@ public class ActivityRecordTests extends ActivityTestsBase {
        assertEquals(STARTED, mActivity.getState());
    }

    @Test
    public void testSizeCompatMode_KeepBoundsWhenChangingFromFreeformToFullscreen() {
        setupDisplayContentForCompatDisplayInsets();

        // put display in freeform mode
        ActivityDisplay display = mActivity.getDisplay();
        final Configuration c = new Configuration(display.getRequestedOverrideConfiguration());
        c.windowConfiguration.setBounds(new Rect(0, 0, 2000, 1000));
        c.densityDpi = 300;
        c.windowConfiguration.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
        display.onRequestedOverrideConfigurationChanged(c);

        // launch compat activity in freeform and store bounds
        when(mActivity.getRequestedOrientation()).thenReturn(
                ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        mTask.getRequestedOverrideConfiguration().orientation = Configuration.ORIENTATION_PORTRAIT;
        mTask.setBounds(100, 100, 400, 600);
        mActivity.info.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
        mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
        mActivity.visible = true;
        ensureActivityConfiguration();

        final Rect bounds = new Rect(mActivity.getBounds());
        final int density = mActivity.getConfiguration().densityDpi;

        // change display configuration to fullscreen
        c.windowConfiguration.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
        display.onRequestedOverrideConfigurationChanged(c);

        // check if dimensions stay the same
        assertTrue(mActivity.inSizeCompatMode());
        assertEquals(bounds.width(), mActivity.getBounds().width());
        assertEquals(bounds.height(), mActivity.getBounds().height());
        assertEquals(density, mActivity.getConfiguration().densityDpi);
        assertEquals(WindowConfiguration.WINDOWING_MODE_FULLSCREEN, mActivity.getWindowingMode());
    }

    @Test
    public void testSizeCompatMode_FixedAspectRatioBoundsWithDecor() {
        setupDisplayContentForCompatDisplayInsets();
        final int decorHeight = 200; // e.g. The device has cutout.
        final DisplayPolicy policy = setupDisplayAndParentSize(600, 800).getDisplayPolicy();
        spyOn(policy);
        doAnswer(invocationOnMock -> {
            final int rotation = invocationOnMock.<Integer>getArgument(0);
            final Rect insets = invocationOnMock.<Rect>getArgument(4);
            if (rotation == ROTATION_0) {
                insets.top = decorHeight;
            } else if (rotation == ROTATION_90) {
                insets.left = decorHeight;
            }
            return null;
        }).when(policy).getNonDecorInsetsLw(anyInt() /* rotation */, anyInt() /* width */,
                anyInt() /* height */, any() /* displayCutout */, any() /* outInsets */);
        // set appBounds to incorporate decor
        final Configuration c =
                new Configuration(mStack.getDisplay().getRequestedOverrideConfiguration());
        c.windowConfiguration.getAppBounds().top = decorHeight;
        mStack.getDisplay().onRequestedOverrideConfigurationChanged(c);

        doReturn(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
                .when(mActivity).getRequestedOrientation();
        mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
        mActivity.info.minAspectRatio = mActivity.info.maxAspectRatio = 1;
        mActivity.visible = true;
        ensureActivityConfiguration();
        // The parent configuration doesn't change since the first resolved configuration, so the
        // activity shouldn't be in the size compatibility mode.
        assertFalse(mActivity.inSizeCompatMode());

        final Rect appBounds = mActivity.getWindowConfiguration().getAppBounds();
        // Ensure the app bounds keep the declared aspect ratio.
        assertEquals(appBounds.width(), appBounds.height());
        // The decor height should be a part of the effective bounds.
        assertEquals(mActivity.getBounds().height(), appBounds.height() + decorHeight);

        mTask.getConfiguration().windowConfiguration.setRotation(ROTATION_90);
        mActivity.onConfigurationChanged(mTask.getConfiguration());
        // After changing orientation, the aspect ratio should be the same.
        assertEquals(appBounds.width(), appBounds.height());
        // The decor height will be included in width.
        assertEquals(mActivity.getBounds().width(), appBounds.width() + decorHeight);
    }

    @Test
    public void testSizeCompatMode_FixedScreenConfigurationWhenMovingToDisplay() {
        // Initialize different bounds on a new display.
        final Rect newDisplayBounds = new Rect(0, 0, 1000, 2000);
        DisplayInfo info = new DisplayInfo();
        mService.mContext.getDisplay().getDisplayInfo(info);
        info.logicalWidth = newDisplayBounds.width();
        info.logicalHeight = newDisplayBounds.height();
        info.logicalDensityDpi = 300;

        final ActivityDisplay newDisplay =
                addNewActivityDisplayAt(info, ActivityDisplay.POSITION_TOP);

        final Configuration c =
                new Configuration(mStack.getDisplay().getRequestedOverrideConfiguration());
        c.densityDpi = 200;
        mStack.getDisplay().onRequestedOverrideConfigurationChanged(c);
        mActivity = new ActivityBuilder(mService)
                .setTask(mTask)
                .setResizeMode(RESIZE_MODE_UNRESIZEABLE)
                .setMaxAspectRatio(1.5f)
                .build();
        mActivity.visible = true;

        final Rect originalBounds = new Rect(mActivity.getBounds());
        final int originalDpi = mActivity.getConfiguration().densityDpi;

        // Move the non-resizable activity to the new display.
        mStack.reparent(newDisplay.mDisplayContent, true /* onTop */);

        assertEquals(originalBounds.width(),
                mActivity.getWindowConfiguration().getBounds().width());
        assertEquals(originalBounds.height(),
                mActivity.getWindowConfiguration().getBounds().height());
        assertEquals(originalDpi, mActivity.getConfiguration().densityDpi);
        assertTrue(mActivity.inSizeCompatMode());
    }

    @Test
    public void testSizeCompatMode_FixedScreenBoundsWhenDisplaySizeChanged() {
        setupDisplayContentForCompatDisplayInsets();
        when(mActivity.getRequestedOrientation()).thenReturn(
                ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        mTask.getWindowConfiguration().setAppBounds(mStack.getDisplay().getBounds());
        mTask.getRequestedOverrideConfiguration().orientation = Configuration.ORIENTATION_PORTRAIT;
        mActivity.info.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
        mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
        mActivity.visible = true;

        ensureActivityConfiguration();
        final Rect originalBounds = new Rect(mActivity.getBounds());

        // Change the size of current display.
        setupDisplayAndParentSize(1000, 2000);
        ensureActivityConfiguration();

        assertEquals(originalBounds.width(),
                mActivity.getWindowConfiguration().getBounds().width());
        assertEquals(originalBounds.height(),
                mActivity.getWindowConfiguration().getBounds().height());
        assertTrue(mActivity.inSizeCompatMode());
    }

    @Test
    public void testSizeCompatMode_FixedScreenLayoutSizeBits() {
        final int fixedScreenLayout = Configuration.SCREENLAYOUT_LONG_NO
                | Configuration.SCREENLAYOUT_SIZE_NORMAL;
        final int layoutMask = Configuration.SCREENLAYOUT_LONG_MASK
                | Configuration.SCREENLAYOUT_SIZE_MASK
                | Configuration.SCREENLAYOUT_LAYOUTDIR_MASK;
        mTask.getRequestedOverrideConfiguration().screenLayout = fixedScreenLayout
                | Configuration.SCREENLAYOUT_LAYOUTDIR_LTR;
        prepareFixedAspectRatioUnresizableActivity();

        // The initial configuration should inherit from parent.
        assertEquals(mTask.getConfiguration().screenLayout & layoutMask,
                mActivity.getConfiguration().screenLayout & layoutMask);

        mTask.getConfiguration().screenLayout = Configuration.SCREENLAYOUT_LAYOUTDIR_RTL
                | Configuration.SCREENLAYOUT_LONG_YES | Configuration.SCREENLAYOUT_SIZE_LARGE;
        mActivity.onConfigurationChanged(mTask.getConfiguration());

        // The size and aspect ratio bits don't change, but the layout direction should be updated.
        assertEquals(fixedScreenLayout | Configuration.SCREENLAYOUT_LAYOUTDIR_RTL,
                mActivity.getConfiguration().screenLayout & layoutMask);
    }

    @Test
    public void testSizeCompatMode_ResetNonVisibleActivity() {
        final ActivityDisplay display = mStack.getDisplay();
        spyOn(display);

        prepareFixedAspectRatioUnresizableActivity();
        mActivity.setState(STOPPED, "testSizeCompatMode");
        mActivity.visible = false;
        mActivity.app.setReportedProcState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY);
        // Make the parent bounds to be different so the activity is in size compatibility mode.
        setupDisplayAndParentSize(600, 1200);

        // Simulate the display changes orientation.
        doReturn(ActivityInfo.CONFIG_SCREEN_SIZE | CONFIG_ORIENTATION
                | ActivityInfo.CONFIG_WINDOW_CONFIGURATION)
                        .when(display).getLastOverrideConfigurationChanges();
        mActivity.onConfigurationChanged(mTask.getConfiguration());
        when(display.getLastOverrideConfigurationChanges()).thenCallRealMethod();
        // The override configuration should not change so it is still in size compatibility mode.
        assertTrue(mActivity.inSizeCompatMode());

        // Change display density
        final DisplayContent displayContent = mStack.getDisplay().mDisplayContent;
        displayContent.mBaseDisplayDensity = (int) (0.7f * displayContent.mBaseDisplayDensity);
        final Configuration c = new Configuration();
        displayContent.computeScreenConfiguration(c);
        mService.mAmInternal = mock(ActivityManagerInternal.class);
        mStack.getDisplay().onRequestedOverrideConfigurationChanged(c);

        // The override configuration should be reset and the activity's process will be killed.
        assertFalse(mActivity.inSizeCompatMode());
        verify(mActivity).restartProcessIfVisible();
        waitHandlerIdle(mService.mH);
        verify(mService.mAmInternal).killProcess(
                eq(mActivity.app.mName), eq(mActivity.app.mUid), anyString());
    }

    @Test
    public void testTakeOptions() {
        ActivityOptions opts = ActivityOptions.makeRemoteAnimation(
@@ -1340,33 +1102,4 @@ public class ActivityRecordTests extends ActivityTestsBase {

        verify(mActivity).removeFromHistory(anyString());
    }

    /** Setup {@link #mActivity} as a size-compat-mode-able activity without fixed orientation. */
    private void prepareFixedAspectRatioUnresizableActivity() {
        setupDisplayContentForCompatDisplayInsets();
        mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
        mActivity.info.maxAspectRatio = 1.5f;
        mActivity.visible = true;
        ensureActivityConfiguration();
    }

    private void setupDisplayContentForCompatDisplayInsets() {
        final Rect displayBounds = mStack.getDisplay().getBounds();
        setupDisplayAndParentSize(displayBounds.width(), displayBounds.height());
    }

    private DisplayContent setupDisplayAndParentSize(int width, int height) {
        final DisplayContent displayContent = mStack.getDisplay().mDisplayContent;
        displayContent.mBaseDisplayWidth = width;
        displayContent.mBaseDisplayHeight = height;
        final Configuration c =
                new Configuration(mStack.getDisplay().getRequestedOverrideConfiguration());
        c.windowConfiguration.setBounds(new Rect(0, 0, width, height));
        c.windowConfiguration.setAppBounds(0, 0, width, height);
        c.windowConfiguration.setRotation(ROTATION_0);
        c.orientation = width > height
                ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT;
        mStack.getDisplay().onRequestedOverrideConfigurationChanged(c);
        return displayContent;
    }
}
+1 −5
Original line number Diff line number Diff line
@@ -715,14 +715,10 @@ public class DisplayContentTests extends WindowTestsBase {

        activity.setRequestedOrientation(newOrientation);

        final ArgumentCaptor<Configuration> captor = ArgumentCaptor.forClass(Configuration.class);
        verify(dc.mActivityDisplay).updateDisplayOverrideConfigurationLocked(captor.capture(),
                same(activity), anyBoolean(), same(null));
        final Configuration newDisplayConfig = captor.getValue();
        final int expectedOrientation = newOrientation == SCREEN_ORIENTATION_PORTRAIT
                ? Configuration.ORIENTATION_PORTRAIT
                : Configuration.ORIENTATION_LANDSCAPE;
        assertEquals(expectedOrientation, newDisplayConfig.orientation);
        assertEquals(expectedOrientation, dc.getConfiguration().orientation);
    }

    @Test
+426 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading