Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0bec56ca authored by Vaibhav Devmurari's avatar Vaibhav Devmurari Committed by Android (Google) Code Review
Browse files

Merge "Add ALS controlled keyboard backlight support" into udc-qpr-dev

parents be70a74c e048d65f
Loading
Loading
Loading
Loading
+28 −0
Original line number Diff line number Diff line
@@ -412,6 +412,15 @@ public abstract class DisplayManagerInternal {
    @Nullable
    public abstract HostUsiVersion getHostUsiVersion(int displayId);

    /**
     * Get the ALS data for a particular display.
     *
     * @param displayId The id of the display.
     * @return {@link AmbientLightSensorData}
     */
    @Nullable
    public abstract AmbientLightSensorData getAmbientLightSensorData(int displayId);

    /**
     * Get all available DisplayGroupIds.
     */
@@ -669,4 +678,23 @@ public abstract class DisplayManagerInternal {
            return "RefreshRateLimitation(" + type + ": " + range + ")";
        }
    }

    /**
     * Class to provide Ambient sensor data using the API
     * {@link DisplayManagerInternal#getAmbientLightSensorData(int)}
     */
    public static final class AmbientLightSensorData {
        public String sensorName;
        public String sensorType;

        public AmbientLightSensorData(String name, String type) {
            sensorName = name;
            sensorType = type;
        }

        @Override
        public String toString() {
            return "AmbientLightSensorData(" + sensorName + ", " + sensorType + ")";
        }
    }
}
+44 −4
Original line number Diff line number Diff line
@@ -1675,16 +1675,56 @@
         in the config_autoBrightnessLevels array.  This array should have size one greater
         than the size of the config_autoBrightnessLevels array.
         The brightness values must be between 0 and 255 and be non-decreasing.

         This must be overridden in platform specific overlays -->
    <integer-array name="config_autoBrightnessButtonBacklightValues">
    </integer-array>

    <!-- Smoothing constant for Ambient keyboard backlight change. It should contain value
         in the range (0.0, 1.0] that will be used to calculate smoothed lux values using
         simple exponential smoothing. This value indicated how quickly we transition to
         the lux values provided by the Ambient light sensor.
         Simple formula for newLuxValue = (1-constant)*currLuxValue + constant*rawLuxValue, where
         rawLuxValue is the value provided by the Ambient light sensor. (e.g. value of 1.0 means we
         immediately start using the value provided by the Ambient light sensor)
         This must be overridden in platform specific overlays -->
    <item name="config_autoKeyboardBrightnessSmoothingConstant" format="float" type="dimen">
        1.0
    </item>

    <!-- Array of output values for keyboard backlight corresponding to the lux values
         in the config_autoBrightnessLevels array.  This array should have size one greater
         than the size of the config_autoBrightnessLevels array.
         in the config_autoKeyboardBacklight(Increase/Decrease)LuxThreshold arrays.
         The brightness values must be between 0 and 255 and be non-decreasing.
         This must be overridden in platform specific overlays -->
    <integer-array name="config_autoBrightnessKeyboardBacklightValues">

         This can be overridden in platform specific overlays -->
    <integer-array name="config_autoKeyboardBacklightBrightnessValues">
        <item>102</item>
        <item>153</item>
        <item>0</item>
    </integer-array>

    <!-- Array of threshold values for keyboard backlight corresponding to the values
         in the config_autoKeyboardBacklightBrightnessValues array.
         These lux values indicate when to move to a lower keyboard backlight value,
         as defined in config_autoKeyboardBacklightBrightnessValues array.

         This can be overridden in platform specific overlays -->
    <integer-array name="config_autoKeyboardBacklightDecreaseLuxThreshold">
        <item>-1</item>
        <item>40</item>
        <item>150</item>
    </integer-array>

    <!-- Array of threshold values for keyboard backlight corresponding to the values
         in the config_autoKeyboardBacklightBrightnessValues array.
         These lux values indicate when to move to a higher keyboard backlight value,
         as defined in config_autoKeyboardBacklightBrightnessValues array.

         This can be overridden in platform specific overlays -->
    <integer-array name="config_autoKeyboardBacklightIncreaseLuxThreshold">
        <item>55</item>
        <item>200</item>
        <item>-1</item>
    </integer-array>

    <!-- An array describing the screen's backlight values corresponding to the brightness
