diff options
5 files changed, 581 insertions, 111 deletions
diff --git a/core/java/Android.bp b/core/java/Android.bp index 7f9485b6ea45..77589a213e17 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -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 diff --git a/core/java/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java index c9a9e51dceed..627631a376f7 100644 --- a/core/java/com/android/internal/display/BrightnessSynchronizer.java +++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java @@ -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 final Context mContext; + private static int sBrightnessUpdateCount = 1; - 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 final Context mContext; + private final BrightnessSyncObserver mBrightnessSyncObserver; + private final Clock mClock; + private final Handler mHandler; - 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); } - - 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 (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)"); + + 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); } /** @@ -155,83 +175,93 @@ public class BrightnessSynchronizer { } /** - * Gets the stored screen brightness float value from the display brightness setting. - * @return brightness + * Consumes a brightness change event for the float-based brightness. + * + * @param brightness Float brightness. */ - private float getScreenBrightnessFloat() { - return mDisplayManager.getBrightness(Display.DEFAULT_DISPLAY); + private void handleBrightnessChangeFloat(float brightness) { + mLatestFloatBrightness = brightness; + handleBrightnessChange(BrightnessUpdate.TYPE_FLOAT, brightness); } /** - * Gets the stored screen brightness int from the system settings. - * @param context for accessing settings + * Consumes a brightness change event for the int-based brightness. * - * @return brightness + * @param brightness Int brightness. */ - private static int getScreenBrightnessInt(Context context) { - return Settings.System.getIntForUser(context.getContentResolver(), - Settings.System.SCREEN_BRIGHTNESS, PowerManager.BRIGHTNESS_INVALID, - UserHandle.USER_CURRENT); + private void handleBrightnessChangeInt(int brightness) { + mLatestIntBrightness = brightness; + handleBrightnessChange(BrightnessUpdate.TYPE_INT, brightness); } /** - * 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. + * Consumes a brightness change event. * - * 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. + * @param type Type of the brightness change (int/float) + * @param brightness brightness. */ - private void updateBrightnessFloatFromInt(int value) { - if (brightnessFloatToInt(mPreferredSettingValue) == value) { - return; + 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(); - mPreferredSettingValue = brightnessIntToFloat(value); - final int newBrightnessAsIntBits = Float.floatToIntBits(mPreferredSettingValue); - mHandler.removeMessages(MSG_UPDATE_BOTH); - mHandler.obtainMessage(MSG_UPDATE_BOTH, newBrightnessAsIntBits, 0).sendToTarget(); + // 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); + } } /** - * 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. + * Runs updates for current and pending BrightnessUpdates. */ - private void updateBrightnessIntFromFloat(float value) { - if (floatEquals(mPreferredSettingValue, value)) { - return; + private void runUpdate() { + if (DEBUG) { + Slog.d(TAG, "Running update mCurrent=" + mCurrentUpdate + + ", mPending=" + mPendingUpdate); } - mPreferredSettingValue = value; - final int newBrightnessAsIntBits = Float.floatToIntBits(mPreferredSettingValue); - mHandler.removeMessages(MSG_UPDATE_BOTH); - mHandler.obtainMessage(MSG_UPDATE_BOTH, newBrightnessAsIntBits, 0).sendToTarget(); + // 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); } - /** - * 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 + * Gets the stored screen brightness float value from the display brightness setting. + * @return brightness */ - 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); - } + private float getScreenBrightnessFloat() { + return mDisplayManager.getBrightness(Display.DEFAULT_DISPLAY); + } + /** + * Gets the stored screen brightness int from the system settings. + * @return brightness + */ + private int getScreenBrightnessInt() { + return Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS, PowerManager.BRIGHTNESS_INVALID, + UserHandle.USER_CURRENT); } /** @@ -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); - } } } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 2cf11c657aa0..e77693650eda 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -2503,6 +2503,7 @@ public final class DisplayManagerService extends SystemService { } pw.println(); mDisplayModeDirector.dump(pw); + mBrightnessSynchronizer.dump(pw); } private static float[] getFloatArray(TypedArray array) { diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp index 08c68b9a469e..acdfbb0b1744 100644 --- a/services/tests/mockingservicestests/Android.bp +++ b/services/tests/mockingservicestests/Android.bp @@ -37,6 +37,7 @@ android_test { srcs: [ "src/**/*.java", "src/**/*.kt", + ":framework-internal-display-sources", ], static_libs: [ diff --git a/services/tests/mockingservicestests/src/com/android/server/display/BrightnessSynchronizerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/BrightnessSynchronizerTest.java new file mode 100644 index 000000000000..2fd6e5fb6892 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/display/BrightnessSynchronizerTest.java @@ -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(); + } +} |