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

Commit 7e857c39 authored by Oleg Petsjonkin's avatar Oleg Petsjonkin
Browse files

Extracting VotesStorage to separate class with local locking

Bug: b/266789924
Test: atest com.android.server.display.mode
Change-Id: I8fb46b393a96db6476db58a025cf881516754bf9
parent 9c6a31ea
Loading
Loading
Loading
Loading
+41 −356

File changed.

Preview size limit exceeded, changes collapsed.

+13 −13
Original line number Diff line number Diff line
@@ -37,7 +37,7 @@ final class SkinThermalStatusObserver extends IThermalEventListener.Stub impleme
        DisplayManager.DisplayListener {
    private static final String TAG = "SkinThermalStatusObserver";

    private final DisplayModeDirector.BallotBox mBallotBox;
    private final VotesStorage mVotesStorage;
    private final DisplayModeDirector.Injector mInjector;

    private boolean mLoggingEnabled;
@@ -52,15 +52,15 @@ final class SkinThermalStatusObserver extends IThermalEventListener.Stub impleme
            mThermalThrottlingByDisplay = new SparseArray<>();

    SkinThermalStatusObserver(DisplayModeDirector.Injector injector,
            DisplayModeDirector.BallotBox ballotBox) {
        this(injector, ballotBox, BackgroundThread.getHandler());
            VotesStorage votesStorage) {
        this(injector, votesStorage, BackgroundThread.getHandler());
    }

    @VisibleForTesting
    SkinThermalStatusObserver(DisplayModeDirector.Injector injector,
            DisplayModeDirector.BallotBox ballotBox, Handler handler) {
            VotesStorage votesStorage, Handler handler) {
        mInjector = injector;
        mBallotBox = ballotBox;
        mVotesStorage = votesStorage;
        mHandler = handler;
    }

@@ -112,8 +112,8 @@ final class SkinThermalStatusObserver extends IThermalEventListener.Stub impleme
    public void onDisplayRemoved(int displayId) {
        synchronized (mThermalObserverLock) {
            mThermalThrottlingByDisplay.remove(displayId);
            mHandler.post(() -> mBallotBox.vote(displayId,
                    DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE, null));
            mHandler.post(() -> mVotesStorage.updateVote(displayId,
                    Vote.PRIORITY_SKIN_TEMPERATURE, null));
        }
        if (mLoggingEnabled) {
            Slog.d(TAG, "Display removed and voted: displayId=" + displayId);
@@ -218,11 +218,11 @@ final class SkinThermalStatusObserver extends IThermalEventListener.Stub impleme
        SurfaceControl.RefreshRateRange foundRange = findBestMatchingRefreshRateRange(currentStatus,
                throttlingMap);
        // if status <= currentStatus not found in the map reset vote
        DisplayModeDirector.Vote vote = null;
        Vote vote = null;
        if (foundRange != null) { // otherwise vote with found range
            vote = DisplayModeDirector.Vote.forRenderFrameRates(foundRange.min, foundRange.max);
            vote = Vote.forRenderFrameRates(foundRange.min, foundRange.max);
        }
        mBallotBox.vote(displayId, DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE, vote);
        mVotesStorage.updateVote(displayId, Vote.PRIORITY_SKIN_TEMPERATURE, vote);
        if (mLoggingEnabled) {
            Slog.d(TAG, "Voted: vote=" + vote + ", display =" + displayId);
        }
@@ -244,11 +244,11 @@ final class SkinThermalStatusObserver extends IThermalEventListener.Stub impleme

    private void fallbackReportThrottlingIfNeeded(int displayId,
            @Temperature.ThrottlingStatus int currentStatus) {
        DisplayModeDirector.Vote vote = null;
        Vote vote = null;
        if (currentStatus >= Temperature.THROTTLING_CRITICAL) {
            vote = DisplayModeDirector.Vote.forRenderFrameRates(0f, 60f);
            vote = Vote.forRenderFrameRates(0f, 60f);
        }
        mBallotBox.vote(displayId, DisplayModeDirector.Vote.PRIORITY_SKIN_TEMPERATURE, vote);
        mVotesStorage.updateVote(displayId, Vote.PRIORITY_SKIN_TEMPERATURE, vote);
        if (mLoggingEnabled) {
            Slog.d(TAG, "Voted(fallback): vote=" + vote + ", display =" + displayId);
        }
+237 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.mode;

import android.view.SurfaceControl;

final class Vote {
    // DEFAULT_RENDER_FRAME_RATE votes for render frame rate [0, DEFAULT]. As the lowest
    // priority vote, it's overridden by all other considerations. It acts to set a default
    // frame rate for a device.
    static final int PRIORITY_DEFAULT_RENDER_FRAME_RATE = 0;

    // 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.
    static final int PRIORITY_FLICKER_REFRESH_RATE = 1;

    // High-brightness-mode may need a specific range of refresh-rates to function properly.
    static final int PRIORITY_HIGH_BRIGHTNESS_MODE = 2;

    // SETTING_MIN_RENDER_FRAME_RATE is used to propose a lower bound of the render frame rate.
    // It votes [minRefreshRate, Float.POSITIVE_INFINITY]
    static final int PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE = 3;

    // APP_REQUEST_RENDER_FRAME_RATE_RANGE is used to for internal apps to limit the render
    // frame rate in certain cases, mostly to preserve power.
    // @see android.view.WindowManager.LayoutParams#preferredMinRefreshRate
    // @see android.view.WindowManager.LayoutParams#preferredMaxRefreshRate
    // It votes to [preferredMinRefreshRate, preferredMaxRefreshRate].
    static final int PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE = 4;

    // We split the app request into different priorities in case we can satisfy one desire
    // without the other.

    // Application can specify preferred refresh rate with below attrs.
    // @see android.view.WindowManager.LayoutParams#preferredRefreshRate
    // @see android.view.WindowManager.LayoutParams#preferredDisplayModeId
    //
    // When the app specifies a LayoutParams#preferredDisplayModeId, in addition to the
    // refresh rate, it also chooses a preferred size (resolution) as part of the selected
    // mode id. The app preference is then translated to APP_REQUEST_BASE_MODE_REFRESH_RATE and
    // optionally to APP_REQUEST_SIZE as well, if a mode id was selected.
    // The system also forces some apps like denylisted app to run at a lower refresh rate.
    // @see android.R.array#config_highRefreshRateBlacklist
    //
    // When summarizing the votes and filtering the allowed display modes, these votes determine
    // which mode id should be the base mode id to be sent to SurfaceFlinger:
    // - APP_REQUEST_BASE_MODE_REFRESH_RATE is used to validate the vote summary. If a summary
    //   includes a base mode refresh rate, but it is not in the refresh rate range, then the
    //   summary is considered invalid so we could drop a lower priority vote and try again.
    // - APP_REQUEST_SIZE is used to filter out display modes of a different size.
    //
    // The preferred refresh rate is set on the main surface of the app outside of
    // DisplayModeDirector.
    // @see com.android.server.wm.WindowState#updateFrameRateSelectionPriorityIfNeeded
    static final int PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE = 5;
    static final int PRIORITY_APP_REQUEST_SIZE = 6;

    // SETTING_PEAK_RENDER_FRAME_RATE has a high priority and will restrict the bounds of the
    // rest of low priority voters. It votes [0, max(PEAK, MIN)]
    static final int PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE = 7;

    // To avoid delay in switching between 60HZ -> 90HZ when activating LHBM, set refresh
    // rate to max value (same as for PRIORITY_UDFPS) on lock screen
    static final int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 8;

    // For concurrent displays we want to limit refresh rate on all displays
    static final int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 9;

    // LOW_POWER_MODE force the render frame rate to [0, 60HZ] if
    // Settings.Global.LOW_POWER_MODE is on.
    static final int PRIORITY_LOW_POWER_MODE = 10;

    // 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.
    static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 11;

    // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL.
    static final int PRIORITY_SKIN_TEMPERATURE = 12;

    // The proximity sensor needs the refresh rate to be locked in order to function, so this is
    // set to a high priority.
    static final int PRIORITY_PROXIMITY = 13;

    // 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.
    static final int PRIORITY_UDFPS = 14;

    // 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.

    static final int MIN_PRIORITY = PRIORITY_DEFAULT_RENDER_FRAME_RATE;
    static final int MAX_PRIORITY = PRIORITY_UDFPS;

    // The cutoff for the app request refresh rate range. Votes with priorities lower than this
    // value will not be considered when constructing the app request refresh rate range.
    static final int APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF =
            PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE;

    /**
     * A value signifying an invalid width or height in a vote.
     */
    static final int INVALID_SIZE = -1;

    /**
     * The requested width of the display in pixels, or INVALID_SIZE;
     */
    public final int width;
    /**
     * The requested height of the display in pixels, or INVALID_SIZE;
     */
    public final int height;
    /**
     * Information about the refresh rate frame rate ranges DM would like to set the display to.
     */
    public final SurfaceControl.RefreshRateRanges refreshRateRanges;

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

    /**
     * The preferred refresh rate selected by the app. It is used to validate that the summary
     * refresh rate ranges include this value, and are not restricted by a lower priority vote.
     */
    public final float appRequestBaseModeRefreshRate;

    static Vote forPhysicalRefreshRates(float minRefreshRate, float maxRefreshRate) {
        return new Vote(INVALID_SIZE, INVALID_SIZE, minRefreshRate, maxRefreshRate, 0,
                Float.POSITIVE_INFINITY,
                minRefreshRate == maxRefreshRate, 0f);
    }

    static Vote forRenderFrameRates(float minFrameRate, float maxFrameRate) {
        return new Vote(INVALID_SIZE, INVALID_SIZE, 0, Float.POSITIVE_INFINITY, minFrameRate,
                maxFrameRate,
                false, 0f);
    }

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

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

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

    private Vote(int width, int height,
            float minPhysicalRefreshRate,
            float maxPhysicalRefreshRate,
            float minRenderFrameRate,
            float maxRenderFrameRate,
            boolean disableRefreshRateSwitching,
            float baseModeRefreshRate) {
        this.width = width;
        this.height = height;
        this.refreshRateRanges = new SurfaceControl.RefreshRateRanges(
                new SurfaceControl.RefreshRateRange(minPhysicalRefreshRate, maxPhysicalRefreshRate),
                new SurfaceControl.RefreshRateRange(minRenderFrameRate, maxRenderFrameRate));
        this.disableRefreshRateSwitching = disableRefreshRateSwitching;
        this.appRequestBaseModeRefreshRate = baseModeRefreshRate;
    }

    static String priorityToString(int priority) {
        switch (priority) {
            case PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE:
                return "PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE";
            case PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE:
                return "PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE";
            case PRIORITY_APP_REQUEST_SIZE:
                return "PRIORITY_APP_REQUEST_SIZE";
            case PRIORITY_DEFAULT_RENDER_FRAME_RATE:
                return "PRIORITY_DEFAULT_REFRESH_RATE";
            case PRIORITY_FLICKER_REFRESH_RATE:
                return "PRIORITY_FLICKER_REFRESH_RATE";
            case PRIORITY_FLICKER_REFRESH_RATE_SWITCH:
                return "PRIORITY_FLICKER_REFRESH_RATE_SWITCH";
            case PRIORITY_HIGH_BRIGHTNESS_MODE:
                return "PRIORITY_HIGH_BRIGHTNESS_MODE";
            case PRIORITY_PROXIMITY:
                return "PRIORITY_PROXIMITY";
            case PRIORITY_LOW_POWER_MODE:
                return "PRIORITY_LOW_POWER_MODE";
            case PRIORITY_SKIN_TEMPERATURE:
                return "PRIORITY_SKIN_TEMPERATURE";
            case PRIORITY_UDFPS:
                return "PRIORITY_UDFPS";
            case PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE:
                return "PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE";
            case PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE:
                return "PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE";
            case PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE:
                return "PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE";
            case PRIORITY_LAYOUT_LIMITED_FRAME_RATE:
                return "PRIORITY_LAYOUT_LIMITED_FRAME_RATE";
            default:
                return Integer.toString(priority);
        }
    }

    @Override
    public String toString() {
        return "Vote: {"
                + "width: " + width + ", height: " + height
                + ", refreshRateRanges: " + refreshRateRanges
                + ", disableRefreshRateSwitching: " + disableRefreshRateSwitching
                + ", appRequestBaseModeRefreshRate: "  + appRequestBaseModeRefreshRate + "}";
    }
}
+152 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.mode;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.Slog;
import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;

import java.io.PrintWriter;

class VotesStorage {
    private static final String TAG = "VotesStorage";
    // Special ID used to indicate that given vote is to be applied globally, rather than to a
    // specific display.
    private static final int GLOBAL_ID = -1;

    private boolean mLoggingEnabled;

    private final Listener mListener;

    private final Object mStorageLock = new Object();
    // A map from the display ID to the collection of votes and their priority. The latter takes
    // the form of another map from the priority to the vote itself so that each priority is
    // guaranteed to have exactly one vote, which is also easily and efficiently replaceable.
    @GuardedBy("mStorageLock")
    private final SparseArray<SparseArray<Vote>> mVotesByDisplay = new SparseArray<>();

    VotesStorage(@NonNull Listener listener) {
        mListener = listener;
    }
    /** sets logging enabled/disabled for this class */
    void setLoggingEnabled(boolean loggingEnabled) {
        mLoggingEnabled = loggingEnabled;
    }
    /**
     * gets all votes for specific display, note that global display votes are also added to result
     */
    @NonNull
    SparseArray<Vote> getVotes(int displayId) {
        SparseArray<Vote> votesLocal;
        SparseArray<Vote> globalVotesLocal;
        synchronized (mStorageLock) {
            SparseArray<Vote> displayVotes = mVotesByDisplay.get(displayId);
            votesLocal = displayVotes != null ? displayVotes.clone() : new SparseArray<>();
            SparseArray<Vote> globalVotes = mVotesByDisplay.get(GLOBAL_ID);
            globalVotesLocal = globalVotes != null ? globalVotes.clone() : new SparseArray<>();
        }
        for (int i = 0; i < globalVotesLocal.size(); i++) {
            int priority = globalVotesLocal.keyAt(i);
            if (!votesLocal.contains(priority)) {
                votesLocal.put(priority, globalVotesLocal.valueAt(i));
            }
        }
        return votesLocal;
    }

    /** updates vote storage for all displays */
    void updateGlobalVote(int priority, @Nullable Vote vote) {
        updateVote(GLOBAL_ID, priority, vote);
    }

    /** updates vote storage */
    void updateVote(int displayId, int priority, @Nullable Vote vote) {
        if (mLoggingEnabled) {
            Slog.i(TAG, "updateVoteLocked(displayId=" + displayId
                    + ", priority=" + Vote.priorityToString(priority)
                    + ", vote=" + vote + ")");
        }
        if (priority < Vote.MIN_PRIORITY || priority > Vote.MAX_PRIORITY) {
            Slog.w(TAG, "Received a vote with an invalid priority, ignoring:"
                    + " priority=" + Vote.priorityToString(priority)
                    + ", vote=" + vote);
            return;
        }
        SparseArray<Vote> votes;
        synchronized (mStorageLock) {
            if (mVotesByDisplay.contains(displayId)) {
                votes = mVotesByDisplay.get(displayId);
            } else {
                votes = new SparseArray<>();
                mVotesByDisplay.put(displayId, votes);
            }
            if (vote != null) {
                votes.put(priority, vote);
            } else {
                votes.remove(priority);
            }
        }
        if (mLoggingEnabled) {
            Slog.i(TAG, "Updated votes for display=" + displayId + " votes=" + votes);
        }
        mListener.onChanged();
    }

    /** dump class values, for debugging */
    void dump(@NonNull PrintWriter pw) {
        SparseArray<SparseArray<Vote>> votesByDisplayLocal = new SparseArray<>();
        synchronized (mStorageLock) {
            for (int i = 0; i < mVotesByDisplay.size(); i++) {
                votesByDisplayLocal.put(mVotesByDisplay.keyAt(i),
                        mVotesByDisplay.valueAt(i).clone());
            }
        }
        pw.println("  mVotesByDisplay:");
        for (int i = 0; i < votesByDisplayLocal.size(); i++) {
            SparseArray<Vote> votes = votesByDisplayLocal.valueAt(i);
            if (votes.size() == 0) {
                continue;
            }
            pw.println("    " + votesByDisplayLocal.keyAt(i) + ":");
            for (int p = Vote.MAX_PRIORITY; p >= Vote.MIN_PRIORITY; p--) {
                Vote vote = votes.get(p);
                if (vote == null) {
                    continue;
                }
                pw.println("      " + Vote.priorityToString(p) + " -> " + vote);
            }
        }
    }

    @VisibleForTesting
    void injectVotesByDisplay(SparseArray<SparseArray<Vote>> votesByDisplay) {
        synchronized (mStorageLock) {
            mVotesByDisplay.clear();
            for (int i = 0; i < votesByDisplay.size(); i++) {
                mVotesByDisplay.put(votesByDisplay.keyAt(i), votesByDisplay.valueAt(i));
            }
        }
    }

    interface Listener {
        void onChanged();
    }
}
+2 −4
Original line number Diff line number Diff line
@@ -27,7 +27,7 @@ import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_R
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE;

import static com.android.server.display.mode.DisplayModeDirector.Vote.INVALID_SIZE;
import static com.android.server.display.mode.Vote.INVALID_SIZE;

import static com.google.common.truth.Truth.assertThat;

@@ -94,7 +94,6 @@ import com.android.server.display.DisplayDeviceConfig;
import com.android.server.display.TestUtils;
import com.android.server.display.mode.DisplayModeDirector.BrightnessObserver;
import com.android.server.display.mode.DisplayModeDirector.DesiredDisplayModeSpecs;
import com.android.server.display.mode.DisplayModeDirector.Vote;
import com.android.server.sensors.SensorManagerInternal;
import com.android.server.sensors.SensorManagerInternal.ProximityActiveListener;
import com.android.server.statusbar.StatusBarManagerInternal;
@@ -223,8 +222,7 @@ public class DisplayModeDirectorTest {
        assertThat(modeSpecs.appRequest.render.min).isEqualTo(0f);
        assertThat(modeSpecs.appRequest.render.max).isEqualTo(Float.POSITIVE_INFINITY);

        int numPriorities =
                DisplayModeDirector.Vote.MAX_PRIORITY - DisplayModeDirector.Vote.MIN_PRIORITY + 1;
        int numPriorities =  Vote.MAX_PRIORITY - Vote.MIN_PRIORITY + 1;

        // Ensure vote priority works as expected. As we add new votes with higher priority, they
        // should take precedence over lower priority votes.
Loading