diff options
9 files changed, 517 insertions, 42 deletions
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 8ebb8ecd4c06..01bf49eb03a6 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, Comparable<Configuration break; } - switch (config.uiMode & Configuration.UI_MODE_TYPE_MASK) { - case Configuration.UI_MODE_TYPE_APPLIANCE: - parts.add("appliance"); - break; - case Configuration.UI_MODE_TYPE_DESK: - parts.add("desk"); - break; - case Configuration.UI_MODE_TYPE_TELEVISION: - parts.add("television"); - break; - case Configuration.UI_MODE_TYPE_CAR: - parts.add("car"); - break; - case Configuration.UI_MODE_TYPE_WATCH: - parts.add("watch"); - break; - case Configuration.UI_MODE_TYPE_VR_HEADSET: - parts.add("vrheadset"); - break; - default: - break; + final String uiModeTypeString = + getUiModeTypeString(config.uiMode & Configuration.UI_MODE_TYPE_MASK); + if (uiModeTypeString != null) { + parts.add(uiModeTypeString); } switch (config.uiMode & Configuration.UI_MODE_NIGHT_MASK) { @@ -2587,6 +2570,28 @@ public final class Configuration implements Parcelable, Comparable<Configuration } /** + * @hide + */ + public static String getUiModeTypeString(int uiModeType) { + switch (uiModeType) { + case Configuration.UI_MODE_TYPE_APPLIANCE: + return "appliance"; + case Configuration.UI_MODE_TYPE_DESK: + return "desk"; + case Configuration.UI_MODE_TYPE_TELEVISION: + return "television"; + case Configuration.UI_MODE_TYPE_CAR: + return "car"; + case Configuration.UI_MODE_TYPE_WATCH: + return "watch"; + case Configuration.UI_MODE_TYPE_VR_HEADSET: + return "vrheadset"; + default: + return null; + } + } + + /** * Generate a delta Configuration between <code>base</code> and <code>change</code>. The * resulting delta can be used with {@link #updateFrom(Configuration)}. * <p /> 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 000000000000..4aafd148a6dd --- /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 2ae5cbbbf24b..a9e1647446cb 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<String> 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<File> 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<File> 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<Density> 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 b6d13e0c5bbf..300f59ee1dd4 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 1c93b99fde9e..e80a9b9f2a8e 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -2834,8 +2834,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" @@ -2892,6 +2898,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 429edf175be4..2f4dd57ab15b 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 @@ <xs:element name="displayConfiguration"> <xs:complexType> <xs:sequence> + <xs:element type="densityMap" name="densityMap" minOccurs="0" maxOccurs="1"> + <xs:annotation name="nullable"/> + <xs:annotation name="final"/> + </xs:element> <xs:element type="nitsMap" name="screenBrightnessMap"> <xs:annotation name="nonnull"/> <xs:annotation name="final"/> @@ -181,5 +185,27 @@ </xs:sequence> </xs:complexType> + <xs:complexType name="densityMap"> + <xs:sequence> + <xs:element name="density" type="density" maxOccurs="unbounded" minOccurs="1"> + </xs:element> + </xs:sequence> + </xs:complexType> + <xs:complexType name="density"> + <xs:sequence> + <xs:element type="xs:nonNegativeInteger" name="width"> + <xs:annotation name="nonnull"/> + <xs:annotation name="final"/> + </xs:element> + <xs:element type="xs:nonNegativeInteger" name="height"> + <xs:annotation name="nonnull"/> + <xs:annotation name="final"/> + </xs:element> + <xs:element type="xs:nonNegativeInteger" name="density"> + <xs:annotation name="nonnull"/> + <xs:annotation name="final"/> + </xs:element> + </xs:sequence> + </xs:complexType> </xs:schema> diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index ad186026d30c..5b2b87c3f14e 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<com.android.server.display.config.Density> 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 000000000000..3f69f1b723e3 --- /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 deba83530b45..dc0e02800bb2 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); @@ -756,6 +756,33 @@ public class DisplayContentTests extends WindowTestsBase { } @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(); dc.mInitialDisplayWidth = 200; |