blob: c9a9e51dceed8233079df129295b488edc6e1193 [file] [log] [blame]
/*
* Copyright (C) 2019 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.internal.display;
import android.content.ContentResolver;
import android.content.Context;
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.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.MathUtils;
import android.view.Display;
/**
* 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 Uri BRIGHTNESS_URI =
Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
// 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 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;
public BrightnessSynchronizer(Context context) {
mContext = context;
}
/**
* Starts brightnessSyncObserver to ensure that the float and int brightness values stay
* in sync.
* This also ensures that values are synchronized at system start up too.
* So we force an update to the int value, since float is the source of truth. Fallback to int
* value, if float is invalid. If both are invalid, use default float value from config.
*/
public void startSynchronizing() {
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);
} else {
final float defaultBrightness = mContext.getResources().getFloat(
com.android.internal.R.dimen.config_screenBrightnessSettingDefaultFloat);
mDisplayManager.setBrightness(Display.DEFAULT_DISPLAY, defaultBrightness);
}
}
/**
* Converts between the int brightness system and the float brightness system.
*/
public static float brightnessIntToFloat(int brightnessInt) {
if (brightnessInt == PowerManager.BRIGHTNESS_OFF) {
return PowerManager.BRIGHTNESS_OFF_FLOAT;
} else if (brightnessInt == PowerManager.BRIGHTNESS_INVALID) {
return PowerManager.BRIGHTNESS_INVALID_FLOAT;
} else {
final float minFloat = PowerManager.BRIGHTNESS_MIN;
final float maxFloat = PowerManager.BRIGHTNESS_MAX;
final float minInt = PowerManager.BRIGHTNESS_OFF + 1;
final float maxInt = PowerManager.BRIGHTNESS_ON;
return MathUtils.constrainedMap(minFloat, maxFloat, minInt, maxInt, brightnessInt);
}
}
/**
* Converts between the float brightness system and the int brightness system.
*/
public static int brightnessFloatToInt(float brightnessFloat) {
return Math.round(brightnessFloatToIntRange(brightnessFloat));
}
/**
* Translates specified value from the float brightness system to the int brightness system,
* given the min/max of each range. Accounts for special values such as OFF and invalid values.
* Value returned as a float primitive (to preserve precision), but is a value within the
* int-system range.
*/
public static float brightnessFloatToIntRange(float brightnessFloat) {
if (floatEquals(brightnessFloat, PowerManager.BRIGHTNESS_OFF_FLOAT)) {
return PowerManager.BRIGHTNESS_OFF;
} else if (Float.isNaN(brightnessFloat)) {
return PowerManager.BRIGHTNESS_INVALID;
} else {
final float minFloat = PowerManager.BRIGHTNESS_MIN;
final float maxFloat = PowerManager.BRIGHTNESS_MAX;
final float minInt = PowerManager.BRIGHTNESS_OFF + 1;
final float maxInt = PowerManager.BRIGHTNESS_ON;
return MathUtils.constrainedMap(minInt, maxInt, minFloat, maxFloat, brightnessFloat);
}
}
/**
* Gets the stored screen brightness float value from the display brightness setting.
* @return brightness
*/
private float getScreenBrightnessFloat() {
return mDisplayManager.getBrightness(Display.DEFAULT_DISPLAY);
}
/**
* 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(),
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.
* @param a first float to compare
* @param b second float to compare
* @return whether the two values are within a small enough tolerance value
*/
public static boolean floatEquals(float a, float b) {
if (a == b) {
return true;
} else if (Float.isNaN(a) && Float.isNaN(b)) {
return true;
} else if (Math.abs(a - b) < EPSILON) {
return true;
} else {
return false;
}
}
private class BrightnessSyncObserver {
private final DisplayListener mListener = new DisplayListener() {
@Override
public void onDisplayAdded(int displayId) {}
@Override
public void onDisplayRemoved(int displayId) {}
@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();
}
};
private final ContentObserver mContentObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange, Uri uri) {
if (selfChange) {
return;
}
if (BRIGHTNESS_URI.equals(uri)) {
int currentBrightness = getScreenBrightnessInt(mContext);
mHandler.removeMessages(MSG_UPDATE_FLOAT);
mHandler.obtainMessage(MSG_UPDATE_FLOAT, currentBrightness, 0).sendToTarget();
}
}
};
public 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);
}
public void stopObserving() {
final ContentResolver cr = mContext.getContentResolver();
cr.unregisterContentObserver(mContentObserver);
mDisplayManager.unregisterDisplayListener(mListener);
}
}
}