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

Commit a3139a40 authored by Ady Abraham's avatar Ady Abraham
Browse files

Enable preferredDisplayModeId for frame rate override

Route preferredDisplayModeId vote to SurfaceFlinger using the
setFrameRate API to frame rate override to be enabled for applications
that vote with preferredDisplayModeId.

Bug: 183225713
Test: atest RefreshRatePolicyTest
Test: atest DisplayModeDirectorTest
Test: atest FrameRateSelectionPriorityTests
Test: atest DisplayTest
Test: Launch an app that sets preferredDisplayModeId and observe
refresh rate

Change-Id: I0f9110adc83eb1c7c994440e6b40f3e1036176e6
parent 26c83452
Loading
Loading
Loading
Loading
+141 −68
Original line number Diff line number Diff line
@@ -198,6 +198,8 @@ public class DisplayModeDirector {
        public float maxRefreshRate;
        public int width;
        public int height;
        public boolean disableRefreshRateSwitching;
        public float baseModeRefreshRate;

        VoteSummary() {
            reset();
@@ -208,6 +210,8 @@ public class DisplayModeDirector {
            maxRefreshRate = Float.POSITIVE_INFINITY;
            width = Vote.INVALID_SIZE;
            height = Vote.INVALID_SIZE;
            disableRefreshRateSwitching = false;
            baseModeRefreshRate = 0f;
        }
    }

@@ -229,13 +233,20 @@ public class DisplayModeDirector {
            // For refresh rates, just use the tightest bounds of all the votes
            summary.minRefreshRate = Math.max(summary.minRefreshRate, vote.refreshRateRange.min);
            summary.maxRefreshRate = Math.min(summary.maxRefreshRate, vote.refreshRateRange.max);
            // For display size, use only the first vote we come across (i.e. the highest
            // priority vote that includes the width / height).
            // For display size, disable refresh rate switching and base mode refresh rate use only
            // the first vote we come across (i.e. the highest priority vote that includes the
            // attribute).
            if (summary.height == Vote.INVALID_SIZE && summary.width == Vote.INVALID_SIZE
                    && vote.height > 0 && vote.width > 0) {
                summary.width = vote.width;
                summary.height = vote.height;
            }
            if (!summary.disableRefreshRateSwitching && vote.disableRefreshRateSwitching) {
                summary.disableRefreshRateSwitching = true;
            }
            if (summary.baseModeRefreshRate == 0f && vote.baseModeRefreshRate > 0f) {
                summary.baseModeRefreshRate = vote.baseModeRefreshRate;
            }
        }
    }

@@ -260,13 +271,14 @@ public class DisplayModeDirector {
                return new DesiredDisplayModeSpecs();
            }

            int[] availableModes = new int[]{defaultMode.getModeId()};
            ArrayList<Display.Mode> availableModes = new ArrayList<>();
            availableModes.add(defaultMode);
            VoteSummary primarySummary = new VoteSummary();
            int lowestConsideredPriority = Vote.MIN_PRIORITY;
            int highestConsideredPriority = Vote.MAX_PRIORITY;

            if (mAlwaysRespectAppRequest) {
                lowestConsideredPriority = Vote.PRIORITY_APP_REQUEST_REFRESH_RATE;
                lowestConsideredPriority = Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE;
                highestConsideredPriority = Vote.PRIORITY_APP_REQUEST_SIZE;
            }

@@ -286,16 +298,19 @@ public class DisplayModeDirector {
                }

                availableModes = filterModes(modes, primarySummary);
                if (availableModes.length > 0) {
                if (!availableModes.isEmpty()) {
                    if (mLoggingEnabled) {
                        Slog.w(TAG, "Found available modes=" + Arrays.toString(availableModes)
                        Slog.w(TAG, "Found available modes=" + availableModes
                                + " with lowest priority considered "
                                + Vote.priorityToString(lowestConsideredPriority)
                                + " and constraints: "
                                + "width=" + primarySummary.width
                                + ", height=" + primarySummary.height
                                + ", minRefreshRate=" + primarySummary.minRefreshRate
                                + ", maxRefreshRate=" + primarySummary.maxRefreshRate);
                                + ", maxRefreshRate=" + primarySummary.maxRefreshRate
                                + ", disableRefreshRateSwitching="
                                + primarySummary.disableRefreshRateSwitching
                                + ", baseModeRefreshRate=" + primarySummary.baseModeRefreshRate);
                    }
                    break;
                }
@@ -307,7 +322,10 @@ public class DisplayModeDirector {
                            + "width=" + primarySummary.width
                            + ", height=" + primarySummary.height
                            + ", minRefreshRate=" + primarySummary.minRefreshRate
                            + ", maxRefreshRate=" + primarySummary.maxRefreshRate);
                            + ", maxRefreshRate=" + primarySummary.maxRefreshRate
                            + ", disableRefreshRateSwitching="
                            + primarySummary.disableRefreshRateSwitching
                            + ", baseModeRefreshRate=" + primarySummary.baseModeRefreshRate);
                }

                // If we haven't found anything with the current set of votes, drop the
