/* * Copyright (C) 2021 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 android.server.wm; import static android.server.wm.app.Components.UI_SCALING_TEST_ACTIVITY; import static android.server.wm.app.Components.UiScalingTestActivity.COMMAND_ADD_SUBVIEW; import static android.server.wm.app.Components.UiScalingTestActivity.COMMAND_CLEAR_DEFAULT_VIEW; import static android.server.wm.app.Components.UiScalingTestActivity.COMMAND_GET_RESOURCES_CONFIG; import static android.server.wm.app.Components.UiScalingTestActivity.COMMAND_GET_SUBVIEW_SIZE; import static android.server.wm.app.Components.UiScalingTestActivity.COMMAND_UPDATE_RESOURCES_CONFIG; import static android.server.wm.app.Components.UiScalingTestActivity.KEY_COMMAND_SUCCESS; import static android.server.wm.app.Components.UiScalingTestActivity.KEY_RESOURCES_CONFIG; import static android.server.wm.app.Components.UiScalingTestActivity.KEY_SUBVIEW_ID; import static android.server.wm.app.Components.UiScalingTestActivity.KEY_TEXT_SIZE; import static android.server.wm.app.Components.UiScalingTestActivity.KEY_VIEW_SIZE; import static android.server.wm.app.Components.UiScalingTestActivity.SUBVIEW_ID1; import static android.server.wm.app.Components.UiScalingTestActivity.SUBVIEW_ID2; import static org.hamcrest.Matchers.closeTo; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.notNullValue; import android.content.ComponentName; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; import android.os.LocaleList; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ErrorCollector; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import java.util.Arrays; /** * The test is focused on compatibility scaling, and tests the feature form two sides. * 1. It checks that the applications "sees" the metrics in PXs, but the DP metrics remain the same. * 2. It checks the WindowManagerServer state, and makes sure that the scaling is correctly * reflected in the WindowState. * * This is achieved by launching a {@link android.server.wm.app.UiScalingTestActivity} and having it * reporting the metrics it receives. * The Activity also draws 3 UI elements: a text, a red square with a 100dp side and a blue square * with a 100px side. * The text and the red square should have the same when rendered on the screen (by HWC) both when * the compat downscaling is enabled and disabled. * TODO(b/180098454): Add tests to make sure that the UI elements, which have their sizes declared * in DPs (the text and the red square) have the same sizes on the screen (after composition). * *

Build/Install/Run: * atest CtsWindowManagerDeviceTestCases:CompatScaleTests */ @RunWith(Parameterized.class) public class CompatScaleTests extends ActivityManagerTestBase { /** * If application size is 1280, then Upscaling by 0.3 will make the surface 1280/0.3 = 4267. * Some devices do not support this high resolution, so limiting Upscaling test case for * scaling >= 0.5. */ public static float MAX_UPSCALING_TESTED = 0.5f; @Parameterized.Parameters(name = "{0}") public static Iterable data() { return Arrays.asList(new Object[][] { { "DOWNSCALE_30", 0.3f }, { "DOWNSCALE_35", 0.35f }, { "DOWNSCALE_40", 0.4f }, { "DOWNSCALE_45", 0.45f }, { "DOWNSCALE_50", 0.5f }, { "DOWNSCALE_55", 0.55f }, { "DOWNSCALE_60", 0.6f }, { "DOWNSCALE_65", 0.65f }, { "DOWNSCALE_70", 0.7f }, { "DOWNSCALE_75", 0.75f }, { "DOWNSCALE_80", 0.8f }, { "DOWNSCALE_85", 0.85f }, { "DOWNSCALE_90", 0.9f }, }); } @Rule public ErrorCollector collector = new ErrorCollector(); private static final ComponentName ACTIVITY_UNDER_TEST = UI_SCALING_TEST_ACTIVITY; private static final String PACKAGE_UNDER_TEST = ACTIVITY_UNDER_TEST.getPackageName(); private static final float EPSILON_GLOBAL_SCALE = 0.01f; private final String mCompatChangeName; private final float mCompatScale; private final float mInvCompatScale; private CommandSession.SizeInfo mAppSizesNormal; private CommandSession.SizeInfo mAppSizesDownscaled; private CommandSession.SizeInfo mAppSizesUpscaled; private WindowManagerState.WindowState mWindowStateNormal; private WindowManagerState.WindowState mWindowStateDownscaled; private WindowManagerState.WindowState mWindowStateUpscaled; public CompatScaleTests(String compatChangeName, float compatScale) { mCompatChangeName = compatChangeName; mCompatScale = compatScale; mInvCompatScale = 1 / mCompatScale; } @Test public void testUpdateResourcesConfiguration() { // Launch activity with down/up scaling *disabled* try (var session = new BaseActivitySessionCloseable(ACTIVITY_UNDER_TEST)) { runTestUpdateResourcesConfiguration(session.getActivitySession()); } try (var scale = new CompatChangeCloseable(mCompatChangeName, PACKAGE_UNDER_TEST)) { // Now launch the same activity with downscaling *enabled* try (var down = new CompatChangeCloseable("DOWNSCALED", PACKAGE_UNDER_TEST); var session = new BaseActivitySessionCloseable(ACTIVITY_UNDER_TEST)) { runTestUpdateResourcesConfiguration(session.getActivitySession()); } if (mCompatScale >= MAX_UPSCALING_TESTED) { // Now launch the same activity with upscaling *enabled* try (var up = new CompatChangeCloseable("DOWNSCALED_INVERSE", PACKAGE_UNDER_TEST); var session = new BaseActivitySessionCloseable(ACTIVITY_UNDER_TEST)) { runTestUpdateResourcesConfiguration(session.getActivitySession()); } } } } private void runTestUpdateResourcesConfiguration(CommandSession.ActivitySession activity) { activity.sendCommandAndWaitReply(COMMAND_CLEAR_DEFAULT_VIEW); addSubview(activity, SUBVIEW_ID1); Bundle subviewSize1 = getSubViewSize(activity, SUBVIEW_ID1); collector.checkThat(subviewSize1.getParcelable(KEY_TEXT_SIZE, Rect.class), not(equalTo(new Rect()))); collector.checkThat(subviewSize1.getParcelable(KEY_VIEW_SIZE, Rect.class), not(equalTo(new Rect()))); collector.checkThat(subviewSize1.getBoolean(KEY_COMMAND_SUCCESS), is(true)); Configuration config = activity.sendCommandAndWaitReply( COMMAND_GET_RESOURCES_CONFIG).getParcelable(KEY_RESOURCES_CONFIG, Configuration.class); config.setLocales(LocaleList.forLanguageTags("en-US,en-XC")); Bundle data = new Bundle(); data.putParcelable(KEY_RESOURCES_CONFIG, config); collector.checkThat("Failed to update resources configuration", activity.sendCommandAndWaitReply(COMMAND_UPDATE_RESOURCES_CONFIG, data).getBoolean( KEY_COMMAND_SUCCESS), is(true)); addSubview(activity, SUBVIEW_ID2); Bundle subviewSize2 = getSubViewSize(activity, SUBVIEW_ID2); collector.checkThat(subviewSize2.getBoolean(KEY_COMMAND_SUCCESS), is(true)); collector.checkThat(subviewSize1.getParcelable(KEY_TEXT_SIZE, Rect.class), equalTo(subviewSize2.getParcelable(KEY_TEXT_SIZE, Rect.class))); collector.checkThat(subviewSize1.getParcelable(KEY_VIEW_SIZE, Rect.class), equalTo(subviewSize2.getParcelable(KEY_VIEW_SIZE, Rect.class))); } /** * Tests that the parameters that the application receives from the * {@link android.content.res.Configuration} are correctly scaled. */ @Test public void test_scalesCorrectly() { // Launch activity with down/up scaling *disabled* and get the sizes it reports and its // Window state. try (var session = new BaseActivitySessionCloseable(ACTIVITY_UNDER_TEST)) { mAppSizesNormal = getActivityReportedSizes(); mWindowStateNormal = getPackageWindowState(); } try (var scale = new CompatChangeCloseable(mCompatChangeName, PACKAGE_UNDER_TEST)) { // Now launch the same activity with downscaling *enabled* and get the sizes it reports // and its Window state. try (var down = new CompatChangeCloseable("DOWNSCALED", PACKAGE_UNDER_TEST); var session = new BaseActivitySessionCloseable(ACTIVITY_UNDER_TEST)) { mAppSizesDownscaled = getActivityReportedSizes(); mWindowStateDownscaled = getPackageWindowState(); } test_scalesCorrectly_inCompatDownscalingMode(); test_windowState_inCompatDownscalingMode(); if (mCompatScale >= MAX_UPSCALING_TESTED) { // Now launch the same activity with upscaling *enabled* and get the sizes it // reports and its Window state. try (var up = new CompatChangeCloseable("DOWNSCALED_INVERSE", PACKAGE_UNDER_TEST); var session = new BaseActivitySessionCloseable(ACTIVITY_UNDER_TEST)) { mAppSizesUpscaled = getActivityReportedSizes(); mWindowStateUpscaled = getPackageWindowState(); } test_scalesCorrectly_inCompatUpscalingMode(); test_windowState_inCompatUpscalingMode(); } } } private void test_scalesCorrectly_inCompatDownscalingMode() { checkScaled("Density DPI should scale by " + mCompatScale, mAppSizesNormal.densityDpi, mCompatScale, mAppSizesDownscaled.densityDpi); collector.checkThat("Width shouldn't change", mAppSizesNormal.widthDp, equalTo(mAppSizesDownscaled.widthDp)); collector.checkThat("Height shouldn't change", mAppSizesNormal.heightDp, equalTo(mAppSizesDownscaled.heightDp)); collector.checkThat("Smallest Width shouldn't change", mAppSizesNormal.smallestWidthDp, equalTo(mAppSizesDownscaled.smallestWidthDp)); checkScaled("Width should scale by " + mCompatScale, mAppSizesNormal.windowWidth, mCompatScale, mAppSizesDownscaled.windowWidth); checkScaled("Height should scale by " + mCompatScale, mAppSizesNormal.windowHeight, mCompatScale, mAppSizesDownscaled.windowHeight); checkScaled("App width should scale by " + mCompatScale, mAppSizesNormal.windowAppWidth, mCompatScale, mAppSizesDownscaled.windowAppWidth); checkScaled("App height should scale by " + mCompatScale, mAppSizesNormal.windowAppHeight, mCompatScale, mAppSizesDownscaled.windowAppHeight); checkScaled("Width should scale by " + mCompatScale, mAppSizesNormal.metricsWidth, mCompatScale, mAppSizesDownscaled.metricsWidth); checkScaled("Height should scale by " + mCompatScale, mAppSizesNormal.metricsHeight, mCompatScale, mAppSizesDownscaled.metricsHeight); checkScaled("Width should scale by " + mCompatScale, mAppSizesNormal.displayWidth, mCompatScale, mAppSizesDownscaled.displayWidth); checkScaled("Height should scale by " + mCompatScale, mAppSizesNormal.displayHeight, mCompatScale, mAppSizesDownscaled.displayHeight); } private void test_scalesCorrectly_inCompatUpscalingMode() { checkScaled("Density DPI should scale by " + mInvCompatScale, mAppSizesNormal.densityDpi, mInvCompatScale, mAppSizesUpscaled.densityDpi); collector.checkThat("Width shouldn't change", mAppSizesNormal.widthDp, equalTo(mAppSizesUpscaled.widthDp)); collector.checkThat("Height shouldn't change", mAppSizesNormal.heightDp, equalTo(mAppSizesUpscaled.heightDp)); collector.checkThat("Smallest Width shouldn't change", mAppSizesNormal.smallestWidthDp, equalTo(mAppSizesUpscaled.smallestWidthDp)); checkScaled("Width should scale by " + mInvCompatScale, mAppSizesNormal.windowWidth, mInvCompatScale, mAppSizesUpscaled.windowWidth); checkScaled("Height should scale by " + mInvCompatScale, mAppSizesNormal.windowHeight, mInvCompatScale, mAppSizesUpscaled.windowHeight); checkScaled("App width should scale by " + mInvCompatScale, mAppSizesNormal.windowAppWidth, mInvCompatScale, mAppSizesUpscaled.windowAppWidth); checkScaled("App height should scale by " + mInvCompatScale, mAppSizesNormal.windowAppHeight, mInvCompatScale, mAppSizesUpscaled.windowAppHeight); checkScaled("Width should scale by " + mInvCompatScale, mAppSizesNormal.metricsWidth, mInvCompatScale, mAppSizesUpscaled.metricsWidth); checkScaled("Height should scale by " + mInvCompatScale, mAppSizesNormal.metricsHeight, mInvCompatScale, mAppSizesUpscaled.metricsHeight); checkScaled("Width should scale by " + mInvCompatScale, mAppSizesNormal.displayWidth, mInvCompatScale, mAppSizesUpscaled.displayWidth); checkScaled("Height should scale by " + mInvCompatScale, mAppSizesNormal.displayHeight, mInvCompatScale, mAppSizesUpscaled.displayHeight); } private void test_windowState_inCompatDownscalingMode() { // Check the "normal" window's state for disabled compat mode and appropriate global scale. collector.checkThat("The Window should not be in the size compat mode", mWindowStateNormal.hasCompatScale(), is(false)); collector.checkThat("The window should not be scaled", 1d, closeTo(mWindowStateNormal.getGlobalScale(), EPSILON_GLOBAL_SCALE)); // Check the "downscaled" window's state for enabled compat mode and appropriate global // scale. collector.checkThat("The Window should be in the size compat mode", mWindowStateDownscaled.hasCompatScale(), is(true)); collector.checkThat("The window should have global scale of " + mInvCompatScale, (double) mInvCompatScale, closeTo(mWindowStateDownscaled.getGlobalScale(), EPSILON_GLOBAL_SCALE)); // Make sure the frame sizes changed correctly. collector.checkThat("Window frame on should not change", mWindowStateNormal.getFrame(), equalTo(mWindowStateDownscaled.getFrame())); checkScaled("Requested width should scale by " + mCompatScale, mWindowStateNormal.getRequestedWidth(), mCompatScale, mWindowStateDownscaled.getRequestedWidth()); checkScaled("Requested height " + mWindowStateNormal.getRequestedHeight() + " should scale by " + mCompatScale, mWindowStateNormal.getRequestedHeight(), mCompatScale, mWindowStateDownscaled.getRequestedHeight()); } private void test_windowState_inCompatUpscalingMode() { // Check the "normal" window's state for disabled compat mode and appropriate global scale. collector.checkThat("The Window should not be in the size compat mode", mWindowStateNormal.hasCompatScale(), is(false)); collector.checkThat("The window should not be scaled", 1d, closeTo(mWindowStateNormal.getGlobalScale(), EPSILON_GLOBAL_SCALE)); // Check the "upscaled" window's state for enabled compat mode and appropriate global // scale. collector.checkThat("The Window should be in the size compat mode", mWindowStateUpscaled.hasCompatScale(), is(true)); collector.checkThat("The window should have global scale of " + mCompatScale, (double) mCompatScale, closeTo(mWindowStateUpscaled.getGlobalScale(), EPSILON_GLOBAL_SCALE)); // Make sure the frame sizes changed correctly. collector.checkThat("Window frame on should not change", mWindowStateNormal.getFrame(), equalTo(mWindowStateUpscaled.getFrame())); checkScaled("Requested width should scale by " + mInvCompatScale, mWindowStateNormal.getRequestedWidth(), mInvCompatScale, mWindowStateUpscaled.getRequestedWidth()); checkScaled("Requested height should scale by " + mInvCompatScale, mWindowStateNormal.getRequestedHeight(), mInvCompatScale, mWindowStateUpscaled.getRequestedHeight()); } private CommandSession.SizeInfo getActivityReportedSizes() { final CommandSession.SizeInfo details = getLastReportedSizesForActivity(ACTIVITY_UNDER_TEST); collector.checkThat(details, notNullValue()); return details; } private WindowManagerState.WindowState getPackageWindowState() { return getPackageWindowState(PACKAGE_UNDER_TEST); } private void addSubview(CommandSession.ActivitySession activity, String subviewId) { Bundle data = new Bundle(); data.putString(KEY_SUBVIEW_ID, subviewId); Bundle res = activity.sendCommandAndWaitReply(COMMAND_ADD_SUBVIEW, data); collector.checkThat("Failed to add subview " + subviewId, res.getBoolean(KEY_COMMAND_SUCCESS), is(true)); } private Bundle getSubViewSize(CommandSession.ActivitySession activity, String subviewId) { Bundle data = new Bundle(); data.putString(KEY_SUBVIEW_ID, subviewId); return activity.sendCommandAndWaitReply(COMMAND_GET_SUBVIEW_SIZE, data); } private void checkScaled(String message, int baseValue, double expectedScale, int actualValue) { // In order to account for possible rounding errors, let's calculate the actual scale and // compare it's against the expected scale (allowing a small delta). final double actualScale = ((double) actualValue) / baseValue; collector.checkThat(message, actualScale, closeTo(expectedScale, EPSILON_GLOBAL_SCALE)); } }