diff options
6 files changed, 290 insertions, 41 deletions
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 77a0f89fcad1..fd5d7575e7e6 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4145,8 +4145,27 @@ for higher refresh rates to be automatically used out of the box --> <integer name="config_defaultPeakRefreshRate">60</integer> - <!-- The default brightness threshold that allows to switch to higher refresh rate --> - <integer name="config_brightnessThresholdOfPeakRefreshRate">-1</integer> + <!-- The display uses different gamma curves for different refresh rates. It's hard for panel + vendor to tune the curves to have exact same brightness for different refresh rate. So + flicker could be observed at switch time. The issue is worse at the gamma lower end. + In addition, human eyes are more sensitive to the flicker at darker environment. + To prevent flicker, we only support higher refresh rates if the display brightness is above + a threshold. And the darker environment could have higher threshold. + For example, no higher refresh rate if + display brightness <= disp0 && ambient brightness <= amb0 + || display brightness <= disp1 && ambient brightness <= amb1 --> + <integer-array translatable="false" name="config_brightnessThresholdsOfPeakRefreshRate"> + <!-- + <item>disp0</item> + <item>disp1</item> + --> + </integer-array> + <integer-array translatable="false" name="config_ambientThresholdsOfPeakRefreshRate"> + <!-- + <item>amb0</item> + <item>amb1</item> + --> + </integer-array> <!-- The type of the light sensor to be used by the display framework for things like auto-brightness. If unset, then it just gets the default sensor of type TYPE_LIGHT. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a1dce0390a67..5f092af0cbf0 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3793,7 +3793,8 @@ <!-- For high refresh rate displays --> <java-symbol type="integer" name="config_defaultPeakRefreshRate" /> - <java-symbol type="integer" name="config_brightnessThresholdOfPeakRefreshRate" /> + <java-symbol type="array" name="config_brightnessThresholdsOfPeakRefreshRate" /> + <java-symbol type="array" name="config_ambientThresholdsOfPeakRefreshRate" /> <!-- For Auto-Brightness --> <java-symbol type="string" name="config_displayLightSensorType" /> diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 99341d1c96ae..4f33ebb035da 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -303,6 +303,8 @@ public final class DisplayManagerService extends SystemService { private final Spline mMinimumBrightnessSpline; private final ColorSpace mWideColorSpace; + private SensorManager mSensorManager; + public DisplayManagerService(Context context) { this(context, new Injector()); } @@ -430,7 +432,7 @@ public final class DisplayManagerService extends SystemService { } mDisplayModeDirector.setListener(new AllowedDisplayModeObserver()); - mDisplayModeDirector.start(); + mDisplayModeDirector.start(mSensorManager); mHandler.sendEmptyMessage(MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS); } @@ -2358,6 +2360,7 @@ public final class DisplayManagerService extends SystemService { }; mDisplayPowerController = new DisplayPowerController( mContext, callbacks, handler, sensorManager, blanker); + mSensorManager = sensorManager; } mHandler.sendEmptyMessage(MSG_LOAD_BRIGHTNESS_CONFIGURATION); diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java index 14bd2d8150da..78a48dac6fb5 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/DisplayModeDirector.java @@ -18,26 +18,41 @@ package com.android.server.display; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Resources; import android.database.ContentObserver; import android.hardware.display.DisplayManager; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; + import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.UserHandle; +import android.os.PowerManager; +import android.os.SystemClock; import android.provider.Settings; +import android.text.TextUtils; import android.util.Slog; import android.util.SparseArray; import android.view.Display; import android.view.DisplayInfo; import com.android.internal.R; +import com.android.server.display.whitebalance.DisplayWhiteBalanceFactory; +import com.android.server.display.whitebalance.AmbientFilter; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Objects; /** @@ -74,7 +89,7 @@ public class DisplayModeDirector { private final AppRequestObserver mAppRequestObserver; private final SettingsObserver mSettingsObserver; private final DisplayObserver mDisplayObserver; - + private final BrightnessObserver mBrightnessObserver; private Listener mListener; @@ -87,6 +102,8 @@ public class DisplayModeDirector { mAppRequestObserver = new AppRequestObserver(); mSettingsObserver = new SettingsObserver(context, handler); mDisplayObserver = new DisplayObserver(context, handler); + mBrightnessObserver = new BrightnessObserver(context, handler); + } /** @@ -96,15 +113,17 @@ public class DisplayModeDirector { * This has to be deferred because the object may be constructed before the rest of the system * is ready. */ - public void start() { + public void start(SensorManager sensorManager) { mSettingsObserver.observe(); mDisplayObserver.observe(); mSettingsObserver.observe(); + mBrightnessObserver.observe(sensorManager); 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. notifyAllowedModesChangedLocked(); } + } /** @@ -315,6 +334,7 @@ public class DisplayModeDirector { } mSettingsObserver.dumpLocked(pw); mAppRequestObserver.dumpLocked(pw); + mBrightnessObserver.dumpLocked(pw); } } @@ -486,20 +506,15 @@ public class DisplayModeDirector { Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE); private final Uri mLowPowerModeSetting = Settings.Global.getUriFor(Settings.Global.LOW_POWER_MODE); - private final Uri mBrightnessSetting = - Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS); private final Context mContext; private final float mDefaultPeakRefreshRate; - private final int mBrightnessThreshold; SettingsObserver(@NonNull Context context, @NonNull Handler handler) { super(handler); mContext = context; mDefaultPeakRefreshRate = (float) context.getResources().getInteger( R.integer.config_defaultPeakRefreshRate); - mBrightnessThreshold = context.getResources().getInteger( - R.integer.config_brightnessThresholdOfPeakRefreshRate); } public void observe() { @@ -508,14 +523,9 @@ public class DisplayModeDirector { UserHandle.USER_SYSTEM); cr.registerContentObserver(mLowPowerModeSetting, false /*notifyDescendants*/, this, UserHandle.USER_SYSTEM); - if (mBrightnessThreshold >= 0) { - cr.registerContentObserver(mBrightnessSetting, false /*notifyDescendants*/, this, - UserHandle.USER_SYSTEM); - } synchronized (mLock) { updateRefreshRateSettingLocked(); updateLowPowerModeSettingLocked(); - updateBrightnessSettingLocked(); } } @@ -526,8 +536,6 @@ public class DisplayModeDirector { updateRefreshRateSettingLocked(); } else if (mLowPowerModeSetting.equals(uri)) { updateLowPowerModeSettingLocked(); - } else if (mBrightnessThreshold >=0 && mBrightnessSetting.equals(uri)) { - updateBrightnessSettingLocked(); } } } @@ -542,6 +550,7 @@ public class DisplayModeDirector { vote = null; } updateVoteLocked(Vote.PRIORITY_LOW_POWER_MODE, vote); + mBrightnessObserver.onLowPowerModeEnabled(inLowPowerMode); } private void updateRefreshRateSettingLocked() { @@ -549,23 +558,7 @@ public class DisplayModeDirector { Settings.System.PEAK_REFRESH_RATE, mDefaultPeakRefreshRate); Vote vote = Vote.forRefreshRates(0f, peakRefreshRate); updateVoteLocked(Vote.PRIORITY_USER_SETTING_REFRESH_RATE, vote); - } - - private void updateBrightnessSettingLocked() { - int brightness = Settings.System.getInt(mContext.getContentResolver(), - Settings.System.SCREEN_BRIGHTNESS, -1); - - if (brightness < 0) { - return; - } - - final Vote vote; - if (brightness <= mBrightnessThreshold) { - vote = Vote.forRefreshRates(0f, 60f); - } else { - vote = null; - } - updateVoteLocked(Vote.PRIORITY_LOW_BRIGHTNESS, vote); + mBrightnessObserver.onPeakRefreshRateEnabled(peakRefreshRate > 60f); } public void dumpLocked(PrintWriter pw) { @@ -715,4 +708,240 @@ public class DisplayModeDirector { } } } + + /** + * 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}. + */ + private class BrightnessObserver extends ContentObserver { + private final Uri mDisplayBrightnessSetting = + Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS); + + private final static int LIGHT_SENSOR_RATE_MS = 250; + private final int[] mDisplayBrightnessThresholds; + private final int[] mAmbientBrightnessThresholds; + // valid threshold if any item from the array >= 0 + private boolean mShouldObserveDisplayChange; + private boolean mShouldObserveAmbientChange; + + private SensorManager mSensorManager; + private Sensor mLightSensor; + // Take it as low brightness before valid sensor data comes + private float mAmbientLux = -1.0f; + private AmbientFilter mAmbientFilter; + + private final Context mContext; + private ScreenStateReceiver mScreenStateReceiver; + + // Enable light sensor only when screen is on, peak refresh rate enabled and low power mode + // off. After initialization, these states will be updated from the same handler thread. + private boolean mScreenOn = false; + private boolean mPeakRefreshRateEnabled = false; + private boolean mLowPowerModeEnabled = false; + + BrightnessObserver(Context context, Handler handler) { + super(handler); + mContext = context; + mDisplayBrightnessThresholds = context.getResources().getIntArray( + R.array.config_brightnessThresholdsOfPeakRefreshRate); + mAmbientBrightnessThresholds = context.getResources().getIntArray( + R.array.config_ambientThresholdsOfPeakRefreshRate); + if (mDisplayBrightnessThresholds.length != mAmbientBrightnessThresholds.length) { + throw new RuntimeException("display brightness threshold array and ambient " + + "brightness threshold array have different length"); + } + + mShouldObserveDisplayChange = checkShouldObserve(mDisplayBrightnessThresholds); + mShouldObserveAmbientChange = checkShouldObserve(mAmbientBrightnessThresholds); + } + + public void observe(SensorManager sensorManager) { + if (mShouldObserveDisplayChange) { + final ContentResolver cr = mContext.getContentResolver(); + cr.registerContentObserver(mDisplayBrightnessSetting, + false /*notifyDescendants*/, this, UserHandle.USER_SYSTEM); + } + + if (mShouldObserveAmbientChange) { + Resources resources = mContext.getResources(); + String lightSensorType = resources.getString( + com.android.internal.R.string.config_displayLightSensorType); + + Sensor lightSensor = null; + if (!TextUtils.isEmpty(lightSensorType)) { + List<Sensor> sensors = sensorManager.getSensorList(Sensor.TYPE_ALL); + for (int i = 0; i < sensors.size(); i++) { + Sensor sensor = sensors.get(i); + if (lightSensorType.equals(sensor.getStringType())) { + lightSensor = sensor; + break; + } + } + } + + if (lightSensor == null) { + lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); + } + + if (lightSensor != null) { + final Resources res = mContext.getResources(); + + mAmbientFilter = DisplayWhiteBalanceFactory.createBrightnessFilter(res); + mSensorManager = sensorManager; + mLightSensor = lightSensor; + + // Intent.ACTION_SCREEN_ON is not sticky. Check current screen status. + if (mContext.getSystemService(PowerManager.class).isInteractive()) { + onScreenOn(true); + } + mScreenStateReceiver = new ScreenStateReceiver(mContext); + } + } + + if (mShouldObserveDisplayChange || mShouldObserveAmbientChange) { + synchronized (mLock) { + onBrightnessChangedLocked(); + } + } + } + + public void onPeakRefreshRateEnabled(boolean b) { + if (mShouldObserveAmbientChange && mPeakRefreshRateEnabled != b) { + mPeakRefreshRateEnabled = b; + updateSensorStatus(); + } + } + + public void onLowPowerModeEnabled(boolean b) { + if (mShouldObserveAmbientChange && mLowPowerModeEnabled != b) { + mLowPowerModeEnabled = b; + updateSensorStatus(); + } + } + + public void dumpLocked(PrintWriter pw) { + pw.println(" BrightnessObserver"); + + for (int d: mDisplayBrightnessThresholds) { + pw.println(" mDisplayBrightnessThreshold: " + d); + } + + for (int d: mAmbientBrightnessThresholds) { + pw.println(" mAmbientBrightnessThreshold: " + d); + } + } + + @Override + public void onChange(boolean selfChange, Uri uri, int userId) { + synchronized (mLock) { + onBrightnessChangedLocked(); + } + } + + /** + * Checks to see if at least one value is positive, in which case it is necessary to listen + * to value changes. + */ + private boolean checkShouldObserve(int[] a) { + for (int d: a) { + if (d >= 0) { + return true; + } + } + + return false; + } + + private void onBrightnessChangedLocked() { + int brightness = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS, -1); + + Vote vote = null; + for (int i = 0; i < mDisplayBrightnessThresholds.length; i++) { + int disp = mDisplayBrightnessThresholds[i]; + int ambi = mAmbientBrightnessThresholds[i]; + + if (disp >= 0 && ambi >= 0) { + if (brightness <= disp && mAmbientLux <= ambi) { + vote = Vote.forRefreshRates(0f, 60f); + } + } else if (disp >= 0) { + if (brightness <= disp) { + vote = Vote.forRefreshRates(0f, 60f); + } + } else if (ambi >= 0) { + if (mAmbientLux <= ambi) { + vote = Vote.forRefreshRates(0f, 60f); + } + } + + if (vote != null) { + break; + } + } + + if (DEBUG) { + Slog.d(TAG, "Display brightness " + brightness + ", ambient lux " + mAmbientLux + + (vote != null ? " 60hz only" : " no refresh rate limit")); + } + updateVoteLocked(Vote.PRIORITY_LOW_BRIGHTNESS, vote); + } + + private void onScreenOn(boolean on) { + // Not check mShouldObserveAmbientChange because Screen status receiver is registered + // only when it is true. + if (mScreenOn != on) { + mScreenOn = on; + updateSensorStatus(); + } + } + + private void updateSensorStatus() { + if (mSensorManager == null || mLightSensorListener == null) { + return; + } + + if (mScreenOn && !mLowPowerModeEnabled && mPeakRefreshRateEnabled) { + mSensorManager.registerListener(mLightSensorListener, + mLightSensor, LIGHT_SENSOR_RATE_MS * 1000, mHandler); + } else { + mSensorManager.unregisterListener(mLightSensorListener); + } + } + + private final SensorEventListener mLightSensorListener = new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent event) { + long now = SystemClock.uptimeMillis(); + mAmbientFilter.addValue(now, event.values[0]); + mAmbientLux = mAmbientFilter.getEstimate(now); + + synchronized (mLock) { + onBrightnessChangedLocked(); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // Not used. + } + }; + + private final class ScreenStateReceiver extends BroadcastReceiver { + public ScreenStateReceiver(Context context) { + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.addAction(Intent.ACTION_SCREEN_ON); + filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); + context.registerReceiver(this, filter, null, mHandler); + } + + @Override + public void onReceive(Context context, Intent intent) { + onScreenOn(Intent.ACTION_SCREEN_ON.equals(intent.getAction())); + } + } + } } diff --git a/services/core/java/com/android/server/display/whitebalance/AmbientFilter.java b/services/core/java/com/android/server/display/whitebalance/AmbientFilter.java index 123cd73a3ff3..35808974b9e4 100644 --- a/services/core/java/com/android/server/display/whitebalance/AmbientFilter.java +++ b/services/core/java/com/android/server/display/whitebalance/AmbientFilter.java @@ -36,7 +36,7 @@ import java.util.Arrays; * - {@link WeightedMovingAverageAmbientFilter} * A weighted average prioritising recent changes. */ -abstract class AmbientFilter { +abstract public class AmbientFilter { protected static final boolean DEBUG = false; // Enable for verbose logs. @@ -156,8 +156,7 @@ abstract class AmbientFilter { /** * A weighted average prioritising recent changes. */ - @VisibleForTesting - public static class WeightedMovingAverageAmbientFilter extends AmbientFilter { + static class WeightedMovingAverageAmbientFilter extends AmbientFilter { // How long the latest ambient value change is predicted to last. private static final int PREDICTION_TIME = 100; // Milliseconds diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java index 4df7d6b14f25..bf0a1d16219d 100644 --- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java +++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java @@ -115,8 +115,7 @@ public class DisplayWhiteBalanceFactory { * Creates a BrightnessFilter which functions as a weighted moving average buffer for recent * brightness values. */ - @VisibleForTesting - static AmbientFilter createBrightnessFilter(Resources resources) { + public static AmbientFilter createBrightnessFilter(Resources resources) { final int horizon = resources.getInteger( com.android.internal.R.integer.config_displayWhiteBalanceBrightnessFilterHorizon); final float intercept = getFloat(resources, @@ -129,7 +128,6 @@ public class DisplayWhiteBalanceFactory { + "expected config_displayWhiteBalanceBrightnessFilterIntercept"); } - /** * Creates an ambient color sensor instance to redirect sensor data to callbacks. */ |