+4 −1
Original line number Diff line number Diff line
@@ -520,6 +520,7 @@
  <java-symbol type="dimen" name="config_viewConfigurationHandwritingSlop" />
  <java-symbol type="dimen" name="config_viewConfigurationHoverSlop" />
  <java-symbol type="dimen" name="config_ambiguousGestureMultiplier" />
  <java-symbol type="dimen" name="config_autoKeyboardBrightnessSmoothingConstant" />
  <java-symbol type="dimen" name="config_viewMinFlingVelocity" />
  <java-symbol type="dimen" name="config_viewMaxFlingVelocity" />
  <java-symbol type="dimen" name="config_viewMinRotaryEncoderFlingVelocity" />
@@ -1888,11 +1889,13 @@
  <java-symbol type="anim" name="dream_activity_open_enter" />
  <java-symbol type="anim" name="dream_activity_close_exit" />
  <java-symbol type="array" name="config_autoBrightnessButtonBacklightValues" />
  <java-symbol type="array" name="config_autoBrightnessKeyboardBacklightValues" />
  <java-symbol type="array" name="config_autoBrightnessLcdBacklightValues" />
  <java-symbol type="array" name="config_autoBrightnessLcdBacklightValues_doze" />
  <java-symbol type="array" name="config_autoBrightnessLevels" />
  <java-symbol type="array" name="config_autoBrightnessLevelsIdle" />
  <java-symbol type="array" name="config_autoKeyboardBacklightBrightnessValues" />
  <java-symbol type="array" name="config_autoKeyboardBacklightDecreaseLuxThreshold" />
  <java-symbol type="array" name="config_autoKeyboardBacklightIncreaseLuxThreshold" />
  <java-symbol type="array" name="config_ambientThresholdLevels" />
  <java-symbol type="array" name="config_ambientBrighteningThresholds" />
  <java-symbol type="array" name="config_ambientDarkeningThresholds" />
