| /* |
| * 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.server.display.whitebalance; |
| |
| import android.util.Slog; |
| |
| import java.io.PrintWriter; |
| import java.util.Arrays; |
| |
| /** |
| * The DisplayWhiteBalanceController uses the DisplayWhiteBalanceThrottler to decide whether the |
| * display color temperature should be updated, suppressing changes that are too frequent or too |
| * minor. |
| */ |
| class DisplayWhiteBalanceThrottler { |
| |
| protected static final String TAG = "DisplayWhiteBalanceThrottler"; |
| protected boolean mLoggingEnabled; |
| |
| private int mIncreaseDebounce; // Milliseconds |
| private int mDecreaseDebounce; // Milliseconds |
| private long mLastTime; // Milliseconds |
| |
| private float[] mBaseThresholds; |
| private float[] mIncreaseThresholds; |
| private float[] mDecreaseThresholds; |
| private float mIncreaseThreshold; |
| private float mDecreaseThreshold; |
| private float mLastValue; |
| |
| /** |
| * @param increaseDebounce |
| * The debounce time for increasing (throttled if {@code time < lastTime + debounce}). |
| * @param decreaseDebounce |
| * The debounce time for decreasing (throttled if {@code time < lastTime + debounce}). |
| * @param baseThresholds |
| * The ambient color temperature values used to determine the threshold as the |
| * corresponding value in increaseThresholds/decreaseThresholds. |
| * @param increaseThresholds |
| * The increase threshold values (throttled if {@code value < value * (1 + threshold)}). |
| * @param decreaseThresholds |
| * The decrease threshold values (throttled if {@code value > value * (1 - threshold)}). |
| * |
| * @throws IllegalArgumentException |
| * - increaseDebounce is negative; |
| * - decreaseDebounce is negative; |
| * - baseThresholds to increaseThresholds is not a valid mapping*; |
| * - baseThresholds to decreaseThresholds is not a valid mapping*; |
| * |
| * (*) The x to y mapping is valid if: |
| * - x and y are not null; |
| * - x and y are not empty; |
| * - x and y contain only non-negative numbers; |
| * - x is strictly increasing. |
| */ |
| DisplayWhiteBalanceThrottler(int increaseDebounce, int decreaseDebounce, |
| float[] baseThresholds, float[] increaseThresholds, float[] decreaseThresholds) { |
| validateArguments(increaseDebounce, decreaseDebounce, baseThresholds, increaseThresholds, |
| decreaseThresholds); |
| mLoggingEnabled = false; |
| mIncreaseDebounce = increaseDebounce; |
| mDecreaseDebounce = decreaseDebounce; |
| mBaseThresholds = baseThresholds; |
| mIncreaseThresholds = increaseThresholds; |
| mDecreaseThresholds = decreaseThresholds; |
| clear(); |
| } |
| |
| /** |
| * Check whether the ambient color temperature should be throttled. |
| * |
| * @param value |
| * The ambient color temperature value. |
| * |
| * @return Whether the ambient color temperature should be throttled. |
| */ |
| public boolean throttle(float value) { |
| if (mLastTime != -1 && (tooSoon(value) || tooClose(value))) { |
| return true; |
| } |
| computeThresholds(value); |
| mLastTime = System.currentTimeMillis(); |
| mLastValue = value; |
| return false; |
| } |
| |
| /** |
| * Clears the throttler state. |
| */ |
| public void clear() { |
| mLastTime = -1; |
| mIncreaseThreshold = -1.0f; |
| mDecreaseThreshold = -1.0f; |
| mLastValue = -1.0f; |
| } |
| |
| /** |
| * Enable/disable logging. |
| * |
| * @param loggingEnabled |
| * Whether logging is on/off. |
| * |
| * @return Whether the method succeeded or not. |
| */ |
| public boolean setLoggingEnabled(boolean loggingEnabled) { |
| if (mLoggingEnabled == loggingEnabled) { |
| return false; |
| } |
| mLoggingEnabled = loggingEnabled; |
| return true; |
| } |
| |
| /** |
| * Dump the state. |
| * |
| * @param writer |
| * The PrintWriter used to dump the state. |
| */ |
| public void dump(PrintWriter writer) { |
| writer.println(" DisplayWhiteBalanceThrottler"); |
| writer.println(" mLoggingEnabled=" + mLoggingEnabled); |
| writer.println(" mIncreaseDebounce=" + mIncreaseDebounce); |
| writer.println(" mDecreaseDebounce=" + mDecreaseDebounce); |
| writer.println(" mLastTime=" + mLastTime); |
| writer.println(" mBaseThresholds=" + Arrays.toString(mBaseThresholds)); |
| writer.println(" mIncreaseThresholds=" + Arrays.toString(mIncreaseThresholds)); |
| writer.println(" mDecreaseThresholds=" + Arrays.toString(mDecreaseThresholds)); |
| writer.println(" mIncreaseThreshold=" + mIncreaseThreshold); |
| writer.println(" mDecreaseThreshold=" + mDecreaseThreshold); |
| writer.println(" mLastValue=" + mLastValue); |
| } |
| |
| private void validateArguments(float increaseDebounce, float decreaseDebounce, |
| float[] baseThresholds, float[] increaseThresholds, float[] decreaseThresholds) { |
| if (Float.isNaN(increaseDebounce) || increaseDebounce < 0.0f) { |
| throw new IllegalArgumentException("increaseDebounce must be a non-negative number."); |
| } |
| if (Float.isNaN(decreaseDebounce) || decreaseDebounce < 0.0f) { |
| throw new IllegalArgumentException("decreaseDebounce must be a non-negative number."); |
| } |
| if (!isValidMapping(baseThresholds, increaseThresholds)) { |
| throw new IllegalArgumentException( |
| "baseThresholds to increaseThresholds is not a valid mapping."); |
| } |
| if (!isValidMapping(baseThresholds, decreaseThresholds)) { |
| throw new IllegalArgumentException( |
| "baseThresholds to decreaseThresholds is not a valid mapping."); |
| } |
| } |
| |
| private static boolean isValidMapping(float[] x, float[] y) { |
| if (x == null || y == null || x.length == 0 || y.length == 0 || x.length != y.length) { |
| return false; |
| } |
| float prevX = -1.0f; |
| for (int i = 0; i < x.length; i++) { |
| if (Float.isNaN(x[i]) || Float.isNaN(y[i]) || x[i] < 0 || prevX >= x[i]) { |
| return false; |
| } |
| prevX = x[i]; |
| } |
| return true; |
| } |
| |
| private boolean tooSoon(float value) { |
| final long time = System.currentTimeMillis(); |
| final long earliestTime; |
| if (value > mLastValue) { |
| earliestTime = mLastTime + mIncreaseDebounce; |
| } else { // value <= mLastValue |
| earliestTime = mLastTime + mDecreaseDebounce; |
| } |
| final boolean tooSoon = time < earliestTime; |
| if (mLoggingEnabled) { |
| Slog.d(TAG, (tooSoon ? "too soon: " : "late enough: ") + time |
| + (tooSoon ? " < " : " > ") + earliestTime); |
| } |
| return tooSoon; |
| } |
| |
| private boolean tooClose(float value) { |
| final float threshold; |
| final boolean tooClose; |
| if (value > mLastValue) { |
| threshold = mIncreaseThreshold; |
| tooClose = value < threshold; |
| } else { // value <= mLastValue |
| threshold = mDecreaseThreshold; |
| tooClose = value > threshold; |
| } |
| if (mLoggingEnabled) { |
| Slog.d(TAG, (tooClose ? "too close: " : "far enough: ") + value |
| + (value > threshold ? " > " : " < ") + threshold); |
| } |
| return tooClose; |
| } |
| |
| private void computeThresholds(float value) { |
| final int index = getHighestIndexBefore(value, mBaseThresholds); |
| mIncreaseThreshold = value * (1.0f + mIncreaseThresholds[index]); |
| mDecreaseThreshold = value * (1.0f - mDecreaseThresholds[index]); |
| } |
| |
| private int getHighestIndexBefore(float value, float[] values) { |
| for (int i = 0; i < values.length; i++) { |
| if (values[i] >= value) { |
| return i; |
| } |
| } |
| return values.length - 1; |
| } |
| |
| } |