From a822ccd755d3796f70c37662616112f46709e7f7 Mon Sep 17 00:00:00 2001 From: Marin Shalamanov Date: Fri, 5 Nov 2021 18:59:30 +0100 Subject: [PATCH] Add XML configuration for density This CL adds a element to the existing display device configuration XML files. This can be used to configure the logical display density per resolution. Additionally densityMap can be placed in a default.xml file. This config will be used if there is no per display configuration, for example for external displays. If there is no densityMap, the will fallback to the previous logic and will get the density value provided by SurfaceFlinger (which is the value of ro.sf.lcd_density). Bug: 160147805 Fixes: 207330669 Test: atest DensityMapTest DisplayContentTests Test: manually change the display mode on a device and make sure that the density changes (= icons on the launcher stay at the same size) Test: manually on device with config_maxUiWidth make sure that when changing to a resolution bigger than config_maxUiWidth the density is scaled propertly Change-Id: I7e31ffb7ab5d1ba4fbeff902d2ddb6fa9b2995fd --- .../android/content/res/Configuration.java | 47 +++--- .../android/server/display/DensityMap.java | 137 +++++++++++++++++ .../server/display/DisplayDeviceConfig.java | 132 +++++++++++++--- .../server/display/LocalDisplayAdapter.java | 13 +- .../com/android/server/wm/DisplayContent.java | 15 +- .../display-device-config.xsd | 26 ++++ .../display-device-config/schema/current.txt | 17 +++ .../server/display/DensityMapTest.java | 143 ++++++++++++++++++ .../server/wm/DisplayContentTests.java | 29 +++- 9 files changed, 517 insertions(+), 42 deletions(-) create mode 100644 services/core/java/com/android/server/display/DensityMap.java create mode 100644 services/tests/mockingservicestests/src/com/android/server/display/DensityMapTest.java diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 8ebb8ecd4c060..01bf49eb03a63 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -2432,27 +2432,10 @@ public final class Configuration implements Parcelable, Comparablebase and change. The * resulting delta can be used with {@link #updateFrom(Configuration)}. diff --git a/services/core/java/com/android/server/display/DensityMap.java b/services/core/java/com/android/server/display/DensityMap.java new file mode 100644 index 0000000000000..4aafd148a6dda --- /dev/null +++ b/services/core/java/com/android/server/display/DensityMap.java @@ -0,0 +1,137 @@ +/* + * 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 com.android.server.display; + +import java.util.Arrays; +import java.util.Comparator; + +/** + * Class which can compute the logical density for a display resolution. It holds a collection + * of pre-configured densities, which are used for look-up and interpolation. + */ +public class DensityMap { + + // Instead of resolutions we store the squared diagonal size. Diagonals make the map + // keys invariant to rotations and are useful for interpolation because they're scalars. + // Squared diagonals have the same properties as diagonals (the square function is monotonic) + // but also allow us to use integer types and avoid floating point arithmetics. + private final Entry[] mSortedDensityMapEntries; + + /** + * Creates a density map. The newly created object takes ownership of the passed array. + */ + static DensityMap createByOwning(Entry[] densityMapEntries) { + return new DensityMap(densityMapEntries); + } + + private DensityMap(Entry[] densityMapEntries) { + Arrays.sort(densityMapEntries, Comparator.comparingInt(entry -> entry.squaredDiagonal)); + mSortedDensityMapEntries = densityMapEntries; + verifyDensityMap(mSortedDensityMapEntries); + } + + /** + * Returns the logical density for the given resolution. + * + * If the resolution matches one of the entries in the map, the corresponding density is + * returned. Otherwise the return value is interpolated using the closest entries in the map. + */ + public int getDensityForResolution(int width, int height) { + int squaredDiagonal = width * width + height * height; + + // Search for two pre-configured entries "left" and "right" with the following criteria + // * left <= squaredDiagonal + // * squaredDiagonal - left is minimal + // * right > squaredDiagonal + // * right - squaredDiagonal is minimal + Entry left = Entry.ZEROES; + Entry right = null; + + for (Entry entry : mSortedDensityMapEntries) { + if (entry.squaredDiagonal <= squaredDiagonal) { + left = entry; + } else { + right = entry; + break; + } + } + + // Check if we found an exact match. + if (left.squaredDiagonal == squaredDiagonal) { + return left.density; + } + + // If no configured resolution is higher than the specified resolution, interpolate + // between (0,0) and (maxConfiguredDiagonal, maxConfiguredDensity). + if (right == null) { + right = left; // largest entry in the sorted array + left = Entry.ZEROES; + } + + double leftDiagonal = Math.sqrt(left.squaredDiagonal); + double rightDiagonal = Math.sqrt(right.squaredDiagonal); + double diagonal = Math.sqrt(squaredDiagonal); + + return (int) Math.round((diagonal - leftDiagonal) * (right.density - left.density) + / (rightDiagonal - leftDiagonal) + left.density); + } + + private static void verifyDensityMap(Entry[] sortedEntries) { + for (int i = 1; i < sortedEntries.length; i++) { + Entry prev = sortedEntries[i - 1]; + Entry curr = sortedEntries[i]; + + if (prev.squaredDiagonal == curr.squaredDiagonal) { + // This will most often happen because there are two entries with the same + // resolution (AxB and AxB) or rotated resolution (AxB and BxA), but it can also + // happen in the very rare cases when two different resolutions happen to have + // the same diagonal (e.g. 100x700 and 500x500). + throw new IllegalStateException("Found two entries in the density map with" + + " the same diagonal: " + prev + ", " + curr); + } else if (prev.density > curr.density) { + throw new IllegalStateException("Found two entries in the density map with" + + " increasing diagonal but decreasing density: " + prev + ", " + curr); + } + } + } + + @Override + public String toString() { + return "DensityMap{" + + "mDensityMapEntries=" + Arrays.toString(mSortedDensityMapEntries) + + '}'; + } + + static class Entry { + public static final Entry ZEROES = new Entry(0, 0, 0); + + public final int squaredDiagonal; + public final int density; + + Entry(int width, int height, int density) { + this.squaredDiagonal = width * width + height * height; + this.density = density; + } + + @Override + public String toString() { + return "DensityMapEntry{" + + "squaredDiagonal=" + squaredDiagonal + + ", density=" + density + '}'; + } + } +} diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 2ae5cbbbf24ba..a9e1647446cb8 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -18,6 +18,7 @@ package com.android.server.display; import android.annotation.NonNull; import android.content.Context; +import android.content.res.Configuration; import android.content.res.Resources; import android.hardware.display.DisplayManagerInternal; import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation; @@ -31,6 +32,7 @@ import android.view.DisplayAddress; import com.android.internal.R; import com.android.internal.display.BrightnessSynchronizer; +import com.android.server.display.config.Density; import com.android.server.display.config.DisplayConfiguration; import com.android.server.display.config.DisplayQuirks; import com.android.server.display.config.HbmTiming; @@ -52,6 +54,7 @@ import java.io.InputStream; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import javax.xml.datatype.DatatypeConfigurationException; @@ -70,6 +73,8 @@ public class DisplayDeviceConfig { private static final String ETC_DIR = "etc"; private static final String DISPLAY_CONFIG_DIR = "displayconfig"; private static final String CONFIG_FILE_FORMAT = "display_%s.xml"; + private static final String DEFAULT_CONFIG_FILE = "default.xml"; + private static final String DEFAULT_CONFIG_FILE_WITH_UIMODE_FORMAT = "default_%s.xml"; private static final String PORT_SUFFIX_FORMAT = "port_%d"; private static final String STABLE_ID_SUFFIX_FORMAT = "id_%d"; private static final String NO_SUFFIX_FORMAT = "%d"; @@ -121,6 +126,7 @@ public class DisplayDeviceConfig { private List mQuirks; private boolean mIsHighBrightnessModeEnabled = false; private HighBrightnessModeData mHbmData; + private DensityMap mDensityMap; private String mLoadedFrom = null; private DisplayDeviceConfig(Context context) { @@ -141,6 +147,33 @@ public class DisplayDeviceConfig { */ public static DisplayDeviceConfig create(Context context, long physicalDisplayId, boolean isDefaultDisplay) { + final DisplayDeviceConfig config = createWithoutDefaultValues(context, physicalDisplayId, + isDefaultDisplay); + + config.copyUninitializedValuesFromSecondaryConfig(loadDefaultConfigurationXml(context)); + return config; + } + + /** + * Creates an instance using global values since no display device config xml exists. + * Uses values from config or PowerManager. + * + * @param context + * @param useConfigXml + * @return A configuration instance. + */ + public static DisplayDeviceConfig create(Context context, boolean useConfigXml) { + final DisplayDeviceConfig config; + if (useConfigXml) { + config = getConfigFromGlobalXml(context); + } else { + config = getConfigFromPmValues(context); + } + return config; + } + + private static DisplayDeviceConfig createWithoutDefaultValues(Context context, + long physicalDisplayId, boolean isDefaultDisplay) { DisplayDeviceConfig config; config = loadConfigFromDirectory(context, Environment.getProductDirectory(), @@ -161,22 +194,53 @@ public class DisplayDeviceConfig { return create(context, isDefaultDisplay); } - /** - * Creates an instance using global values since no display device config xml exists. - * Uses values from config or PowerManager. - * - * @param context - * @param useConfigXml - * @return A configuration instance. - */ - public static DisplayDeviceConfig create(Context context, boolean useConfigXml) { - DisplayDeviceConfig config; - if (useConfigXml) { - config = getConfigFromGlobalXml(context); - } else { - config = getConfigFromPmValues(context); + private static DisplayConfiguration loadDefaultConfigurationXml(Context context) { + List defaultXmlLocations = new ArrayList<>(); + defaultXmlLocations.add(Environment.buildPath(Environment.getProductDirectory(), + ETC_DIR, DISPLAY_CONFIG_DIR, DEFAULT_CONFIG_FILE)); + defaultXmlLocations.add(Environment.buildPath(Environment.getVendorDirectory(), + ETC_DIR, DISPLAY_CONFIG_DIR, DEFAULT_CONFIG_FILE)); + + // Read config_defaultUiModeType directly because UiModeManager hasn't started yet. + final int uiModeType = context.getResources() + .getInteger(com.android.internal.R.integer.config_defaultUiModeType); + final String uiModeTypeStr = Configuration.getUiModeTypeString(uiModeType); + if (uiModeTypeStr != null) { + defaultXmlLocations.add(Environment.buildPath(Environment.getRootDirectory(), + ETC_DIR, DISPLAY_CONFIG_DIR, + String.format(DEFAULT_CONFIG_FILE_WITH_UIMODE_FORMAT, uiModeTypeStr))); } - return config; + defaultXmlLocations.add(Environment.buildPath(Environment.getRootDirectory(), + ETC_DIR, DISPLAY_CONFIG_DIR, DEFAULT_CONFIG_FILE)); + + final File configFile = getFirstExistingFile(defaultXmlLocations); + if (configFile == null) { + // Display configuration files aren't required to exist. + return null; + } + + DisplayConfiguration defaultConfig = null; + + try (InputStream in = new BufferedInputStream(new FileInputStream(configFile))) { + defaultConfig = XmlParser.read(in); + if (defaultConfig == null) { + Slog.i(TAG, "Default DisplayDeviceConfig file is null"); + } + } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) { + Slog.e(TAG, "Encountered an error while reading/parsing display config file: " + + configFile, e); + } + + return defaultConfig; + } + + private static File getFirstExistingFile(Collection files) { + for (File file : files) { + if (file.exists() && file.isFile()) { + return file; + } + } + return null; } private static DisplayDeviceConfig loadConfigFromDirectory(Context context, @@ -316,9 +380,13 @@ public class DisplayDeviceConfig { return mRefreshRateLimitations; } + public DensityMap getDensityMap() { + return mDensityMap; + } + @Override public String toString() { - String str = "DisplayDeviceConfig{" + return "DisplayDeviceConfig{" + "mLoadedFrom=" + mLoadedFrom + ", mBacklight=" + Arrays.toString(mBacklight) + ", mNits=" + Arrays.toString(mNits) @@ -340,8 +408,8 @@ public class DisplayDeviceConfig { + ", mAmbientLightSensor=" + mAmbientLightSensor + ", mProximitySensor=" + mProximitySensor + ", mRefreshRateLimitations= " + Arrays.toString(mRefreshRateLimitations.toArray()) + + ", mDensityMap= " + mDensityMap + "}"; - return str; } private static DisplayDeviceConfig getConfigFromSuffix(Context context, File baseDirectory, @@ -384,6 +452,7 @@ public class DisplayDeviceConfig { try (InputStream in = new BufferedInputStream(new FileInputStream(configFile))) { final DisplayConfiguration config = XmlParser.read(in); if (config != null) { + loadDensityMap(config); loadBrightnessDefaultFromDdcXml(config); loadBrightnessConstraintsFromConfigXml(); loadBrightnessMap(config); @@ -429,6 +498,35 @@ public class DisplayDeviceConfig { setProxSensorUnspecified(); } + private void copyUninitializedValuesFromSecondaryConfig(DisplayConfiguration defaultConfig) { + if (defaultConfig == null) { + return; + } + + if (mDensityMap == null) { + loadDensityMap(defaultConfig); + } + } + + private void loadDensityMap(DisplayConfiguration config) { + if (config.getDensityMap() == null) { + return; + } + + final List entriesFromXml = config.getDensityMap().getDensity(); + + final DensityMap.Entry[] entries = + new DensityMap.Entry[entriesFromXml.size()]; + for (int i = 0; i < entriesFromXml.size(); i++) { + final Density density = entriesFromXml.get(i); + entries[i] = new DensityMap.Entry( + density.getWidth().intValue(), + density.getHeight().intValue(), + density.getDensity().intValue()); + } + mDensityMap = DensityMap.createByOwning(entries); + } + private void loadBrightnessDefaultFromDdcXml(DisplayConfiguration config) { // Default brightness values are stored in the displayDeviceConfig file, // Or we fallback standard values if not. diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index b6d13e0c5bbf1..300f59ee1dd42 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -426,6 +426,15 @@ final class LocalDisplayAdapter extends DisplayAdapter { : mDefaultModeId; } + private int getLogicalDensity() { + DensityMap densityMap = getDisplayDeviceConfig().getDensityMap(); + if (densityMap == null) { + return (int) (mStaticDisplayInfo.density * 160 + 0.5); + } + + return densityMap.getDensityForResolution(mInfo.width, mInfo.height); + } + private void loadDisplayDeviceConfig() { // Load display device config final Context context = getOverlayContext(); @@ -591,7 +600,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { final DisplayAddress.Physical physicalAddress = DisplayAddress.fromPhysicalDisplayId(mPhysicalDisplayId); mInfo.address = physicalAddress; - mInfo.densityDpi = (int) (mStaticDisplayInfo.density * 160 + 0.5f); + mInfo.densityDpi = getLogicalDensity(); mInfo.xDpi = mActiveSfDisplayMode.xDpi; mInfo.yDpi = mActiveSfDisplayMode.yDpi; mInfo.deviceProductInfo = mStaticDisplayInfo.deviceProductInfo; @@ -1029,7 +1038,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { for (int i = 0; i < mSupportedModes.size(); i++) { pw.println(" " + mSupportedModes.valueAt(i)); } - pw.println("mSupportedColorModes=" + mSupportedColorModes.toString()); + pw.println("mSupportedColorModes=" + mSupportedColorModes); pw.println("mDisplayDeviceConfig=" + mDisplayDeviceConfig); } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 42c81248da34b..1ae67f323323c 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -2821,8 +2821,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mBaseDisplayDensity = baseDensity; if (mMaxUiWidth > 0 && mBaseDisplayWidth > mMaxUiWidth) { - mBaseDisplayHeight = (mMaxUiWidth * mBaseDisplayHeight) / mBaseDisplayWidth; + final float ratio = mMaxUiWidth / (float) mBaseDisplayWidth; + mBaseDisplayHeight = (int) (mBaseDisplayHeight * ratio); mBaseDisplayWidth = mMaxUiWidth; + if (!mIsDensityForced) { + // Update the density proportionally so the size of the UI elements won't change + // from the user's perspective. + mBaseDisplayDensity = (int) (mBaseDisplayDensity * ratio); + } if (DEBUG_DISPLAY) { Slog.v(TAG_WM, "Applying config restraints:" + mBaseDisplayWidth + "x" @@ -2879,6 +2885,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** If the given width and height equal to initial size, the setting will be cleared. */ void setForcedSize(int width, int height) { + // Can't force size higher than the maximal allowed + if (mMaxUiWidth > 0 && width > mMaxUiWidth) { + final float ratio = mMaxUiWidth / (float) width; + height = (int) (height * ratio); + width = mMaxUiWidth; + } + mIsSizeForced = mInitialDisplayWidth != width || mInitialDisplayHeight != height; if (mIsSizeForced) { // Set some sort of reasonable bounds on the size of the display that we will try diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index 429edf175be4c..2f4dd57ab15b0 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -26,6 +26,10 @@ + + + + @@ -181,5 +185,27 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index ad186026d30c1..5b2b87c3f14e4 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -1,8 +1,24 @@ // Signature format: 2.0 package com.android.server.display.config { + public class Density { + ctor public Density(); + method @NonNull public final java.math.BigInteger getDensity(); + method @NonNull public final java.math.BigInteger getHeight(); + method @NonNull public final java.math.BigInteger getWidth(); + method public final void setDensity(@NonNull java.math.BigInteger); + method public final void setHeight(@NonNull java.math.BigInteger); + method public final void setWidth(@NonNull java.math.BigInteger); + } + + public class DensityMap { + ctor public DensityMap(); + method public java.util.List getDensity(); + } + public class DisplayConfiguration { ctor public DisplayConfiguration(); + method @Nullable public final com.android.server.display.config.DensityMap getDensityMap(); method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode(); method public final com.android.server.display.config.SensorDetails getLightSensor(); method public final com.android.server.display.config.SensorDetails getProxSensor(); @@ -13,6 +29,7 @@ package com.android.server.display.config { method public final java.math.BigDecimal getScreenBrightnessRampFastIncrease(); method public final java.math.BigDecimal getScreenBrightnessRampSlowDecrease(); method public final java.math.BigDecimal getScreenBrightnessRampSlowIncrease(); + method public final void setDensityMap(@Nullable com.android.server.display.config.DensityMap); method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode); method public final void setLightSensor(com.android.server.display.config.SensorDetails); method public final void setProxSensor(com.android.server.display.config.SensorDetails); diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DensityMapTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DensityMapTest.java new file mode 100644 index 0000000000000..3f69f1b723e34 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/display/DensityMapTest.java @@ -0,0 +1,143 @@ +/* + * 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 com.android.server.display; + +import static com.android.server.display.DensityMap.Entry; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class DensityMapTest { + + @Test + public void testConstructor_withBadConfig_throwsException() { + assertThrows(IllegalStateException.class, () -> + DensityMap.createByOwning(new Entry[]{ + new Entry(1080, 1920, 320), + new Entry(1080, 1920, 320)}) + ); + + assertThrows(IllegalStateException.class, () -> + DensityMap.createByOwning(new Entry[]{ + new Entry(1080, 1920, 320), + new Entry(1920, 1080, 120)}) + ); + + assertThrows(IllegalStateException.class, () -> + DensityMap.createByOwning(new Entry[]{ + new Entry(1080, 1920, 320), + new Entry(2160, 3840, 120)}) + ); + + assertThrows(IllegalStateException.class, () -> + DensityMap.createByOwning(new Entry[]{ + new Entry(1080, 1920, 320), + new Entry(3840, 2160, 120)}) + ); + + // Two entries with the same diagonal + assertThrows(IllegalStateException.class, () -> + DensityMap.createByOwning(new Entry[]{ + new Entry(500, 500, 123), + new Entry(100, 700, 456)}) + ); + } + + @Test + public void testGetDensityForResolution_withResolutionMatch_returnsDensityFromConfig() { + DensityMap densityMap = DensityMap.createByOwning(new Entry[]{ + new Entry(720, 1280, 213), + new Entry(1080, 1920, 320), + new Entry(2160, 3840, 640)}); + + assertEquals(213, densityMap.getDensityForResolution(720, 1280)); + assertEquals(213, densityMap.getDensityForResolution(1280, 720)); + + assertEquals(320, densityMap.getDensityForResolution(1080, 1920)); + assertEquals(320, densityMap.getDensityForResolution(1920, 1080)); + + assertEquals(640, densityMap.getDensityForResolution(2160, 3840)); + assertEquals(640, densityMap.getDensityForResolution(3840, 2160)); + } + + @Test + public void testGetDensityForResolution_withDiagonalMatch_returnsDensityFromConfig() { + DensityMap densityMap = DensityMap.createByOwning( + new Entry[]{ new Entry(500, 500, 123)}); + + // 500x500 has the same diagonal as 100x700 + assertEquals(123, densityMap.getDensityForResolution(100, 700)); + } + + @Test + public void testGetDensityForResolution_withOneEntry_withNoMatch_returnsExtrapolatedDensity() { + DensityMap densityMap = DensityMap.createByOwning( + new Entry[]{ new Entry(1080, 1920, 320)}); + + assertEquals(320, densityMap.getDensityForResolution(1081, 1920)); + assertEquals(320, densityMap.getDensityForResolution(1080, 1921)); + + assertEquals(640, densityMap.getDensityForResolution(2160, 3840)); + assertEquals(640, densityMap.getDensityForResolution(3840, 2160)); + + assertEquals(213, densityMap.getDensityForResolution(720, 1280)); + assertEquals(213, densityMap.getDensityForResolution(1280, 720)); + } + + @Test + public void testGetDensityForResolution_withTwoEntries_withNoMatch_returnExtrapolatedDensity() { + DensityMap densityMap = DensityMap.createByOwning(new Entry[]{ + new Entry(1080, 1920, 320), + new Entry(2160, 3840, 320)}); + + // Resolution is smaller than all entries + assertEquals(213, densityMap.getDensityForResolution(720, 1280)); + assertEquals(213, densityMap.getDensityForResolution(1280, 720)); + + // Resolution is bigger than all entries + assertEquals(320 * 2, densityMap.getDensityForResolution(2160 * 2, 3840 * 2)); + assertEquals(320 * 2, densityMap.getDensityForResolution(3840 * 2, 2160 * 2)); + } + + @Test + public void testGetDensityForResolution_withNoMatch_returnsInterpolatedDensity() { + { + DensityMap densityMap = DensityMap.createByOwning(new Entry[]{ + new Entry(1080, 1920, 320), + new Entry(2160, 3840, 320)}); + + assertEquals(320, densityMap.getDensityForResolution(2000, 2000)); + } + + { + DensityMap densityMap = DensityMap.createByOwning(new Entry[]{ + new Entry(720, 1280, 213), + new Entry(2160, 3840, 640)}); + + assertEquals(320, densityMap.getDensityForResolution(1080, 1920)); + assertEquals(320, densityMap.getDensityForResolution(1920, 1080)); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 08be15e16b37d..d7a0ab3afbc2f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -681,7 +681,7 @@ public class DisplayContentTests extends WindowTestsBase { final int maxWidth = 300; final int resultingHeight = (maxWidth * baseHeight) / baseWidth; - final int resultingDensity = baseDensity; + final int resultingDensity = (baseDensity * maxWidth) / baseWidth; displayContent.setMaxUiWidth(maxWidth); verifySizes(displayContent, maxWidth, resultingHeight, resultingDensity); @@ -755,6 +755,33 @@ public class DisplayContentTests extends WindowTestsBase { verifySizes(displayContent, baseWidth, baseHeight, baseDensity); } + @Test + public void testSetForcedDensity() { + final DisplayContent displayContent = createDisplayNoUpdateDisplayInfo(); + final int baseWidth = 1280; + final int baseHeight = 720; + final int baseDensity = 320; + + displayContent.mInitialDisplayWidth = baseWidth; + displayContent.mInitialDisplayHeight = baseHeight; + displayContent.mInitialDisplayDensity = baseDensity; + displayContent.updateBaseDisplayMetrics(baseWidth, baseHeight, baseDensity); + + final int forcedDensity = 600; + + // Verify that forcing the density is honored and the size doesn't change. + displayContent.setForcedDensity(forcedDensity, 0 /* userId */); + verifySizes(displayContent, baseWidth, baseHeight, forcedDensity); + + // Verify that forcing the density is idempotent. + displayContent.setForcedDensity(forcedDensity, 0 /* userId */); + verifySizes(displayContent, baseWidth, baseHeight, forcedDensity); + + // Verify that forcing resolution won't affect the already forced density. + displayContent.setForcedSize(1800, 1200); + verifySizes(displayContent, 1800, 1200, forcedDensity); + } + @Test public void testDisplayCutout_rot0() { final DisplayContent dc = createNewDisplay();