summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/Android.bp6
-rw-r--r--core/java/com/android/internal/display/BrightnessSynchronizer.java429
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java1
-rw-r--r--services/tests/mockingservicestests/Android.bp1
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/display/BrightnessSynchronizerTest.java255
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();
+ }
+}