Update BrightnessSynchronizer Logic
There was an error where if two auto-brightness changes came in quick succession, the first could be treated as a user-initiated brightness change. 1. First float change comes in: Xf 2. We write the brightness to the int-setting: Xi 3. Second float change comes in: Yf 4. Notification of the Xi change comes back to us and because X is different from Y, we treat it as a user-change. The result of this is that auto-brightness learned from these erroneous "user inputs" and both the short and long term model get affected negatively for the user. Often manifesting in brightness learning to be too low. The quick succession changes can happen in various situations, but were most prominent on startup before and after a valid lux is read. The fix is the revamp the data synchronization algorithm we use in BrightnessSynchronizer to have updates + confirmation callback be treated as an atomic operation before executing any other updates that come in. In the example above, that means we run (1) (2) and (4) before handling the new update found in (3). Fixes: 227483176 Test: Manually verify through debug logging. Test: atest BrightnessSynchronizerTest Change-Id: I93856fd74e50bcda04e88a7b7e736e02faa76c8e
This commit is contained in:
@@ -51,6 +51,12 @@ filegroup {
|
||||
srcs: ["android/tracing/TraceReportParams.aidl"],
|
||||
}
|
||||
|
||||
filegroup {
|
||||
name: "framework-internal-display-sources",
|
||||
srcs: ["com/android/internal/display/BrightnessSynchronizer.java"],
|
||||
visibility: ["//frameworks/base/services/tests/mockingservicestests"],
|
||||
}
|
||||
|
||||
// These are subset of framework-core-sources that are needed by the
|
||||
// android.test.mock library. The implementation of android.test.mock references
|
||||
// private members of various components to allow mocking of classes that cannot
|
||||
|
||||
@@ -26,57 +26,60 @@ import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.PowerManager;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.util.MathUtils;
|
||||
import android.util.Slog;
|
||||
import android.view.Display;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* BrightnessSynchronizer helps convert between the int (old) system and float
|
||||
* (new) system for storing the brightness. It has methods to convert between the two and also
|
||||
* observes for when one of the settings is changed and syncs this with the other.
|
||||
*/
|
||||
public class BrightnessSynchronizer {
|
||||
|
||||
private static final int MSG_UPDATE_FLOAT = 1;
|
||||
private static final int MSG_UPDATE_INT = 2;
|
||||
private static final int MSG_UPDATE_BOTH = 3;
|
||||
|
||||
private static final String TAG = "BrightnessSynchronizer";
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
private static final Uri BRIGHTNESS_URI =
|
||||
Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
|
||||
|
||||
private static final long WAIT_FOR_RESPONSE_MILLIS = 200;
|
||||
|
||||
private static final int MSG_RUN_UPDATE = 1;
|
||||
|
||||
// The tolerance within which we consider brightness values approximately equal to eachother.
|
||||
// This value is approximately 1/3 of the smallest possible brightness value.
|
||||
public static final float EPSILON = 0.001f;
|
||||
|
||||
private DisplayManager mDisplayManager;
|
||||
private static int sBrightnessUpdateCount = 1;
|
||||
|
||||
private final Context mContext;
|
||||
private final BrightnessSyncObserver mBrightnessSyncObserver;
|
||||
private final Clock mClock;
|
||||
private final Handler mHandler;
|
||||
|
||||
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_UPDATE_FLOAT:
|
||||
updateBrightnessFloatFromInt(msg.arg1);
|
||||
break;
|
||||
case MSG_UPDATE_INT:
|
||||
updateBrightnessIntFromFloat(Float.intBitsToFloat(msg.arg1));
|
||||
break;
|
||||
case MSG_UPDATE_BOTH:
|
||||
updateBoth(Float.intBitsToFloat(msg.arg1));
|
||||
break;
|
||||
default:
|
||||
super.handleMessage(msg);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
private float mPreferredSettingValue;
|
||||
private DisplayManager mDisplayManager;
|
||||
private int mLatestIntBrightness;
|
||||
private float mLatestFloatBrightness;
|
||||
private BrightnessUpdate mCurrentUpdate;
|
||||
private BrightnessUpdate mPendingUpdate;
|
||||
|
||||
public BrightnessSynchronizer(Context context) {
|
||||
this(context, Looper.getMainLooper(), SystemClock::uptimeMillis);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public BrightnessSynchronizer(Context context, Looper looper, Clock clock) {
|
||||
mContext = context;
|
||||
mClock = clock;
|
||||
mBrightnessSyncObserver = new BrightnessSyncObserver();
|
||||
mHandler = new BrightnessSynchronizerHandler(looper);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,24 +93,41 @@ public class BrightnessSynchronizer {
|
||||
if (mDisplayManager == null) {
|
||||
mDisplayManager = mContext.getSystemService(DisplayManager.class);
|
||||
}
|
||||
if (mBrightnessSyncObserver.isObserving()) {
|
||||
Slog.wtf(TAG, "Brightness sync observer requesting synchronization a second time.");
|
||||
return;
|
||||
}
|
||||
mLatestFloatBrightness = getScreenBrightnessFloat();
|
||||
mLatestIntBrightness = getScreenBrightnessInt();
|
||||
Slog.i(TAG, "Initial brightness readings: " + mLatestIntBrightness + "(int), "
|
||||
+ mLatestFloatBrightness + "(float)");
|
||||
|
||||
final BrightnessSyncObserver brightnessSyncObserver;
|
||||
brightnessSyncObserver = new BrightnessSyncObserver();
|
||||
brightnessSyncObserver.startObserving();
|
||||
|
||||
final float currentFloatBrightness = getScreenBrightnessFloat();
|
||||
final int currentIntBrightness = getScreenBrightnessInt(mContext);
|
||||
|
||||
if (!Float.isNaN(currentFloatBrightness)) {
|
||||
updateBrightnessIntFromFloat(currentFloatBrightness);
|
||||
} else if (currentIntBrightness != -1) {
|
||||
updateBrightnessFloatFromInt(currentIntBrightness);
|
||||
if (!Float.isNaN(mLatestFloatBrightness)) {
|
||||
mPendingUpdate = new BrightnessUpdate(BrightnessUpdate.TYPE_FLOAT,
|
||||
mLatestFloatBrightness);
|
||||
} else if (mLatestIntBrightness != PowerManager.BRIGHTNESS_INVALID) {
|
||||
mPendingUpdate = new BrightnessUpdate(BrightnessUpdate.TYPE_INT,
|
||||
mLatestIntBrightness);
|
||||
} else {
|
||||
final float defaultBrightness = mContext.getResources().getFloat(
|
||||
com.android.internal.R.dimen.config_screenBrightnessSettingDefaultFloat);
|
||||
mDisplayManager.setBrightness(Display.DEFAULT_DISPLAY, defaultBrightness);
|
||||
|
||||
mPendingUpdate = new BrightnessUpdate(BrightnessUpdate.TYPE_FLOAT, defaultBrightness);
|
||||
Slog.i(TAG, "Setting initial brightness to default value of: " + defaultBrightness);
|
||||
}
|
||||
|
||||
mBrightnessSyncObserver.startObserving();
|
||||
mHandler.sendEmptyMessageAtTime(MSG_RUN_UPDATE, mClock.uptimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints data on dumpsys.
|
||||
*/
|
||||
public void dump(PrintWriter pw) {
|
||||
pw.println("BrightnessSynchronizer");
|
||||
pw.println(" mLatestIntBrightness=" + mLatestIntBrightness);
|
||||
pw.println(" mLatestFloatBrightness=" + mLatestFloatBrightness);
|
||||
pw.println(" mCurrentUpdate=" + mCurrentUpdate);
|
||||
pw.println(" mPendingUpdate=" + mPendingUpdate);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,6 +174,78 @@ public class BrightnessSynchronizer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumes a brightness change event for the float-based brightness.
|
||||
*
|
||||
* @param brightness Float brightness.
|
||||
*/
|
||||
private void handleBrightnessChangeFloat(float brightness) {
|
||||
mLatestFloatBrightness = brightness;
|
||||
handleBrightnessChange(BrightnessUpdate.TYPE_FLOAT, brightness);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumes a brightness change event for the int-based brightness.
|
||||
*
|
||||
* @param brightness Int brightness.
|
||||
*/
|
||||
private void handleBrightnessChangeInt(int brightness) {
|
||||
mLatestIntBrightness = brightness;
|
||||
handleBrightnessChange(BrightnessUpdate.TYPE_INT, brightness);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumes a brightness change event.
|
||||
*
|
||||
* @param type Type of the brightness change (int/float)
|
||||
* @param brightness brightness.
|
||||
*/
|
||||
private void handleBrightnessChange(int type, float brightness) {
|
||||
boolean swallowUpdate = mCurrentUpdate != null
|
||||
&& mCurrentUpdate.swallowUpdate(type, brightness);
|
||||
BrightnessUpdate prevUpdate = null;
|
||||
if (!swallowUpdate) {
|
||||
prevUpdate = mPendingUpdate;
|
||||
mPendingUpdate = new BrightnessUpdate(type, brightness);
|
||||
}
|
||||
runUpdate();
|
||||
|
||||
// If we created a new update and it is still pending after the update, add a log.
|
||||
if (!swallowUpdate && mPendingUpdate != null) {
|
||||
Slog.i(TAG, "New PendingUpdate: " + mPendingUpdate + ", prev=" + prevUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs updates for current and pending BrightnessUpdates.
|
||||
*/
|
||||
private void runUpdate() {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Running update mCurrent=" + mCurrentUpdate
|
||||
+ ", mPending=" + mPendingUpdate);
|
||||
}
|
||||
|
||||
// do-while instead of while to allow mCurrentUpdate to get set if there's a pending update.
|
||||
do {
|
||||
if (mCurrentUpdate != null) {
|
||||
mCurrentUpdate.update();
|
||||
if (mCurrentUpdate.isRunning()) {
|
||||
break; // current update is still running, nothing to do.
|
||||
} else if (mCurrentUpdate.isCompleted()) {
|
||||
if (mCurrentUpdate.madeUpdates()) {
|
||||
Slog.i(TAG, "Completed Update: " + mCurrentUpdate);
|
||||
}
|
||||
mCurrentUpdate = null;
|
||||
}
|
||||
}
|
||||
// No current update any more, lets start the next update if there is one.
|
||||
if (mCurrentUpdate == null && mPendingUpdate != null) {
|
||||
mCurrentUpdate = mPendingUpdate;
|
||||
mPendingUpdate = null;
|
||||
}
|
||||
} while (mCurrentUpdate != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the stored screen brightness float value from the display brightness setting.
|
||||
* @return brightness
|
||||
@@ -164,76 +256,14 @@ public class BrightnessSynchronizer {
|
||||
|
||||
/**
|
||||
* Gets the stored screen brightness int from the system settings.
|
||||
* @param context for accessing settings
|
||||
*
|
||||
* @return brightness
|
||||
*/
|
||||
private static int getScreenBrightnessInt(Context context) {
|
||||
return Settings.System.getIntForUser(context.getContentResolver(),
|
||||
private int getScreenBrightnessInt() {
|
||||
return Settings.System.getIntForUser(mContext.getContentResolver(),
|
||||
Settings.System.SCREEN_BRIGHTNESS, PowerManager.BRIGHTNESS_INVALID,
|
||||
UserHandle.USER_CURRENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the settings based on a passed in int value. This is called whenever the int
|
||||
* setting changes. mPreferredSettingValue holds the most recently updated brightness value
|
||||
* as a float that we would like the display to be set to.
|
||||
*
|
||||
* We then schedule an update to both the int and float settings, but, remove all the other
|
||||
* messages to update all, to prevent us getting stuck in a loop.
|
||||
*
|
||||
* @param value Brightness value as int to store in the float setting.
|
||||
*/
|
||||
private void updateBrightnessFloatFromInt(int value) {
|
||||
if (brightnessFloatToInt(mPreferredSettingValue) == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
mPreferredSettingValue = brightnessIntToFloat(value);
|
||||
final int newBrightnessAsIntBits = Float.floatToIntBits(mPreferredSettingValue);
|
||||
mHandler.removeMessages(MSG_UPDATE_BOTH);
|
||||
mHandler.obtainMessage(MSG_UPDATE_BOTH, newBrightnessAsIntBits, 0).sendToTarget();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the settings based on a passed in float value. This is called whenever the float
|
||||
* setting changes. mPreferredSettingValue holds the most recently updated brightness value
|
||||
* as a float that we would like the display to be set to.
|
||||
*
|
||||
* We then schedule an update to both the int and float settings, but, remove all the other
|
||||
* messages to update all, to prevent us getting stuck in a loop.
|
||||
*
|
||||
* @param value Brightness setting as float to store in int setting.
|
||||
*/
|
||||
private void updateBrightnessIntFromFloat(float value) {
|
||||
if (floatEquals(mPreferredSettingValue, value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mPreferredSettingValue = value;
|
||||
final int newBrightnessAsIntBits = Float.floatToIntBits(mPreferredSettingValue);
|
||||
mHandler.removeMessages(MSG_UPDATE_BOTH);
|
||||
mHandler.obtainMessage(MSG_UPDATE_BOTH, newBrightnessAsIntBits, 0).sendToTarget();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates both setting values if they have changed
|
||||
* mDisplayManager.setBrightness automatically checks for changes
|
||||
* Settings.System.putIntForUser needs to be checked, to prevent an extra callback to this class
|
||||
*
|
||||
* @param newBrightnessFloat Brightness setting as float to store in both settings
|
||||
*/
|
||||
private void updateBoth(float newBrightnessFloat) {
|
||||
int newBrightnessInt = brightnessFloatToInt(newBrightnessFloat);
|
||||
mDisplayManager.setBrightness(Display.DEFAULT_DISPLAY, newBrightnessFloat);
|
||||
if (getScreenBrightnessInt(mContext) != newBrightnessInt) {
|
||||
Settings.System.putIntForUser(mContext.getContentResolver(),
|
||||
Settings.System.SCREEN_BRIGHTNESS, newBrightnessInt, UserHandle.USER_CURRENT);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether two brightness float values are within a small enough tolerance
|
||||
* of each other.
|
||||
@@ -253,7 +283,192 @@ public class BrightnessSynchronizer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates a brightness change event and contains logic for synchronizing the appropriate
|
||||
* settings for the specified brightness change.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public class BrightnessUpdate {
|
||||
static final int TYPE_INT = 0x1;
|
||||
static final int TYPE_FLOAT = 0x2;
|
||||
|
||||
private static final int STATE_NOT_STARTED = 1;
|
||||
private static final int STATE_RUNNING = 2;
|
||||
private static final int STATE_COMPLETED = 3;
|
||||
|
||||
private final int mSourceType;
|
||||
private final float mBrightness;
|
||||
|
||||
private long mTimeUpdated;
|
||||
private int mState;
|
||||
private int mUpdatedTypes;
|
||||
private int mConfirmedTypes;
|
||||
private int mId;
|
||||
|
||||
BrightnessUpdate(int sourceType, float brightness) {
|
||||
mId = sBrightnessUpdateCount++;
|
||||
mSourceType = sourceType;
|
||||
mBrightness = brightness;
|
||||
mTimeUpdated = 0;
|
||||
mUpdatedTypes = 0x0;
|
||||
mConfirmedTypes = 0x0;
|
||||
mState = STATE_NOT_STARTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{[" + mId + "] " + toStringLabel(mSourceType, mBrightness)
|
||||
+ ", mUpdatedTypes=" + mUpdatedTypes + ", mConfirmedTypes=" + mConfirmedTypes
|
||||
+ ", mTimeUpdated=" + mTimeUpdated + "}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the synchronization process, moving forward through the internal state machine.
|
||||
*/
|
||||
void update() {
|
||||
if (mState == STATE_NOT_STARTED) {
|
||||
mState = STATE_RUNNING;
|
||||
|
||||
// check if we need to update int
|
||||
int brightnessInt = getBrightnessAsInt();
|
||||
if (mLatestIntBrightness != brightnessInt) {
|
||||
Settings.System.putIntForUser(mContext.getContentResolver(),
|
||||
Settings.System.SCREEN_BRIGHTNESS, brightnessInt,
|
||||
UserHandle.USER_CURRENT);
|
||||
mLatestIntBrightness = brightnessInt;
|
||||
mUpdatedTypes |= TYPE_INT;
|
||||
}
|
||||
|
||||
// check if we need to update float
|
||||
float brightnessFloat = getBrightnessAsFloat();
|
||||
if (!floatEquals(mLatestFloatBrightness, brightnessFloat)) {
|
||||
mDisplayManager.setBrightness(Display.DEFAULT_DISPLAY, brightnessFloat);
|
||||
mLatestFloatBrightness = brightnessFloat;
|
||||
mUpdatedTypes |= TYPE_FLOAT;
|
||||
}
|
||||
|
||||
// If we made updates, lets wait for responses.
|
||||
if (mUpdatedTypes != 0x0) {
|
||||
// Give some time for our updates to return a confirmation response. If they
|
||||
// don't return by that time, MSG_RUN_UPDATE will get sent and we will stop
|
||||
// listening for responses and mark this update as complete.
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Sending MSG_RUN_UPDATE for "
|
||||
+ toStringLabel(mSourceType, mBrightness));
|
||||
}
|
||||
Slog.i(TAG, "[" + mId + "] New Update "
|
||||
+ toStringLabel(mSourceType, mBrightness) + " set brightness values: "
|
||||
+ toStringLabel(mUpdatedTypes & TYPE_FLOAT, brightnessFloat) + " "
|
||||
+ toStringLabel(mUpdatedTypes & TYPE_INT, brightnessInt));
|
||||
|
||||
mHandler.sendEmptyMessageAtTime(MSG_RUN_UPDATE,
|
||||
mClock.uptimeMillis() + WAIT_FOR_RESPONSE_MILLIS);
|
||||
}
|
||||
mTimeUpdated = mClock.uptimeMillis();
|
||||
}
|
||||
|
||||
if (mState == STATE_RUNNING) {
|
||||
// If we're not waiting on any more confirmations or the time has expired, move to
|
||||
// completed state.
|
||||
if (mConfirmedTypes == mUpdatedTypes
|
||||
|| (mTimeUpdated + WAIT_FOR_RESPONSE_MILLIS) < mClock.uptimeMillis()) {
|
||||
mState = STATE_COMPLETED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to consume the specified brightness change if it is determined that the change
|
||||
* is a notification of a change previously made by this class.
|
||||
*
|
||||
* @param type The type of change (int|float)
|
||||
* @param brightness The brightness value.
|
||||
* @return True if the change was caused by this class, thus swallowed.
|
||||
*/
|
||||
boolean swallowUpdate(int type, float brightness) {
|
||||
if ((mUpdatedTypes & type) != type || (mConfirmedTypes & type) != 0x0) {
|
||||
// It's either a type we didn't update, or one we've already confirmed.
|
||||
return false;
|
||||
}
|
||||
|
||||
final boolean floatUpdateConfirmed =
|
||||
type == TYPE_FLOAT && floatEquals(getBrightnessAsFloat(), brightness);
|
||||
final boolean intUpdateConfirmed =
|
||||
type == TYPE_INT && getBrightnessAsInt() == (int) brightness;
|
||||
|
||||
if (floatUpdateConfirmed || intUpdateConfirmed) {
|
||||
mConfirmedTypes |= type;
|
||||
Slog.i(TAG, "Swallowing update of " + toStringLabel(type, brightness)
|
||||
+ " by update: " + this);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isRunning() {
|
||||
return mState == STATE_RUNNING;
|
||||
}
|
||||
|
||||
boolean isCompleted() {
|
||||
return mState == STATE_COMPLETED;
|
||||
}
|
||||
|
||||
boolean madeUpdates() {
|
||||
return mUpdatedTypes != 0x0;
|
||||
}
|
||||
|
||||
private int getBrightnessAsInt() {
|
||||
if (mSourceType == TYPE_INT) {
|
||||
return (int) mBrightness;
|
||||
}
|
||||
return brightnessFloatToInt(mBrightness);
|
||||
}
|
||||
|
||||
private float getBrightnessAsFloat() {
|
||||
if (mSourceType == TYPE_FLOAT) {
|
||||
return mBrightness;
|
||||
}
|
||||
return brightnessIntToFloat((int) mBrightness);
|
||||
}
|
||||
|
||||
private String toStringLabel(int type, float brightness) {
|
||||
return (type == TYPE_INT) ? ((int) brightness) + "(i)"
|
||||
: ((type == TYPE_FLOAT) ? brightness + "(f)"
|
||||
: "");
|
||||
}
|
||||
}
|
||||
|
||||
/** Functional interface for providing time. */
|
||||
@VisibleForTesting
|
||||
public interface Clock {
|
||||
/** @return system uptime in milliseconds. */
|
||||
long uptimeMillis();
|
||||
}
|
||||
|
||||
class BrightnessSynchronizerHandler extends Handler {
|
||||
BrightnessSynchronizerHandler(Looper looper) {
|
||||
super(looper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_RUN_UPDATE:
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "MSG_RUN_UPDATE");
|
||||
}
|
||||
runUpdate();
|
||||
break;
|
||||
default:
|
||||
super.handleMessage(msg);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
private class BrightnessSyncObserver {
|
||||
private boolean mIsObserving;
|
||||
|
||||
private final DisplayListener mListener = new DisplayListener() {
|
||||
@Override
|
||||
public void onDisplayAdded(int displayId) {}
|
||||
@@ -263,10 +478,7 @@ public class BrightnessSynchronizer {
|
||||
|
||||
@Override
|
||||
public void onDisplayChanged(int displayId) {
|
||||
float currentFloat = getScreenBrightnessFloat();
|
||||
int toSend = Float.floatToIntBits(currentFloat);
|
||||
mHandler.removeMessages(MSG_UPDATE_INT);
|
||||
mHandler.obtainMessage(MSG_UPDATE_INT, toSend, 0).sendToTarget();
|
||||
handleBrightnessChangeFloat(getScreenBrightnessFloat());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -277,28 +489,23 @@ public class BrightnessSynchronizer {
|
||||
return;
|
||||
}
|
||||
if (BRIGHTNESS_URI.equals(uri)) {
|
||||
int currentBrightness = getScreenBrightnessInt(mContext);
|
||||
mHandler.removeMessages(MSG_UPDATE_FLOAT);
|
||||
mHandler.obtainMessage(MSG_UPDATE_FLOAT, currentBrightness, 0).sendToTarget();
|
||||
handleBrightnessChangeInt(getScreenBrightnessInt());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public void startObserving() {
|
||||
boolean isObserving() {
|
||||
return mIsObserving;
|
||||
}
|
||||
|
||||
void startObserving() {
|
||||
final ContentResolver cr = mContext.getContentResolver();
|
||||
cr.unregisterContentObserver(mContentObserver);
|
||||
cr.registerContentObserver(BRIGHTNESS_URI, false, mContentObserver,
|
||||
UserHandle.USER_ALL);
|
||||
|
||||
mDisplayManager.registerDisplayListener(mListener, mHandler,
|
||||
DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
|
||||
| DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
|
||||
DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
|
||||
mIsObserving = true;
|
||||
}
|
||||
|
||||
public void stopObserving() {
|
||||
final ContentResolver cr = mContext.getContentResolver();
|
||||
cr.unregisterContentObserver(mContentObserver);
|
||||
mDisplayManager.unregisterDisplayListener(mListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2503,6 +2503,7 @@ public final class DisplayManagerService extends SystemService {
|
||||
}
|
||||
pw.println();
|
||||
mDisplayModeDirector.dump(pw);
|
||||
mBrightnessSynchronizer.dump(pw);
|
||||
}
|
||||
|
||||
private static float[] getFloatArray(TypedArray array) {
|
||||
|
||||
@@ -37,6 +37,7 @@ android_test {
|
||||
srcs: [
|
||||
"src/**/*.java",
|
||||
"src/**/*.kt",
|
||||
":framework-internal-display-sources",
|
||||
],
|
||||
|
||||
static_libs: [
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
* Copyright 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.display;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.isA;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.database.ContentObserver;
|
||||
import android.hardware.display.DisplayManager;
|
||||
import android.hardware.display.DisplayManager.DisplayListener;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.UserHandle;
|
||||
import android.os.test.TestLooper;
|
||||
import android.provider.Settings;
|
||||
import android.test.mock.MockContentResolver;
|
||||
import android.view.Display;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.internal.display.BrightnessSynchronizer;
|
||||
import com.android.internal.util.test.FakeSettingsProvider;
|
||||
import com.android.server.testutils.OffsettableClock;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class BrightnessSynchronizerTest {
|
||||
private static final float EPSILON = 0.00001f;
|
||||
private static final Uri BRIGHTNESS_URI =
|
||||
Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
|
||||
|
||||
private Context mContext;
|
||||
private MockContentResolver mContentResolverSpy;
|
||||
private OffsettableClock mClock;
|
||||
private DisplayListener mDisplayListener;
|
||||
private ContentObserver mContentObserver;
|
||||
private TestLooper mTestLooper;
|
||||
|
||||
@Mock private DisplayManager mDisplayManagerMock;
|
||||
@Captor private ArgumentCaptor<DisplayListener> mDisplayListenerCaptor;
|
||||
@Captor private ArgumentCaptor<ContentObserver> mContentObserverCaptor;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
|
||||
mContentResolverSpy = spy(new MockContentResolver(mContext));
|
||||
mContentResolverSpy.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
|
||||
when(mContext.getContentResolver()).thenReturn(mContentResolverSpy);
|
||||
when(mContext.getSystemService(DisplayManager.class)).thenReturn(mDisplayManagerMock);
|
||||
mClock = new OffsettableClock.Stopped();
|
||||
mTestLooper = new TestLooper(mClock::now);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetFloat() throws Exception {
|
||||
putFloatSetting(0.5f);
|
||||
putIntSetting(128);
|
||||
start();
|
||||
|
||||
// Set float brightness to 0.4
|
||||
putFloatSetting(0.4f);
|
||||
advanceTime(10);
|
||||
verifyIntWasSetTo(fToI(0.4f));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetInt() {
|
||||
putFloatSetting(0.5f);
|
||||
putIntSetting(128);
|
||||
start();
|
||||
|
||||
// Set int brightness to 64
|
||||
putIntSetting(64);
|
||||
advanceTime(10);
|
||||
verifyFloatWasSetTo(iToF(64));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetIntQuickSuccession() {
|
||||
putFloatSetting(0.5f);
|
||||
putIntSetting(128);
|
||||
start();
|
||||
|
||||
putIntSetting(50);
|
||||
putIntSetting(40);
|
||||
advanceTime(10);
|
||||
|
||||
verifyFloatWasSetTo(iToF(50));
|
||||
|
||||
// now confirm the first value (via callback) so that we can process the second one.
|
||||
putFloatSetting(iToF(50));
|
||||
advanceTime(10);
|
||||
verifyFloatWasSetTo(iToF(40));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetSameIntValue_nothingUpdated() {
|
||||
putFloatSetting(0.5f);
|
||||
putIntSetting(128);
|
||||
start();
|
||||
|
||||
putIntSetting(128);
|
||||
advanceTime(10);
|
||||
verify(mDisplayManagerMock, times(0)).setBrightness(
|
||||
eq(Display.DEFAULT_DISPLAY), eq(iToF(128)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateDuringResponseIsNotOverwritten() {
|
||||
putFloatSetting(0.5f);
|
||||
putIntSetting(128);
|
||||
start();
|
||||
|
||||
// First, change the float to 0.4f
|
||||
putFloatSetting(0.4f);
|
||||
advanceTime(10);
|
||||
|
||||
// Now set the int to something else (not equal to 0.4f)
|
||||
putIntSetting(20);
|
||||
advanceTime(10);
|
||||
|
||||
// Verify that this update did not get sent to float, because synchronizer
|
||||
// is still waiting for confirmation of its first value.
|
||||
verify(mDisplayManagerMock, times(0)).setBrightness(
|
||||
eq(Display.DEFAULT_DISPLAY), eq(iToF(20)));
|
||||
|
||||
// Send the confirmation of the initial change. This should trigger the new value to
|
||||
// finally be processed and we can verify that the new value (20) is sent.
|
||||
putIntSetting(fToI(0.4f));
|
||||
advanceTime(10);
|
||||
verify(mDisplayManagerMock).setBrightness(
|
||||
eq(Display.DEFAULT_DISPLAY), eq(iToF(20)));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetFloat_outOfTimeForResponse() {
|
||||
putFloatSetting(0.5f);
|
||||
putIntSetting(128);
|
||||
start();
|
||||
advanceTime(210);
|
||||
|
||||
// First, change the float to 0.4f
|
||||
putFloatSetting(0.4f);
|
||||
advanceTime(10);
|
||||
|
||||
// Now set the int to something else (not equal to 0.4f)
|
||||
putIntSetting(20);
|
||||
|
||||
// Now, go beyond the timeout so that the last 20 event gets executed.
|
||||
advanceTime(200);
|
||||
|
||||
// Verify that the new value gets sent because the timeout expired.
|
||||
verify(mDisplayManagerMock).setBrightness(
|
||||
eq(Display.DEFAULT_DISPLAY), eq(iToF(20)));
|
||||
|
||||
// Send a confirmation of the initial event, BrightnessSynchronizer should treat this as a
|
||||
// new event because the timeout had already expired
|
||||
putIntSetting(fToI(0.4f));
|
||||
// Because the previous setting will be treated as a new event, we actually want to send
|
||||
// confirmation of the setBrightness() we just verified so that it can be executed as well.
|
||||
putFloatSetting(iToF(20));
|
||||
advanceTime(10);
|
||||
|
||||
// Verify we sent what would have been the confirmation as a new event to displaymanager.
|
||||
// We do both fToI and iToF because the conversions are not symmetric.
|
||||
verify(mDisplayManagerMock).setBrightness(
|
||||
eq(Display.DEFAULT_DISPLAY), eq(iToF(fToI(0.4f))));
|
||||
}
|
||||
|
||||
private BrightnessSynchronizer start() {
|
||||
BrightnessSynchronizer bs = new BrightnessSynchronizer(mContext, mTestLooper.getLooper(),
|
||||
mClock::now);
|
||||
bs.startSynchronizing();
|
||||
verify(mDisplayManagerMock).registerDisplayListener(mDisplayListenerCaptor.capture(),
|
||||
isA(Handler.class), eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
|
||||
mDisplayListener = mDisplayListenerCaptor.getValue();
|
||||
|
||||
verify(mContentResolverSpy).registerContentObserver(eq(BRIGHTNESS_URI), eq(false),
|
||||
mContentObserverCaptor.capture(), eq(UserHandle.USER_ALL));
|
||||
mContentObserver = mContentObserverCaptor.getValue();
|
||||
return bs;
|
||||
}
|
||||
|
||||
private int getIntSetting() throws Exception {
|
||||
return Settings.System.getInt(mContentResolverSpy, Settings.System.SCREEN_BRIGHTNESS);
|
||||
}
|
||||
|
||||
private void putIntSetting(int brightness) {
|
||||
Settings.System.putInt(mContentResolverSpy, Settings.System.SCREEN_BRIGHTNESS, brightness);
|
||||
if (mContentObserver != null) {
|
||||
mContentObserver.onChange(false /*=selfChange*/, BRIGHTNESS_URI);
|
||||
}
|
||||
}
|
||||
|
||||
private void putFloatSetting(float brightness) {
|
||||
when(mDisplayManagerMock.getBrightness(eq(Display.DEFAULT_DISPLAY))).thenReturn(brightness);
|
||||
if (mDisplayListener != null) {
|
||||
mDisplayListener.onDisplayChanged(Display.DEFAULT_DISPLAY);
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyIntWasSetTo(int brightness) throws Exception {
|
||||
assertEquals(brightness, getIntSetting());
|
||||
}
|
||||
|
||||
private void verifyFloatWasSetTo(float brightness) {
|
||||
verify(mDisplayManagerMock).setBrightness(eq(Display.DEFAULT_DISPLAY), eq(brightness));
|
||||
}
|
||||
|
||||
private int fToI(float brightness) {
|
||||
return BrightnessSynchronizer.brightnessFloatToInt(brightness);
|
||||
}
|
||||
|
||||
private float iToF(int brightness) {
|
||||
return BrightnessSynchronizer.brightnessIntToFloat(brightness);
|
||||
}
|
||||
|
||||
private void advanceTime(long timeMs) {
|
||||
mClock.fastForward(timeMs);
|
||||
mTestLooper.dispatchAll();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user