@@ -332,26 +350,38 @@ public class DisplayModeDirector {
                                appRequestSummary.maxRefreshRate));
            }

            int baseModeId = INVALID_DISPLAY_MODE_ID;
            // Select the base mode id based on the base mode refresh rate, if available, since this
            // will be the mode id the app voted for.
            Display.Mode baseMode = null;
            for (Display.Mode availableMode : availableModes) {
                if (primarySummary.baseModeRefreshRate
                        >= availableMode.getRefreshRate() - FLOAT_TOLERANCE
                        && primarySummary.baseModeRefreshRate
                        <= availableMode.getRefreshRate() + FLOAT_TOLERANCE) {
                    baseMode = availableMode;
                }
            }

            // Select the default mode if available. This is important because SurfaceFlinger
            // can do only seamless switches by default. Some devices (e.g. TV) don't support
            // seamless switching so the mode we select here won't be changed.
            for (int availableMode : availableModes) {
                if (availableMode == defaultMode.getModeId()) {
                    baseModeId = defaultMode.getModeId();
            if (baseMode == null) {
                for (Display.Mode availableMode : availableModes) {
                    if (availableMode.getModeId() == defaultMode.getModeId()) {
                        baseMode = defaultMode;
                        break;
                    }
                }
            }

            // If the application requests a display mode by setting
            // LayoutParams.preferredDisplayModeId, it will be the only available mode and it'll
            // be stored as baseModeId.
            if (baseModeId == INVALID_DISPLAY_MODE_ID && availableModes.length > 0) {
                baseModeId = availableModes[0];
            if (baseMode == null && !availableModes.isEmpty()) {
                baseMode = availableModes.get(0);
            }

            if (baseModeId == INVALID_DISPLAY_MODE_ID) {
            if (baseMode == null) {
                Slog.w(TAG, "Can't find a set of allowed modes which satisfies the votes. Falling"
                        + " back to the default mode. Display = " + displayId + ", votes = " + votes
                        + ", supported modes = " + Arrays.toString(modes));
@@ -363,31 +393,19 @@ public class DisplayModeDirector {
                        new RefreshRateRange(fps, fps));
            }

            if (mModeSwitchingType == DisplayManager.SWITCHING_TYPE_NONE
                    || primarySummary.disableRefreshRateSwitching) {
                float fps = baseMode.getRefreshRate();
                primarySummary.minRefreshRate = primarySummary.maxRefreshRate = fps;
                if (mModeSwitchingType == DisplayManager.SWITCHING_TYPE_NONE) {
                Display.Mode baseMode = null;
                for (Display.Mode mode : modes) {
                    if (mode.getModeId() == baseModeId) {
                        baseMode = mode;
                        break;
                    appRequestSummary.minRefreshRate = appRequestSummary.maxRefreshRate = fps;
                }
            }
                if (baseMode == null) {
                    // This should never happen.
                    throw new IllegalStateException(
                            "The base mode with id " + baseModeId
                                    + " is not in the list of supported modes.");
                }
                float fps = baseMode.getRefreshRate();
                return new DesiredDisplayModeSpecs(baseModeId,
                        /*allowGroupSwitching */ false,
                        new RefreshRateRange(fps, fps),
                        new RefreshRateRange(fps, fps));
            }

            boolean allowGroupSwitching =
                    mModeSwitchingType == DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS;

            return new DesiredDisplayModeSpecs(baseModeId,
            return new DesiredDisplayModeSpecs(baseMode.getModeId(),
                    allowGroupSwitching,
                    new RefreshRateRange(
                            primarySummary.minRefreshRate, primarySummary.maxRefreshRate),
@@ -396,8 +414,10 @@ public class DisplayModeDirector {
        }
    }

    private int[] filterModes(Display.Mode[] supportedModes, VoteSummary summary) {
    private ArrayList<Display.Mode> filterModes(Display.Mode[] supportedModes,
            VoteSummary summary) {
        ArrayList<Display.Mode> availableModes = new ArrayList<>();
        boolean missingBaseModeRefreshRate = summary.baseModeRefreshRate > 0f;
        for (Display.Mode mode : supportedModes) {
            if (mode.getPhysicalWidth() != summary.width
                    || mode.getPhysicalHeight() != summary.height) {
@@ -426,13 +446,16 @@ public class DisplayModeDirector {
                continue;
            }
            availableModes.add(mode);
            if (mode.getRefreshRate() >= summary.baseModeRefreshRate - FLOAT_TOLERANCE
                    && mode.getRefreshRate() <= summary.baseModeRefreshRate + FLOAT_TOLERANCE) {
                missingBaseModeRefreshRate = false;
            }
        final int size = availableModes.size();
        int[] availableModeIds = new int[size];
        for (int i = 0; i < size; i++) {
            availableModeIds[i] = availableModes.get(i).getModeId();
        }
        return availableModeIds;
        if (missingBaseModeRefreshRate) {
            return new ArrayList<>();
        }

        return availableModes;
    }

    /**
@@ -912,11 +935,10 @@ public class DisplayModeDirector {
        // by all other considerations. It acts to set a default frame rate for a device.
        public static final int PRIORITY_DEFAULT_REFRESH_RATE = 0;

        // FLICKER votes for a single refresh rate like [60,60], [90,90] or null.
        // If the higher voters result is a range, it will fix the rate to a single choice.
        // It's used to avoid refresh rate switches in certain conditions which may result in the
        // user seeing the display flickering when the switches occur.
        public static final int PRIORITY_FLICKER = 1;
        // PRIORITY_FLICKER_REFRESH_RATE votes for a single refresh rate like [60,60], [90,90] or
        // null. It is used to set a preferred refresh rate value in case the higher priority votes
        // result is a range.
        public static final int PRIORITY_FLICKER_REFRESH_RATE = 1;

        // SETTING_MIN_REFRESH_RATE is used to propose a lower bound of display refresh rate.
        // It votes [MIN_REFRESH_RATE, Float.POSITIVE_INFINITY]
@@ -933,9 +955,14 @@ public class DisplayModeDirector {
        // Application can specify preferred refresh rate with below attrs.
        // @see android.view.WindowManager.LayoutParams#preferredRefreshRate
        // @see android.view.WindowManager.LayoutParams#preferredDisplayModeId
        // System also forces some apps like denylisted app to run at a lower refresh rate.
        // These translates into votes for the base mode refresh rate and resolution to be
        // used by SurfaceFlinger as the policy of choosing the display mode. The system also
        // forces some apps like denylisted app to run at a lower refresh rate.
        // @see android.R.array#config_highRefreshRateBlacklist
        public static final int PRIORITY_APP_REQUEST_REFRESH_RATE = 4;
        // The preferred refresh rate is set on the main surface of the app outside of
        // DisplayModeDirector.
        // @see com.android.server.wm.WindowState#updateFrameRateSelectionPriorityIfNeeded
        public static final int PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE = 4;
        public static final int PRIORITY_APP_REQUEST_SIZE = 5;

        // SETTING_PEAK_REFRESH_RATE has a high priority and will restrict the bounds of the rest
@@ -945,9 +972,15 @@ public class DisplayModeDirector {
        // LOW_POWER_MODE force display to [0, 60HZ] if Settings.Global.LOW_POWER_MODE is on.
        public static final int PRIORITY_LOW_POWER_MODE = 7;

        // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the
        // higher priority voters' result is a range, it will fix the rate to a single choice.
        // It's used to avoid refresh rate switches in certain conditions which may result in the
        // user seeing the display flickering when the switches occur.
        public static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 8;

        // The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order
        // to function, so this needs to be the highest priority of all votes.
        public static final int PRIORITY_UDFPS = 8;
        public static final int PRIORITY_UDFPS = 9;

        // Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and
        // APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString.
@@ -978,34 +1011,64 @@ public class DisplayModeDirector {
         */
        public final RefreshRateRange refreshRateRange;

        /**
         * Whether refresh rate switching should be disabled (i.e. the refresh rate range is
         * a single value).
         */
        public final boolean disableRefreshRateSwitching;

        /**
         * The base mode refresh rate to be used for this display. This would be used when deciding
         * the base mode id.
         */
        public final float baseModeRefreshRate;

        public static Vote forRefreshRates(float minRefreshRate, float maxRefreshRate) {
            return new Vote(INVALID_SIZE, INVALID_SIZE, minRefreshRate, maxRefreshRate);
            return new Vote(INVALID_SIZE, INVALID_SIZE, minRefreshRate, maxRefreshRate,
                    minRefreshRate == maxRefreshRate, 0f);
        }

        public static Vote forSize(int width, int height) {
            return new Vote(width, height, 0f, Float.POSITIVE_INFINITY);
            return new Vote(width, height, 0f, Float.POSITIVE_INFINITY, false,
                    0f);
        }

        public static Vote forDisableRefreshRateSwitching() {
            return new Vote(INVALID_SIZE, INVALID_SIZE, 0f, Float.POSITIVE_INFINITY, true,
                    0f);
        }

        public static Vote forBaseModeRefreshRate(float baseModeRefreshRate) {
            return new Vote(INVALID_SIZE, INVALID_SIZE, 0f, Float.POSITIVE_INFINITY, false,
                    baseModeRefreshRate);
        }

        private Vote(int width, int height,
                float minRefreshRate, float maxRefreshRate) {
                float minRefreshRate, float maxRefreshRate,
                boolean disableRefreshRateSwitching,
                float baseModeRefreshRate) {
            this.width = width;
            this.height = height;
            this.refreshRateRange =
                    new RefreshRateRange(minRefreshRate, maxRefreshRate);
            this.disableRefreshRateSwitching = disableRefreshRateSwitching;
            this.baseModeRefreshRate = baseModeRefreshRate;
        }

        public static String priorityToString(int priority) {
            switch (priority) {
                case PRIORITY_DEFAULT_REFRESH_RATE:
                    return "PRIORITY_DEFAULT_REFRESH_RATE";
                case PRIORITY_FLICKER:
                    return "PRIORITY_FLICKER";
                case PRIORITY_FLICKER_REFRESH_RATE:
                    return "PRIORITY_FLICKER_REFRESH_RATE";
                case PRIORITY_FLICKER_REFRESH_RATE_SWITCH:
                    return "PRIORITY_FLICKER_REFRESH_RATE_SWITCH";
                case PRIORITY_USER_SETTING_MIN_REFRESH_RATE:
                    return "PRIORITY_USER_SETTING_MIN_REFRESH_RATE";
                case PRIORITY_APP_REQUEST_MAX_REFRESH_RATE:
                    return "PRIORITY_APP_REQUEST_MAX_REFRESH_RATE";
                case PRIORITY_APP_REQUEST_REFRESH_RATE:
                    return "PRIORITY_APP_REQUEST_REFRESH_RATE";
                case PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE:
                    return "PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE";
                case PRIORITY_APP_REQUEST_SIZE:
                    return "PRIORITY_APP_REQUEST_SIZE";
                case PRIORITY_USER_SETTING_PEAK_REFRESH_RATE:
@@ -1025,7 +1088,9 @@ public class DisplayModeDirector {
            return "Vote{"
                + "width=" + width + ", height=" + height
                + ", minRefreshRate=" + refreshRateRange.min
                + ", maxRefreshRate=" + refreshRateRange.max + "}";
                + ", maxRefreshRate=" + refreshRateRange.max
                + ", disableRefreshRateSwitching=" + disableRefreshRateSwitching
                + ", baseModeRefreshRate=" + baseModeRefreshRate + "}";
        }
    }

@@ -1209,21 +1274,22 @@ public class DisplayModeDirector {
                return;
            }

            final Vote refreshRateVote;
            final Vote baseModeRefreshRateVote;
            final Vote sizeVote;
            if (requestedMode != null) {
                mAppRequestedModeByDisplay.put(displayId, requestedMode);
                float refreshRate = requestedMode.getRefreshRate();
                refreshRateVote = Vote.forRefreshRates(refreshRate, refreshRate);
                baseModeRefreshRateVote =
                        Vote.forBaseModeRefreshRate(requestedMode.getRefreshRate());
                sizeVote = Vote.forSize(requestedMode.getPhysicalWidth(),
                        requestedMode.getPhysicalHeight());
            } else {
                mAppRequestedModeByDisplay.remove(displayId);
                refreshRateVote = null;
                baseModeRefreshRateVote = null;
                sizeVote = null;
            }

            updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, refreshRateVote);
            updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
                    baseModeRefreshRateVote);
            updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote);
        }

@@ -1525,7 +1591,8 @@ public class DisplayModeDirector {
                updateSensorStatus();
                if (!changeable) {
                    // Revoke previous vote from BrightnessObserver
                    updateVoteLocked(Vote.PRIORITY_FLICKER, null);
                    updateVoteLocked(Vote.PRIORITY_FLICKER_REFRESH_RATE, null);
                    updateVoteLocked(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, null);
                }
            }
        }
@@ -1773,7 +1840,8 @@ public class DisplayModeDirector {
            return false;
        }
        private void onBrightnessChangedLocked() {
            Vote vote = null;
            Vote refreshRateVote = null;
            Vote refreshRateSwitchingVote = null;

            if (mBrightness < 0) {
                // Either the setting isn't available or we shouldn't be observing yet anyways.
@@ -1783,20 +1851,25 @@ public class DisplayModeDirector {

            boolean insideLowZone = hasValidLowZone() && isInsideLowZone(mBrightness, mAmbientLux);
            if (insideLowZone) {
                vote = Vote.forRefreshRates(mRefreshRateInLowZone, mRefreshRateInLowZone);
                refreshRateVote =
                        Vote.forRefreshRates(mRefreshRateInLowZone, mRefreshRateInLowZone);
                refreshRateSwitchingVote = Vote.forDisableRefreshRateSwitching();
            }

            boolean insideHighZone = hasValidHighZone()
                    && isInsideHighZone(mBrightness, mAmbientLux);
            if (insideHighZone) {
                vote = Vote.forRefreshRates(mRefreshRateInHighZone, mRefreshRateInHighZone);
                refreshRateVote =
                        Vote.forRefreshRates(mRefreshRateInHighZone, mRefreshRateInHighZone);
                refreshRateSwitchingVote = Vote.forDisableRefreshRateSwitching();
            }

            if (mLoggingEnabled) {
                Slog.d(TAG, "Display brightness " + mBrightness + ", ambient lux " +  mAmbientLux
                        + ", Vote " + vote);
                        + ", Vote " + refreshRateVote);
            }
            updateVoteLocked(Vote.PRIORITY_FLICKER, vote);
            updateVoteLocked(Vote.PRIORITY_FLICKER_REFRESH_RATE, refreshRateVote);
            updateVoteLocked(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, refreshRateSwitchingVote);
        }

        private boolean hasValidLowZone() {
+15 −1
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;

import android.util.ArraySet;
import android.view.Display;
import android.view.Display.Mode;
import android.view.DisplayInfo;

@@ -137,6 +138,19 @@ class RefreshRatePolicy {
        if (mHighRefreshRateDenylist.isDenylisted(packageName)) {
            return mLowRefreshRateMode.getRefreshRate();
        }

        final int preferredModeId = getPreferredModeId(w);
        if (preferredModeId > 0) {
            DisplayInfo info = w.getDisplayInfo();
            if (info != null) {
                for (Display.Mode mode : info.supportedModes) {
                    if (preferredModeId == mode.getModeId()) {
                        return mode.getRefreshRate();
                    }
                }
            }
        }

        return 0;
    }

@@ -147,7 +161,7 @@ class RefreshRatePolicy {
            return 0;
        }

        // If the app set a preferredDisplayModeId, we ignore the max preferred refresh rate
        // If app requests a certain refresh rate or mode, don't override it.
        if (w.mAttrs.preferredDisplayModeId != 0) {
            return 0;
        }
+8 −7
Original line number Diff line number Diff line
@@ -751,11 +751,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
    int mFrameRateSelectionPriority = RefreshRatePolicy.LAYER_PRIORITY_UNSET;

    /**
     * This is the frame rate which is passed to SurfaceFlinger if the window is part of the
     * high refresh rate deny list. The variable is cached, so we do not send too many updates to
     * SF.
     * This is the frame rate which is passed to SurfaceFlinger if the window set a
     * preferredDisplayModeId or is part of the high refresh rate deny list.
     * The variable is cached, so we do not send too many updates to SF.
     */
    float mDenyListFrameRate = 0f;
    float mAppPreferredFrameRate = 0f;

    static final int BLAST_TIMEOUT_DURATION = 5000; /* milliseconds */

@@ -5406,10 +5406,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
        }

        final float refreshRate = refreshRatePolicy.getPreferredRefreshRate(this);
        if (mDenyListFrameRate != refreshRate) {
            mDenyListFrameRate = refreshRate;
        if (mAppPreferredFrameRate != refreshRate) {
            mAppPreferredFrameRate = refreshRate;
            getPendingTransaction().setFrameRate(
                    mSurfaceControl, mDenyListFrameRate, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
                    mSurfaceControl, mAppPreferredFrameRate,
                    Surface.FRAME_RATE_COMPATIBILITY_EXACT, Surface.CHANGE_FRAME_RATE_ALWAYS);
        }
    }

+395 −66

File changed.

Preview size limit exceeded, changes collapsed.

+24 −23

File changed.

Preview size limit exceeded, changes collapsed.

Loading