1 /* 2 * Copyright 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.input; 18 19 import android.annotation.MainThread; 20 import android.annotation.Nullable; 21 import android.content.Context; 22 import android.content.res.Resources; 23 import android.hardware.Sensor; 24 import android.hardware.SensorEvent; 25 import android.hardware.SensorEventListener; 26 import android.hardware.SensorManager; 27 import android.hardware.display.DisplayManager; 28 import android.hardware.display.DisplayManagerInternal; 29 import android.os.Handler; 30 import android.os.Looper; 31 import android.os.Message; 32 import android.util.Log; 33 import android.util.Slog; 34 import android.util.TypedValue; 35 import android.view.Display; 36 import android.view.DisplayInfo; 37 38 import com.android.internal.annotations.GuardedBy; 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.server.LocalServices; 41 import com.android.server.display.utils.SensorUtils; 42 43 import java.util.ArrayList; 44 import java.util.Arrays; 45 import java.util.List; 46 import java.util.Objects; 47 48 /** 49 * A thread-safe component of {@link InputManagerService} responsible for managing the keyboard 50 * backlight based on ambient light sensor. 51 */ 52 final class AmbientKeyboardBacklightController implements DisplayManager.DisplayListener, 53 SensorEventListener { 54 55 private static final String TAG = "KbdBacklightController"; 56 57 // To enable these logs, run: 58 // 'adb shell setprop log.tag.KbdBacklightController DEBUG' (requires restart) 59 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 60 61 // Number of light sensor responses required to overcome temporal hysteresis. 62 @VisibleForTesting 63 public static final int HYSTERESIS_THRESHOLD = 2; 64 65 private static final int MSG_BRIGHTNESS_CALLBACK = 0; 66 private static final int MSG_SETUP_DISPLAY_AND_SENSOR = 1; 67 68 private static final Object sAmbientControllerLock = new Object(); 69 70 private final Context mContext; 71 private final Handler mHandler; 72 73 @Nullable 74 @GuardedBy("sAmbientControllerLock") 75 private Sensor mLightSensor; 76 @GuardedBy("sAmbientControllerLock") 77 private String mCurrentDefaultDisplayUniqueId; 78 79 // List of currently registered ambient backlight listeners 80 @GuardedBy("sAmbientControllerLock") 81 private final List<AmbientKeyboardBacklightListener> mAmbientKeyboardBacklightListeners = 82 new ArrayList<>(); 83 84 private BrightnessStep[] mBrightnessSteps; 85 private int mCurrentBrightnessStepIndex; 86 private HysteresisState mHysteresisState; 87 private int mHysteresisCount = 0; 88 private float mSmoothingConstant; 89 private int mSmoothedLux; 90 private int mSmoothedLuxAtLastAdjustment; 91 92 private enum HysteresisState { 93 // The most-recent mSmoothedLux matched mSmoothedLuxAtLastAdjustment. 94 STABLE, 95 // The most-recent mSmoothedLux was less than mSmoothedLuxAtLastAdjustment. 96 DECREASING, 97 // The most-recent mSmoothedLux was greater than mSmoothedLuxAtLastAdjustment. 98 INCREASING, 99 // The brightness should be adjusted immediately after the next sensor reading. 100 IMMEDIATE, 101 } 102 AmbientKeyboardBacklightController(Context context, Looper looper)103 AmbientKeyboardBacklightController(Context context, Looper looper) { 104 mContext = context; 105 mHandler = new Handler(looper, this::handleMessage); 106 initConfiguration(); 107 } 108 systemRunning()109 public void systemRunning() { 110 mHandler.sendEmptyMessage(MSG_SETUP_DISPLAY_AND_SENSOR); 111 DisplayManager displayManager = Objects.requireNonNull( 112 mContext.getSystemService(DisplayManager.class)); 113 displayManager.registerDisplayListener(this, mHandler); 114 } 115 registerAmbientBacklightListener(AmbientKeyboardBacklightListener listener)116 public void registerAmbientBacklightListener(AmbientKeyboardBacklightListener listener) { 117 synchronized (sAmbientControllerLock) { 118 if (mAmbientKeyboardBacklightListeners.contains(listener)) { 119 throw new IllegalStateException( 120 "AmbientKeyboardBacklightListener was already registered, listener = " 121 + listener); 122 } 123 if (mAmbientKeyboardBacklightListeners.isEmpty()) { 124 // Add sensor listener when we add the first ambient backlight listener. 125 addSensorListener(mLightSensor); 126 } 127 mAmbientKeyboardBacklightListeners.add(listener); 128 } 129 } 130 unregisterAmbientBacklightListener(AmbientKeyboardBacklightListener listener)131 public void unregisterAmbientBacklightListener(AmbientKeyboardBacklightListener listener) { 132 synchronized (sAmbientControllerLock) { 133 if (!mAmbientKeyboardBacklightListeners.contains(listener)) { 134 throw new IllegalStateException( 135 "AmbientKeyboardBacklightListener was never registered, listener = " 136 + listener); 137 } 138 mAmbientKeyboardBacklightListeners.remove(listener); 139 if (mAmbientKeyboardBacklightListeners.isEmpty()) { 140 removeSensorListener(mLightSensor); 141 } 142 } 143 } 144 sendBrightnessAdjustment(int brightnessValue)145 private void sendBrightnessAdjustment(int brightnessValue) { 146 Message msg = Message.obtain(mHandler, MSG_BRIGHTNESS_CALLBACK, brightnessValue); 147 mHandler.sendMessage(msg); 148 } 149 150 @MainThread handleBrightnessCallback(int brightnessValue)151 private void handleBrightnessCallback(int brightnessValue) { 152 synchronized (sAmbientControllerLock) { 153 for (AmbientKeyboardBacklightListener listener : mAmbientKeyboardBacklightListeners) { 154 listener.onKeyboardBacklightValueChanged(brightnessValue); 155 } 156 } 157 } 158 159 @MainThread handleAmbientLuxChange(float rawLux)160 private void handleAmbientLuxChange(float rawLux) { 161 if (rawLux < 0) { 162 Slog.w(TAG, "Light sensor doesn't have valid value"); 163 return; 164 } 165 updateSmoothedLux(rawLux); 166 167 if (mHysteresisState != HysteresisState.IMMEDIATE 168 && mSmoothedLux == mSmoothedLuxAtLastAdjustment) { 169 mHysteresisState = HysteresisState.STABLE; 170 return; 171 } 172 173 int newStepIndex = Math.max(0, mCurrentBrightnessStepIndex); 174 int numSteps = mBrightnessSteps.length; 175 176 if (mSmoothedLux > mSmoothedLuxAtLastAdjustment) { 177 if (mHysteresisState != HysteresisState.IMMEDIATE 178 && mHysteresisState != HysteresisState.INCREASING) { 179 if (DEBUG) { 180 Slog.d(TAG, "ALS transitioned to brightness increasing state"); 181 } 182 mHysteresisState = HysteresisState.INCREASING; 183 mHysteresisCount = 0; 184 } 185 for (; newStepIndex < numSteps; newStepIndex++) { 186 if (mSmoothedLux < mBrightnessSteps[newStepIndex].mIncreaseLuxThreshold) { 187 break; 188 } 189 } 190 } else if (mSmoothedLux < mSmoothedLuxAtLastAdjustment) { 191 if (mHysteresisState != HysteresisState.IMMEDIATE 192 && mHysteresisState != HysteresisState.DECREASING) { 193 if (DEBUG) { 194 Slog.d(TAG, "ALS transitioned to brightness decreasing state"); 195 } 196 mHysteresisState = HysteresisState.DECREASING; 197 mHysteresisCount = 0; 198 } 199 for (; newStepIndex >= 0; newStepIndex--) { 200 if (mSmoothedLux > mBrightnessSteps[newStepIndex].mDecreaseLuxThreshold) { 201 break; 202 } 203 } 204 } 205 206 if (mHysteresisState == HysteresisState.IMMEDIATE) { 207 mCurrentBrightnessStepIndex = newStepIndex; 208 mSmoothedLuxAtLastAdjustment = mSmoothedLux; 209 mHysteresisState = HysteresisState.STABLE; 210 mHysteresisCount = 0; 211 sendBrightnessAdjustment(mBrightnessSteps[newStepIndex].mBrightnessValue); 212 return; 213 } 214 215 if (newStepIndex == mCurrentBrightnessStepIndex) { 216 return; 217 } 218 219 mHysteresisCount++; 220 if (DEBUG) { 221 Slog.d(TAG, "Incremented hysteresis count to " + mHysteresisCount + " (lux went from " 222 + mSmoothedLuxAtLastAdjustment + " to " + mSmoothedLux + ")"); 223 } 224 if (mHysteresisCount >= HYSTERESIS_THRESHOLD) { 225 mCurrentBrightnessStepIndex = newStepIndex; 226 mSmoothedLuxAtLastAdjustment = mSmoothedLux; 227 mHysteresisCount = 1; 228 sendBrightnessAdjustment(mBrightnessSteps[newStepIndex].mBrightnessValue); 229 } 230 } 231 232 @MainThread handleDisplayChange()233 private void handleDisplayChange() { 234 DisplayManagerInternal displayManagerInternal = LocalServices.getService( 235 DisplayManagerInternal.class); 236 DisplayInfo displayInfo = displayManagerInternal.getDisplayInfo(Display.DEFAULT_DISPLAY); 237 synchronized (sAmbientControllerLock) { 238 if (Objects.equals(mCurrentDefaultDisplayUniqueId, displayInfo.uniqueId)) { 239 return; 240 } 241 if (DEBUG) { 242 Slog.d(TAG, "Default display changed: resetting the light sensor"); 243 } 244 // Keep track of current default display 245 mCurrentDefaultDisplayUniqueId = displayInfo.uniqueId; 246 // Clear all existing sensor listeners 247 if (!mAmbientKeyboardBacklightListeners.isEmpty()) { 248 removeSensorListener(mLightSensor); 249 } 250 mLightSensor = getAmbientLightSensor( 251 displayManagerInternal.getAmbientLightSensorData(Display.DEFAULT_DISPLAY)); 252 // Re-add sensor listeners if required; 253 if (!mAmbientKeyboardBacklightListeners.isEmpty()) { 254 addSensorListener(mLightSensor); 255 } 256 } 257 } 258 getAmbientLightSensor( DisplayManagerInternal.AmbientLightSensorData ambientSensor)259 private Sensor getAmbientLightSensor( 260 DisplayManagerInternal.AmbientLightSensorData ambientSensor) { 261 SensorManager sensorManager = Objects.requireNonNull( 262 mContext.getSystemService(SensorManager.class)); 263 if (DEBUG) { 264 Slog.d(TAG, "Ambient Light sensor data: " + ambientSensor); 265 } 266 return SensorUtils.findSensor(sensorManager, ambientSensor.sensorType, 267 ambientSensor.sensorName, Sensor.TYPE_LIGHT); 268 } 269 updateSmoothedLux(float rawLux)270 private void updateSmoothedLux(float rawLux) { 271 // For the first sensor reading, use raw lux value directly without smoothing. 272 if (mHysteresisState == HysteresisState.IMMEDIATE) { 273 mSmoothedLux = (int) rawLux; 274 } else { 275 mSmoothedLux = 276 (int) (mSmoothingConstant * rawLux + (1 - mSmoothingConstant) * mSmoothedLux); 277 } 278 if (DEBUG) { 279 Slog.d(TAG, "Current smoothed lux from ALS = " + mSmoothedLux); 280 } 281 } 282 283 @VisibleForTesting addSensorListener(@ullable Sensor sensor)284 public void addSensorListener(@Nullable Sensor sensor) { 285 SensorManager sensorManager = mContext.getSystemService(SensorManager.class); 286 if (sensorManager == null || sensor == null) { 287 return; 288 } 289 // Reset values before registering listener 290 reset(); 291 sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL, mHandler); 292 if (DEBUG) { 293 Slog.d(TAG, "Registering ALS listener"); 294 } 295 } 296 removeSensorListener(@ullable Sensor sensor)297 private void removeSensorListener(@Nullable Sensor sensor) { 298 SensorManager sensorManager = mContext.getSystemService(SensorManager.class); 299 if (sensorManager == null || sensor == null) { 300 return; 301 } 302 sensorManager.unregisterListener(this, sensor); 303 if (DEBUG) { 304 Slog.d(TAG, "Unregistering ALS listener"); 305 } 306 } 307 initConfiguration()308 private void initConfiguration() { 309 Resources res = mContext.getResources(); 310 int[] brightnessValueArray = res.getIntArray( 311 com.android.internal.R.array.config_autoKeyboardBacklightBrightnessValues); 312 int[] decreaseThresholdArray = res.getIntArray( 313 com.android.internal.R.array.config_autoKeyboardBacklightDecreaseLuxThreshold); 314 int[] increaseThresholdArray = res.getIntArray( 315 com.android.internal.R.array.config_autoKeyboardBacklightIncreaseLuxThreshold); 316 if (brightnessValueArray.length != decreaseThresholdArray.length 317 || decreaseThresholdArray.length != increaseThresholdArray.length) { 318 throw new IllegalArgumentException( 319 "The config files for auto keyboard backlight brightness must contain arrays " 320 + "of equal lengths"); 321 } 322 final int size = brightnessValueArray.length; 323 mBrightnessSteps = new BrightnessStep[size]; 324 for (int i = 0; i < size; i++) { 325 int increaseThreshold = 326 increaseThresholdArray[i] < 0 ? Integer.MAX_VALUE : increaseThresholdArray[i]; 327 int decreaseThreshold = 328 decreaseThresholdArray[i] < 0 ? Integer.MIN_VALUE : decreaseThresholdArray[i]; 329 mBrightnessSteps[i] = new BrightnessStep(brightnessValueArray[i], increaseThreshold, 330 decreaseThreshold); 331 } 332 333 int numSteps = mBrightnessSteps.length; 334 if (numSteps == 0 || mBrightnessSteps[0].mDecreaseLuxThreshold != Integer.MIN_VALUE 335 || mBrightnessSteps[numSteps - 1].mIncreaseLuxThreshold != Integer.MAX_VALUE) { 336 throw new IllegalArgumentException( 337 "The config files for auto keyboard backlight brightness must contain arrays " 338 + "of length > 0 and have -1 or Integer.MIN_VALUE as lower bound for " 339 + "decrease thresholds and -1 or Integer.MAX_VALUE as upper bound for " 340 + "increase thresholds"); 341 } 342 343 final TypedValue smoothingConstantValue = new TypedValue(); 344 res.getValue( 345 com.android.internal.R.dimen.config_autoKeyboardBrightnessSmoothingConstant, 346 smoothingConstantValue, 347 true /*resolveRefs*/); 348 mSmoothingConstant = smoothingConstantValue.getFloat(); 349 if (mSmoothingConstant <= 0.0 || mSmoothingConstant > 1.0) { 350 throw new IllegalArgumentException( 351 "The config files for auto keyboard backlight brightness must contain " 352 + "smoothing constant in range (0.0, 1.0]."); 353 } 354 355 if (DEBUG) { 356 Log.d(TAG, "Brightness steps: " + Arrays.toString(mBrightnessSteps) 357 + " Smoothing constant = " + mSmoothingConstant); 358 } 359 } 360 361 private void reset() { 362 mHysteresisState = HysteresisState.IMMEDIATE; 363 mSmoothedLux = 0; 364 mSmoothedLuxAtLastAdjustment = 0; 365 mCurrentBrightnessStepIndex = -1; 366 } 367 368 private boolean handleMessage(Message msg) { 369 switch (msg.what) { 370 case MSG_BRIGHTNESS_CALLBACK: 371 handleBrightnessCallback((int) msg.obj); 372 return true; 373 case MSG_SETUP_DISPLAY_AND_SENSOR: 374 handleDisplayChange(); 375 return true; 376 } 377 return false; 378 } 379 380 @Override 381 public void onSensorChanged(SensorEvent event) { 382 handleAmbientLuxChange(event.values[0]); 383 } 384 385 @Override 386 public void onAccuracyChanged(Sensor sensor, int accuracy) { 387 } 388 389 @Override 390 public void onDisplayAdded(int displayId) { 391 handleDisplayChange(); 392 } 393 394 @Override 395 public void onDisplayRemoved(int displayId) { 396 handleDisplayChange(); 397 } 398 399 @Override 400 public void onDisplayChanged(int displayId) { 401 handleDisplayChange(); 402 } 403 404 public interface AmbientKeyboardBacklightListener { 405 /** 406 * @param value between [0, 255] to which keyboard backlight needs to be set according 407 * to Ambient light sensor. 408 */ 409 void onKeyboardBacklightValueChanged(int value); 410 } 411 412 private static class BrightnessStep { 413 private final int mBrightnessValue; 414 private final int mIncreaseLuxThreshold; 415 private final int mDecreaseLuxThreshold; 416 417 private BrightnessStep(int brightnessValue, int increaseLuxThreshold, 418 int decreaseLuxThreshold) { 419 mBrightnessValue = brightnessValue; 420 mIncreaseLuxThreshold = increaseLuxThreshold; 421 mDecreaseLuxThreshold = decreaseLuxThreshold; 422 } 423 424 @Override 425 public String toString() { 426 return "BrightnessStep{" + "mBrightnessValue=" + mBrightnessValue 427 + ", mIncreaseThreshold=" + mIncreaseLuxThreshold + ", mDecreaseThreshold=" 428 + mDecreaseLuxThreshold + '}'; 429 } 430 } 431 } 432