diff options
14 files changed, 705 insertions, 98 deletions
diff --git a/core/java/android/hardware/display/BrightnessInfo.aidl b/core/java/android/hardware/display/BrightnessInfo.aidl new file mode 100644 index 000000000000..5da55c34f3b4 --- /dev/null +++ b/core/java/android/hardware/display/BrightnessInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 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 android.hardware.display; + +parcelable BrightnessInfo; diff --git a/core/java/android/hardware/display/BrightnessInfo.java b/core/java/android/hardware/display/BrightnessInfo.java new file mode 100644 index 000000000000..4289860d4e8a --- /dev/null +++ b/core/java/android/hardware/display/BrightnessInfo.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2021 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 android.hardware.display; + +import android.annotation.IntDef; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Data about the current brightness state. + * {@see android.view.Display.getBrightnessInfo()} + * + * @hide + */ +public final class BrightnessInfo implements Parcelable { + + @IntDef(prefix = {"HIGH_BRIGHTNESS_MODE_"}, value = { + HIGH_BRIGHTNESS_MODE_OFF, + HIGH_BRIGHTNESS_MODE_SUNLIGHT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface HighBrightnessMode {} + + /** + * High brightness mode is OFF. The high brightness range is not currently accessible to the + * user. + */ + public static final int HIGH_BRIGHTNESS_MODE_OFF = 0; + + /** + * High brightness mode is ON due to high ambient light (sunlight). The high brightness range is + * currently accessible to the user. + */ + public static final int HIGH_BRIGHTNESS_MODE_SUNLIGHT = 1; + + /** Brightness */ + public final float brightness; + + /** Current minimum supported brightness. */ + public final float brightnessMinimum; + + /** Current maximum supported brightness. */ + public final float brightnessMaximum; + + /** + * Current state of high brightness mode. + * Can be any of HIGH_BRIGHTNESS_MODE_* values. + */ + public final int highBrightnessMode; + + public BrightnessInfo(float brightness, float brightnessMinimum, float brightnessMaximum, + @HighBrightnessMode int highBrightnessMode) { + this.brightness = brightness; + this.brightnessMinimum = brightnessMinimum; + this.brightnessMaximum = brightnessMaximum; + this.highBrightnessMode = highBrightnessMode; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeFloat(brightness); + dest.writeFloat(brightnessMinimum); + dest.writeFloat(brightnessMaximum); + dest.writeInt(highBrightnessMode); + } + + public static final @android.annotation.NonNull Creator<BrightnessInfo> CREATOR = + new Creator<BrightnessInfo>() { + @Override + public BrightnessInfo createFromParcel(Parcel source) { + return new BrightnessInfo(source); + } + + @Override + public BrightnessInfo[] newArray(int size) { + return new BrightnessInfo[size]; + } + }; + + private BrightnessInfo(Parcel source) { + brightness = source.readFloat(); + brightnessMinimum = source.readFloat(); + brightnessMaximum = source.readFloat(); + highBrightnessMode = source.readInt(); + } + +} diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 6c2d1402b303..de32adb1f545 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -418,6 +418,7 @@ public final class DisplayManager { EVENT_FLAG_DISPLAY_ADDED, EVENT_FLAG_DISPLAY_CHANGED, EVENT_FLAG_DISPLAY_REMOVED, + EVENT_FLAG_DISPLAY_BRIGHTNESS }) @Retention(RetentionPolicy.SOURCE) public @interface EventsMask {} @@ -449,6 +450,17 @@ public final class DisplayManager { */ public static final long EVENT_FLAG_DISPLAY_CHANGED = 1L << 2; + /** + * Event flag to register for a display's brightness changes. This notification is sent + * through the {@link DisplayListener#onDisplayChanged} callback method. New brightness + * values can be retrieved via {@link android.view.Display#getBrightnessInfo()}. + * + * @see #registerDisplayListener(DisplayListener, Handler, long) + * + * @hide + */ + public static final long EVENT_FLAG_DISPLAY_BRIGHTNESS = 1L << 3; + /** @hide */ public DisplayManager(Context context) { mContext = context; @@ -583,6 +595,7 @@ public final class DisplayManager { * @see #EVENT_FLAG_DISPLAY_ADDED * @see #EVENT_FLAG_DISPLAY_CHANGED * @see #EVENT_FLAG_DISPLAY_REMOVED + * @see #EVENT_FLAG_DISPLAY_BRIGHTNESS * @see #registerDisplayListener(DisplayListener, Handler) * @see #unregisterDisplayListener * diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 983a43af0802..df51734bc17d 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -79,6 +79,7 @@ public final class DisplayManagerGlobal { EVENT_DISPLAY_ADDED, EVENT_DISPLAY_CHANGED, EVENT_DISPLAY_REMOVED, + EVENT_DISPLAY_BRIGHTNESS_CHANGED }) @Retention(RetentionPolicy.SOURCE) public @interface DisplayEvent {} @@ -86,6 +87,7 @@ public final class DisplayManagerGlobal { public static final int EVENT_DISPLAY_ADDED = 1; public static final int EVENT_DISPLAY_CHANGED = 2; public static final int EVENT_DISPLAY_REMOVED = 3; + public static final int EVENT_DISPLAY_BRIGHTNESS_CHANGED = 4; @UnsupportedAppUsage private static DisplayManagerGlobal sInstance; @@ -665,6 +667,17 @@ public final class DisplayManagerGlobal { } /** + * Retrieves Brightness Info for the specified display. + */ + public BrightnessInfo getBrightnessInfo(int displayId) { + try { + return mDm.getBrightnessInfo(displayId); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** * Gets the preferred wide gamut color space for all displays. * The wide gamut color space is returned from composition pipeline * based on hardware capability. @@ -934,6 +947,11 @@ public final class DisplayManagerGlobal { } } break; + case EVENT_DISPLAY_BRIGHTNESS_CHANGED: + if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS) != 0) { + mListener.onDisplayChanged(msg.arg1); + } + break; case EVENT_DISPLAY_REMOVED: if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_REMOVED) != 0) { mListener.onDisplayRemoved(msg.arg1); diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index 5ca4e0cc657c..2303353ad101 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -19,6 +19,7 @@ package android.hardware.display; import android.content.pm.ParceledListSlice; import android.graphics.Point; import android.hardware.display.BrightnessConfiguration; +import android.hardware.display.BrightnessInfo; import android.hardware.display.Curve; import android.hardware.display.IDisplayManagerCallback; import android.hardware.display.IVirtualDisplayCallback; @@ -143,6 +144,9 @@ interface IDisplayManager { // Get the minimum brightness curve. Curve getMinimumBrightnessCurve(); + // Get Brightness Information for the specified display. + BrightnessInfo getBrightnessInfo(int displayId); + // Gets the id of the preferred wide gamut color space for all displays. // The wide gamut color space is returned from composition pipeline // based on hardware capability. diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index c87db65c9de2..9cb0d1ff2c3f 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -17,6 +17,7 @@ package android.view; import static android.Manifest.permission.CONFIGURE_DISPLAY_COLOR_MODE; +import static android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS; import android.annotation.IntDef; import android.annotation.NonNull; @@ -36,6 +37,7 @@ import android.graphics.ColorSpace; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; +import android.hardware.display.BrightnessInfo; import android.hardware.display.DeviceProductInfo; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; @@ -730,6 +732,15 @@ public final class Display { } /** + * @return Brightness information about the display. + * @hide + */ + @RequiresPermission(CONTROL_DISPLAY_BRIGHTNESS) + public @Nullable BrightnessInfo getBrightnessInfo() { + return mGlobal.getBrightnessInfo(mDisplayId); + } + + /** * Gets the size of the display, in pixels. * Value returned by this method does not necessarily represent the actual raw size * (native resolution) of the display. diff --git a/core/java/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java index 6776c27fc16e..bd908900fccc 100644 --- a/core/java/com/android/internal/display/BrightnessSynchronizer.java +++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java @@ -16,11 +16,11 @@ 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; @@ -95,7 +95,7 @@ public class BrightnessSynchronizer { } final BrightnessSyncObserver brightnessSyncObserver; - brightnessSyncObserver = new BrightnessSyncObserver(mHandler); + brightnessSyncObserver = new BrightnessSyncObserver(); brightnessSyncObserver.startObserving(); final float currentFloatBrightness = getScreenBrightnessFloat(); @@ -232,47 +232,52 @@ public class BrightnessSynchronizer { } } - private class BrightnessSyncObserver extends ContentObserver { - /** - * Creates a content observer. - * @param handler The handler to run {@link #onChange} on, or null if none. - */ - BrightnessSyncObserver(Handler handler) { - super(handler); - } + private class BrightnessSyncObserver { + private final DisplayListener mListener = new DisplayListener() { + @Override + public void onDisplayAdded(int displayId) {} - @Override - public void onChange(boolean selfChange) { - onChange(selfChange, null); - } + @Override + public void onDisplayRemoved(int displayId) {} - @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(); - } else if (BRIGHTNESS_FLOAT_URI.equals(uri)) { + @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(this); - cr.registerContentObserver(BRIGHTNESS_URI, false, this, UserHandle.USER_ALL); - cr.registerContentObserver(BRIGHTNESS_FLOAT_URI, false, this, UserHandle.USER_ALL); + 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(this); + cr.unregisterContentObserver(mContentObserver); + mDisplayManager.unregisterDisplayListener(mListener); } } } diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java index 357256cba131..1ad253e7f867 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java @@ -24,7 +24,9 @@ import android.animation.ValueAnimator; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; +import android.hardware.display.BrightnessInfo; import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManager.DisplayListener; import android.net.Uri; import android.os.AsyncTask; import android.os.Handler; @@ -64,17 +66,11 @@ public class BrightnessController implements ToggleSlider.Listener { private static final Uri BRIGHTNESS_MODE_URI = Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE); - private static final Uri BRIGHTNESS_URI = - Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS); - private static final Uri BRIGHTNESS_FLOAT_URI = - Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FLOAT); private static final Uri BRIGHTNESS_FOR_VR_FLOAT_URI = Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT); - private final float mDefaultBacklight; private final float mMinimumBacklightForVr; private final float mMaximumBacklightForVr; - private final float mDefaultBacklightForVr; private final int mDisplayId; private final Context mContext; @@ -86,6 +82,20 @@ public class BrightnessController implements ToggleSlider.Listener { private final Handler mBackgroundHandler; private final BrightnessObserver mBrightnessObserver; + private final DisplayListener mDisplayListener = new DisplayListener() { + @Override + public void onDisplayAdded(int displayId) {} + + @Override + public void onDisplayRemoved(int displayId) {} + + @Override + public void onDisplayChanged(int displayId) { + mBackgroundHandler.post(mUpdateSliderRunnable); + notifyCallbacks(); + } + }; + private ArrayList<BrightnessStateChangeCallback> mChangeCallbacks = new ArrayList<BrightnessStateChangeCallback>(); @@ -94,6 +104,8 @@ public class BrightnessController implements ToggleSlider.Listener { private boolean mListening; private boolean mExternalChange; private boolean mControlValueInitialized; + private float mBrightnessMin = PowerManager.BRIGHTNESS_MIN; + private float mBrightnessMax = PowerManager.BRIGHTNESS_MAX; private ValueAnimator mSliderAnimator; @@ -110,28 +122,19 @@ public class BrightnessController implements ToggleSlider.Listener { } @Override - public void onChange(boolean selfChange) { - onChange(selfChange, null); - } - - @Override public void onChange(boolean selfChange, Uri uri) { if (selfChange) return; if (BRIGHTNESS_MODE_URI.equals(uri)) { mBackgroundHandler.post(mUpdateModeRunnable); mBackgroundHandler.post(mUpdateSliderRunnable); - } else if (BRIGHTNESS_FLOAT_URI.equals(uri)) { - mBackgroundHandler.post(mUpdateSliderRunnable); } else if (BRIGHTNESS_FOR_VR_FLOAT_URI.equals(uri)) { mBackgroundHandler.post(mUpdateSliderRunnable); } else { mBackgroundHandler.post(mUpdateModeRunnable); mBackgroundHandler.post(mUpdateSliderRunnable); } - for (BrightnessStateChangeCallback cb : mChangeCallbacks) { - cb.onBrightnessLevelChanged(); - } + notifyCallbacks(); } public void startObserving() { @@ -141,19 +144,16 @@ public class BrightnessController implements ToggleSlider.Listener { BRIGHTNESS_MODE_URI, false, this, UserHandle.USER_ALL); cr.registerContentObserver( - BRIGHTNESS_URI, - false, this, UserHandle.USER_ALL); - cr.registerContentObserver( - BRIGHTNESS_FLOAT_URI, - false, this, UserHandle.USER_ALL); - cr.registerContentObserver( BRIGHTNESS_FOR_VR_FLOAT_URI, false, this, UserHandle.USER_ALL); + mDisplayManager.registerDisplayListener(mDisplayListener, mHandler, + DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS); } public void stopObserving() { final ContentResolver cr = mContext.getContentResolver(); cr.unregisterContentObserver(this); + mDisplayManager.unregisterDisplayListener(mDisplayListener); } } @@ -233,11 +233,15 @@ public class BrightnessController implements ToggleSlider.Listener { private final Runnable mUpdateSliderRunnable = new Runnable() { @Override public void run() { - final float valFloat; final boolean inVrMode = mIsVrModeEnabled; - valFloat = mDisplayManager.getBrightness(mDisplayId); + final BrightnessInfo info = mContext.getDisplay().getBrightnessInfo(); + if (info == null) { + return; + } + mBrightnessMax = info.brightnessMaximum; + mBrightnessMin = info.brightnessMinimum; // Value is passed as intbits, since this is what the message takes. - final int valueAsIntBits = Float.floatToIntBits(valFloat); + final int valueAsIntBits = Float.floatToIntBits(info.brightness); mHandler.obtainMessage(MSG_UPDATE_SLIDER, valueAsIntBits, inVrMode ? 1 : 0).sendToTarget(); } @@ -295,13 +299,10 @@ public class BrightnessController implements ToggleSlider.Listener { mDisplayId = mContext.getDisplayId(); PowerManager pm = context.getSystemService(PowerManager.class); - mDefaultBacklight = mContext.getDisplay().getBrightnessDefault(); mMinimumBacklightForVr = pm.getBrightnessConstraint( PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR); mMaximumBacklightForVr = pm.getBrightnessConstraint( PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM_VR); - mDefaultBacklightForVr = pm.getBrightnessConstraint( - PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT_VR); mDisplayManager = context.getSystemService(DisplayManager.class); mVrManager = IVrManager.Stub.asInterface(ServiceManager.getService( @@ -337,7 +338,6 @@ public class BrightnessController implements ToggleSlider.Listener { final float minBacklight; final float maxBacklight; final int metric; - final String settingToChange; if (mIsVrModeEnabled) { metric = MetricsEvent.ACTION_BRIGHTNESS_FOR_VR; @@ -347,12 +347,12 @@ public class BrightnessController implements ToggleSlider.Listener { metric = mAutomatic ? MetricsEvent.ACTION_BRIGHTNESS_AUTO : MetricsEvent.ACTION_BRIGHTNESS; - minBacklight = PowerManager.BRIGHTNESS_MIN; - maxBacklight = PowerManager.BRIGHTNESS_MAX; + minBacklight = mBrightnessMin; + maxBacklight = mBrightnessMax; } - final float valFloat = MathUtils.min(convertGammaToLinearFloat(value, - minBacklight, maxBacklight), - 1.0f); + final float valFloat = MathUtils.min( + convertGammaToLinearFloat(value, minBacklight, maxBacklight), + maxBacklight); if (stopTracking) { // TODO(brightnessfloat): change to use float value instead. MetricsLogger.action(mContext, metric, @@ -403,8 +403,8 @@ public class BrightnessController implements ToggleSlider.Listener { min = mMinimumBacklightForVr; max = mMaximumBacklightForVr; } else { - min = PowerManager.BRIGHTNESS_MIN; - max = PowerManager.BRIGHTNESS_MAX; + min = mBrightnessMin; + max = mBrightnessMax; } // convertGammaToLinearFloat returns 0-1 if (BrightnessSynchronizer.floatEquals(brightnessValue, @@ -439,6 +439,13 @@ public class BrightnessController implements ToggleSlider.Listener { mSliderAnimator.start(); } + private void notifyCallbacks() { + final int size = mChangeCallbacks.size(); + for (int i = 0; i < size; i++) { + mChangeCallbacks.get(i).onBrightnessLevelChanged(); + } + } + /** Factory for creating a {@link BrightnessController}. */ public static class Factory { private final Context mContext; diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index ace466acdaf9..2d7145fef69c 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -695,6 +695,17 @@ public class DisplayDeviceConfig { /** Minimum time that HBM can be on before being enabled. */ public long timeMinMillis; + HighBrightnessModeData() {} + + HighBrightnessModeData(float minimumLux, float transitionPoint, + long timeWindowMillis, long timeMaxMillis, long timeMinMillis) { + this.minimumLux = minimumLux; + this.transitionPoint = transitionPoint; + this.timeWindowMillis = timeWindowMillis; + this.timeMaxMillis = timeMaxMillis; + this.timeMinMillis = timeMinMillis; + } + /** * Copies the HBM data to the specified parameter instance. * @param other the instance to copy data to. diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 393a4eb83dd6..3f7ec32cb430 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -55,6 +55,7 @@ import android.hardware.devicestate.DeviceStateManager; import android.hardware.display.AmbientBrightnessDayStats; import android.hardware.display.BrightnessChangeEvent; import android.hardware.display.BrightnessConfiguration; +import android.hardware.display.BrightnessInfo; import android.hardware.display.Curve; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; @@ -842,8 +843,6 @@ public final class DisplayManagerService extends SystemService { return overriddenInfo; } - - return info; } @@ -2062,10 +2061,16 @@ public final class DisplayManagerService extends SystemService { display, mContext); final DisplayPowerController displayPowerController = new DisplayPowerController( mContext, mDisplayPowerCallbacks, mPowerHandler, mSensorManager, - mDisplayBlanker, display, mBrightnessTracker, brightnessSetting); + mDisplayBlanker, display, mBrightnessTracker, brightnessSetting, + () -> handleBrightnessChange(display)); mDisplayPowerControllers.append(display.getDisplayIdLocked(), displayPowerController); } + private void handleBrightnessChange(LogicalDisplay display) { + sendDisplayEventLocked(display.getDisplayIdLocked(), + DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED); + } + private final class DisplayManagerHandler extends Handler { public DisplayManagerHandler(Looper looper) { super(looper, null, true /*async*/); @@ -2219,6 +2224,8 @@ public final class DisplayManagerService extends SystemService { return (mask & DisplayManager.EVENT_FLAG_DISPLAY_ADDED) != 0; case DisplayManagerGlobal.EVENT_DISPLAY_CHANGED: return (mask & DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) != 0; + case DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED: + return (mask & DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS) != 0; case DisplayManagerGlobal.EVENT_DISPLAY_REMOVED: return (mask & DisplayManager.EVENT_FLAG_DISPLAY_REMOVED) != 0; default: @@ -2781,6 +2788,25 @@ public final class DisplayManagerService extends SystemService { } } + @Override + public BrightnessInfo getBrightnessInfo(int displayId) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS, + "Permission required to read the display's brightness info."); + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSyncRoot) { + DisplayPowerController dpc = mDisplayPowerControllers.get(displayId); + if (dpc != null) { + return dpc.getBrightnessInfo(); + } + } + } finally { + Binder.restoreCallingIdentity(token); + } + return null; + } + @Override // Binder call public boolean isMinimalPostProcessingRequested(int displayId) { synchronized (mSyncRoot) { @@ -3232,4 +3258,17 @@ public final class DisplayManagerService extends SystemService { } } }; + + /** + * Functional interface for providing time. + * TODO(b/184781936): merge with PowerManagerService.Clock + */ + @VisibleForTesting + public interface Clock { + /** + * Returns current time in milliseconds since boot, not counting time spent in deep sleep. + */ + long uptimeMillis(); + } + } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 86e4fd00db7f..7c0709e48fa0 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -32,6 +32,7 @@ import android.hardware.SensorManager; import android.hardware.display.AmbientBrightnessDayStats; import android.hardware.display.BrightnessChangeEvent; import android.hardware.display.BrightnessConfiguration; +import android.hardware.display.BrightnessInfo; import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks; import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; import android.metrics.LogMaker; @@ -53,6 +54,7 @@ import android.util.Slog; import android.util.TimeUtils; import android.view.Display; +import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IBatteryStats; import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.logging.MetricsLogger; @@ -146,6 +148,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private static final int REPORTED_TO_POLICY_SCREEN_ON = 2; private static final int REPORTED_TO_POLICY_SCREEN_TURNING_OFF = 3; + private static final Uri BRIGHTNESS_FLOAT_URI = + Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FLOAT); + private final Object mLock = new Object(); private final Context mContext; @@ -215,6 +220,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // Whether or not the color fade on screen on / off is enabled. private final boolean mColorFadeEnabled; + @GuardedBy("mCachedBrightnessInfo") + private final CachedBrightnessInfo mCachedBrightnessInfo = new CachedBrightnessInfo(); + // True if we should fade the screen while turning it off, false if we should play // a stylish color fade animation instead. private boolean mColorFadeFadesConfig; @@ -359,6 +367,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private final BrightnessSetting mBrightnessSetting; + private final Runnable mOnBrightnessChangeRunnable; + // A record of state for skipping brightness ramps. private int mSkipRampState = RAMP_STATE_SKIP_NONE; @@ -428,7 +438,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call public DisplayPowerController(Context context, DisplayPowerCallbacks callbacks, Handler handler, SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay, - BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting) { + BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting, + Runnable onBrightnessChangeRunnable) { mLogicalDisplay = logicalDisplay; mDisplayId = mLogicalDisplay.getDisplayIdLocked(); mHandler = new DisplayControllerHandler(handler.getLooper()); @@ -446,8 +457,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mBlanker = blanker; mContext = context; mBrightnessTracker = brightnessTracker; - mBrightnessSetting = brightnessSetting; + mOnBrightnessChangeRunnable = onBrightnessChangeRunnable; + PowerManager pm = context.getSystemService(PowerManager.class); final Resources resources = context.getResources(); @@ -493,6 +505,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mHbmController = createHbmController(); + // Seed the cached brightness + saveBrightnessInfo(getScreenBrightnessSetting()); + if (mUseSoftwareAutoBrightnessConfig) { final float dozeScaleFactor = resources.getFraction( com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor, @@ -1163,6 +1178,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mBrightnessReasonTemp.setReason(BrightnessReason.REASON_MANUAL); } + // Save out the brightness info now that the brightness state for this iteration has been + // finalized and before we send out notifications about the brightness changing. + saveBrightnessInfo(brightnessState); + if (updateScreenBrightnessSetting) { // Tell the rest of the system about the new brightness in case we had to change it // for things like auto-brightness or high-brightness-mode. Note that we do this @@ -1395,13 +1414,36 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call msg.sendToTarget(); } + public BrightnessInfo getBrightnessInfo() { + synchronized (mCachedBrightnessInfo) { + return new BrightnessInfo( + mCachedBrightnessInfo.brightness, + mCachedBrightnessInfo.brightnessMin, + mCachedBrightnessInfo.brightnessMax, + mCachedBrightnessInfo.hbmMode); + } + } + + private void saveBrightnessInfo(float brightness) { + synchronized (mCachedBrightnessInfo) { + mCachedBrightnessInfo.brightness = brightness; + mCachedBrightnessInfo.brightnessMin = mHbmController.getCurrentBrightnessMin(); + mCachedBrightnessInfo.brightnessMax = mHbmController.getCurrentBrightnessMax(); + mCachedBrightnessInfo.hbmMode = mHbmController.getHighBrightnessMode(); + } + } + private HighBrightnessModeController createHbmController() { final DisplayDeviceConfig ddConfig = mLogicalDisplay.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig(); final DisplayDeviceConfig.HighBrightnessModeData hbmData = ddConfig != null ? ddConfig.getHighBrightnessModeData() : null; return new HighBrightnessModeController(mHandler, PowerManager.BRIGHTNESS_MIN, - PowerManager.BRIGHTNESS_MAX, hbmData, () -> sendUpdatePowerStateLocked()); + PowerManager.BRIGHTNESS_MAX, hbmData, + () -> { + sendUpdatePowerStateLocked(); + mHandler.post(mOnBrightnessChangeRunnable); + }); } private void blockScreenOn() { @@ -1832,7 +1874,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mPendingScreenBrightnessSetting = getScreenBrightnessSetting(); if (userSwitch) { // Don't treat user switches as user initiated change. - mCurrentScreenBrightnessSetting = mPendingScreenBrightnessSetting; + setCurrentScreenBrightness(mPendingScreenBrightnessSetting); if (mAutomaticBrightnessController != null) { mAutomaticBrightnessController.resetShortTermModel(); } @@ -1871,11 +1913,18 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private void putScreenBrightnessSetting(float brightnessValue, boolean updateCurrent) { if (updateCurrent) { - mCurrentScreenBrightnessSetting = brightnessValue; + setCurrentScreenBrightness(brightnessValue); } mBrightnessSetting.setBrightness(brightnessValue); } + private void setCurrentScreenBrightness(float brightnessValue) { + if (brightnessValue != mCurrentScreenBrightnessSetting) { + mCurrentScreenBrightnessSetting = brightnessValue; + mHandler.post(mOnBrightnessChangeRunnable); + } + } + private void putAutoBrightnessAdjustmentSetting(float adjustment) { if (mDisplayId == Display.DEFAULT_DISPLAY) { mAutoBrightnessAdjustment = adjustment; @@ -1908,7 +1957,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; return false; } - mCurrentScreenBrightnessSetting = mPendingScreenBrightnessSetting; + setCurrentScreenBrightness(mPendingScreenBrightnessSetting); mLastUserSetScreenBrightness = mPendingScreenBrightnessSetting; mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT; mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; @@ -2051,10 +2100,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call pw.println(" mPendingProximityDebounceTime=" + TimeUtils.formatUptime(mPendingProximityDebounceTime)); pw.println(" mScreenOffBecauseOfProximity=" + mScreenOffBecauseOfProximity); - pw.println(" mLastUserSetScreenBrightnessFloat=" + mLastUserSetScreenBrightness); - pw.println(" mPendingScreenBrightnessSettingFloat=" + pw.println(" mLastUserSetScreenBrightness=" + mLastUserSetScreenBrightness); + pw.println(" mPendingScreenBrightnessSetting=" + mPendingScreenBrightnessSetting); - pw.println(" mTemporaryScreenBrightnessFloat=" + mTemporaryScreenBrightness); + pw.println(" mTemporaryScreenBrightness=" + mTemporaryScreenBrightness); pw.println(" mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment); pw.println(" mBrightnessReason=" + mBrightnessReason); pw.println(" mTemporaryAutoBrightnessAdjustment=" + mTemporaryAutoBrightnessAdjustment); @@ -2097,6 +2146,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mAutomaticBrightnessController.dump(pw); } + if (mHbmController != null) { + mHbmController.dump(pw); + } + pw.println(); if (mDisplayWhiteBalanceController != null) { mDisplayWhiteBalanceController.dump(pw); @@ -2425,4 +2478,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } } } + + static class CachedBrightnessInfo { + public float brightness; + public float brightnessMin; + public float brightnessMax; + public int hbmMode; + } } diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java index 2e5561dc0aea..e6486bd2a79a 100644 --- a/services/core/java/com/android/server/display/HighBrightnessModeController.java +++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java @@ -16,13 +16,17 @@ package com.android.server.display; +import android.hardware.display.BrightnessInfo; import android.os.Handler; import android.os.PowerManager; import android.os.SystemClock; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData; +import com.android.server.display.DisplayManagerService.Clock; +import java.io.PrintWriter; import java.util.Iterator; import java.util.LinkedList; @@ -45,11 +49,13 @@ class HighBrightnessModeController { private final Handler mHandler; private final Runnable mHbmChangeCallback; private final Runnable mRecalcRunnable; + private final Clock mClock; private boolean mIsInAllowedAmbientRange = false; private boolean mIsTimeAvailable = false; private boolean mIsAutoBrightnessEnabled = false; private float mAutoBrightness; + private int mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF; /** * If HBM is currently running, this is the start time for the current HBM session. @@ -65,28 +71,25 @@ class HighBrightnessModeController { HighBrightnessModeController(Handler handler, float brightnessMin, float brightnessMax, HighBrightnessModeData hbmData, Runnable hbmChangeCallback) { + this(SystemClock::uptimeMillis, handler, brightnessMin, brightnessMax, hbmData, + hbmChangeCallback); + } + + @VisibleForTesting + HighBrightnessModeController(Clock clock, Handler handler, float brightnessMin, + float brightnessMax, HighBrightnessModeData hbmData, Runnable hbmChangeCallback) { + mClock = clock; mHandler = handler; mBrightnessMin = brightnessMin; mBrightnessMax = brightnessMax; mHbmData = hbmData; mHbmChangeCallback = hbmChangeCallback; mAutoBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; - - mRecalcRunnable = () -> { - boolean oldIsAllowed = isCurrentlyAllowed(); - recalculateTimeAllowance(); - if (oldIsAllowed != isCurrentlyAllowed()) { - // Our allowed state has changed; tell AutomaticBrightnessController - // to update the brightness. - if (mHbmChangeCallback != null) { - mHbmChangeCallback.run(); - } - } - }; + mRecalcRunnable = this::recalculateTimeAllowance; } void setAutoBrightnessEnabled(boolean isEnabled) { - if (isEnabled == mIsAutoBrightnessEnabled) { + if (!deviceSupportsHbm() || isEnabled == mIsAutoBrightnessEnabled) { return; } if (DEBUG) { @@ -94,6 +97,7 @@ class HighBrightnessModeController { } mIsAutoBrightnessEnabled = isEnabled; mIsInAllowedAmbientRange = false; // reset when auto-brightness switches + recalculateTimeAllowance(); } float getCurrentBrightnessMin() { @@ -137,7 +141,7 @@ class HighBrightnessModeController { final boolean wasOldBrightnessHigh = oldAutoBrightness > mHbmData.transitionPoint; final boolean isNewBrightnessHigh = mAutoBrightness > mHbmData.transitionPoint; if (wasOldBrightnessHigh != isNewBrightnessHigh) { - final long currentTime = SystemClock.uptimeMillis(); + final long currentTime = mClock.uptimeMillis(); if (isNewBrightnessHigh) { mRunningStartTimeMillis = currentTime; } else { @@ -153,6 +157,21 @@ class HighBrightnessModeController { recalculateTimeAllowance(); } + int getHighBrightnessMode() { + return mHbmMode; + } + + void dump(PrintWriter pw) { + pw.println("HighBrightnessModeController:"); + pw.println(" mBrightnessMin=" + mBrightnessMin); + pw.println(" mBrightnessMax=" + mBrightnessMax); + pw.println(" mHbmData=" + mHbmData); + pw.println(" mIsInAllowedAmbientRange=" + mIsInAllowedAmbientRange); + pw.println(" mIsTimeAvailable= " + mIsTimeAvailable); + pw.println(" mIsAutoBrightnessEnabled=" + mIsAutoBrightnessEnabled); + pw.println(" mAutoBrightness=" + mAutoBrightness); + } + private boolean isCurrentlyAllowed() { return mIsAutoBrightnessEnabled && mIsTimeAvailable && mIsInAllowedAmbientRange; } @@ -165,7 +184,7 @@ class HighBrightnessModeController { * Recalculates the allowable HBM time. */ private void recalculateTimeAllowance() { - final long currentTime = SystemClock.uptimeMillis(); + final long currentTime = mClock.uptimeMillis(); long timeAlreadyUsed = 0; // First, lets see how much time we've taken for any currently running @@ -247,8 +266,22 @@ class HighBrightnessModeController { if (nextTimeout != -1) { mHandler.removeCallbacks(mRecalcRunnable); - mHandler.postAtTime(mRecalcRunnable, nextTimeout); + mHandler.postAtTime(mRecalcRunnable, nextTimeout + 1); + } + + // Update the state of the world + int newHbmMode = calculateHighBrightnessMode(); + if (mHbmMode != newHbmMode) { + mHbmMode = newHbmMode; + mHbmChangeCallback.run(); + } + } + + private int calculateHighBrightnessMode() { + if (deviceSupportsHbm() && isCurrentlyAllowed()) { + return BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT; } + return BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF; } /** diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java index 7cd60284627f..f156779034ab 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -93,9 +93,10 @@ public class DisplayManagerServiceTest { private static final long SHORT_DEFAULT_DISPLAY_TIMEOUT_MILLIS = 10; private static final String VIRTUAL_DISPLAY_NAME = "Test Virtual Display"; private static final String PACKAGE_NAME = "com.android.frameworks.servicestests"; - private static final long ALL_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED + private static final long STANDARD_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED; + @Rule public TestRule compatChangeRule = new PlatformCompatChangeRule(); @@ -764,7 +765,8 @@ public class DisplayManagerServiceTest { // register display listener callback FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback(); - displayManagerBinderService.registerCallbackWithEventMask(callback, ALL_DISPLAY_EVENTS); + displayManagerBinderService.registerCallbackWithEventMask( + callback, STANDARD_DISPLAY_EVENTS); waitForIdleHandler(handler); @@ -793,7 +795,7 @@ public class DisplayManagerServiceTest { // register display listener callback FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback(); - long allEventsExceptDisplayAdded = ALL_DISPLAY_EVENTS + long allEventsExceptDisplayAdded = STANDARD_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED; displayManagerBinderService.registerCallbackWithEventMask(callback, allEventsExceptDisplayAdded); @@ -862,7 +864,7 @@ public class DisplayManagerServiceTest { waitForIdleHandler(handler); FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback(); - long allEventsExceptDisplayRemoved = ALL_DISPLAY_EVENTS + long allEventsExceptDisplayRemoved = STANDARD_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED; displayManagerBinderService.registerCallbackWithEventMask(callback, allEventsExceptDisplayRemoved); @@ -1032,7 +1034,8 @@ public class DisplayManagerServiceTest { // register display listener callback FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback(displayId); - displayManagerBinderService.registerCallbackWithEventMask(callback, ALL_DISPLAY_EVENTS); + displayManagerBinderService.registerCallbackWithEventMask( + callback, STANDARD_DISPLAY_EVENTS); return callback; } diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java new file mode 100644 index 000000000000..88a21b4a8fa8 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2021 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 android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF; +import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT; + +import static org.junit.Assert.assertEquals; + +import android.os.Handler; +import android.os.Message; +import android.os.test.TestLooper; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData; +import com.android.server.testutils.OffsettableClock; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class HighBrightnessModeControllerTest { + + private static final int MINIMUM_LUX = 100; + private static final float TRANSITION_POINT = 0.763f; + private static final long TIME_WINDOW_MILLIS = 55 * 1000; + private static final long TIME_ALLOWED_IN_WINDOW_MILLIS = 12 * 1000; + private static final long TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS = 5 * 1000; + + private static final float DEFAULT_MIN = 0.01f; + private static final float DEFAULT_MAX = 0.80f; + + private static final float EPSILON = 0.000001f; + + private OffsettableClock mClock; + private TestLooper mTestLooper; + private Handler mHandler; + + private static final HighBrightnessModeData DEFAULT_HBM_DATA = + new HighBrightnessModeData(MINIMUM_LUX, TRANSITION_POINT, TIME_WINDOW_MILLIS, + TIME_ALLOWED_IN_WINDOW_MILLIS, TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS); + + @Before + public void setUp() { + mClock = new OffsettableClock.Stopped(); + mTestLooper = new TestLooper(mClock::now); + mHandler = new Handler(mTestLooper.getLooper(), new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + return true; + } + }); + } + + ///////////////// + // Test Methods + ///////////////// + + @Test + public void testNoHbmData() { + final HighBrightnessModeController hbmc = new HighBrightnessModeController( + mClock::now, mHandler, DEFAULT_MIN, DEFAULT_MAX, null, () -> {}); + assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF); + } + + @Test + public void testNoHbmData_Enabled() { + final HighBrightnessModeController hbmc = new HighBrightnessModeController( + mClock::now, mHandler, DEFAULT_MIN, DEFAULT_MAX, null, () -> {}); + hbmc.setAutoBrightnessEnabled(true); + hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range + assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF); + } + + @Test + public void testOffByDefault() { + final HighBrightnessModeController hbmc = createDefaultHbm(); + + assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF); + } + + @Test + public void testAutoBrightnessEnabled_NoLux() { + final HighBrightnessModeController hbmc = createDefaultHbm(); + + hbmc.setAutoBrightnessEnabled(true); + assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF); + } + + @Test + public void testAutoBrightnessEnabled_LowLux() { + final HighBrightnessModeController hbmc = createDefaultHbm(); + + hbmc.setAutoBrightnessEnabled(true); + hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range + assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF); + } + + @Test + public void testAutoBrightnessEnabled_HighLux() { + final HighBrightnessModeController hbmc = createDefaultHbm(); + + hbmc.setAutoBrightnessEnabled(true); + hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); + assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT); + } + + @Test + public void testAutoBrightnessEnabled_HighLux_ThenDisable() { + final HighBrightnessModeController hbmc = createDefaultHbm(); + + hbmc.setAutoBrightnessEnabled(true); + hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); + hbmc.setAutoBrightnessEnabled(false); + + assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF); + } + + @Test + public void testWithinHighRange_thenOverTime_thenEarnBackTime() { + final HighBrightnessModeController hbmc = createDefaultHbm(); + + hbmc.setAutoBrightnessEnabled(true); + hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); + hbmc.onAutoBrightnessChanged(TRANSITION_POINT + 0.01f); + + // Verify we are in HBM + assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT); + + // Use up all the time in the window. + advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS); + + // Verify we are not out of HBM + assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT); + + // Shift time so that the HBM event is at the beginning of the current window + advanceTime(TIME_WINDOW_MILLIS - TIME_ALLOWED_IN_WINDOW_MILLIS); + // Shift time again so that we are just below the minimum allowable + advanceTime(TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS - 1); + + // Verify we are not out of HBM + assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT); + + // Advance the necessary millisecond + advanceTime(1); + + // Verify we are allowed HBM again. + assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT); + } + + @Test + public void testInHBM_ThenLowLux() { + final HighBrightnessModeController hbmc = createDefaultHbm(); + + hbmc.setAutoBrightnessEnabled(true); + hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); + hbmc.onAutoBrightnessChanged(TRANSITION_POINT + 0.01f); + + // Verify we are in HBM + assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT); + + advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2); + + // Verify we are in HBM + assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT); + + hbmc.onAmbientLuxChange(1); + advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2 + 1); + + // Verify we are out of HBM + assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF); + + } + + @Test + public void testInHBM_TestMultipleEvents_DueToAutoBrightness() { + final HighBrightnessModeController hbmc = createDefaultHbm(); + + hbmc.setAutoBrightnessEnabled(true); + hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); + + hbmc.onAutoBrightnessChanged(TRANSITION_POINT + 0.01f); + advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2); + + // Verify we are in HBM + assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT); + + hbmc.onAutoBrightnessChanged(TRANSITION_POINT - 0.01f); + advanceTime(1); + + assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT); + + hbmc.onAutoBrightnessChanged(TRANSITION_POINT + 0.01f); + advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2); + + assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT); + + advanceTime(2); + + // Now we should be out again. + assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF); + } + + @Test + public void testInHBM_TestMultipleEvents_DueToLux() { + final HighBrightnessModeController hbmc = createDefaultHbm(); + + hbmc.setAutoBrightnessEnabled(true); + hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); + + // Go into HBM for half the allowed window + hbmc.onAutoBrightnessChanged(TRANSITION_POINT + 0.01f); + advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2); + assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT); + + // Move lux below threshold (ending first event); + hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); + hbmc.onAutoBrightnessChanged(TRANSITION_POINT); + assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF); + + // Move up some amount of time so that there's still time in the window even after a + // second event. + advanceTime((TIME_WINDOW_MILLIS - TIME_ALLOWED_IN_WINDOW_MILLIS) / 2); + assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF); + + // Go into HBM for just under the second half of allowed window + hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); + hbmc.onAutoBrightnessChanged(TRANSITION_POINT + 1); + advanceTime((TIME_ALLOWED_IN_WINDOW_MILLIS / 2) - 1); + + assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT); + + // Now exhaust the time + advanceTime(2); + assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF); + } + + private void assertState(HighBrightnessModeController hbmc, + float brightnessMin, float brightnessMax, int hbmMode) { + assertEquals(brightnessMin, hbmc.getCurrentBrightnessMin(), EPSILON); + assertEquals(brightnessMax, hbmc.getCurrentBrightnessMax(), EPSILON); + assertEquals(hbmMode, hbmc.getHighBrightnessMode()); + } + + // Creates instance with standard initialization values. + private HighBrightnessModeController createDefaultHbm() { + return new HighBrightnessModeController(mClock::now, mHandler, DEFAULT_MIN, DEFAULT_MAX, + DEFAULT_HBM_DATA, () -> {}); + } + + private void advanceTime(long timeMs) { + mClock.fastForward(timeMs); + mTestLooper.dispatchAll(); + } +} |