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

Commit 8a40b4bf authored by Marin Shalamanov's avatar Marin Shalamanov Committed by Android (Google) Code Review
Browse files

Merge "Add XML configuration for density"

parents 5eedc933 a822ccd7
Loading
Loading
Loading
Loading
+26 −21
Original line number Diff line number Diff line
@@ -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) {
@@ -2586,6 +2569,28 @@ public final class Configuration implements Parcelable, Comparable<Configuration
        return TextUtils.join("-", parts);
    }

    /**
     * @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)}.
+137 −0
Original line number Diff line number Diff line
/*
 * 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 + '}';
        }
    }
}
+115 −17
Original line number Diff line number Diff line
@@ -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.
+11 −2
Original line number Diff line number Diff line
@@ -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);
        }

+14 −1
Original line number Diff line number Diff line
@@ -2833,8 +2833,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"
@@ -2891,6 +2897,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
Loading