+16 −0
Original line number Diff line number Diff line
@@ -4627,6 +4627,22 @@ public final class DisplayManagerService extends SystemService {
            }
        }

        @Override
        public AmbientLightSensorData getAmbientLightSensorData(int displayId) {
            synchronized (mSyncRoot) {
                final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
                if (display == null) {
                    return null;
                }
                final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
                if (device == null) {
                    return null;
                }
                SensorData data = device.getDisplayDeviceConfig().getAmbientLightSensor();
                return new AmbientLightSensorData(data.name, data.type);
            }
        }

        @Override
        public IntArray getDisplayGroupIds() {
            Set<Integer> visitedIds = new ArraySet<>();
+431 −0
Original line number Diff line number Diff line
/*
 * Copyright 2023 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.input;

import android.annotation.MainThread;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.util.Slog;
import android.util.TypedValue;
import android.view.Display;
import android.view.DisplayInfo;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.display.utils.SensorUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * A thread-safe component of {@link InputManagerService} responsible for managing the keyboard
 * backlight based on ambient light sensor.
 */
final class AmbientKeyboardBacklightController implements DisplayManager.DisplayListener,
        SensorEventListener {

    private static final String TAG = "KbdBacklightController";

    // To enable these logs, run:
    // 'adb shell setprop log.tag.KbdBacklightController DEBUG' (requires restart)
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

    // Number of light sensor responses required to overcome temporal hysteresis.
    @VisibleForTesting
    public static final int HYSTERESIS_THRESHOLD = 2;

    private static final int MSG_BRIGHTNESS_CALLBACK = 0;
    private static final int MSG_SETUP_DISPLAY_AND_SENSOR = 1;

    private static final Object sAmbientControllerLock = new Object();

    private final Context mContext;
    private final Handler mHandler;

    @Nullable
    @GuardedBy("sAmbientControllerLock")
    private Sensor mLightSensor;
    @GuardedBy("sAmbientControllerLock")
    private String mCurrentDefaultDisplayUniqueId;

    // List of currently registered ambient backlight listeners
    @GuardedBy("sAmbientControllerLock")
    private final List<AmbientKeyboardBacklightListener> mAmbientKeyboardBacklightListeners =
            new ArrayList<>();

    private BrightnessStep[] mBrightnessSteps;
    private int mCurrentBrightnessStepIndex;
    private HysteresisState mHysteresisState;
    private int mHysteresisCount = 0;
    private float mSmoothingConstant;
    private int mSmoothedLux;
    private int mSmoothedLuxAtLastAdjustment;

    private enum HysteresisState {
        // The most-recent mSmoothedLux matched mSmoothedLuxAtLastAdjustment.
        STABLE,
        // The most-recent mSmoothedLux was less than mSmoothedLuxAtLastAdjustment.
        DECREASING,
        // The most-recent mSmoothedLux was greater than mSmoothedLuxAtLastAdjustment.
        INCREASING,
        // The brightness should be adjusted immediately after the next sensor reading.
        IMMEDIATE,
    }

    AmbientKeyboardBacklightController(Context context, Looper looper) {
        mContext = context;
        mHandler = new Handler(looper, this::handleMessage);
        initConfiguration();
    }

    public void systemRunning() {
        mHandler.sendEmptyMessage(MSG_SETUP_DISPLAY_AND_SENSOR);
        DisplayManager displayManager = Objects.requireNonNull(
                mContext.getSystemService(DisplayManager.class));
        displayManager.registerDisplayListener(this, mHandler);
    }

    public void registerAmbientBacklightListener(AmbientKeyboardBacklightListener listener) {
        synchronized (sAmbientControllerLock) {
            if (mAmbientKeyboardBacklightListeners.contains(listener)) {
                throw new IllegalStateException(
                        "AmbientKeyboardBacklightListener was already registered, listener = "
                                + listener);
            }
            if (mAmbientKeyboardBacklightListeners.isEmpty()) {
                // Add sensor listener when we add the first ambient backlight listener.
                addSensorListener(mLightSensor);
            }
            mAmbientKeyboardBacklightListeners.add(listener);
        }
    }

    public void unregisterAmbientBacklightListener(AmbientKeyboardBacklightListener listener) {
        synchronized (sAmbientControllerLock) {
            if (!mAmbientKeyboardBacklightListeners.contains(listener)) {
                throw new IllegalStateException(
                        "AmbientKeyboardBacklightListener was never registered, listener = "
                                + listener);
            }
            mAmbientKeyboardBacklightListeners.remove(listener);
            if (mAmbientKeyboardBacklightListeners.isEmpty()) {
                removeSensorListener(mLightSensor);
            }
        }
    }

    private void sendBrightnessAdjustment(int brightnessValue) {
        Message msg = Message.obtain(mHandler, MSG_BRIGHTNESS_CALLBACK, brightnessValue);
        mHandler.sendMessage(msg);
    }

    @MainThread
    private void handleBrightnessCallback(int brightnessValue) {
        synchronized (sAmbientControllerLock) {
            for (AmbientKeyboardBacklightListener listener : mAmbientKeyboardBacklightListeners) {
                listener.onKeyboardBacklightValueChanged(brightnessValue);
            }
        }
    }

    @MainThread
    private void handleAmbientLuxChange(float rawLux) {
        if (rawLux < 0) {
            Slog.w(TAG, "Light sensor doesn't have valid value");
            return;
        }
        updateSmoothedLux(rawLux);

        if (mHysteresisState != HysteresisState.IMMEDIATE
                && mSmoothedLux == mSmoothedLuxAtLastAdjustment) {
            mHysteresisState = HysteresisState.STABLE;
            return;
        }

        int newStepIndex = Math.max(0, mCurrentBrightnessStepIndex);
        int numSteps = mBrightnessSteps.length;

        if (mSmoothedLux > mSmoothedLuxAtLastAdjustment) {
            if (mHysteresisState != HysteresisState.IMMEDIATE
                    && mHysteresisState != HysteresisState.INCREASING) {
                if (DEBUG) {
                    Slog.d(TAG, "ALS transitioned to brightness increasing state");
                }
                mHysteresisState = HysteresisState.INCREASING;
                mHysteresisCount = 0;
            }
            for (; newStepIndex < numSteps; newStepIndex++) {
                if (mSmoothedLux < mBrightnessSteps[newStepIndex].mIncreaseLuxThreshold) {
                    break;
                }
            }
        } else if (mSmoothedLux < mSmoothedLuxAtLastAdjustment) {
            if (mHysteresisState != HysteresisState.IMMEDIATE
                    && mHysteresisState != HysteresisState.DECREASING) {
                if (DEBUG) {
                    Slog.d(TAG, "ALS transitioned to brightness decreasing state");
                }
                mHysteresisState = HysteresisState.DECREASING;
                mHysteresisCount = 0;
            }
            for (; newStepIndex >= 0; newStepIndex--) {
                if (mSmoothedLux > mBrightnessSteps[newStepIndex].mDecreaseLuxThreshold) {
                    break;
                }
            }
        }

        if (mHysteresisState == HysteresisState.IMMEDIATE) {
            mCurrentBrightnessStepIndex = newStepIndex;
            mSmoothedLuxAtLastAdjustment = mSmoothedLux;
            mHysteresisState = HysteresisState.STABLE;
            mHysteresisCount = 0;
            sendBrightnessAdjustment(mBrightnessSteps[newStepIndex].mBrightnessValue);
            return;
        }

        if (newStepIndex == mCurrentBrightnessStepIndex) {
            return;
        }

        mHysteresisCount++;
        if (DEBUG) {
            Slog.d(TAG, "Incremented hysteresis count to " + mHysteresisCount + " (lux went from "
                    + mSmoothedLuxAtLastAdjustment + " to " + mSmoothedLux + ")");
        }
        if (mHysteresisCount >= HYSTERESIS_THRESHOLD) {
            mCurrentBrightnessStepIndex = newStepIndex;
            mSmoothedLuxAtLastAdjustment = mSmoothedLux;
            mHysteresisCount = 1;
            sendBrightnessAdjustment(mBrightnessSteps[newStepIndex].mBrightnessValue);
        }
    }

    @MainThread
    private void handleDisplayChange() {
        DisplayManagerInternal displayManagerInternal = LocalServices.getService(
                DisplayManagerInternal.class);
        DisplayInfo displayInfo = displayManagerInternal.getDisplayInfo(Display.DEFAULT_DISPLAY);
        synchronized (sAmbientControllerLock) {
            if (Objects.equals(mCurrentDefaultDisplayUniqueId, displayInfo.uniqueId)) {
                return;
            }
            if (DEBUG) {
                Slog.d(TAG, "Default display changed: resetting the light sensor");
            }
            // Keep track of current default display
            mCurrentDefaultDisplayUniqueId = displayInfo.uniqueId;
            // Clear all existing sensor listeners
            if (!mAmbientKeyboardBacklightListeners.isEmpty()) {
                removeSensorListener(mLightSensor);
            }
            mLightSensor = getAmbientLightSensor(
                    displayManagerInternal.getAmbientLightSensorData(Display.DEFAULT_DISPLAY));
            // Re-add sensor listeners if required;
            if (!mAmbientKeyboardBacklightListeners.isEmpty()) {
                addSensorListener(mLightSensor);
            }
        }
    }

    private Sensor getAmbientLightSensor(
            DisplayManagerInternal.AmbientLightSensorData ambientSensor) {
        SensorManager sensorManager = Objects.requireNonNull(
                mContext.getSystemService(SensorManager.class));
        if (DEBUG) {
            Slog.d(TAG, "Ambient Light sensor data: " + ambientSensor);
        }
        return SensorUtils.findSensor(sensorManager, ambientSensor.sensorType,
                ambientSensor.sensorName, Sensor.TYPE_LIGHT);
    }

    private void updateSmoothedLux(float rawLux) {
        // For the first sensor reading, use raw lux value directly without smoothing.
        if (mHysteresisState == HysteresisState.IMMEDIATE) {
            mSmoothedLux = (int) rawLux;
        } else {
            mSmoothedLux =
                    (int) (mSmoothingConstant * rawLux + (1 - mSmoothingConstant) * mSmoothedLux);
        }
        if (DEBUG) {
            Slog.d(TAG, "Current smoothed lux from ALS = " + mSmoothedLux);
        }
    }

    @VisibleForTesting
    public void addSensorListener(@Nullable Sensor sensor) {
        SensorManager sensorManager = mContext.getSystemService(SensorManager.class);
        if (sensorManager == null || sensor == null) {
            return;
        }
        // Reset values before registering listener
        reset();
        sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL, mHandler);
        if (DEBUG) {
            Slog.d(TAG, "Registering ALS listener");
        }
    }

    private void removeSensorListener(@Nullable Sensor sensor) {
        SensorManager sensorManager = mContext.getSystemService(SensorManager.class);
        if (sensorManager == null || sensor == null) {
            return;
        }
        sensorManager.unregisterListener(this, sensor);
        if (DEBUG) {
            Slog.d(TAG, "Unregistering ALS listener");
        }
    }

    private void initConfiguration() {
        Resources res = mContext.getResources();
        int[] brightnessValueArray = res.getIntArray(
                com.android.internal.R.array.config_autoKeyboardBacklightBrightnessValues);
        int[] decreaseThresholdArray = res.getIntArray(
                com.android.internal.R.array.config_autoKeyboardBacklightDecreaseLuxThreshold);
        int[] increaseThresholdArray = res.getIntArray(
                com.android.internal.R.array.config_autoKeyboardBacklightIncreaseLuxThreshold);
        if (brightnessValueArray.length != decreaseThresholdArray.length
                || decreaseThresholdArray.length != increaseThresholdArray.length) {
            throw new IllegalArgumentException(
                    "The config files for auto keyboard backlight brightness must contain arrays "
                            + "of equal lengths");
        }
        final int size = brightnessValueArray.length;
        mBrightnessSteps = new BrightnessStep[size];
        for (int i = 0; i < size; i++) {
            int increaseThreshold =
                    increaseThresholdArray[i] < 0 ? Integer.MAX_VALUE : increaseThresholdArray[i];
            int decreaseThreshold =
                    decreaseThresholdArray[i] < 0 ? Integer.MIN_VALUE : decreaseThresholdArray[i];
            mBrightnessSteps[i] = new BrightnessStep(brightnessValueArray[i], increaseThreshold,
                    decreaseThreshold);
        }

        int numSteps = mBrightnessSteps.length;
        if (numSteps == 0 || mBrightnessSteps[0].mDecreaseLuxThreshold != Integer.MIN_VALUE
                || mBrightnessSteps[numSteps - 1].mIncreaseLuxThreshold != Integer.MAX_VALUE) {
            throw new IllegalArgumentException(
                    "The config files for auto keyboard backlight brightness must contain arrays "
                            + "of length > 0 and have -1 or Integer.MIN_VALUE as lower bound for "
                            + "decrease thresholds and -1 or Integer.MAX_VALUE as upper bound for "
                            + "increase thresholds");
        }

        final TypedValue smoothingConstantValue = new TypedValue();
        res.getValue(
                com.android.internal.R.dimen.config_autoKeyboardBrightnessSmoothingConstant,
                smoothingConstantValue,
                true /*resolveRefs*/);
        mSmoothingConstant = smoothingConstantValue.getFloat();
        if (mSmoothingConstant <= 0.0 || mSmoothingConstant > 1.0) {
            throw new IllegalArgumentException(
                    "The config files for auto keyboard backlight brightness must contain "
                            + "smoothing constant in range (0.0, 1.0].");
        }

        if (DEBUG) {
            Log.d(TAG, "Brightness steps: " + Arrays.toString(mBrightnessSteps)
                    + " Smoothing constant = " + mSmoothingConstant);
        }
    }

    private void reset() {
        mHysteresisState = HysteresisState.IMMEDIATE;
        mSmoothedLux = 0;
        mSmoothedLuxAtLastAdjustment = 0;
        mCurrentBrightnessStepIndex = -1;
    }

    private boolean handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_BRIGHTNESS_CALLBACK:
                handleBrightnessCallback((int) msg.obj);
                return true;
            case MSG_SETUP_DISPLAY_AND_SENSOR:
                handleDisplayChange();
                return true;
        }
        return false;
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        handleAmbientLuxChange(event.values[0]);
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
    }

    @Override
    public void onDisplayAdded(int displayId) {
        handleDisplayChange();
    }

    @Override
    public void onDisplayRemoved(int displayId) {
        handleDisplayChange();
    }

    @Override
    public void onDisplayChanged(int displayId) {
        handleDisplayChange();
    }

    public interface AmbientKeyboardBacklightListener {
        /**
         * @param value between [0, 255] to which keyboard backlight needs to be set according
         *              to Ambient light sensor.
         */
        void onKeyboardBacklightValueChanged(int value);
    }

    private static class BrightnessStep {
        private final int mBrightnessValue;
        private final int mIncreaseLuxThreshold;
        private final int mDecreaseLuxThreshold;

        private BrightnessStep(int brightnessValue, int increaseLuxThreshold,
                int decreaseLuxThreshold) {
            mBrightnessValue = brightnessValue;
            mIncreaseLuxThreshold = increaseLuxThreshold;
            mDecreaseLuxThreshold = decreaseLuxThreshold;
        }

        @Override
        public String toString() {
            return "BrightnessStep{" + "mBrightnessValue=" + mBrightnessValue
                    + ", mIncreaseThreshold=" + mIncreaseLuxThreshold + ", mDecreaseThreshold="
                    + mDecreaseLuxThreshold + '}';
        }
    }
}
Loading