Merge "Update BrightnessSynchronizer Logic" into tm-dev am: 5b0c795a65

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/18334422

Change-Id: I57735cd1bc24078a2d27bc37f8411ecc19d852c2
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
TreeHugger Robot
2022-05-19 08:53:42 +00:00
committed by Automerger Merge Worker
5 changed files with 591 additions and 121 deletions

View File

@@ -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

View File

@@ -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);
}
}
}

View File

@@ -2503,6 +2503,7 @@ public final class DisplayManagerService extends SystemService {
}
pw.println();
mDisplayModeDirector.dump(pw);
mBrightnessSynchronizer.dump(pw);
}
private static float[] getFloatArray(TypedArray array) {

View File

@@ -37,6 +37,7 @@ android_test {
srcs: [
"src/**/*.java",
"src/**/*.kt",
":framework-internal-display-sources",
],
static_libs: [

View File

@@ -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();
}
}