| /* |
| * Copyright (C) 2019 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 static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED; |
| import static android.hardware.display.DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE; |
| import static android.os.PowerManager.BRIGHTNESS_INVALID_FLOAT; |
| import static android.view.Display.Mode.INVALID_MODE_ID; |
| |
| import static com.android.server.display.DisplayDeviceConfig.DEFAULT_LOW_REFRESH_RATE; |
| |
| import android.annotation.IntegerRes; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.database.ContentObserver; |
| import android.hardware.Sensor; |
| import android.hardware.SensorEvent; |
| import android.hardware.SensorEventListener; |
| import android.hardware.SensorManager; |
| import android.hardware.display.BrightnessInfo; |
| import android.hardware.display.DisplayManager; |
| import android.hardware.display.DisplayManagerInternal; |
| import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation; |
| import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback; |
| import android.net.Uri; |
| import android.os.Handler; |
| import android.os.IThermalEventListener; |
| import android.os.IThermalService; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.PowerManager; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.os.SystemClock; |
| import android.os.Temperature; |
| import android.os.UserHandle; |
| import android.provider.DeviceConfig; |
| import android.provider.DeviceConfigInterface; |
| import android.provider.Settings; |
| import android.sysprop.SurfaceFlingerProperties; |
| import android.util.IndentingPrintWriter; |
| import android.util.Pair; |
| import android.util.Slog; |
| import android.util.SparseArray; |
| import android.util.SparseBooleanArray; |
| import android.util.SparseIntArray; |
| import android.view.Display; |
| import android.view.DisplayInfo; |
| import android.view.SurfaceControl.RefreshRateRange; |
| import android.view.SurfaceControl.RefreshRateRanges; |
| |
| import com.android.internal.R; |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.display.BrightnessSynchronizer; |
| import com.android.internal.os.BackgroundThread; |
| import com.android.server.LocalServices; |
| import com.android.server.display.DisplayDeviceConfig; |
| import com.android.server.display.feature.DeviceConfigParameterProvider; |
| import com.android.server.display.feature.DisplayManagerFlags; |
| import com.android.server.display.utils.AmbientFilter; |
| import com.android.server.display.utils.AmbientFilterFactory; |
| import com.android.server.display.utils.DeviceConfigParsingUtils; |
| import com.android.server.display.utils.SensorUtils; |
| import com.android.server.sensors.SensorManagerInternal; |
| import com.android.server.sensors.SensorManagerInternal.ProximityActiveListener; |
| import com.android.server.statusbar.StatusBarManagerInternal; |
| |
| import java.io.PrintWriter; |
| import java.text.SimpleDateFormat; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Date; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.concurrent.Callable; |
| import java.util.function.Function; |
| import java.util.function.IntSupplier; |
| |
| /** |
| * The DisplayModeDirector is responsible for determining what modes are allowed to be automatically |
| * picked by the system based on system-wide and display-specific configuration. |
| */ |
| public class DisplayModeDirector { |
| public static final float SYNCHRONIZED_REFRESH_RATE_TARGET = DEFAULT_LOW_REFRESH_RATE; |
| public static final float SYNCHRONIZED_REFRESH_RATE_TOLERANCE = 1; |
| private static final String TAG = "DisplayModeDirector"; |
| private boolean mLoggingEnabled; |
| |
| private static final int MSG_REFRESH_RATE_RANGE_CHANGED = 1; |
| private static final int MSG_LOW_BRIGHTNESS_THRESHOLDS_CHANGED = 2; |
| private static final int MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED = 3; |
| private static final int MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED = 4; |
| private static final int MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED = 5; |
| private static final int MSG_HIGH_BRIGHTNESS_THRESHOLDS_CHANGED = 6; |
| private static final int MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED = 7; |
| private static final int MSG_REFRESH_RATE_IN_HBM_HDR_CHANGED = 8; |
| |
| private static final float FLOAT_TOLERANCE = RefreshRateRange.FLOAT_TOLERANCE; |
| |
| private final Object mLock = new Object(); |
| private final Context mContext; |
| |
| private final DisplayModeDirectorHandler mHandler; |
| private final Injector mInjector; |
| |
| private final AppRequestObserver mAppRequestObserver; |
| private final SettingsObserver mSettingsObserver; |
| private final DisplayObserver mDisplayObserver; |
| private final UdfpsObserver mUdfpsObserver; |
| private final SensorObserver mSensorObserver; |
| private final HbmObserver mHbmObserver; |
| private final SkinThermalStatusObserver mSkinThermalStatusObserver; |
| private final DeviceConfigParameterProvider mConfigParameterProvider; |
| private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings; |
| |
| @GuardedBy("mLock") |
| @Nullable |
| private DisplayDeviceConfig mDefaultDisplayDeviceConfig; |
| |
| // A map from the display ID to the supported modes on that display. |
| private SparseArray<Display.Mode[]> mSupportedModesByDisplay; |
| // A map from the display ID to the default mode of that display. |
| private SparseArray<Display.Mode> mDefaultModeByDisplay; |
| |
| private BrightnessObserver mBrightnessObserver; |
| |
| private DesiredDisplayModeSpecsListener mDesiredDisplayModeSpecsListener; |
| |
| private boolean mAlwaysRespectAppRequest; |
| |
| private final boolean mSupportsFrameRateOverride; |
| |
| private final VotesStorage mVotesStorage; |
| |
| /** |
| * The allowed refresh rate switching type. This is used by SurfaceFlinger. |
| */ |
| @DisplayManager.SwitchingType |
| private int mModeSwitchingType = DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS; |
| |
| /** |
| * Whether resolution range voting feature is enabled. |
| */ |
| private final boolean mIsDisplayResolutionRangeVotingEnabled; |
| |
| /** |
| * Whether user preferred mode voting feature is enabled. |
| */ |
| private final boolean mIsUserPreferredModeVoteEnabled; |
| |
| /** |
| * Whether limit display mode feature is enabled. |
| */ |
| private final boolean mIsExternalDisplayLimitModeEnabled; |
| |
| private final boolean mIsDisplaysRefreshRatesSynchronizationEnabled; |
| |
| public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler, |
| @NonNull DisplayManagerFlags displayManagerFlags) { |
| this(context, handler, new RealInjector(context), displayManagerFlags); |
| } |
| |
| public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler, |
| @NonNull Injector injector, |
| @NonNull DisplayManagerFlags displayManagerFlags) { |
| mIsDisplayResolutionRangeVotingEnabled = displayManagerFlags |
| .isDisplayResolutionRangeVotingEnabled(); |
| mIsUserPreferredModeVoteEnabled = displayManagerFlags.isUserPreferredModeVoteEnabled(); |
| mIsExternalDisplayLimitModeEnabled = displayManagerFlags |
| .isExternalDisplayLimitModeEnabled(); |
| mIsDisplaysRefreshRatesSynchronizationEnabled = displayManagerFlags |
| .isDisplaysRefreshRatesSynchronizationEnabled(); |
| mContext = context; |
| mHandler = new DisplayModeDirectorHandler(handler.getLooper()); |
| mInjector = injector; |
| mSupportedModesByDisplay = new SparseArray<>(); |
| mDefaultModeByDisplay = new SparseArray<>(); |
| mAppRequestObserver = new AppRequestObserver(); |
| mConfigParameterProvider = new DeviceConfigParameterProvider(injector.getDeviceConfig()); |
| mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings(); |
| mSettingsObserver = new SettingsObserver(context, handler); |
| mBrightnessObserver = new BrightnessObserver(context, handler, injector); |
| mDefaultDisplayDeviceConfig = null; |
| mUdfpsObserver = new UdfpsObserver(); |
| mVotesStorage = new VotesStorage(this::notifyDesiredDisplayModeSpecsChangedLocked); |
| mDisplayObserver = new DisplayObserver(context, handler, mVotesStorage); |
| mSensorObserver = new SensorObserver(context, mVotesStorage, injector); |
| mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, mVotesStorage); |
| mHbmObserver = new HbmObserver(injector, mVotesStorage, BackgroundThread.getHandler(), |
| mDeviceConfigDisplaySettings); |
| mAlwaysRespectAppRequest = false; |
| mSupportsFrameRateOverride = injector.supportsFrameRateOverride(); |
| } |
| |
| /** |
| * Tells the DisplayModeDirector to update allowed votes and begin observing relevant system |
| * state. |
| * |
| * This has to be deferred because the object may be constructed before the rest of the system |
| * is ready. |
| */ |
| public void start(SensorManager sensorManager) { |
| mSettingsObserver.observe(); |
| mDisplayObserver.observe(); |
| mBrightnessObserver.observe(sensorManager); |
| mSensorObserver.observe(); |
| mHbmObserver.observe(); |
| mSkinThermalStatusObserver.observe(); |
| synchronized (mLock) { |
| // We may have a listener already registered before the call to start, so go ahead and |
| // notify them to pick up our newly initialized state. |
| notifyDesiredDisplayModeSpecsChangedLocked(); |
| } |
| } |
| |
| /** |
| * Same as {@link #start(SensorManager)}, but for observers that need to be delayed even more, |
| * for example until SystemUI is ready. |
| */ |
| public void onBootCompleted() { |
| // UDFPS observer registers a listener with SystemUI which might not be ready until the |
| // system is fully booted. |
| mUdfpsObserver.observe(); |
| } |
| |
| /** |
| * Enables or disables component logging |
| */ |
| public void setLoggingEnabled(boolean loggingEnabled) { |
| if (mLoggingEnabled == loggingEnabled) { |
| return; |
| } |
| mLoggingEnabled = loggingEnabled; |
| mBrightnessObserver.setLoggingEnabled(loggingEnabled); |
| mSkinThermalStatusObserver.setLoggingEnabled(loggingEnabled); |
| mVotesStorage.setLoggingEnabled(loggingEnabled); |
| } |
| |
| private static final class VoteSummary { |
| public float minPhysicalRefreshRate; |
| public float maxPhysicalRefreshRate; |
| public float minRenderFrameRate; |
| public float maxRenderFrameRate; |
| public int width; |
| public int height; |
| public int minWidth; |
| public int minHeight; |
| public boolean disableRefreshRateSwitching; |
| public float appRequestBaseModeRefreshRate; |
| |
| VoteSummary() { |
| reset(); |
| } |
| |
| public void reset() { |
| minPhysicalRefreshRate = 0f; |
| maxPhysicalRefreshRate = Float.POSITIVE_INFINITY; |
| minRenderFrameRate = 0f; |
| maxRenderFrameRate = Float.POSITIVE_INFINITY; |
| width = Vote.INVALID_SIZE; |
| height = Vote.INVALID_SIZE; |
| minWidth = 0; |
| minHeight = 0; |
| disableRefreshRateSwitching = false; |
| appRequestBaseModeRefreshRate = 0f; |
| } |
| |
| @Override |
| public String toString() { |
| return "minPhysicalRefreshRate=" + minPhysicalRefreshRate |
| + ", maxPhysicalRefreshRate=" + maxPhysicalRefreshRate |
| + ", minRenderFrameRate=" + minRenderFrameRate |
| + ", maxRenderFrameRate=" + maxRenderFrameRate |
| + ", width=" + width |
| + ", height=" + height |
| + ", minWidth=" + minWidth |
| + ", minHeight=" + minHeight |
| + ", disableRefreshRateSwitching=" + disableRefreshRateSwitching |
| + ", appRequestBaseModeRefreshRate=" + appRequestBaseModeRefreshRate; |
| } |
| } |
| |
| // VoteSummary is returned as an output param to cut down a bit on the number of temporary |
| // objects. |
| private void summarizeVotes( |
| SparseArray<Vote> votes, |
| int lowestConsideredPriority, |
| int highestConsideredPriority, |
| /*out*/ VoteSummary summary) { |
| summary.reset(); |
| for (int priority = highestConsideredPriority; |
| priority >= lowestConsideredPriority; |
| priority--) { |
| Vote vote = votes.get(priority); |
| if (vote == null) { |
| continue; |
| } |
| |
| // For physical refresh rates, just use the tightest bounds of all the votes. |
| // The refresh rate cannot be lower than the minimal render frame rate. |
| final float minPhysicalRefreshRate = Math.max(vote.refreshRateRanges.physical.min, |
| vote.refreshRateRanges.render.min); |
| summary.minPhysicalRefreshRate = Math.max(summary.minPhysicalRefreshRate, |
| minPhysicalRefreshRate); |
| summary.maxPhysicalRefreshRate = Math.min(summary.maxPhysicalRefreshRate, |
| vote.refreshRateRanges.physical.max); |
| |
| // Same goes to render frame rate, but frame rate cannot exceed the max physical |
| // refresh rate |
| final float maxRenderFrameRate = Math.min(vote.refreshRateRanges.render.max, |
| vote.refreshRateRanges.physical.max); |
| summary.minRenderFrameRate = Math.max(summary.minRenderFrameRate, |
| vote.refreshRateRanges.render.min); |
| summary.maxRenderFrameRate = Math.min(summary.maxRenderFrameRate, maxRenderFrameRate); |
| |
| // 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 (vote.height > 0 && vote.width > 0) { |
| if (summary.width == Vote.INVALID_SIZE && summary.height == Vote.INVALID_SIZE) { |
| summary.width = vote.width; |
| summary.height = vote.height; |
| summary.minWidth = vote.minWidth; |
| summary.minHeight = vote.minHeight; |
| } else if (mIsDisplayResolutionRangeVotingEnabled) { |
| summary.width = Math.min(summary.width, vote.width); |
| summary.height = Math.min(summary.height, vote.height); |
| summary.minWidth = Math.max(summary.minWidth, vote.minWidth); |
| summary.minHeight = Math.max(summary.minHeight, vote.minHeight); |
| } |
| } |
| if (!summary.disableRefreshRateSwitching && vote.disableRefreshRateSwitching) { |
| summary.disableRefreshRateSwitching = true; |
| } |
| if (summary.appRequestBaseModeRefreshRate == 0f |
| && vote.appRequestBaseModeRefreshRate > 0f) { |
| summary.appRequestBaseModeRefreshRate = vote.appRequestBaseModeRefreshRate; |
| } |
| |
| if (mLoggingEnabled) { |
| Slog.w(TAG, "Vote summary for priority " + Vote.priorityToString(priority) |
| + ": " + summary); |
| } |
| } |
| } |
| |
| private boolean equalsWithinFloatTolerance(float a, float b) { |
| return a >= b - FLOAT_TOLERANCE && a <= b + FLOAT_TOLERANCE; |
| } |
| |
| private Display.Mode selectBaseMode(VoteSummary summary, |
| ArrayList<Display.Mode> availableModes, Display.Mode defaultMode) { |
| // The base mode should be as close as possible to the app requested mode. Since all the |
| // available modes already have the same size, we just need to look for a matching refresh |
| // rate. If the summary doesn't include an app requested refresh rate, we'll use the default |
| // mode refresh rate. 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. |
| float preferredRefreshRate = |
| summary.appRequestBaseModeRefreshRate > 0 |
| ? summary.appRequestBaseModeRefreshRate : defaultMode.getRefreshRate(); |
| for (Display.Mode availableMode : availableModes) { |
| if (equalsWithinFloatTolerance(preferredRefreshRate, availableMode.getRefreshRate())) { |
| return availableMode; |
| } |
| } |
| |
| // If we couldn't find a mode id based on the refresh rate, it means that the available |
| // modes were filtered by the app requested size, which is different that the default mode |
| // size, and the requested app refresh rate was dropped from the summary due to a higher |
| // priority vote. Since we don't have any other hint about the refresh rate, |
| // we just pick the first. |
| return !availableModes.isEmpty() ? availableModes.get(0) : null; |
| } |
| |
| private void disableModeSwitching(VoteSummary summary, float fps) { |
| summary.minPhysicalRefreshRate = summary.maxPhysicalRefreshRate = fps; |
| summary.maxRenderFrameRate = Math.min(summary.maxRenderFrameRate, fps); |
| |
| if (mLoggingEnabled) { |
| Slog.i(TAG, "Disabled mode switching on summary: " + summary); |
| } |
| } |
| |
| private void disableRenderRateSwitching(VoteSummary summary, float fps) { |
| summary.minRenderFrameRate = summary.maxRenderFrameRate; |
| |
| if (!isRenderRateAchievable(fps, summary)) { |
| summary.minRenderFrameRate = summary.maxRenderFrameRate = fps; |
| } |
| |
| if (mLoggingEnabled) { |
| Slog.i(TAG, "Disabled render rate switching on summary: " + summary); |
| } |
| } |
| |
| /** |
| * Calculates the refresh rate ranges and display modes that the system is allowed to freely |
| * switch between based on global and display-specific constraints. |
| * |
| * @param displayId The display to query for. |
| * @return The ID of the default mode the system should use, and the refresh rate range the |
| * system is allowed to switch between. |
| */ |
| @NonNull |
| public DesiredDisplayModeSpecs getDesiredDisplayModeSpecs(int displayId) { |
| synchronized (mLock) { |
| SparseArray<Vote> votes = mVotesStorage.getVotes(displayId); |
| Display.Mode[] modes = mSupportedModesByDisplay.get(displayId); |
| Display.Mode defaultMode = mDefaultModeByDisplay.get(displayId); |
| if (modes == null || defaultMode == null) { |
| Slog.e(TAG, |
| "Asked about unknown display, returning empty display mode specs!" |
| + "(id=" + displayId + ")"); |
| return new DesiredDisplayModeSpecs(); |
| } |
| |
| 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_RENDER_FRAME_RATE_RANGE; |
| highestConsideredPriority = Vote.PRIORITY_APP_REQUEST_SIZE; |
| } |
| |
| // We try to find a range of priorities which define a non-empty set of allowed display |
| // modes. Each time we fail we increase the lowest priority. |
| while (lowestConsideredPriority <= highestConsideredPriority) { |
| summarizeVotes( |
| votes, lowestConsideredPriority, highestConsideredPriority, primarySummary); |
| |
| // If we don't have anything specifying the width / height of the display, just use |
| // the default width and height. We don't want these switching out from underneath |
| // us since it's a pretty disruptive behavior. |
| if (primarySummary.height == Vote.INVALID_SIZE |
| || primarySummary.width == Vote.INVALID_SIZE) { |
| primarySummary.width = defaultMode.getPhysicalWidth(); |
| primarySummary.height = defaultMode.getPhysicalHeight(); |
| } else if (mIsDisplayResolutionRangeVotingEnabled) { |
| updateSummaryWithBestAllowedResolution(modes, primarySummary); |
| } |
| |
| availableModes = filterModes(modes, primarySummary); |
| if (!availableModes.isEmpty()) { |
| if (mLoggingEnabled) { |
| Slog.w(TAG, "Found available modes=" + availableModes |
| + " with lowest priority considered " |
| + Vote.priorityToString(lowestConsideredPriority) |
| + " and constraints: " |
| + "width=" + primarySummary.width |
| + ", height=" + primarySummary.height |
| + ", minPhysicalRefreshRate=" |
| + primarySummary.minPhysicalRefreshRate |
| + ", maxPhysicalRefreshRate=" |
| + primarySummary.maxPhysicalRefreshRate |
| + ", minRenderFrameRate=" + primarySummary.minRenderFrameRate |
| + ", maxRenderFrameRate=" + primarySummary.maxRenderFrameRate |
| + ", disableRefreshRateSwitching=" |
| + primarySummary.disableRefreshRateSwitching |
| + ", appRequestBaseModeRefreshRate=" |
| + primarySummary.appRequestBaseModeRefreshRate); |
| } |
| break; |
| } |
| |
| if (mLoggingEnabled) { |
| Slog.w(TAG, "Couldn't find available modes with lowest priority set to " |
| + Vote.priorityToString(lowestConsideredPriority) |
| + " and with the following constraints: " |
| + "width=" + primarySummary.width |
| + ", height=" + primarySummary.height |
| + ", minPhysicalRefreshRate=" + primarySummary.minPhysicalRefreshRate |
| + ", maxPhysicalRefreshRate=" + primarySummary.maxPhysicalRefreshRate |
| + ", minRenderFrameRate=" + primarySummary.minRenderFrameRate |
| + ", maxRenderFrameRate=" + primarySummary.maxRenderFrameRate |
| + ", disableRefreshRateSwitching=" |
| + primarySummary.disableRefreshRateSwitching |
| + ", appRequestBaseModeRefreshRate=" |
| + primarySummary.appRequestBaseModeRefreshRate); |
| } |
| |
| // If we haven't found anything with the current set of votes, drop the |
| // current lowest priority vote. |
| lowestConsideredPriority++; |
| } |
| |
| if (mLoggingEnabled) { |
| Slog.i(TAG, |
| "Primary physical range: [" |
| + primarySummary.minPhysicalRefreshRate |
| + " " |
| + primarySummary.maxPhysicalRefreshRate |
| + "] render frame rate range: [" |
| + primarySummary.minRenderFrameRate |
| + " " |
| + primarySummary.maxRenderFrameRate |
| + "]"); |
| } |
| |
| VoteSummary appRequestSummary = new VoteSummary(); |
| summarizeVotes( |
| votes, |
| Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, |
| Vote.MAX_PRIORITY, |
| appRequestSummary); |
| appRequestSummary.minPhysicalRefreshRate = |
| Math.min(appRequestSummary.minPhysicalRefreshRate, |
| primarySummary.minPhysicalRefreshRate); |
| appRequestSummary.maxPhysicalRefreshRate = |
| Math.max(appRequestSummary.maxPhysicalRefreshRate, |
| primarySummary.maxPhysicalRefreshRate); |
| appRequestSummary.minRenderFrameRate = |
| Math.min(appRequestSummary.minRenderFrameRate, |
| primarySummary.minRenderFrameRate); |
| appRequestSummary.maxRenderFrameRate = |
| Math.max(appRequestSummary.maxRenderFrameRate, |
| primarySummary.maxRenderFrameRate); |
| if (mLoggingEnabled) { |
| Slog.i(TAG, |
| "App request range: [" |
| + appRequestSummary.minPhysicalRefreshRate |
| + " " |
| + appRequestSummary.maxPhysicalRefreshRate |
| + "] Frame rate range: [" |
| + appRequestSummary.minRenderFrameRate |
| + " " |
| + appRequestSummary.maxRenderFrameRate |
| + "]"); |
| } |
| |
| Display.Mode baseMode = selectBaseMode(primarySummary, availableModes, defaultMode); |
| 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)); |
| |
| float fps = defaultMode.getRefreshRate(); |
| final RefreshRateRange range = new RefreshRateRange(fps, fps); |
| final RefreshRateRanges ranges = new RefreshRateRanges(range, range); |
| return new DesiredDisplayModeSpecs(defaultMode.getModeId(), |
| /*allowGroupSwitching */ false, |
| ranges, ranges); |
| } |
| |
| boolean modeSwitchingDisabled = |
| mModeSwitchingType == DisplayManager.SWITCHING_TYPE_NONE |
| || mModeSwitchingType |
| == DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY; |
| |
| if (modeSwitchingDisabled || primarySummary.disableRefreshRateSwitching) { |
| float fps = baseMode.getRefreshRate(); |
| disableModeSwitching(primarySummary, fps); |
| if (modeSwitchingDisabled) { |
| disableModeSwitching(appRequestSummary, fps); |
| disableRenderRateSwitching(primarySummary, fps); |
| |
| if (mModeSwitchingType == DisplayManager.SWITCHING_TYPE_NONE) { |
| disableRenderRateSwitching(appRequestSummary, fps); |
| } |
| } |
| } |
| |
| boolean allowGroupSwitching = |
| mModeSwitchingType == DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS; |
| |
| return new DesiredDisplayModeSpecs(baseMode.getModeId(), |
| allowGroupSwitching, |
| new RefreshRateRanges( |
| new RefreshRateRange( |
| primarySummary.minPhysicalRefreshRate, |
| primarySummary.maxPhysicalRefreshRate), |
| new RefreshRateRange( |
| primarySummary.minRenderFrameRate, |
| primarySummary.maxRenderFrameRate)), |
| new RefreshRateRanges( |
| new RefreshRateRange( |
| appRequestSummary.minPhysicalRefreshRate, |
| appRequestSummary.maxPhysicalRefreshRate), |
| new RefreshRateRange( |
| appRequestSummary.minRenderFrameRate, |
| appRequestSummary.maxRenderFrameRate))); |
| } |
| } |
| |
| private boolean isRenderRateAchievable(float physicalRefreshRate, VoteSummary summary) { |
| // Check whether the render frame rate range is achievable by the mode's physical |
| // refresh rate, meaning that if a divisor of the physical refresh rate is in range |
| // of the render frame rate. |
| // For example for the render frame rate [50, 70]: |
| // - 120Hz is in range as we can render at 60hz by skipping every other frame, |
| // which is within the render rate range |
| // - 90hz is not in range as none of the even divisors (i.e. 90, 45, 30) |
| // fall within the acceptable render range. |
| final int divisor = |
| (int) Math.ceil((physicalRefreshRate / summary.maxRenderFrameRate) |
| - FLOAT_TOLERANCE); |
| float adjustedPhysicalRefreshRate = physicalRefreshRate / divisor; |
| return adjustedPhysicalRefreshRate >= (summary.minRenderFrameRate - FLOAT_TOLERANCE); |
| } |
| |
| private ArrayList<Display.Mode> filterModes(Display.Mode[] supportedModes, |
| VoteSummary summary) { |
| if (summary.minRenderFrameRate > summary.maxRenderFrameRate + FLOAT_TOLERANCE) { |
| if (mLoggingEnabled) { |
| Slog.w(TAG, "Vote summary resulted in empty set (invalid frame rate range)" |
| + ": minRenderFrameRate=" + summary.minRenderFrameRate |
| + ", maxRenderFrameRate=" + summary.maxRenderFrameRate); |
| } |
| return new ArrayList<>(); |
| } |
| |
| ArrayList<Display.Mode> availableModes = new ArrayList<>(); |
| boolean missingBaseModeRefreshRate = summary.appRequestBaseModeRefreshRate > 0f; |
| for (Display.Mode mode : supportedModes) { |
| if (mode.getPhysicalWidth() != summary.width |
| || mode.getPhysicalHeight() != summary.height) { |
| if (mLoggingEnabled) { |
| Slog.w(TAG, "Discarding mode " + mode.getModeId() + ", wrong size" |
| + ": desiredWidth=" + summary.width |
| + ": desiredHeight=" + summary.height |
| + ": actualWidth=" + mode.getPhysicalWidth() |
| + ": actualHeight=" + mode.getPhysicalHeight()); |
| } |
| continue; |
| } |
| final float physicalRefreshRate = mode.getRefreshRate(); |
| // Some refresh rates are calculated based on frame timings, so they aren't *exactly* |
| // equal to expected refresh rate. Given that, we apply a bit of tolerance to this |
| // comparison. |
| if (physicalRefreshRate < (summary.minPhysicalRefreshRate - FLOAT_TOLERANCE) |
| || physicalRefreshRate > (summary.maxPhysicalRefreshRate + FLOAT_TOLERANCE)) { |
| if (mLoggingEnabled) { |
| Slog.w(TAG, "Discarding mode " + mode.getModeId() |
| + ", outside refresh rate bounds" |
| + ": minPhysicalRefreshRate=" + summary.minPhysicalRefreshRate |
| + ", maxPhysicalRefreshRate=" + summary.maxPhysicalRefreshRate |
| + ", modeRefreshRate=" + physicalRefreshRate); |
| } |
| continue; |
| } |
| |
| // The physical refresh rate must be in the render frame rate range, unless |
| // frame rate override is supported. |
| if (!mSupportsFrameRateOverride) { |
| if (physicalRefreshRate < (summary.minRenderFrameRate - FLOAT_TOLERANCE) |
| || physicalRefreshRate > (summary.maxRenderFrameRate + FLOAT_TOLERANCE)) { |
| if (mLoggingEnabled) { |
| Slog.w(TAG, "Discarding mode " + mode.getModeId() |
| + ", outside render rate bounds" |
| + ": minPhysicalRefreshRate=" + summary.minPhysicalRefreshRate |
| + ", maxPhysicalRefreshRate=" + summary.maxPhysicalRefreshRate |
| + ", modeRefreshRate=" + physicalRefreshRate); |
| } |
| continue; |
| } |
| } |
| |
| if (!isRenderRateAchievable(physicalRefreshRate, summary)) { |
| if (mLoggingEnabled) { |
| Slog.w(TAG, "Discarding mode " + mode.getModeId() |
| + ", outside frame rate bounds" |
| + ": minRenderFrameRate=" + summary.minRenderFrameRate |
| + ", maxRenderFrameRate=" + summary.maxRenderFrameRate |
| + ", modePhysicalRefreshRate=" + physicalRefreshRate); |
| } |
| continue; |
| } |
| |
| availableModes.add(mode); |
| if (equalsWithinFloatTolerance(mode.getRefreshRate(), |
| summary.appRequestBaseModeRefreshRate)) { |
| missingBaseModeRefreshRate = false; |
| } |
| } |
| if (missingBaseModeRefreshRate) { |
| return new ArrayList<>(); |
| } |
| |
| return availableModes; |
| } |
| |
| private void updateSummaryWithBestAllowedResolution(final Display.Mode[] supportedModes, |
| VoteSummary outSummary) { |
| final int maxAllowedWidth = outSummary.width; |
| final int maxAllowedHeight = outSummary.height; |
| if (mLoggingEnabled) { |
| Slog.i(TAG, "updateSummaryWithBestAllowedResolution " + outSummary); |
| } |
| outSummary.width = Vote.INVALID_SIZE; |
| outSummary.height = Vote.INVALID_SIZE; |
| |
| int maxNumberOfPixels = 0; |
| for (Display.Mode mode : supportedModes) { |
| if (mode.getPhysicalWidth() > maxAllowedWidth |
| || mode.getPhysicalHeight() > maxAllowedHeight |
| || mode.getPhysicalWidth() < outSummary.minWidth |
| || mode.getPhysicalHeight() < outSummary.minHeight) { |
| continue; |
| } |
| |
| int numberOfPixels = mode.getPhysicalHeight() * mode.getPhysicalWidth(); |
| if (numberOfPixels > maxNumberOfPixels || (mode.getPhysicalWidth() == maxAllowedWidth |
| && mode.getPhysicalHeight() == maxAllowedHeight)) { |
| if (mLoggingEnabled) { |
| Slog.i(TAG, "updateSummaryWithBestAllowedResolution updated with " + mode); |
| } |
| maxNumberOfPixels = numberOfPixels; |
| outSummary.width = mode.getPhysicalWidth(); |
| outSummary.height = mode.getPhysicalHeight(); |
| } |
| } |
| } |
| |
| /** |
| * Gets the observer responsible for application display mode requests. |
| */ |
| @NonNull |
| public AppRequestObserver getAppRequestObserver() { |
| // We don't need to lock here because mAppRequestObserver is a final field, which is |
| // guaranteed to be visible on all threads after construction. |
| return mAppRequestObserver; |
| } |
| |
| /** |
| * Sets the desiredDisplayModeSpecsListener for changes to display mode and refresh rate ranges. |
| */ |
| public void setDesiredDisplayModeSpecsListener( |
| @Nullable DesiredDisplayModeSpecsListener desiredDisplayModeSpecsListener) { |
| synchronized (mLock) { |
| mDesiredDisplayModeSpecsListener = desiredDisplayModeSpecsListener; |
| } |
| } |
| |
| /** |
| * Called when the underlying display device of the default display is changed. |
| * Some data in this class relates to the physical display of the device, and so we need to |
| * reload the configurations based on this. |
| * E.g. the brightness sensors and refresh rate capabilities depend on the physical display |
| * device that is being used, so will be reloaded. |
| * |
| * @param displayDeviceConfig configurations relating to the underlying display device. |
| */ |
| public void defaultDisplayDeviceUpdated(DisplayDeviceConfig displayDeviceConfig) { |
| synchronized (mLock) { |
| mDefaultDisplayDeviceConfig = displayDeviceConfig; |
| mSettingsObserver.setRefreshRates(displayDeviceConfig, |
| /* attemptReadFromFeatureParams= */ true); |
| mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig, |
| /* attemptReadFromFeatureParams= */ true); |
| mBrightnessObserver.reloadLightSensor(displayDeviceConfig); |
| mHbmObserver.setupHdrRefreshRates(displayDeviceConfig); |
| } |
| } |
| |
| /** |
| * When enabled the app requested display mode is always selected and all |
| * other votes will be ignored. This is used for testing purposes. |
| */ |
| public void setShouldAlwaysRespectAppRequestedMode(boolean enabled) { |
| synchronized (mLock) { |
| if (mAlwaysRespectAppRequest != enabled) { |
| mAlwaysRespectAppRequest = enabled; |
| notifyDesiredDisplayModeSpecsChangedLocked(); |
| } |
| } |
| } |
| |
| /** |
| * Returns whether we are running in a mode which always selects the app requested display mode |
| * and ignores user settings and policies for low brightness, low battery etc. |
| */ |
| public boolean shouldAlwaysRespectAppRequestedMode() { |
| synchronized (mLock) { |
| return mAlwaysRespectAppRequest; |
| } |
| } |
| |
| /** |
| * Sets the display mode switching type. |
| * @param newType new mode switching type |
| */ |
| public void setModeSwitchingType(@DisplayManager.SwitchingType int newType) { |
| synchronized (mLock) { |
| if (newType != mModeSwitchingType) { |
| mModeSwitchingType = newType; |
| notifyDesiredDisplayModeSpecsChangedLocked(); |
| } |
| } |
| } |
| |
| /** |
| * Returns the display mode switching type. |
| */ |
| @DisplayManager.SwitchingType |
| public int getModeSwitchingType() { |
| synchronized (mLock) { |
| return mModeSwitchingType; |
| } |
| } |
| |
| /** |
| * Retrieve the Vote for the given display and priority. Intended only for testing purposes. |
| * |
| * @param displayId the display to query for |
| * @param priority the priority of the vote to return |
| * @return the vote corresponding to the given {@code displayId} and {@code priority}, |
| * or {@code null} if there isn't one |
| */ |
| @VisibleForTesting |
| @Nullable |
| Vote getVote(int displayId, int priority) { |
| SparseArray<Vote> votes = mVotesStorage.getVotes(displayId); |
| return votes.get(priority); |
| } |
| |
| /** |
| * Print the object's state and debug information into the given stream. |
| * |
| * @param pw The stream to dump information to. |
| */ |
| public void dump(PrintWriter pw) { |
| pw.println("DisplayModeDirector"); |
| synchronized (mLock) { |
| pw.println(" mSupportedModesByDisplay:"); |
| for (int i = 0; i < mSupportedModesByDisplay.size(); i++) { |
| final int id = mSupportedModesByDisplay.keyAt(i); |
| final Display.Mode[] modes = mSupportedModesByDisplay.valueAt(i); |
| pw.println(" " + id + " -> " + Arrays.toString(modes)); |
| } |
| pw.println(" mDefaultModeByDisplay:"); |
| for (int i = 0; i < mDefaultModeByDisplay.size(); i++) { |
| final int id = mDefaultModeByDisplay.keyAt(i); |
| final Display.Mode mode = mDefaultModeByDisplay.valueAt(i); |
| pw.println(" " + id + " -> " + mode); |
| } |
| pw.println(" mModeSwitchingType: " + switchingTypeToString(mModeSwitchingType)); |
| pw.println(" mAlwaysRespectAppRequest: " + mAlwaysRespectAppRequest); |
| mSettingsObserver.dumpLocked(pw); |
| mAppRequestObserver.dumpLocked(pw); |
| mBrightnessObserver.dumpLocked(pw); |
| mUdfpsObserver.dumpLocked(pw); |
| mHbmObserver.dumpLocked(pw); |
| mSkinThermalStatusObserver.dumpLocked(pw); |
| } |
| mVotesStorage.dump(pw); |
| mSensorObserver.dump(pw); |
| } |
| |
| @GuardedBy("mLock") |
| private float getMaxRefreshRateLocked(int displayId) { |
| Display.Mode[] modes = mSupportedModesByDisplay.get(displayId); |
| float maxRefreshRate = 0f; |
| for (Display.Mode mode : modes) { |
| if (mode.getRefreshRate() > maxRefreshRate) { |
| maxRefreshRate = mode.getRefreshRate(); |
| } |
| } |
| return maxRefreshRate; |
| } |
| |
| private void notifyDesiredDisplayModeSpecsChangedLocked() { |
| if (mDesiredDisplayModeSpecsListener != null |
| && !mHandler.hasMessages(MSG_REFRESH_RATE_RANGE_CHANGED)) { |
| // We need to post this to a handler to avoid calling out while holding the lock |
| // since we know there are things that both listen for changes as well as provide |
| // information. If we did call out while holding the lock, then there's no |
| // guaranteed lock order and we run the real of risk deadlock. |
| Message msg = mHandler.obtainMessage( |
| MSG_REFRESH_RATE_RANGE_CHANGED, mDesiredDisplayModeSpecsListener); |
| msg.sendToTarget(); |
| } |
| } |
| |
| private static String switchingTypeToString(@DisplayManager.SwitchingType int type) { |
| switch (type) { |
| case DisplayManager.SWITCHING_TYPE_NONE: |
| return "SWITCHING_TYPE_NONE"; |
| case DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS: |
| return "SWITCHING_TYPE_WITHIN_GROUPS"; |
| case DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS: |
| return "SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS"; |
| case DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY: |
| return "SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY"; |
| default: |
| return "Unknown SwitchingType " + type; |
| } |
| } |
| |
| @VisibleForTesting |
| void injectSupportedModesByDisplay(SparseArray<Display.Mode[]> supportedModesByDisplay) { |
| mSupportedModesByDisplay = supportedModesByDisplay; |
| } |
| |
| @VisibleForTesting |
| void injectDefaultModeByDisplay(SparseArray<Display.Mode> defaultModeByDisplay) { |
| mDefaultModeByDisplay = defaultModeByDisplay; |
| } |
| |
| @VisibleForTesting |
| void injectVotesByDisplay(SparseArray<SparseArray<Vote>> votesByDisplay) { |
| mVotesStorage.injectVotesByDisplay(votesByDisplay); |
| } |
| |
| @VisibleForTesting |
| void injectBrightnessObserver(BrightnessObserver brightnessObserver) { |
| mBrightnessObserver = brightnessObserver; |
| } |
| |
| @VisibleForTesting |
| BrightnessObserver getBrightnessObserver() { |
| return mBrightnessObserver; |
| } |
| |
| @VisibleForTesting |
| SettingsObserver getSettingsObserver() { |
| return mSettingsObserver; |
| } |
| |
| @VisibleForTesting |
| UdfpsObserver getUdpfsObserver() { |
| return mUdfpsObserver; |
| } |
| |
| @VisibleForTesting |
| HbmObserver getHbmObserver() { |
| return mHbmObserver; |
| } |
| |
| @VisibleForTesting |
| DesiredDisplayModeSpecs getDesiredDisplayModeSpecsWithInjectedFpsSettings( |
| float minRefreshRate, float peakRefreshRate, float defaultRefreshRate) { |
| synchronized (mLock) { |
| mSettingsObserver.updateRefreshRateSettingLocked( |
| minRefreshRate, peakRefreshRate, defaultRefreshRate); |
| return getDesiredDisplayModeSpecs(Display.DEFAULT_DISPLAY); |
| } |
| } |
| |
| /** |
| * Listens for changes refresh rate coordination. |
| */ |
| public interface DesiredDisplayModeSpecsListener { |
| /** |
| * Called when the refresh rate range may have changed. |
| */ |
| void onDesiredDisplayModeSpecsChanged(); |
| } |
| |
| private final class DisplayModeDirectorHandler extends Handler { |
| DisplayModeDirectorHandler(Looper looper) { |
| super(looper, null, true /*async*/); |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case MSG_LOW_BRIGHTNESS_THRESHOLDS_CHANGED: { |
| Pair<float[], float[]> thresholds = (Pair<float[], float[]>) msg.obj; |
| mBrightnessObserver.onDeviceConfigLowBrightnessThresholdsChanged( |
| thresholds.first, thresholds.second); |
| break; |
| } |
| |
| case MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED: { |
| int refreshRateInZone = msg.arg1; |
| mBrightnessObserver.onDeviceConfigRefreshRateInLowZoneChanged( |
| refreshRateInZone); |
| break; |
| } |
| |
| case MSG_HIGH_BRIGHTNESS_THRESHOLDS_CHANGED: { |
| Pair<float[], float[]> thresholds = (Pair<float[], float[]>) msg.obj; |
| |
| mBrightnessObserver.onDeviceConfigHighBrightnessThresholdsChanged( |
| thresholds.first, thresholds.second); |
| |
| break; |
| } |
| |
| case MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED: { |
| int refreshRateInZone = msg.arg1; |
| mBrightnessObserver.onDeviceConfigRefreshRateInHighZoneChanged( |
| refreshRateInZone); |
| break; |
| } |
| |
| case MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED: |
| Float defaultPeakRefreshRate = (Float) msg.obj; |
| mSettingsObserver.onDeviceConfigDefaultPeakRefreshRateChanged( |
| defaultPeakRefreshRate); |
| break; |
| |
| case MSG_REFRESH_RATE_RANGE_CHANGED: |
| DesiredDisplayModeSpecsListener desiredDisplayModeSpecsListener = |
| (DesiredDisplayModeSpecsListener) msg.obj; |
| desiredDisplayModeSpecsListener.onDesiredDisplayModeSpecsChanged(); |
| break; |
| |
| case MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED: { |
| int refreshRateInHbmSunlight = msg.arg1; |
| mHbmObserver.onDeviceConfigRefreshRateInHbmSunlightChanged( |
| refreshRateInHbmSunlight); |
| break; |
| } |
| |
| case MSG_REFRESH_RATE_IN_HBM_HDR_CHANGED: { |
| int refreshRateInHbmHdr = msg.arg1; |
| mHbmObserver.onDeviceConfigRefreshRateInHbmHdrChanged(refreshRateInHbmHdr); |
| break; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Information about the desired display mode to be set by the system. Includes the base |
| * mode ID and the primary and app request refresh rate ranges. |
| * |
| * We have this class in addition to SurfaceControl.DesiredDisplayConfigSpecs to make clear the |
| * distinction between the config ID / physical index that |
| * SurfaceControl.DesiredDisplayConfigSpecs uses, and the mode ID used here. |
| */ |
| public static final class DesiredDisplayModeSpecs { |
| |
| /** |
| * Base mode ID. This is what system defaults to for all other settings, or |
| * if the refresh rate range is not available. |
| */ |
| public int baseModeId; |
| |
| /** |
| * If true this will allow switching between modes in different display configuration |
| * groups. This way the user may see visual interruptions when the display mode changes. |
| */ |
| public boolean allowGroupSwitching; |
| |
| /** |
| * The primary refresh rate ranges. |
| */ |
| public final RefreshRateRanges primary; |
| /** |
| * The app request refresh rate ranges. Lower priority considerations won't be included in |
| * this range, allowing SurfaceFlinger to consider additional refresh rates for apps that |
| * call setFrameRate(). This range will be greater than or equal to the primary refresh rate |
| * range, never smaller. |
| */ |
| public final RefreshRateRanges appRequest; |
| |
| public DesiredDisplayModeSpecs() { |
| primary = new RefreshRateRanges(); |
| appRequest = new RefreshRateRanges(); |
| } |
| |
| public DesiredDisplayModeSpecs(int baseModeId, |
| boolean allowGroupSwitching, |
| @NonNull RefreshRateRanges primary, |
| @NonNull RefreshRateRanges appRequest) { |
| this.baseModeId = baseModeId; |
| this.allowGroupSwitching = allowGroupSwitching; |
| this.primary = primary; |
| this.appRequest = appRequest; |
| } |
| |
| /** |
| * Returns a string representation of the object. |
| */ |
| @Override |
| public String toString() { |
| return String.format("baseModeId=%d allowGroupSwitching=%b" |
| + " primary=%s" |
| + " appRequest=%s", |
| baseModeId, allowGroupSwitching, primary.toString(), |
| appRequest.toString()); |
| } |
| |
| /** |
| * Checks whether the two objects have the same values. |
| */ |
| @Override |
| public boolean equals(Object other) { |
| if (other == this) { |
| return true; |
| } |
| |
| if (!(other instanceof DesiredDisplayModeSpecs)) { |
| return false; |
| } |
| |
| DesiredDisplayModeSpecs desiredDisplayModeSpecs = (DesiredDisplayModeSpecs) other; |
| |
| if (baseModeId != desiredDisplayModeSpecs.baseModeId) { |
| return false; |
| } |
| if (allowGroupSwitching != desiredDisplayModeSpecs.allowGroupSwitching) { |
| return false; |
| } |
| if (!primary.equals(desiredDisplayModeSpecs.primary)) { |
| return false; |
| } |
| if (!appRequest.equals( |
| desiredDisplayModeSpecs.appRequest)) { |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(baseModeId, allowGroupSwitching, primary, appRequest); |
| } |
| |
| /** |
| * Copy values from the other object. |
| */ |
| public void copyFrom(DesiredDisplayModeSpecs other) { |
| baseModeId = other.baseModeId; |
| allowGroupSwitching = other.allowGroupSwitching; |
| primary.physical.min = other.primary.physical.min; |
| primary.physical.max = other.primary.physical.max; |
| primary.render.min = other.primary.render.min; |
| primary.render.max = other.primary.render.max; |
| |
| appRequest.physical.min = other.appRequest.physical.min; |
| appRequest.physical.max = other.appRequest.physical.max; |
| appRequest.render.min = other.appRequest.render.min; |
| appRequest.render.max = other.appRequest.render.max; |
| } |
| } |
| |
| @VisibleForTesting |
| final class SettingsObserver extends ContentObserver { |
| private final Uri mPeakRefreshRateSetting = |
| Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE); |
| private final Uri mMinRefreshRateSetting = |
| Settings.System.getUriFor(Settings.System.MIN_REFRESH_RATE); |
| private final Uri mLowPowerModeSetting = |
| Settings.Global.getUriFor(Settings.Global.LOW_POWER_MODE); |
| private final Uri mMatchContentFrameRateSetting = |
| Settings.Secure.getUriFor(Settings.Secure.MATCH_CONTENT_FRAME_RATE); |
| |
| private final Context mContext; |
| private float mDefaultPeakRefreshRate; |
| private float mDefaultRefreshRate; |
| |
| SettingsObserver(@NonNull Context context, @NonNull Handler handler) { |
| super(handler); |
| mContext = context; |
| // We don't want to load from the DeviceConfig while constructing since this leads to |
| // a spike in the latency of DisplayManagerService startup. This happens because |
| // reading from the DeviceConfig is an intensive IO operation and having it in the |
| // startup phase where we thrive to keep the latency very low has significant impact. |
| setRefreshRates(/* displayDeviceConfig= */ null, |
| /* attemptReadFromFeatureParams= */ false); |
| } |
| |
| /** |
| * This is used to update the refresh rate configs from the DeviceConfig, which |
| * if missing from DisplayDeviceConfig, and finally fallback to config.xml. |
| */ |
| public void setRefreshRates(DisplayDeviceConfig displayDeviceConfig, |
| boolean attemptReadFromFeatureParams) { |
| setDefaultPeakRefreshRate(displayDeviceConfig, attemptReadFromFeatureParams); |
| mDefaultRefreshRate = |
| (displayDeviceConfig == null) ? (float) mContext.getResources().getInteger( |
| R.integer.config_defaultRefreshRate) |
| : (float) displayDeviceConfig.getDefaultRefreshRate(); |
| } |
| |
| public void observe() { |
| final ContentResolver cr = mContext.getContentResolver(); |
| mInjector.registerPeakRefreshRateObserver(cr, this); |
| cr.registerContentObserver(mMinRefreshRateSetting, false /*notifyDescendants*/, this, |
| UserHandle.USER_SYSTEM); |
| cr.registerContentObserver(mLowPowerModeSetting, false /*notifyDescendants*/, this, |
| UserHandle.USER_SYSTEM); |
| cr.registerContentObserver(mMatchContentFrameRateSetting, false /*notifyDescendants*/, |
| this); |
| |
| float deviceConfigDefaultPeakRefresh = |
| mConfigParameterProvider.getPeakRefreshRateDefault(); |
| if (deviceConfigDefaultPeakRefresh != -1) { |
| mDefaultPeakRefreshRate = deviceConfigDefaultPeakRefresh; |
| } |
| |
| synchronized (mLock) { |
| updateRefreshRateSettingLocked(); |
| updateLowPowerModeSettingLocked(); |
| updateModeSwitchingTypeSettingLocked(); |
| } |
| } |
| |
| public void setDefaultRefreshRate(float refreshRate) { |
| synchronized (mLock) { |
| mDefaultRefreshRate = refreshRate; |
| updateRefreshRateSettingLocked(); |
| } |
| } |
| |
| public void onDeviceConfigDefaultPeakRefreshRateChanged(Float defaultPeakRefreshRate) { |
| synchronized (mLock) { |
| if (defaultPeakRefreshRate == null) { |
| setDefaultPeakRefreshRate(mDefaultDisplayDeviceConfig, |
| /* attemptReadFromFeatureParams= */ false); |
| updateRefreshRateSettingLocked(); |
| } else if (mDefaultPeakRefreshRate != defaultPeakRefreshRate) { |
| mDefaultPeakRefreshRate = defaultPeakRefreshRate; |
| updateRefreshRateSettingLocked(); |
| } |
| } |
| } |
| |
| @Override |
| public void onChange(boolean selfChange, Uri uri, int userId) { |
| synchronized (mLock) { |
| if (mPeakRefreshRateSetting.equals(uri) |
| || mMinRefreshRateSetting.equals(uri)) { |
| updateRefreshRateSettingLocked(); |
| } else if (mLowPowerModeSetting.equals(uri)) { |
| updateLowPowerModeSettingLocked(); |
| } else if (mMatchContentFrameRateSetting.equals(uri)) { |
| updateModeSwitchingTypeSettingLocked(); |
| } |
| } |
| } |
| |
| @VisibleForTesting |
| float getDefaultRefreshRate() { |
| return mDefaultRefreshRate; |
| } |
| |
| @VisibleForTesting |
| float getDefaultPeakRefreshRate() { |
| return mDefaultPeakRefreshRate; |
| } |
| |
| private void setDefaultPeakRefreshRate(DisplayDeviceConfig displayDeviceConfig, |
| boolean attemptReadFromFeatureParams) { |
| float defaultPeakRefreshRate = -1; |
| |
| if (attemptReadFromFeatureParams) { |
| try { |
| defaultPeakRefreshRate = mConfigParameterProvider.getPeakRefreshRateDefault(); |
| } catch (Exception exception) { |
| // Do nothing |
| } |
| } |
| if (defaultPeakRefreshRate == -1) { |
| defaultPeakRefreshRate = |
| (displayDeviceConfig == null) ? (float) mContext.getResources().getInteger( |
| R.integer.config_defaultPeakRefreshRate) |
| : (float) displayDeviceConfig.getDefaultPeakRefreshRate(); |
| } |
| mDefaultPeakRefreshRate = defaultPeakRefreshRate; |
| } |
| |
| private void updateLowPowerModeSettingLocked() { |
| boolean inLowPowerMode = Settings.Global.getInt(mContext.getContentResolver(), |
| Settings.Global.LOW_POWER_MODE, 0 /*default*/) != 0; |
| final Vote vote; |
| if (inLowPowerMode) { |
| vote = Vote.forRenderFrameRates(0f, 60f); |
| } else { |
| vote = null; |
| } |
| mVotesStorage.updateGlobalVote(Vote.PRIORITY_LOW_POWER_MODE, vote); |
| mBrightnessObserver.onLowPowerModeEnabledLocked(inLowPowerMode); |
| } |
| |
| private void updateRefreshRateSettingLocked() { |
| final ContentResolver cr = mContext.getContentResolver(); |
| float minRefreshRate = Settings.System.getFloatForUser(cr, |
| Settings.System.MIN_REFRESH_RATE, 0f, cr.getUserId()); |
| float peakRefreshRate = Settings.System.getFloatForUser(cr, |
| Settings.System.PEAK_REFRESH_RATE, mDefaultPeakRefreshRate, cr.getUserId()); |
| updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate); |
| } |
| |
| private void updateRefreshRateSettingLocked( |
| float minRefreshRate, float peakRefreshRate, float defaultRefreshRate) { |
| // TODO(b/156304339): The logic in here, aside from updating the refresh rate votes, is |
| // used to predict if we're going to be doing frequent refresh rate switching, and if |
| // so, enable the brightness observer. The logic here is more complicated and fragile |
| // than necessary, and we should improve it. See b/156304339 for more info. |
| Vote peakVote = peakRefreshRate == 0f |
| ? null |
| : Vote.forRenderFrameRates(0f, Math.max(minRefreshRate, peakRefreshRate)); |
| mVotesStorage.updateGlobalVote(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE, |
| peakVote); |
| mVotesStorage.updateGlobalVote(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE, |
| Vote.forRenderFrameRates(minRefreshRate, Float.POSITIVE_INFINITY)); |
| Vote defaultVote = |
| defaultRefreshRate == 0f |
| ? null : Vote.forRenderFrameRates(0f, defaultRefreshRate); |
| mVotesStorage.updateGlobalVote(Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE, defaultVote); |
| |
| float maxRefreshRate; |
| if (peakRefreshRate == 0f && defaultRefreshRate == 0f) { |
| // We require that at least one of the peak or default refresh rate values are |
| // set. The brightness observer requires that we're able to predict whether or not |
| // we're going to do frequent refresh rate switching, and with the way the code is |
| // currently written, we need either a default or peak refresh rate value for that. |
| Slog.e(TAG, "Default and peak refresh rates are both 0. One of them should be set" |
| + " to a valid value."); |
| maxRefreshRate = minRefreshRate; |
| } else if (peakRefreshRate == 0f) { |
| maxRefreshRate = defaultRefreshRate; |
| } else if (defaultRefreshRate == 0f) { |
| maxRefreshRate = peakRefreshRate; |
| } else { |
| maxRefreshRate = Math.min(defaultRefreshRate, peakRefreshRate); |
| } |
| |
| mBrightnessObserver.onRefreshRateSettingChangedLocked(minRefreshRate, maxRefreshRate); |
| } |
| |
| private void updateModeSwitchingTypeSettingLocked() { |
| final ContentResolver cr = mContext.getContentResolver(); |
| int switchingType = Settings.Secure.getIntForUser( |
| cr, Settings.Secure.MATCH_CONTENT_FRAME_RATE, mModeSwitchingType /*default*/, |
| cr.getUserId()); |
| if (switchingType != mModeSwitchingType) { |
| mModeSwitchingType = switchingType; |
| notifyDesiredDisplayModeSpecsChangedLocked(); |
| } |
| } |
| |
| public void dumpLocked(PrintWriter pw) { |
| pw.println(" SettingsObserver"); |
| pw.println(" mDefaultRefreshRate: " + mDefaultRefreshRate); |
| pw.println(" mDefaultPeakRefreshRate: " + mDefaultPeakRefreshRate); |
| } |
| } |
| |
| /** |
| * Responsible for keeping track of app requested refresh rates per display |
| */ |
| public final class AppRequestObserver { |
| private final SparseArray<Display.Mode> mAppRequestedModeByDisplay; |
| private final SparseArray<RefreshRateRange> mAppPreferredRefreshRateRangeByDisplay; |
| |
| AppRequestObserver() { |
| mAppRequestedModeByDisplay = new SparseArray<>(); |
| mAppPreferredRefreshRateRangeByDisplay = new SparseArray<>(); |
| } |
| |
| /** |
| * Sets refresh rates from app request |
| */ |
| public void setAppRequest(int displayId, int modeId, float requestedMinRefreshRateRange, |
| float requestedMaxRefreshRateRange) { |
| synchronized (mLock) { |
| setAppRequestedModeLocked(displayId, modeId); |
| setAppPreferredRefreshRateRangeLocked(displayId, requestedMinRefreshRateRange, |
| requestedMaxRefreshRateRange); |
| } |
| } |
| |
| private void setAppRequestedModeLocked(int displayId, int modeId) { |
| final Display.Mode requestedMode = findModeByIdLocked(displayId, modeId); |
| if (Objects.equals(requestedMode, mAppRequestedModeByDisplay.get(displayId))) { |
| return; |
| } |
| |
| final Vote baseModeRefreshRateVote; |
| final Vote sizeVote; |
| if (requestedMode != null) { |
| mAppRequestedModeByDisplay.put(displayId, requestedMode); |
| baseModeRefreshRateVote = |
| Vote.forBaseModeRefreshRate(requestedMode.getRefreshRate()); |
| sizeVote = Vote.forSize(requestedMode.getPhysicalWidth(), |
| requestedMode.getPhysicalHeight()); |
| } else { |
| mAppRequestedModeByDisplay.remove(displayId); |
| baseModeRefreshRateVote = null; |
| sizeVote = null; |
| } |
| |
| mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, |
| baseModeRefreshRateVote); |
| mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote); |
| } |
| |
| private void setAppPreferredRefreshRateRangeLocked(int displayId, |
| float requestedMinRefreshRateRange, float requestedMaxRefreshRateRange) { |
| final Vote vote; |
| |
| RefreshRateRange refreshRateRange = null; |
| if (requestedMinRefreshRateRange > 0 || requestedMaxRefreshRateRange > 0) { |
| float min = requestedMinRefreshRateRange; |
| float max = requestedMaxRefreshRateRange > 0 |
| ? requestedMaxRefreshRateRange : Float.POSITIVE_INFINITY; |
| refreshRateRange = new RefreshRateRange(min, max); |
| if (refreshRateRange.min == 0 && refreshRateRange.max == 0) { |
| // requestedMinRefreshRateRange/requestedMaxRefreshRateRange were invalid |
| refreshRateRange = null; |
| } |
| } |
| |
| if (Objects.equals(refreshRateRange, |
| mAppPreferredRefreshRateRangeByDisplay.get(displayId))) { |
| return; |
| } |
| |
| if (refreshRateRange != null) { |
| mAppPreferredRefreshRateRangeByDisplay.put(displayId, refreshRateRange); |
| vote = Vote.forRenderFrameRates(refreshRateRange.min, refreshRateRange.max); |
| } else { |
| mAppPreferredRefreshRateRangeByDisplay.remove(displayId); |
| vote = null; |
| } |
| mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE, |
| vote); |
| } |
| |
| private Display.Mode findModeByIdLocked(int displayId, int modeId) { |
| Display.Mode[] modes = mSupportedModesByDisplay.get(displayId); |
| if (modes == null) { |
| return null; |
| } |
| for (Display.Mode mode : modes) { |
| if (mode.getModeId() == modeId) { |
| return mode; |
| } |
| } |
| return null; |
| } |
| |
| private void dumpLocked(PrintWriter pw) { |
| pw.println(" AppRequestObserver"); |
| pw.println(" mAppRequestedModeByDisplay:"); |
| for (int i = 0; i < mAppRequestedModeByDisplay.size(); i++) { |
| final int id = mAppRequestedModeByDisplay.keyAt(i); |
| final Display.Mode mode = mAppRequestedModeByDisplay.valueAt(i); |
| pw.println(" " + id + " -> " + mode); |
| } |
| pw.println(" mAppPreferredRefreshRateRangeByDisplay:"); |
| for (int i = 0; i < mAppPreferredRefreshRateRangeByDisplay.size(); i++) { |
| final int id = mAppPreferredRefreshRateRangeByDisplay.keyAt(i); |
| final RefreshRateRange refreshRateRange = |
| mAppPreferredRefreshRateRangeByDisplay.valueAt(i); |
| pw.println(" " + id + " -> " + refreshRateRange); |
| } |
| } |
| } |
| |
| private final class DisplayObserver implements DisplayManager.DisplayListener { |
| // Note that we can never call into DisplayManager or any of the non-POD classes it |
| // returns, while holding mLock since it may call into DMS, which might be simultaneously |
| // calling into us already holding its own lock. |
| private final Context mContext; |
| private final Handler mHandler; |
| private final VotesStorage mVotesStorage; |
| private int mExternalDisplayPeakWidth; |
| private int mExternalDisplayPeakHeight; |
| private int mExternalDisplayPeakRefreshRate; |
| private final boolean mRefreshRateSynchronizationEnabled; |
| private final Set<Integer> mExternalDisplaysConnected = new HashSet<>(); |
| |
| DisplayObserver(Context context, Handler handler, VotesStorage votesStorage) { |
| mContext = context; |
| mHandler = handler; |
| mVotesStorage = votesStorage; |
| mExternalDisplayPeakRefreshRate = mContext.getResources().getInteger( |
| R.integer.config_externalDisplayPeakRefreshRate); |
| mExternalDisplayPeakWidth = mContext.getResources().getInteger( |
| R.integer.config_externalDisplayPeakWidth); |
| mExternalDisplayPeakHeight = mContext.getResources().getInteger( |
| R.integer.config_externalDisplayPeakHeight); |
| mRefreshRateSynchronizationEnabled = mContext.getResources().getBoolean( |
| R.bool.config_refreshRateSynchronizationEnabled); |
| } |
| |
| private boolean isExternalDisplayLimitModeEnabled() { |
| return mExternalDisplayPeakWidth > 0 |
| && mExternalDisplayPeakHeight > 0 |
| && mExternalDisplayPeakRefreshRate > 0 |
| && mIsExternalDisplayLimitModeEnabled |
| && mIsDisplayResolutionRangeVotingEnabled |
| && mIsUserPreferredModeVoteEnabled; |
| } |
| |
| private boolean isRefreshRateSynchronizationEnabled() { |
| return mRefreshRateSynchronizationEnabled |
| && mIsDisplaysRefreshRatesSynchronizationEnabled; |
| } |
| |
| public void observe() { |
| mInjector.registerDisplayListener(this, mHandler); |
| |
| // Populate existing displays |
| SparseArray<Display.Mode[]> modes = new SparseArray<>(); |
| SparseArray<Display.Mode> defaultModes = new SparseArray<>(); |
| DisplayInfo info = new DisplayInfo(); |
| Display[] displays = mInjector.getDisplays(); |
| for (Display d : displays) { |
| final int displayId = d.getDisplayId(); |
| d.getDisplayInfo(info); |
| modes.put(displayId, info.supportedModes); |
| defaultModes.put(displayId, info.getDefaultMode()); |
| } |
| synchronized (mLock) { |
| final int size = modes.size(); |
| for (int i = 0; i < size; i++) { |
| mSupportedModesByDisplay.put(modes.keyAt(i), modes.valueAt(i)); |
| mDefaultModeByDisplay.put(defaultModes.keyAt(i), defaultModes.valueAt(i)); |
| } |
| } |
| } |
| |
| @Override |
| public void onDisplayAdded(int displayId) { |
| DisplayInfo displayInfo = getDisplayInfo(displayId); |
| updateDisplayModes(displayId, displayInfo); |
| updateLayoutLimitedFrameRate(displayId, displayInfo); |
| updateUserSettingDisplayPreferredSize(displayInfo); |
| updateDisplaysPeakRefreshRateAndResolution(displayInfo); |
| addDisplaysSynchronizedPeakRefreshRate(displayInfo); |
| } |
| |
| @Override |
| public void onDisplayRemoved(int displayId) { |
| synchronized (mLock) { |
| mSupportedModesByDisplay.remove(displayId); |
| mDefaultModeByDisplay.remove(displayId); |
| } |
| updateLayoutLimitedFrameRate(displayId, null); |
| removeUserSettingDisplayPreferredSize(displayId); |
| removeDisplaysPeakRefreshRateAndResolution(displayId); |
| removeDisplaysSynchronizedPeakRefreshRate(displayId); |
| } |
| |
| @Override |
| public void onDisplayChanged(int displayId) { |
| DisplayInfo displayInfo = getDisplayInfo(displayId); |
| updateDisplayModes(displayId, displayInfo); |
| updateLayoutLimitedFrameRate(displayId, displayInfo); |
| updateUserSettingDisplayPreferredSize(displayInfo); |
| } |
| |
| @Nullable |
| private DisplayInfo getDisplayInfo(int displayId) { |
| DisplayInfo info = new DisplayInfo(); |
| // Display info might be invalid, in this case return null |
| return mInjector.getDisplayInfo(displayId, info) ? info : null; |
| } |
| |
| private void updateLayoutLimitedFrameRate(int displayId, @Nullable DisplayInfo info) { |
| Vote vote = info != null && info.layoutLimitedRefreshRate != null |
| ? Vote.forPhysicalRefreshRates(info.layoutLimitedRefreshRate.min, |
| info.layoutLimitedRefreshRate.max) : null; |
| mVotesStorage.updateVote(displayId, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE, vote); |
| } |
| |
| private void removeUserSettingDisplayPreferredSize(int displayId) { |
| if (!mIsUserPreferredModeVoteEnabled) { |
| return; |
| } |
| mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE, |
| null); |
| } |
| |
| private void updateUserSettingDisplayPreferredSize(@Nullable DisplayInfo info) { |
| if (info == null || !mIsUserPreferredModeVoteEnabled) { |
| return; |
| } |
| |
| var preferredMode = findDisplayPreferredMode(info); |
| if (preferredMode == null) { |
| removeUserSettingDisplayPreferredSize(info.displayId); |
| return; |
| } |
| |
| mVotesStorage.updateVote(info.displayId, |
| Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE, |
| Vote.forSize(/* width */ preferredMode.getPhysicalWidth(), |
| /* height */ preferredMode.getPhysicalHeight())); |
| } |
| |
| @Nullable |
| private Display.Mode findDisplayPreferredMode(@NonNull DisplayInfo info) { |
| if (info.userPreferredModeId == INVALID_MODE_ID) { |
| return null; |
| } |
| for (var mode : info.supportedModes) { |
| if (mode.getModeId() == info.userPreferredModeId) { |
| return mode; |
| } |
| } |
| return null; |
| } |
| |
| private void removeDisplaysPeakRefreshRateAndResolution(int displayId) { |
| if (!isExternalDisplayLimitModeEnabled()) { |
| return; |
| } |
| |
| mVotesStorage.updateVote(displayId, |
| Vote.PRIORITY_LIMIT_MODE, null); |
| } |
| |
| private void updateDisplaysPeakRefreshRateAndResolution(@Nullable final DisplayInfo info) { |
| // Only consider external display, only in case the refresh rate and resolution limits |
| // are non-zero. |
| if (info == null || info.type != Display.TYPE_EXTERNAL |
| || !isExternalDisplayLimitModeEnabled()) { |
| return; |
| } |
| |
| mVotesStorage.updateVote(info.displayId, |
| Vote.PRIORITY_LIMIT_MODE, |
| Vote.forSizeAndPhysicalRefreshRatesRange( |
| /* minWidth */ 0, /* minHeight */ 0, |
| mExternalDisplayPeakWidth, |
| mExternalDisplayPeakHeight, |
| /* minPhysicalRefreshRate */ 0, |
| mExternalDisplayPeakRefreshRate)); |
| } |
| |
| /** |
| * Sets 60Hz target refresh rate as the vote with |
| * {@link Vote#PRIORITY_SYNCHRONIZED_REFRESH_RATE} priority. |
| */ |
| private void addDisplaysSynchronizedPeakRefreshRate(@Nullable final DisplayInfo info) { |
| if (info == null || info.type != Display.TYPE_EXTERNAL |
| || !isRefreshRateSynchronizationEnabled()) { |
| return; |
| } |
| synchronized (mLock) { |
| mExternalDisplaysConnected.add(info.displayId); |
| if (mExternalDisplaysConnected.size() != 1) { |
| return; |
| } |
| } |
| // set minRefreshRate as the max refresh rate. |
| mVotesStorage.updateGlobalVote(Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE, |
| Vote.forPhysicalRefreshRates( |
| SYNCHRONIZED_REFRESH_RATE_TARGET |
| - SYNCHRONIZED_REFRESH_RATE_TOLERANCE, |
| SYNCHRONIZED_REFRESH_RATE_TARGET |
| + SYNCHRONIZED_REFRESH_RATE_TOLERANCE)); |
| } |
| |
| private void removeDisplaysSynchronizedPeakRefreshRate(final int displayId) { |
| if (!isRefreshRateSynchronizationEnabled()) { |
| return; |
| } |
| synchronized (mLock) { |
| if (!mExternalDisplaysConnected.contains(displayId)) { |
| return; |
| } |
| mExternalDisplaysConnected.remove(displayId); |
| if (mExternalDisplaysConnected.size() != 0) { |
| return; |
| } |
| } |
| mVotesStorage.updateGlobalVote(Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE, null); |
| } |
| |
| private void updateDisplayModes(int displayId, @Nullable DisplayInfo info) { |
| if (info == null) { |
| return; |
| } |
| boolean changed = false; |
| synchronized (mLock) { |
| if (!Arrays.equals(mSupportedModesByDisplay.get(displayId), info.supportedModes)) { |
| mSupportedModesByDisplay.put(displayId, info.supportedModes); |
| changed = true; |
| } |
| if (!Objects.equals(mDefaultModeByDisplay.get(displayId), info.getDefaultMode())) { |
| changed = true; |
| mDefaultModeByDisplay.put(displayId, info.getDefaultMode()); |
| } |
| if (changed) { |
| notifyDesiredDisplayModeSpecsChangedLocked(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * This class manages brightness threshold for switching between 60 hz and higher refresh rate. |
| * See more information at the definition of |
| * {@link R.array#config_brightnessThresholdsOfPeakRefreshRate} and |
| * {@link R.array#config_ambientThresholdsOfPeakRefreshRate}. |
| */ |
| @VisibleForTesting |
| public class BrightnessObserver implements DisplayManager.DisplayListener { |
| private static final int LIGHT_SENSOR_RATE_MS = 250; |
| |
| /** |
| * Brightness thresholds for the low zone. Paired with lux thresholds. |
| * |
| * A negative value means that only the lux threshold should be applied. |
| */ |
| private float[] mLowDisplayBrightnessThresholds; |
| /** |
| * Lux thresholds for the low zone. Paired with brightness thresholds. |
| * |
| * A negative value means that only the display brightness threshold should be applied. |
| */ |
| private float[] mLowAmbientBrightnessThresholds; |
| |
| /** |
| * Brightness thresholds for the high zone. Paired with lux thresholds. |
| * |
| * A negative value means that only the lux threshold should be applied. |
| */ |
| private float[] mHighDisplayBrightnessThresholds; |
| /** |
| * Lux thresholds for the high zone. Paired with brightness thresholds. |
| * |
| * A negative value means that only the display brightness threshold should be applied. |
| */ |
| private float[] mHighAmbientBrightnessThresholds; |
| // valid threshold if any item from the array >= 0 |
| private boolean mShouldObserveDisplayLowChange; |
| private boolean mShouldObserveAmbientLowChange; |
| private boolean mShouldObserveDisplayHighChange; |
| private boolean mShouldObserveAmbientHighChange; |
| private boolean mLoggingEnabled; |
| |
| private SensorManager mSensorManager; |
| private Sensor mLightSensor; |
| private Sensor mRegisteredLightSensor; |
| private String mLightSensorType; |
| private String mLightSensorName; |
| private final LightSensorEventListener mLightSensorListener = |
| new LightSensorEventListener(); |
| // Take it as low brightness before valid sensor data comes |
| private float mAmbientLux = -1.0f; |
| private AmbientFilter mAmbientFilter; |
| |
| private float mBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; |
| |
| private final Context mContext; |
| private final Injector mInjector; |
| private final Handler mHandler; |
| |
| private final IThermalEventListener.Stub mThermalListener = |
| new IThermalEventListener.Stub() { |
| @Override |
| public void notifyThrottling(Temperature temp) { |
| @Temperature.ThrottlingStatus int currentStatus = temp.getStatus(); |
| synchronized (mLock) { |
| if (mThermalStatus != currentStatus) { |
| mThermalStatus = currentStatus; |
| } |
| onBrightnessChangedLocked(); |
| } |
| } |
| }; |
| private boolean mThermalRegistered; |
| |
| // Enable light sensor only when mShouldObserveAmbientLowChange is true or |
| // mShouldObserveAmbientHighChange is true, screen is on, peak refresh rate |
| // changeable and low power mode off. After initialization, these states will |
| // be updated from the same handler thread. |
| private int mDefaultDisplayState = Display.STATE_UNKNOWN; |
| private boolean mRefreshRateChangeable = false; |
| private boolean mLowPowerModeEnabled = false; |
| |
| @Nullable |
| private SparseArray<RefreshRateRange> mLowZoneRefreshRateForThermals; |
| private int mRefreshRateInLowZone; |
| |
| @Nullable |
| private SparseArray<RefreshRateRange> mHighZoneRefreshRateForThermals; |
| private int mRefreshRateInHighZone; |
| |
| @GuardedBy("mLock") |
| private @Temperature.ThrottlingStatus int mThermalStatus = Temperature.THROTTLING_NONE; |
| |
| BrightnessObserver(Context context, Handler handler, Injector injector) { |
| mContext = context; |
| mHandler = handler; |
| mInjector = injector; |
| updateBlockingZoneThresholds(/* displayDeviceConfig= */ null, |
| /* attemptReadFromFeatureParams= */ false); |
| mRefreshRateInHighZone = context.getResources().getInteger( |
| R.integer.config_fixedRefreshRateInHighZone); |
| } |
| |
| /** |
| * This is used to update the blocking zone thresholds from the DeviceConfig, which |
| * if missing from DisplayDeviceConfig, and finally fallback to config.xml. |
| */ |
| public void updateBlockingZoneThresholds(@Nullable DisplayDeviceConfig displayDeviceConfig, |
| boolean attemptReadFromFeatureParams) { |
| loadLowBrightnessThresholds(displayDeviceConfig, attemptReadFromFeatureParams); |
| loadHighBrightnessThresholds(displayDeviceConfig, attemptReadFromFeatureParams); |
| } |
| |
| @VisibleForTesting |
| float[] getLowDisplayBrightnessThresholds() { |
| return mLowDisplayBrightnessThresholds; |
| } |
| |
| @VisibleForTesting |
| float[] getLowAmbientBrightnessThresholds() { |
| return mLowAmbientBrightnessThresholds; |
| } |
| |
| @VisibleForTesting |
| float[] getHighDisplayBrightnessThresholds() { |
| return mHighDisplayBrightnessThresholds; |
| } |
| |
| @VisibleForTesting |
| float[] getHighAmbientBrightnessThresholds() { |
| return mHighAmbientBrightnessThresholds; |
| } |
| |
| /** |
| * @return the refresh rate to lock to when in a high brightness zone |
| */ |
| @VisibleForTesting |
| int getRefreshRateInHighZone() { |
| return mRefreshRateInHighZone; |
| } |
| |
| /** |
| * @return the refresh rate to lock to when in a low brightness zone |
| */ |
| @VisibleForTesting |
| int getRefreshRateInLowZone() { |
| return mRefreshRateInLowZone; |
| } |
| |
| private void loadLowBrightnessThresholds(@Nullable DisplayDeviceConfig displayDeviceConfig, |
| boolean attemptReadFromFeatureParams) { |
| loadRefreshRateInHighZone(displayDeviceConfig, attemptReadFromFeatureParams); |
| loadRefreshRateInLowZone(displayDeviceConfig, attemptReadFromFeatureParams); |
| mLowDisplayBrightnessThresholds = loadBrightnessThresholds( |
| () -> mConfigParameterProvider.getLowDisplayBrightnessThresholds(), |
| () -> displayDeviceConfig.getLowDisplayBrightnessThresholds(), |
| R.array.config_brightnessThresholdsOfPeakRefreshRate, |
| displayDeviceConfig, attemptReadFromFeatureParams, |
| DeviceConfigParsingUtils::displayBrightnessThresholdsIntToFloat); |
| mLowAmbientBrightnessThresholds = loadBrightnessThresholds( |
| () -> mConfigParameterProvider.getLowAmbientBrightnessThresholds(), |
| () -> displayDeviceConfig.getLowAmbientBrightnessThresholds(), |
| R.array.config_ambientThresholdsOfPeakRefreshRate, |
| displayDeviceConfig, attemptReadFromFeatureParams, |
| DeviceConfigParsingUtils::ambientBrightnessThresholdsIntToFloat); |
| if (mLowDisplayBrightnessThresholds.length != mLowAmbientBrightnessThresholds.length) { |
| throw new RuntimeException("display low brightness threshold array and ambient " |
| + "brightness threshold array have different length: " |
| + "displayBrightnessThresholds=" |
| + Arrays.toString(mLowDisplayBrightnessThresholds) |
| + ", ambientBrightnessThresholds=" |
| + Arrays.toString(mLowAmbientBrightnessThresholds)); |
| } |
| } |
| |
| private void loadRefreshRateInLowZone(DisplayDeviceConfig displayDeviceConfig, |
| boolean attemptReadFromFeatureParams) { |
| int refreshRateInLowZone = -1; |
| if (attemptReadFromFeatureParams) { |
| try { |
| refreshRateInLowZone = mConfigParameterProvider.getRefreshRateInLowZone(); |
| } catch (Exception exception) { |
| // Do nothing |
| } |
| } |
| if (refreshRateInLowZone == -1) { |
| refreshRateInLowZone = (displayDeviceConfig == null) |
| ? mContext.getResources().getInteger( |
| R.integer.config_defaultRefreshRateInZone) |
| : displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate(); |
| } |
| mLowZoneRefreshRateForThermals = displayDeviceConfig == null ? null |
| : displayDeviceConfig.getLowBlockingZoneThermalMap(); |
| mRefreshRateInLowZone = refreshRateInLowZone; |
| } |
| |
| private void loadRefreshRateInHighZone(DisplayDeviceConfig displayDeviceConfig, |
| boolean attemptReadFromFeatureParams) { |
| int refreshRateInHighZone = -1; |
| if (attemptReadFromFeatureParams) { |
| try { |
| refreshRateInHighZone = mConfigParameterProvider.getRefreshRateInHighZone(); |
| } catch (Exception exception) { |
| // Do nothing |
| } |
| } |
| if (refreshRateInHighZone == -1) { |
| refreshRateInHighZone = (displayDeviceConfig == null) |
| ? mContext.getResources().getInteger( |
| R.integer.config_fixedRefreshRateInHighZone) |
| : displayDeviceConfig.getDefaultHighBlockingZoneRefreshRate(); |
| } |
| mHighZoneRefreshRateForThermals = displayDeviceConfig == null ? null |
| : displayDeviceConfig.getHighBlockingZoneThermalMap(); |
| mRefreshRateInHighZone = refreshRateInHighZone; |
| } |
| |
| private void loadHighBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig, |
| boolean attemptReadFromFeatureParams) { |
| mHighDisplayBrightnessThresholds = loadBrightnessThresholds( |
| () -> mConfigParameterProvider.getHighDisplayBrightnessThresholds(), |
| () -> displayDeviceConfig.getHighDisplayBrightnessThresholds(), |
| R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate, |
| displayDeviceConfig, attemptReadFromFeatureParams, |
| DeviceConfigParsingUtils::displayBrightnessThresholdsIntToFloat); |
| mHighAmbientBrightnessThresholds = loadBrightnessThresholds( |
| () -> mConfigParameterProvider.getHighAmbientBrightnessThresholds(), |
| () -> displayDeviceConfig.getHighAmbientBrightnessThresholds(), |
| R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate, |
| displayDeviceConfig, attemptReadFromFeatureParams, |
| DeviceConfigParsingUtils::ambientBrightnessThresholdsIntToFloat); |
| if (mHighDisplayBrightnessThresholds.length |
| != mHighAmbientBrightnessThresholds.length) { |
| throw new RuntimeException("display high brightness threshold array and ambient " |
| + "brightness threshold array have different length: " |
| + "displayBrightnessThresholds=" |
| + Arrays.toString(mHighDisplayBrightnessThresholds) |
| + ", ambientBrightnessThresholds=" |
| + Arrays.toString(mHighAmbientBrightnessThresholds)); |
| } |
| } |
| |
| private float[] loadBrightnessThresholds( |
| Callable<float[]> loadFromDeviceConfigDisplaySettingsCallable, |
| Callable<float[]> loadFromDisplayDeviceConfigCallable, |
| int brightnessThresholdOfFixedRefreshRateKey, |
| DisplayDeviceConfig displayDeviceConfig, boolean attemptReadFromFeatureParams, |
| Function<int[], float[]> conversion) { |
| float[] brightnessThresholds = null; |
| |
| if (attemptReadFromFeatureParams) { |
| try { |
| brightnessThresholds = loadFromDeviceConfigDisplaySettingsCallable.call(); |
| } catch (Exception exception) { |
| // Do nothing |
| } |
| } |
| if (brightnessThresholds == null) { |
| try { |
| brightnessThresholds = displayDeviceConfig == null ? conversion.apply( |
| mContext.getResources().getIntArray( |
| brightnessThresholdOfFixedRefreshRateKey)) : |
| loadFromDisplayDeviceConfigCallable.call(); |
| } catch (Exception e) { |
| Slog.e(TAG, "Unexpectedly failed to load display brightness threshold"); |
| e.printStackTrace(); |
| } |
| } |
| return brightnessThresholds; |
| } |
| |
| private void observe(SensorManager sensorManager) { |
| mSensorManager = sensorManager; |
| mBrightness = getBrightness(Display.DEFAULT_DISPLAY); |
| |
| // DeviceConfig is accessible after system ready. |
| float[] lowDisplayBrightnessThresholds = |
| mConfigParameterProvider.getLowDisplayBrightnessThresholds(); |
| float[] lowAmbientBrightnessThresholds = |
| mConfigParameterProvider.getLowAmbientBrightnessThresholds(); |
| if (lowDisplayBrightnessThresholds != null && lowAmbientBrightnessThresholds != null |
| && lowDisplayBrightnessThresholds.length |
| == lowAmbientBrightnessThresholds.length) { |
| mLowDisplayBrightnessThresholds = lowDisplayBrightnessThresholds; |
| mLowAmbientBrightnessThresholds = lowAmbientBrightnessThresholds; |
| } |
| |
| float[] highDisplayBrightnessThresholds = |
| mConfigParameterProvider.getHighDisplayBrightnessThresholds(); |
| float[] highAmbientBrightnessThresholds = |
| mConfigParameterProvider.getHighAmbientBrightnessThresholds(); |
| if (highDisplayBrightnessThresholds != null && highAmbientBrightnessThresholds != null |
| && highDisplayBrightnessThresholds.length |
| == highAmbientBrightnessThresholds.length) { |
| mHighDisplayBrightnessThresholds = highDisplayBrightnessThresholds; |
| mHighAmbientBrightnessThresholds = highAmbientBrightnessThresholds; |
| } |
| |
| final int refreshRateInLowZone = mConfigParameterProvider.getRefreshRateInLowZone(); |
| if (refreshRateInLowZone != -1) { |
| mRefreshRateInLowZone = refreshRateInLowZone; |
| } |
| |
| final int refreshRateInHighZone = mConfigParameterProvider.getRefreshRateInHighZone(); |
| if (refreshRateInHighZone != -1) { |
| mRefreshRateInHighZone = refreshRateInHighZone; |
| } |
| |
| restartObserver(); |
| mDeviceConfigDisplaySettings.startListening(); |
| |
| mInjector.registerDisplayListener(this, mHandler, |
| DisplayManager.EVENT_FLAG_DISPLAY_CHANGED |
| | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS); |
| } |
| |
| private void setLoggingEnabled(boolean loggingEnabled) { |
| if (mLoggingEnabled == loggingEnabled) { |
| return; |
| } |
| mLoggingEnabled = loggingEnabled; |
| mLightSensorListener.setLoggingEnabled(loggingEnabled); |
| } |
| |
| @VisibleForTesting |
| public void onRefreshRateSettingChangedLocked(float min, float max) { |
| boolean changeable = (max - min > 1f && max > 60f); |
| if (mRefreshRateChangeable != changeable) { |
| mRefreshRateChangeable = changeable; |
| updateSensorStatus(); |
| if (!changeable) { |
| removeFlickerRefreshRateVotes(); |
| } |
| } |
| } |
| |
| @VisibleForTesting |
| void onLowPowerModeEnabledLocked(boolean enabled) { |
| if (mLowPowerModeEnabled != enabled) { |
| mLowPowerModeEnabled = enabled; |
| updateSensorStatus(); |
| if (enabled) { |
| removeFlickerRefreshRateVotes(); |
| } |
| } |
| } |
| |
| private void removeFlickerRefreshRateVotes() { |
| // Revoke previous vote from BrightnessObserver |
| mVotesStorage.updateGlobalVote(Vote.PRIORITY_FLICKER_REFRESH_RATE, null); |
| mVotesStorage.updateGlobalVote(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, null); |
| } |
| |
| private void onDeviceConfigLowBrightnessThresholdsChanged(float[] displayThresholds, |
| float[] ambientThresholds) { |
| if (displayThresholds != null && ambientThresholds != null |
| && displayThresholds.length == ambientThresholds.length) { |
| mLowDisplayBrightnessThresholds = displayThresholds; |
| mLowAmbientBrightnessThresholds = ambientThresholds; |
| } else { |
| DisplayDeviceConfig displayDeviceConfig; |
| synchronized (mLock) { |
| displayDeviceConfig = mDefaultDisplayDeviceConfig; |
| } |
| mLowDisplayBrightnessThresholds = loadBrightnessThresholds( |
| () -> mConfigParameterProvider.getLowDisplayBrightnessThresholds(), |
| () -> displayDeviceConfig.getLowDisplayBrightnessThresholds(), |
| R.array.config_brightnessThresholdsOfPeakRefreshRate, |
| displayDeviceConfig, /* attemptReadFromFeatureParams= */ false, |
| DeviceConfigParsingUtils::displayBrightnessThresholdsIntToFloat); |
| mLowAmbientBrightnessThresholds = loadBrightnessThresholds( |
| () -> mConfigParameterProvider.getLowAmbientBrightnessThresholds(), |
| () -> displayDeviceConfig.getLowAmbientBrightnessThresholds(), |
| R.array.config_ambientThresholdsOfPeakRefreshRate, |
| displayDeviceConfig, /* attemptReadFromFeatureParams= */ false, |
| DeviceConfigParsingUtils::ambientBrightnessThresholdsIntToFloat); |
| } |
| restartObserver(); |
| } |
| |
| /** |
| * Used to reload the lower blocking zone refresh rate in case of changes in the |
| * DeviceConfig properties. |
| */ |
| public void onDeviceConfigRefreshRateInLowZoneChanged(int refreshRate) { |
| if (refreshRate == -1) { |
| // Given there is no value available in DeviceConfig, lets not attempt loading it |
| // from there. |
| synchronized (mLock) { |
| loadRefreshRateInLowZone(mDefaultDisplayDeviceConfig, |
| /* attemptReadFromFeatureParams= */ false); |
| } |
| restartObserver(); |
| } else if (refreshRate != mRefreshRateInLowZone) { |
| mRefreshRateInLowZone = refreshRate; |
| restartObserver(); |
| } |
| } |
| |
| private void onDeviceConfigHighBrightnessThresholdsChanged(float[] displayThresholds, |
| float[] ambientThresholds) { |
| if (displayThresholds != null && ambientThresholds != null |
| && displayThresholds.length == ambientThresholds.length) { |
| mHighDisplayBrightnessThresholds = displayThresholds; |
| mHighAmbientBrightnessThresholds = ambientThresholds; |
| } else { |
| DisplayDeviceConfig displayDeviceConfig; |
| synchronized (mLock) { |
| displayDeviceConfig = mDefaultDisplayDeviceConfig; |
| } |
| mHighDisplayBrightnessThresholds = loadBrightnessThresholds( |
| () -> mConfigParameterProvider.getLowDisplayBrightnessThresholds(), |
| () -> displayDeviceConfig.getHighDisplayBrightnessThresholds(), |
| R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate, |
| displayDeviceConfig, /* attemptReadFromFeatureParams= */ false, |
| DeviceConfigParsingUtils::displayBrightnessThresholdsIntToFloat); |
| mHighAmbientBrightnessThresholds = loadBrightnessThresholds( |
| () -> mConfigParameterProvider.getHighAmbientBrightnessThresholds(), |
| () -> displayDeviceConfig.getHighAmbientBrightnessThresholds(), |
| R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate, |
| displayDeviceConfig, /* attemptReadFromFeatureParams= */ false, |
| DeviceConfigParsingUtils::ambientBrightnessThresholdsIntToFloat); |
| } |
| restartObserver(); |
| } |
| |
| /** |
| * Used to reload the higher blocking zone refresh rate in case of changes in the |
| * DeviceConfig properties. |
| */ |
| public void onDeviceConfigRefreshRateInHighZoneChanged(int refreshRate) { |
| if (refreshRate == -1) { |
| // Given there is no value available in DeviceConfig, lets not attempt loading it |
| // from there. |
| synchronized (mLock) { |
| loadRefreshRateInHighZone(mDefaultDisplayDeviceConfig, |
| /* attemptReadFromFeatureParams= */ false); |
| } |
| restartObserver(); |
| } else if (refreshRate != mRefreshRateInHighZone) { |
| mRefreshRateInHighZone = refreshRate; |
| restartObserver(); |
| } |
| } |
| |
| void dumpLocked(PrintWriter pw) { |
| pw.println(" BrightnessObserver"); |
| pw.println(" mAmbientLux: " + mAmbientLux); |
| pw.println(" mBrightness: " + mBrightness); |
| pw.println(" mDefaultDisplayState: " + mDefaultDisplayState); |
| pw.println(" mLowPowerModeEnabled: " + mLowPowerModeEnabled); |
| pw.println(" mRefreshRateChangeable: " + mRefreshRateChangeable); |
| pw.println(" mShouldObserveDisplayLowChange: " + mShouldObserveDisplayLowChange); |
| pw.println(" mShouldObserveAmbientLowChange: " + mShouldObserveAmbientLowChange); |
| pw.println(" mRefreshRateInLowZone: " + mRefreshRateInLowZone); |
| |
| for (float d : mLowDisplayBrightnessThresholds) { |
| pw.println(" mDisplayLowBrightnessThreshold: " + d); |
| } |
| |
| for (float d : mLowAmbientBrightnessThresholds) { |
| pw.println(" mAmbientLowBrightnessThreshold: " + d); |
| } |
| |
| pw.println(" mShouldObserveDisplayHighChange: " + mShouldObserveDisplayHighChange); |
| pw.println(" mShouldObserveAmbientHighChange: " + mShouldObserveAmbientHighChange); |
| pw.println(" mRefreshRateInHighZone: " + mRefreshRateInHighZone); |
| |
| for (float d : mHighDisplayBrightnessThresholds) { |
| pw.println(" mDisplayHighBrightnessThresholds: " + d); |
| } |
| |
| for (float d : mHighAmbientBrightnessThresholds) { |
| pw.println(" mAmbientHighBrightnessThresholds: " + d); |
| } |
| |
| pw.println(" mRegisteredLightSensor: " + mRegisteredLightSensor); |
| pw.println(" mLightSensor: " + mLightSensor); |
| pw.println(" mLightSensorName: " + mLightSensorName); |
| pw.println(" mLightSensorType: " + mLightSensorType); |
| mLightSensorListener.dumpLocked(pw); |
| |
| if (mAmbientFilter != null) { |
| IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); |
| mAmbientFilter.dump(ipw); |
| } |
| } |
| |
| @Override |
| public void onDisplayAdded(int displayId) {} |
| |
| @Override |
| public void onDisplayRemoved(int displayId) {} |
| |
| @Override |
| public void onDisplayChanged(int displayId) { |
| if (displayId == Display.DEFAULT_DISPLAY) { |
| updateDefaultDisplayState(); |
| |
| // We don't support multiple display blocking zones yet, so only handle |
| // brightness changes for the default display for now. |
| float brightness = getBrightness(displayId); |
| synchronized (mLock) { |
| if (!BrightnessSynchronizer.floatEquals(brightness, mBrightness)) { |
| mBrightness = brightness; |
| onBrightnessChangedLocked(); |
| } |
| } |
| } |
| } |
| |
| private void restartObserver() { |
| if (mRefreshRateInLowZone > 0) { |
| mShouldObserveDisplayLowChange = hasValidThreshold( |
| mLowDisplayBrightnessThresholds); |
| mShouldObserveAmbientLowChange = hasValidThreshold( |
| mLowAmbientBrightnessThresholds); |
| } else { |
| mShouldObserveDisplayLowChange = false; |
| mShouldObserveAmbientLowChange = false; |
| } |
| |
| if (mRefreshRateInHighZone > 0) { |
| mShouldObserveDisplayHighChange = hasValidThreshold( |
| mHighDisplayBrightnessThresholds); |
| mShouldObserveAmbientHighChange = hasValidThreshold( |
| mHighAmbientBrightnessThresholds); |
| } else { |
| mShouldObserveDisplayHighChange = false; |
| mShouldObserveAmbientHighChange = false; |
| } |
| |
| if (mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange) { |
| Sensor lightSensor = getLightSensor(); |
| |
| if (lightSensor != null && lightSensor != mLightSensor) { |
| final Resources res = mContext.getResources(); |
| |
| mAmbientFilter = AmbientFilterFactory.createBrightnessFilter(TAG, res); |
| mLightSensor = lightSensor; |
| } |
| } else { |
| mAmbientFilter = null; |
| mLightSensor = null; |
| } |
| |
| updateSensorStatus(); |
| synchronized (mLock) { |
| onBrightnessChangedLocked(); |
| } |
| } |
| |
| private void reloadLightSensor(DisplayDeviceConfig displayDeviceConfig) { |
| reloadLightSensorData(displayDeviceConfig); |
| restartObserver(); |
| } |
| |
| private void reloadLightSensorData(DisplayDeviceConfig displayDeviceConfig) { |
| // The displayDeviceConfig (ddc) contains display specific preferences. When loaded, |
| // it naturally falls back to the global config.xml. |
| if (displayDeviceConfig != null |
| && displayDeviceConfig.getAmbientLightSensor() != null) { |
| // This covers both the ddc and the config.xml fallback |
| mLightSensorType = displayDeviceConfig.getAmbientLightSensor().type; |
| mLightSensorName = displayDeviceConfig.getAmbientLightSensor().name; |
| } else if (mLightSensorName == null && mLightSensorType == null) { |
| Resources resources = mContext.getResources(); |
| mLightSensorType = resources.getString( |
| com.android.internal.R.string.config_displayLightSensorType); |
| mLightSensorName = ""; |
| } |
| } |
| |
| private Sensor getLightSensor() { |
| return SensorUtils.findSensor(mSensorManager, mLightSensorType, |
| mLightSensorName, Sensor.TYPE_LIGHT); |
| } |
| |
| /** |
| * Checks to see if at least one value is positive, in which case it is necessary to listen |
| * to value changes. |
| */ |
| private boolean hasValidThreshold(float[] a) { |
| for (float d: a) { |
| if (d >= 0) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Check if we're in the low zone where higher refresh rates aren't allowed to prevent |
| * flickering. |
| * @param brightness The brightness value or a negative value meaning that only the lux |
| * threshold should be applied |
| * @param lux The lux value. If negative, only the brightness threshold is applied |
| * @return True if we're in the low zone |
| */ |
| private boolean isInsideLowZone(float brightness, float lux) { |
| for (int i = 0; i < mLowDisplayBrightnessThresholds.length; i++) { |
| float disp = mLowDisplayBrightnessThresholds[i]; |
| float ambi = mLowAmbientBrightnessThresholds[i]; |
| |
| if (disp >= 0 && ambi >= 0) { |
| if (brightness <= disp && lux <= ambi) { |
| return true; |
| } |
| } else if (disp >= 0) { |
| if (brightness <= disp) { |
| return true; |
| } |
| } else if (ambi >= 0) { |
| if (lux <= ambi) { |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Check if we're in the high zone where higher refresh rates aren't allowed to prevent |
| * flickering. |
| * @param brightness The brightness value or a negative value meaning that only the lux |
| * threshold should be applied |
| * @param lux The lux value. If negative, only the brightness threshold is applied |
| * @return True if we're in the high zone |
| */ |
| private boolean isInsideHighZone(float brightness, float lux) { |
| for (int i = 0; i < mHighDisplayBrightnessThresholds.length; i++) { |
| float disp = mHighDisplayBrightnessThresholds[i]; |
| float ambi = mHighAmbientBrightnessThresholds[i]; |
| |
| if (disp >= 0 && ambi >= 0) { |
| if (brightness >= disp && lux >= ambi) { |
| return true; |
| } |
| } else if (disp >= 0) { |
| if (brightness >= disp) { |
| return true; |
| } |
| } else if (ambi >= 0) { |
| if (lux >= ambi) { |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| private void onBrightnessChangedLocked() { |
| if (!mRefreshRateChangeable || mLowPowerModeEnabled) { |
| return; |
| } |
| Vote refreshRateVote = null; |
| Vote refreshRateSwitchingVote = null; |
| |
| if (Float.isNaN(mBrightness)) { |
| // Either the setting isn't available or we shouldn't be observing yet anyways. |
| // Either way, just bail out since there's nothing we can do here. |
| return; |
| } |
| |
| boolean insideLowZone = hasValidLowZone() && isInsideLowZone(mBrightness, mAmbientLux); |
| if (insideLowZone) { |
| refreshRateVote = |
| Vote.forPhysicalRefreshRates(mRefreshRateInLowZone, mRefreshRateInLowZone); |
| if (mLowZoneRefreshRateForThermals != null) { |
| RefreshRateRange range = SkinThermalStatusObserver |
| .findBestMatchingRefreshRateRange(mThermalStatus, |
| mLowZoneRefreshRateForThermals); |
| if (range != null) { |
| refreshRateVote = |
| Vote.forPhysicalRefreshRates(range.min, range.max); |
| } |
| } |
| refreshRateSwitchingVote = Vote.forDisableRefreshRateSwitching(); |
| } |
| |
| boolean insideHighZone = hasValidHighZone() |
| && isInsideHighZone(mBrightness, mAmbientLux); |
| if (insideHighZone) { |
| refreshRateVote = |
| Vote.forPhysicalRefreshRates(mRefreshRateInHighZone, |
| mRefreshRateInHighZone); |
| if (mHighZoneRefreshRateForThermals != null) { |
| RefreshRateRange range = SkinThermalStatusObserver |
| .findBestMatchingRefreshRateRange(mThermalStatus, |
| mHighZoneRefreshRateForThermals); |
| if (range != null) { |
| refreshRateVote = |
| Vote.forPhysicalRefreshRates(range.min, range.max); |
| } |
| } |
| refreshRateSwitchingVote = Vote.forDisableRefreshRateSwitching(); |
| } |
| |
| if (mLoggingEnabled) { |
| Slog.d(TAG, "Display brightness " + mBrightness + ", ambient lux " + mAmbientLux |
| + ", Vote " + refreshRateVote); |
| } |
| mVotesStorage.updateGlobalVote(Vote.PRIORITY_FLICKER_REFRESH_RATE, refreshRateVote); |
| mVotesStorage.updateGlobalVote(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, |
| refreshRateSwitchingVote); |
| } |
| |
| private boolean hasValidLowZone() { |
| return mRefreshRateInLowZone > 0 |
| && (mShouldObserveDisplayLowChange || mShouldObserveAmbientLowChange); |
| } |
| |
| private boolean hasValidHighZone() { |
| return mRefreshRateInHighZone > 0 |
| && (mShouldObserveDisplayHighChange || mShouldObserveAmbientHighChange); |
| } |
| |
| private void updateDefaultDisplayState() { |
| Display display = mInjector.getDisplay(Display.DEFAULT_DISPLAY); |
| if (display == null) { |
| return; |
| } |
| |
| setDefaultDisplayState(display.getState()); |
| } |
| |
| @VisibleForTesting |
| void setDefaultDisplayState(int state) { |
| if (mLoggingEnabled) { |
| Slog.d(TAG, "setDefaultDisplayState: mDefaultDisplayState = " |
| + mDefaultDisplayState + ", state = " + state); |
| } |
| |
| if (mDefaultDisplayState != state) { |
| mDefaultDisplayState = state; |
| updateSensorStatus(); |
| } |
| } |
| |
| private void updateSensorStatus() { |
| if (mSensorManager == null || mLightSensorListener == null) { |
| return; |
| } |
| |
| if (mLoggingEnabled) { |
| Slog.d(TAG, "updateSensorStatus: mShouldObserveAmbientLowChange = " |
| + mShouldObserveAmbientLowChange + ", mShouldObserveAmbientHighChange = " |
| + mShouldObserveAmbientHighChange); |
| Slog.d(TAG, "updateSensorStatus: mLowPowerModeEnabled = " |
| + mLowPowerModeEnabled + ", mRefreshRateChangeable = " |
| + mRefreshRateChangeable); |
| } |
| |
| boolean registerForThermals = false; |
| if ((mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange) |
| && isDeviceActive() && !mLowPowerModeEnabled && mRefreshRateChangeable) { |
| registerLightSensor(); |
| registerForThermals = mLowZoneRefreshRateForThermals != null |
| || mHighZoneRefreshRateForThermals != null; |
| } else { |
| unregisterSensorListener(); |
| } |
| |
| if (registerForThermals && !mThermalRegistered) { |
| mThermalRegistered = mInjector.registerThermalServiceListener(mThermalListener); |
| } else if (!registerForThermals && mThermalRegistered) { |
| mInjector.unregisterThermalServiceListener(mThermalListener); |
| mThermalRegistered = false; |
| synchronized (mLock) { |
| mThermalStatus = Temperature.THROTTLING_NONE; // reset |
| } |
| } |
| } |
| |
| private void registerLightSensor() { |
| if (mRegisteredLightSensor == mLightSensor) { |
| return; |
| } |
| |
| if (mRegisteredLightSensor != null) { |
| unregisterSensorListener(); |
| } |
| |
| mSensorManager.registerListener(mLightSensorListener, |
| mLightSensor, LIGHT_SENSOR_RATE_MS * 1000, mHandler); |
| mRegisteredLightSensor = mLightSensor; |
| if (mLoggingEnabled) { |
| Slog.d(TAG, "updateSensorStatus: registerListener"); |
| } |
| } |
| |
| private void unregisterSensorListener() { |
| mLightSensorListener.removeCallbacks(); |
| mSensorManager.unregisterListener(mLightSensorListener); |
| mRegisteredLightSensor = null; |
| if (mLoggingEnabled) { |
| Slog.d(TAG, "updateSensorStatus: unregisterListener"); |
| } |
| } |
| |
| private boolean isDeviceActive() { |
| return mDefaultDisplayState == Display.STATE_ON; |
| } |
| |
| /** |
| * Get the brightness value for a display |
| * @param displayId The ID of the display |
| * @return The brightness value |
| */ |
| private float getBrightness(int displayId) { |
| final BrightnessInfo info = mInjector.getBrightnessInfo(displayId); |
| if (info != null) { |
| return info.adjustedBrightness; |
| } |
| |
| return BRIGHTNESS_INVALID_FLOAT; |
| } |
| |
| private final class LightSensorEventListener implements SensorEventListener { |
| private static final int INJECT_EVENTS_INTERVAL_MS = LIGHT_SENSOR_RATE_MS; |
| private float mLastSensorData; |
| private long mTimestamp; |
| private boolean mLoggingEnabled; |
| |
| public void dumpLocked(PrintWriter pw) { |
| pw.println(" mLastSensorData: " + mLastSensorData); |
| pw.println(" mTimestamp: " + formatTimestamp(mTimestamp)); |
| } |
| |
| |
| public void setLoggingEnabled(boolean loggingEnabled) { |
| if (mLoggingEnabled == loggingEnabled) { |
| return; |
| } |
| mLoggingEnabled = loggingEnabled; |
| } |
| |
| @Override |
| public void onSensorChanged(SensorEvent event) { |
| mLastSensorData = event.values[0]; |
| if (mLoggingEnabled) { |
| Slog.d(TAG, "On sensor changed: " + mLastSensorData); |
| } |
| |
| boolean lowZoneChanged = isDifferentZone(mLastSensorData, mAmbientLux, |
| mLowAmbientBrightnessThresholds); |
| boolean highZoneChanged = isDifferentZone(mLastSensorData, mAmbientLux, |
| mHighAmbientBrightnessThresholds); |
| if ((lowZoneChanged && mLastSensorData < mAmbientLux) |
| || (highZoneChanged && mLastSensorData > mAmbientLux)) { |
| // Easier to see flicker at lower brightness environment or high brightness |
| // environment. Forget the history to get immediate response. |
| if (mAmbientFilter != null) { |
| mAmbientFilter.clear(); |
| } |
| } |
| |
| long now = SystemClock.uptimeMillis(); |
| mTimestamp = System.currentTimeMillis(); |
| if (mAmbientFilter != null) { |
| mAmbientFilter.addValue(now, mLastSensorData); |
| } |
| |
| mHandler.removeCallbacks(mInjectSensorEventRunnable); |
| processSensorData(now); |
| |
| if ((lowZoneChanged && mLastSensorData > mAmbientLux) |
| || (highZoneChanged && mLastSensorData < mAmbientLux)) { |
| // Sensor may not report new event if there is no brightness change. |
| // Need to keep querying the temporal filter for the latest estimation, |
| // until sensor readout and filter estimation are in the same zone or |
| // is interrupted by a new sensor event. |
| mHandler.postDelayed(mInjectSensorEventRunnable, INJECT_EVENTS_INTERVAL_MS); |
| } |
| } |
| |
| @Override |
| public void onAccuracyChanged(Sensor sensor, int accuracy) { |
| // Not used. |
| } |
| |
| public void removeCallbacks() { |
| mHandler.removeCallbacks(mInjectSensorEventRunnable); |
| } |
| |
| private String formatTimestamp(long time) { |
| SimpleDateFormat dateFormat = |
| new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US); |
| return dateFormat.format(new Date(time)); |
| } |
| |
| private void processSensorData(long now) { |
| if (mAmbientFilter != null) { |
| mAmbientLux = mAmbientFilter.getEstimate(now); |
| } else { |
| mAmbientLux = mLastSensorData; |
| } |
| |
| synchronized (mLock) { |
| onBrightnessChangedLocked(); |
| } |
| } |
| |
| private boolean isDifferentZone(float lux1, float lux2, float[] luxThresholds) { |
| for (final float boundary : luxThresholds) { |
| // Test each boundary. See if the current value and the new value are at |
| // different sides. |
| if ((lux1 <= boundary && lux2 > boundary) |
| || (lux1 > boundary && lux2 <= boundary)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| private final Runnable mInjectSensorEventRunnable = new Runnable() { |
| @Override |
| public void run() { |
| long now = SystemClock.uptimeMillis(); |
| // No need to really inject the last event into a temporal filter. |
| processSensorData(now); |
| |
| // Inject next event if there is a possible zone change. |
| if (isDifferentZone(mLastSensorData, mAmbientLux, |
| mLowAmbientBrightnessThresholds) |
| || isDifferentZone(mLastSensorData, mAmbientLux, |
| mHighAmbientBrightnessThresholds)) { |
| mHandler.postDelayed(mInjectSensorEventRunnable, INJECT_EVENTS_INTERVAL_MS); |
| } |
| } |
| }; |
| } |
| } |
| |
| private class UdfpsObserver extends IUdfpsRefreshRateRequestCallback.Stub { |
| private final SparseBooleanArray mUdfpsRefreshRateEnabled = new SparseBooleanArray(); |
| private final SparseBooleanArray mAuthenticationPossible = new SparseBooleanArray(); |
| |
| public void observe() { |
| StatusBarManagerInternal statusBar = mInjector.getStatusBarManagerInternal(); |
| if (statusBar == null) { |
| return; |
| } |
| |
| // Allow UDFPS vote by registering callback, only |
| // if the device is configured to not ignore UDFPS vote. |
| boolean ignoreUdfpsVote = mContext.getResources() |
| .getBoolean(R.bool.config_ignoreUdfpsVote); |
| if (!ignoreUdfpsVote) { |
| statusBar.setUdfpsRefreshRateCallback(this); |
| } |
| } |
| |
| @Override |
| public void onRequestEnabled(int displayId) { |
| synchronized (mLock) { |
| mUdfpsRefreshRateEnabled.put(displayId, true); |
| updateVoteLocked(displayId, true, Vote.PRIORITY_UDFPS); |
| } |
| } |
| |
| @Override |
| public void onRequestDisabled(int displayId) { |
| synchronized (mLock) { |
| mUdfpsRefreshRateEnabled.put(displayId, false); |
| updateVoteLocked(displayId, false, Vote.PRIORITY_UDFPS); |
| } |
| } |
| |
| @Override |
| public void onAuthenticationPossible(int displayId, boolean isPossible) { |
| synchronized (mLock) { |
| mAuthenticationPossible.put(displayId, isPossible); |
| updateVoteLocked(displayId, isPossible, |
| Vote.PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE); |
| } |
| } |
| |
| @GuardedBy("mLock") |
| private void updateVoteLocked(int displayId, boolean enabled, int votePriority) { |
| final Vote vote; |
| if (enabled) { |
| float maxRefreshRate = DisplayModeDirector.this.getMaxRefreshRateLocked(displayId); |
| vote = Vote.forPhysicalRefreshRates(maxRefreshRate, maxRefreshRate); |
| } else { |
| vote = null; |
| } |
| mVotesStorage.updateVote(displayId, votePriority, vote); |
| } |
| |
| void dumpLocked(PrintWriter pw) { |
| pw.println(" UdfpsObserver"); |
| pw.println(" mUdfpsRefreshRateEnabled: "); |
| for (int i = 0; i < mUdfpsRefreshRateEnabled.size(); i++) { |
| final int displayId = mUdfpsRefreshRateEnabled.keyAt(i); |
| final String enabled = mUdfpsRefreshRateEnabled.valueAt(i) ? "enabled" : "disabled"; |
| pw.println(" Display " + displayId + ": " + enabled); |
| } |
| pw.println(" mAuthenticationPossible: "); |
| for (int i = 0; i < mAuthenticationPossible.size(); i++) { |
| final int displayId = mAuthenticationPossible.keyAt(i); |
| final String isPossible = mAuthenticationPossible.valueAt(i) ? "possible" |
| : "impossible"; |
| pw.println(" Display " + displayId + ": " + isPossible); |
| } |
| } |
| } |
| |
| protected static final class SensorObserver implements ProximityActiveListener, |
| DisplayManager.DisplayListener { |
| private final String mProximitySensorName = null; |
| private final String mProximitySensorType = Sensor.STRING_TYPE_PROXIMITY; |
| |
| private final VotesStorage mVotesStorage; |
| private final Context mContext; |
| private final Injector mInjector; |
| @GuardedBy("mSensorObserverLock") |
| private final SparseBooleanArray mDozeStateByDisplay = new SparseBooleanArray(); |
| private final Object mSensorObserverLock = new Object(); |
| |
| private DisplayManager mDisplayManager; |
| private DisplayManagerInternal mDisplayManagerInternal; |
| @GuardedBy("mSensorObserverLock") |
| private boolean mIsProxActive = false; |
| |
| SensorObserver(Context context, VotesStorage votesStorage, Injector injector) { |
| mContext = context; |
| mVotesStorage = votesStorage; |
| mInjector = injector; |
| } |
| |
| @Override |
| public void onProximityActive(boolean isActive) { |
| synchronized (mSensorObserverLock) { |
| if (mIsProxActive != isActive) { |
| mIsProxActive = isActive; |
| recalculateVotesLocked(); |
| } |
| } |
| } |
| |
| public void observe() { |
| mDisplayManager = mContext.getSystemService(DisplayManager.class); |
| mDisplayManagerInternal = mInjector.getDisplayManagerInternal(); |
| |
| final SensorManagerInternal sensorManager = mInjector.getSensorManagerInternal(); |
| sensorManager.addProximityActiveListener(BackgroundThread.getExecutor(), this); |
| |
| synchronized (mSensorObserverLock) { |
| for (Display d : mInjector.getDisplays()) { |
| mDozeStateByDisplay.put(d.getDisplayId(), mInjector.isDozeState(d)); |
| } |
| } |
| mInjector.registerDisplayListener(this, BackgroundThread.getHandler(), |
| DisplayManager.EVENT_FLAG_DISPLAY_ADDED |
| | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED |
| | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED); |
| } |
| |
| private void recalculateVotesLocked() { |
| final Display[] displays = mInjector.getDisplays(); |
| for (Display d : displays) { |
| int displayId = d.getDisplayId(); |
| Vote vote = null; |
| if (mIsProxActive && !mDozeStateByDisplay.get(displayId)) { |
| final RefreshRateRange rate = |
| mDisplayManagerInternal.getRefreshRateForDisplayAndSensor( |
| displayId, mProximitySensorName, mProximitySensorType); |
| if (rate != null) { |
| vote = Vote.forPhysicalRefreshRates(rate.min, rate.max); |
| } |
| } |
| mVotesStorage.updateVote(displayId, Vote.PRIORITY_PROXIMITY, vote); |
| } |
| } |
| |
| void dump(PrintWriter pw) { |
| pw.println(" SensorObserver"); |
| synchronized (mSensorObserverLock) { |
| pw.println(" mIsProxActive=" + mIsProxActive); |
| pw.println(" mDozeStateByDisplay:"); |
| for (int i = 0; i < mDozeStateByDisplay.size(); i++) { |
| final int id = mDozeStateByDisplay.keyAt(i); |
| final boolean dozed = mDozeStateByDisplay.valueAt(i); |
| pw.println(" " + id + " -> " + dozed); |
| } |
| } |
| } |
| |
| @Override |
| public void onDisplayAdded(int displayId) { |
| boolean isDozeState = mInjector.isDozeState(mInjector.getDisplay(displayId)); |
| synchronized (mSensorObserverLock) { |
| mDozeStateByDisplay.put(displayId, isDozeState); |
| recalculateVotesLocked(); |
| } |
| } |
| |
| @Override |
| public void onDisplayChanged(int displayId) { |
| boolean wasDozeState = mDozeStateByDisplay.get(displayId); |
| synchronized (mSensorObserverLock) { |
| mDozeStateByDisplay.put(displayId, |
| mInjector.isDozeState(mInjector.getDisplay(displayId))); |
| if (wasDozeState != mDozeStateByDisplay.get(displayId)) { |
| recalculateVotesLocked(); |
| } |
| } |
| } |
| |
| @Override |
| public void onDisplayRemoved(int displayId) { |
| synchronized (mSensorObserverLock) { |
| mDozeStateByDisplay.delete(displayId); |
| recalculateVotesLocked(); |
| } |
| } |
| } |
| |
| /** |
| * Listens to DisplayManager for HBM status and applies any refresh-rate restrictions for |
| * HBM that are associated with that display. Restrictions are retrieved from |
| * DisplayManagerInternal but originate in the display-device-config file. |
| */ |
| public class HbmObserver implements DisplayManager.DisplayListener { |
| private final VotesStorage mVotesStorage; |
| private final Handler mHandler; |
| private final SparseIntArray mHbmMode = new SparseIntArray(); |
| private final SparseBooleanArray mHbmActive = new SparseBooleanArray(); |
| private final Injector mInjector; |
| private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings; |
| private int mRefreshRateInHbmSunlight; |
| private int mRefreshRateInHbmHdr; |
| |
| private DisplayManagerInternal mDisplayManagerInternal; |
| |
| HbmObserver(Injector injector, VotesStorage votesStorage, Handler handler, |
| DeviceConfigDisplaySettings displaySettings) { |
| mInjector = injector; |
| mVotesStorage = votesStorage; |
| mHandler = handler; |
| mDeviceConfigDisplaySettings = displaySettings; |
| } |
| |
| /** |
| * Sets up the refresh rate to be used when HDR is enabled |
| */ |
| public void setupHdrRefreshRates(DisplayDeviceConfig displayDeviceConfig) { |
| mRefreshRateInHbmHdr = mDeviceConfigDisplaySettings |
| .getRefreshRateInHbmHdr(displayDeviceConfig); |
| mRefreshRateInHbmSunlight = mDeviceConfigDisplaySettings |
| .getRefreshRateInHbmSunlight(displayDeviceConfig); |
| } |
| |
| /** |
| * Sets up the HDR refresh rates, and starts observing for the changes in the display that |
| * might impact it |
| */ |
| public void observe() { |
| synchronized (mLock) { |
| setupHdrRefreshRates(mDefaultDisplayDeviceConfig); |
| } |
| mDisplayManagerInternal = mInjector.getDisplayManagerInternal(); |
| mInjector.registerDisplayListener(this, mHandler, |
| DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS |
| | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED); |
| } |
| |
| /** |
| * @return the refresh to lock to when the device is in high brightness mode for Sunlight. |
| */ |
| @VisibleForTesting |
| int getRefreshRateInHbmSunlight() { |
| return mRefreshRateInHbmSunlight; |
| } |
| |
| /** |
| * @return the refresh to lock to when the device is in high brightness mode for HDR. |
| */ |
| @VisibleForTesting |
| int getRefreshRateInHbmHdr() { |
| return mRefreshRateInHbmHdr; |
| } |
| |
| /** |
| * Recalculates the HBM vote when the device config has been changed. |
| */ |
| public void onDeviceConfigRefreshRateInHbmSunlightChanged(int refreshRate) { |
| if (refreshRate != mRefreshRateInHbmSunlight) { |
| mRefreshRateInHbmSunlight = refreshRate; |
| onDeviceConfigRefreshRateInHbmChanged(); |
| } |
| } |
| |
| /** |
| * Recalculates the HBM vote when the device config has been changed. |
| */ |
| public void onDeviceConfigRefreshRateInHbmHdrChanged(int refreshRate) { |
| if (refreshRate != mRefreshRateInHbmHdr) { |
| mRefreshRateInHbmHdr = refreshRate; |
| onDeviceConfigRefreshRateInHbmChanged(); |
| } |
| } |
| |
| @Override |
| public void onDisplayAdded(int displayId) {} |
| |
| @Override |
| public void onDisplayRemoved(int displayId) { |
| mVotesStorage.updateVote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, null); |
| mHbmMode.delete(displayId); |
| mHbmActive.delete(displayId); |
| } |
| |
| @Override |
| public void onDisplayChanged(int displayId) { |
| final BrightnessInfo info = mInjector.getBrightnessInfo(displayId); |
| if (info == null) { |
| // Display no longer there. Assume we'll get an onDisplayRemoved very soon. |
| return; |
| } |
| |
| final int hbmMode = info.highBrightnessMode; |
| final boolean isHbmActive = hbmMode != BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF |
| && info.adjustedBrightness > info.highBrightnessTransitionPoint; |
| if (hbmMode == mHbmMode.get(displayId) |
| && isHbmActive == mHbmActive.get(displayId)) { |
| // no change, ignore. |
| return; |
| } |
| mHbmMode.put(displayId, hbmMode); |
| mHbmActive.put(displayId, isHbmActive); |
| recalculateVotesForDisplay(displayId); |
| } |
| |
| private void onDeviceConfigRefreshRateInHbmChanged() { |
| final int[] displayIds = mHbmMode.copyKeys(); |
| if (displayIds != null) { |
| for (int id : displayIds) { |
| recalculateVotesForDisplay(id); |
| } |
| } |
| } |
| |
| private void recalculateVotesForDisplay(int displayId) { |
| Vote vote = null; |
| if (mHbmActive.get(displayId, false)) { |
| final int hbmMode = |
| mHbmMode.get(displayId, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF); |
| if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT) { |
| // Device resource properties take priority over DisplayDeviceConfig |
| if (mRefreshRateInHbmSunlight > 0) { |
| vote = Vote.forPhysicalRefreshRates(mRefreshRateInHbmSunlight, |
| mRefreshRateInHbmSunlight); |
| } else { |
| final List<RefreshRateLimitation> limits = |
| mDisplayManagerInternal.getRefreshRateLimitations(displayId); |
| for (int i = 0; limits != null && i < limits.size(); i++) { |
| final RefreshRateLimitation limitation = limits.get(i); |
| if (limitation.type == REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE) { |
| vote = Vote.forPhysicalRefreshRates(limitation.range.min, |
| limitation.range.max); |
| break; |
| } |
| } |
| } |
| } else if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR |
| && mRefreshRateInHbmHdr > 0) { |
| // HBM for HDR vote isn't supported through DisplayDeviceConfig yet, so look for |
| // a vote from Device properties |
| vote = Vote.forPhysicalRefreshRates(mRefreshRateInHbmHdr, mRefreshRateInHbmHdr); |
| } else { |
| Slog.w(TAG, "Unexpected HBM mode " + hbmMode + " for display ID " + displayId); |
| } |
| |
| } |
| mVotesStorage.updateVote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, vote); |
| } |
| |
| void dumpLocked(PrintWriter pw) { |
| pw.println(" HbmObserver"); |
| pw.println(" mHbmMode: " + mHbmMode); |
| pw.println(" mHbmActive: " + mHbmActive); |
| pw.println(" mRefreshRateInHbmSunlight: " + mRefreshRateInHbmSunlight); |
| pw.println(" mRefreshRateInHbmHdr: " + mRefreshRateInHbmHdr); |
| } |
| } |
| |
| private class DeviceConfigDisplaySettings implements DeviceConfig.OnPropertiesChangedListener { |
| public void startListening() { |
| mConfigParameterProvider.addOnPropertiesChangedListener( |
| BackgroundThread.getExecutor(), this); |
| } |
| |
| private int getRefreshRateInHbmHdr(DisplayDeviceConfig displayDeviceConfig) { |
| return getRefreshRate( |
| () -> mConfigParameterProvider.getRefreshRateInHbmHdr(), |
| () -> displayDeviceConfig.getDefaultRefreshRateInHbmHdr(), |
| R.integer.config_defaultRefreshRateInHbmHdr, |
| displayDeviceConfig |
| ); |
| } |
| |
| private int getRefreshRateInHbmSunlight(DisplayDeviceConfig displayDeviceConfig) { |
| return getRefreshRate( |
| () -> mConfigParameterProvider.getRefreshRateInHbmSunlight(), |
| () -> displayDeviceConfig.getDefaultRefreshRateInHbmSunlight(), |
| R.integer.config_defaultRefreshRateInHbmSunlight, |
| displayDeviceConfig |
| ); |
| } |
| |
| private int getRefreshRate(IntSupplier fromConfigPram, IntSupplier fromDisplayDeviceConfig, |
| @IntegerRes int configKey, DisplayDeviceConfig displayDeviceConfig) { |
| int refreshRate = -1; |
| try { |
| refreshRate = fromConfigPram.getAsInt(); |
| } catch (NullPointerException npe) { |
| // Do Nothing |
| } |
| if (refreshRate == -1) { |
| refreshRate = (displayDeviceConfig == null) |
| ? mContext.getResources().getInteger(configKey) |
| : fromDisplayDeviceConfig.getAsInt(); |
| } |
| return refreshRate; |
| } |
| |
| @Override |
| public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) { |
| float defaultPeakRefreshRate = mConfigParameterProvider.getPeakRefreshRateDefault(); |
| mHandler.obtainMessage(MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED, |
| defaultPeakRefreshRate == -1 ? null : defaultPeakRefreshRate).sendToTarget(); |
| |
| float[] lowDisplayBrightnessThresholds = |
| mConfigParameterProvider.getLowDisplayBrightnessThresholds(); |
| float[] lowAmbientBrightnessThresholds = |
| mConfigParameterProvider.getLowAmbientBrightnessThresholds(); |
| final int refreshRateInLowZone = mConfigParameterProvider.getRefreshRateInLowZone(); |
| |
| mHandler.obtainMessage(MSG_LOW_BRIGHTNESS_THRESHOLDS_CHANGED, |
| new Pair<>(lowDisplayBrightnessThresholds, lowAmbientBrightnessThresholds)) |
| .sendToTarget(); |
| |
| mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone, |
| 0).sendToTarget(); |
| |
| float[] highDisplayBrightnessThresholds = |
| mConfigParameterProvider.getHighDisplayBrightnessThresholds(); |
| float[] highAmbientBrightnessThresholds = |
| mConfigParameterProvider.getHighAmbientBrightnessThresholds(); |
| final int refreshRateInHighZone = mConfigParameterProvider.getRefreshRateInHighZone(); |
| |
| mHandler.obtainMessage(MSG_HIGH_BRIGHTNESS_THRESHOLDS_CHANGED, |
| new Pair<>(highDisplayBrightnessThresholds, highAmbientBrightnessThresholds)) |
| .sendToTarget(); |
| |
| mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED, refreshRateInHighZone, |
| 0).sendToTarget(); |
| |
| synchronized (mLock) { |
| final int refreshRateInHbmSunlight = |
| getRefreshRateInHbmSunlight(mDefaultDisplayDeviceConfig); |
| mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED, |
| refreshRateInHbmSunlight, 0) |
| .sendToTarget(); |
| |
| final int refreshRateInHbmHdr = |
| getRefreshRateInHbmHdr(mDefaultDisplayDeviceConfig); |
| mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HBM_HDR_CHANGED, refreshRateInHbmHdr, 0) |
| .sendToTarget(); |
| } |
| } |
| } |
| |
| interface Injector { |
| Uri PEAK_REFRESH_RATE_URI = Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE); |
| |
| @NonNull |
| DeviceConfigInterface getDeviceConfig(); |
| |
| void registerPeakRefreshRateObserver(@NonNull ContentResolver cr, |
| @NonNull ContentObserver observer); |
| |
| void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener, |
| Handler handler); |
| |
| void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener, |
| Handler handler, long flags); |
| |
| Display getDisplay(int displayId); |
| |
| Display[] getDisplays(); |
| |
| boolean getDisplayInfo(int displayId, DisplayInfo displayInfo); |
| |
| BrightnessInfo getBrightnessInfo(int displayId); |
| |
| boolean isDozeState(Display d); |
| |
| boolean registerThermalServiceListener(IThermalEventListener listener); |
| void unregisterThermalServiceListener(IThermalEventListener listener); |
| |
| boolean supportsFrameRateOverride(); |
| |
| DisplayManagerInternal getDisplayManagerInternal(); |
| |
| StatusBarManagerInternal getStatusBarManagerInternal(); |
| |
| SensorManagerInternal getSensorManagerInternal(); |
| } |
| |
| @VisibleForTesting |
| static class RealInjector implements Injector { |
| private final Context mContext; |
| private DisplayManager mDisplayManager; |
| |
| RealInjector(Context context) { |
| mContext = context; |
| } |
| |
| @Override |
| @NonNull |
| public DeviceConfigInterface getDeviceConfig() { |
| return DeviceConfigInterface.REAL; |
| } |
| |
| @Override |
| public void registerPeakRefreshRateObserver(@NonNull ContentResolver cr, |
| @NonNull ContentObserver observer) { |
| cr.registerContentObserver(PEAK_REFRESH_RATE_URI, false /*notifyDescendants*/, |
| observer, UserHandle.USER_SYSTEM); |
| } |
| |
| @Override |
| public void registerDisplayListener(DisplayManager.DisplayListener listener, |
| Handler handler) { |
| getDisplayManager().registerDisplayListener(listener, handler); |
| } |
| |
| @Override |
| public void registerDisplayListener(DisplayManager.DisplayListener listener, |
| Handler handler, long flags) { |
| getDisplayManager().registerDisplayListener(listener, handler, flags); |
| } |
| |
| @Override |
| public Display getDisplay(int displayId) { |
| return getDisplayManager().getDisplay(displayId); |
| } |
| |
| @Override |
| public Display[] getDisplays() { |
| return getDisplayManager().getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED); |
| } |
| |
| @Override |
| public boolean getDisplayInfo(int displayId, DisplayInfo displayInfo) { |
| Display display = getDisplayManager().getDisplay(displayId); |
| if (display == null) { |
| // We can occasionally get a display added or changed event for a display that was |
| // subsequently removed, which means this returns null. Check this case and bail |
| // out early; if it gets re-attached we'll eventually get another call back for it. |
| return false; |
| } |
| return display.getDisplayInfo(displayInfo); |
| } |
| |
| @Override |
| public BrightnessInfo getBrightnessInfo(int displayId) { |
| final Display display = getDisplayManager().getDisplay(displayId); |
| if (display != null) { |
| return display.getBrightnessInfo(); |
| } |
| return null; |
| } |
| |
| @Override |
| public boolean isDozeState(Display d) { |
| if (d == null) { |
| return false; |
| } |
| return Display.isDozeState(d.getState()); |
| } |
| |
| @Override |
| public boolean registerThermalServiceListener(IThermalEventListener listener) { |
| IThermalService thermalService = getThermalService(); |
| if (thermalService == null) { |
| Slog.w(TAG, "Could not observe thermal status. Service not available"); |
| return false; |
| } |
| try { |
| thermalService.registerThermalEventListenerWithType(listener, |
| Temperature.TYPE_SKIN); |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Failed to register thermal status listener", e); |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public void unregisterThermalServiceListener(IThermalEventListener listener) { |
| IThermalService thermalService = getThermalService(); |
| if (thermalService == null) { |
| Slog.w(TAG, "Could not unregister thermal status. Service not available"); |
| } |
| try { |
| thermalService.unregisterThermalEventListener(listener); |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Failed to unregister thermal status listener", e); |
| } |
| } |
| |
| @Override |
| public boolean supportsFrameRateOverride() { |
| return SurfaceFlingerProperties.enable_frame_rate_override().orElse(true); |
| } |
| |
| @Override |
| public DisplayManagerInternal getDisplayManagerInternal() { |
| return LocalServices.getService(DisplayManagerInternal.class); |
| } |
| |
| @Override |
| public StatusBarManagerInternal getStatusBarManagerInternal() { |
| return LocalServices.getService(StatusBarManagerInternal.class); |
| } |
| |
| @Override |
| public SensorManagerInternal getSensorManagerInternal() { |
| return LocalServices.getService(SensorManagerInternal.class); |
| } |
| |
| private DisplayManager getDisplayManager() { |
| if (mDisplayManager == null) { |
| mDisplayManager = mContext.getSystemService(DisplayManager.class); |
| } |
| return mDisplayManager; |
| } |
| |
| private IThermalService getThermalService() { |
| return IThermalService.Stub.asInterface( |
| ServiceManager.getService(Context.THERMAL_SERVICE)); |
| } |
| } |
| } |