• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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         if (displayInfo == null) {
238             return;
239         }
240         synchronized (sAmbientControllerLock) {
241             if (Objects.equals(mCurrentDefaultDisplayUniqueId, displayInfo.uniqueId)) {
242                 return;
243             }
244             if (DEBUG) {
245                 Slog.d(TAG, "Default display changed: resetting the light sensor");
246             }
247             // Keep track of current default display
248             mCurrentDefaultDisplayUniqueId = displayInfo.uniqueId;
249             // Clear all existing sensor listeners
250             if (!mAmbientKeyboardBacklightListeners.isEmpty()) {
251                 removeSensorListener(mLightSensor);
252             }
253             mLightSensor = getAmbientLightSensor(
254                     displayManagerInternal.getAmbientLightSensorData(Display.DEFAULT_DISPLAY));
255             // Re-add sensor listeners if required;
256             if (!mAmbientKeyboardBacklightListeners.isEmpty()) {
257                 addSensorListener(mLightSensor);
258             }
259         }
260     }
261 
getAmbientLightSensor( DisplayManagerInternal.AmbientLightSensorData ambientSensor)262     private Sensor getAmbientLightSensor(
263             DisplayManagerInternal.AmbientLightSensorData ambientSensor) {
264         SensorManager sensorManager = Objects.requireNonNull(
265                 mContext.getSystemService(SensorManager.class));
266         if (DEBUG) {
267             Slog.d(TAG, "Ambient Light sensor data: " + ambientSensor);
268         }
269         return SensorUtils.findSensor(sensorManager, ambientSensor.sensorType,
270                 ambientSensor.sensorName, Sensor.TYPE_LIGHT);
271     }
272 
updateSmoothedLux(float rawLux)273     private void updateSmoothedLux(float rawLux) {
274         // For the first sensor reading, use raw lux value directly without smoothing.
275         if (mHysteresisState == HysteresisState.IMMEDIATE) {
276             mSmoothedLux = (int) rawLux;
277         } else {
278             mSmoothedLux =
279                     (int) (mSmoothingConstant * rawLux + (1 - mSmoothingConstant) * mSmoothedLux);
280         }
281         if (DEBUG) {
282             Slog.d(TAG, "Current smoothed lux from ALS = " + mSmoothedLux);
283         }
284     }
285 
286     @VisibleForTesting
addSensorListener(@ullable Sensor sensor)287     public void addSensorListener(@Nullable Sensor sensor) {
288         SensorManager sensorManager = mContext.getSystemService(SensorManager.class);
289         if (sensorManager == null || sensor == null) {
290             return;
291         }
292         // Reset values before registering listener
293         reset();
294         sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL, mHandler);
295         if (DEBUG) {
296             Slog.d(TAG, "Registering ALS listener");
297         }
298     }
299 
removeSensorListener(@ullable Sensor sensor)300     private void removeSensorListener(@Nullable Sensor sensor) {
301         SensorManager sensorManager = mContext.getSystemService(SensorManager.class);
302         if (sensorManager == null || sensor == null) {
303             return;
304         }
305         sensorManager.unregisterListener(this, sensor);
306         if (DEBUG) {
307             Slog.d(TAG, "Unregistering ALS listener");
308         }
309     }
310 
initConfiguration()311     private void initConfiguration() {
312         Resources res = mContext.getResources();
313         int[] brightnessValueArray = res.getIntArray(
314                 com.android.internal.R.array.config_autoKeyboardBacklightBrightnessValues);
315         int[] decreaseThresholdArray = res.getIntArray(
316                 com.android.internal.R.array.config_autoKeyboardBacklightDecreaseLuxThreshold);
317         int[] increaseThresholdArray = res.getIntArray(
318                 com.android.internal.R.array.config_autoKeyboardBacklightIncreaseLuxThreshold);
319         if (brightnessValueArray.length != decreaseThresholdArray.length
320                 || decreaseThresholdArray.length != increaseThresholdArray.length) {
321             throw new IllegalArgumentException(
322                     "The config files for auto keyboard backlight brightness must contain arrays "
323                             + "of equal lengths");
324         }
325         final int size = brightnessValueArray.length;
326         mBrightnessSteps = new BrightnessStep[size];
327         for (int i = 0; i < size; i++) {
328             int increaseThreshold =
329                     increaseThresholdArray[i] < 0 ? Integer.MAX_VALUE : increaseThresholdArray[i];
330             int decreaseThreshold =
331                     decreaseThresholdArray[i] < 0 ? Integer.MIN_VALUE : decreaseThresholdArray[i];
332             mBrightnessSteps[i] = new BrightnessStep(brightnessValueArray[i], increaseThreshold,
333                     decreaseThreshold);
334         }
335 
336         int numSteps = mBrightnessSteps.length;
337         if (numSteps == 0 || mBrightnessSteps[0].mDecreaseLuxThreshold != Integer.MIN_VALUE
338                 || mBrightnessSteps[numSteps - 1].mIncreaseLuxThreshold != Integer.MAX_VALUE) {
339             throw new IllegalArgumentException(
340                     "The config files for auto keyboard backlight brightness must contain arrays "
341                             + "of length > 0 and have -1 or Integer.MIN_VALUE as lower bound for "
342                             + "decrease thresholds and -1 or Integer.MAX_VALUE as upper bound for "
343                             + "increase thresholds");
344         }
345 
346         final TypedValue smoothingConstantValue = new TypedValue();
347         res.getValue(
348                 com.android.internal.R.dimen.config_autoKeyboardBrightnessSmoothingConstant,
349                 smoothingConstantValue,
350                 true /*resolveRefs*/);
351         mSmoothingConstant = smoothingConstantValue.getFloat();
352         if (mSmoothingConstant <= 0.0 || mSmoothingConstant > 1.0) {
353             throw new IllegalArgumentException(
354                     "The config files for auto keyboard backlight brightness must contain "
355                             + "smoothing constant in range (0.0, 1.0].");
356         }
357 
358         if (DEBUG) {
359             Log.d(TAG, "Brightness steps: " + Arrays.toString(mBrightnessSteps)
360                     + " Smoothing constant = " + mSmoothingConstant);
361         }
362     }
363 
364     private void reset() {
365         mHysteresisState = HysteresisState.IMMEDIATE;
366         mSmoothedLux = 0;
367         mSmoothedLuxAtLastAdjustment = 0;
368         mCurrentBrightnessStepIndex = -1;
369     }
370 
371     private boolean handleMessage(Message msg) {
372         switch (msg.what) {
373             case MSG_BRIGHTNESS_CALLBACK:
374                 handleBrightnessCallback((int) msg.obj);
375                 return true;
376             case MSG_SETUP_DISPLAY_AND_SENSOR:
377                 handleDisplayChange();
378                 return true;
379         }
380         return false;
381     }
382 
383     @Override
384     public void onSensorChanged(SensorEvent event) {
385         handleAmbientLuxChange(event.values[0]);
386     }
387 
388     @Override
389     public void onAccuracyChanged(Sensor sensor, int accuracy) {
390     }
391 
392     @Override
393     public void onDisplayAdded(int displayId) {
394         handleDisplayChange();
395     }
396 
397     @Override
398     public void onDisplayRemoved(int displayId) {
399         handleDisplayChange();
400     }
401 
402     @Override
403     public void onDisplayChanged(int displayId) {
404         handleDisplayChange();
405     }
406 
407     public interface AmbientKeyboardBacklightListener {
408         /**
409          * @param value between [0, 255] to which keyboard backlight needs to be set according
410          *              to Ambient light sensor.
411          */
412         void onKeyboardBacklightValueChanged(int value);
413     }
414 
415     private static class BrightnessStep {
416         private final int mBrightnessValue;
417         private final int mIncreaseLuxThreshold;
418         private final int mDecreaseLuxThreshold;
419 
420         private BrightnessStep(int brightnessValue, int increaseLuxThreshold,
421                 int decreaseLuxThreshold) {
422             mBrightnessValue = brightnessValue;
423             mIncreaseLuxThreshold = increaseLuxThreshold;
424             mDecreaseLuxThreshold = decreaseLuxThreshold;
425         }
426 
427         @Override
428         public String toString() {
429             return "BrightnessStep{" + "mBrightnessValue=" + mBrightnessValue
430                     + ", mIncreaseThreshold=" + mIncreaseLuxThreshold + ", mDecreaseThreshold="
431                     + mDecreaseLuxThreshold + '}';
432         }
433     }
434 }
435