Loading services/core/java/com/android/server/input/InputManagerInternal.java +12 −0 Original line number Diff line number Diff line Loading @@ -140,4 +140,16 @@ public abstract class InputManagerInternal { * canceled for all other channels. */ public abstract void pilferPointers(IBinder token); /** * Increments keyboard backlight level if the device has an associated keyboard backlight * {@see Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT} */ public abstract void incrementKeyboardBacklight(int deviceId); /** * Decrements keyboard backlight level if the device has an associated keyboard backlight * {@see Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT} */ public abstract void decrementKeyboardBacklight(int deviceId); } services/core/java/com/android/server/input/InputManagerService.java +17 −0 Original line number Diff line number Diff line Loading @@ -312,6 +312,9 @@ public class InputManagerService extends IInputManager.Stub // Manages battery state for input devices. private final BatteryController mBatteryController; // Manages Keyboard backlight private final KeyboardBacklightController mKeyboardBacklightController; // Maximum number of milliseconds to wait for input event injection. private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000; Loading Loading @@ -422,6 +425,8 @@ public class InputManagerService extends IInputManager.Stub mHandler = new InputManagerHandler(injector.getLooper()); mNative = injector.getNativeService(this); mBatteryController = new BatteryController(mContext, mNative, injector.getLooper()); mKeyboardBacklightController = new KeyboardBacklightController(mContext, mNative, mDataStore, injector.getLooper()); mUseDevInputEventForAudioJack = mContext.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack); Loading Loading @@ -563,6 +568,7 @@ public class InputManagerService extends IInputManager.Stub } mBatteryController.systemRunning(); mKeyboardBacklightController.systemRunning(); } private void reloadKeyboardLayouts() { Loading Loading @@ -2680,6 +2686,7 @@ public class InputManagerService extends IInputManager.Stub dumpSpyWindowGestureMonitors(pw, " " /*prefix*/); dumpDisplayInputPropertiesValues(pw, " " /*prefix*/); mBatteryController.dump(pw, " " /*prefix*/); mKeyboardBacklightController.dump(pw, " " /*prefix*/); } private void dumpAssociations(PrintWriter pw, String prefix) { Loading Loading @@ -3757,6 +3764,16 @@ public class InputManagerService extends IInputManager.Stub public void pilferPointers(IBinder token) { mNative.pilferPointers(token); } @Override public void incrementKeyboardBacklight(int deviceId) { mKeyboardBacklightController.incrementKeyboardBacklight(deviceId); } @Override public void decrementKeyboardBacklight(int deviceId) { mKeyboardBacklightController.decrementKeyboardBacklight(deviceId); } } @Override Loading services/core/java/com/android/server/input/KeyboardBacklightController.java 0 → 100644 +227 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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.ColorInt; import android.content.Context; import android.graphics.Color; import android.hardware.input.InputManager; import android.hardware.lights.Light; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.view.InputDevice; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; import java.util.Objects; import java.util.OptionalInt; import java.util.TreeSet; /** * A thread-safe component of {@link InputManagerService} responsible for managing the keyboard * backlight for supported keyboards. */ final class KeyboardBacklightController implements InputManager.InputDeviceListener { 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); private enum Direction { DIRECTION_UP, DIRECTION_DOWN } private static final int MSG_INCREMENT_KEYBOARD_BACKLIGHT = 1; private static final int MSG_DECREMENT_KEYBOARD_BACKLIGHT = 2; private static final int MAX_BRIGHTNESS = 255; private static final int NUM_BRIGHTNESS_CHANGE_STEPS = 10; @VisibleForTesting static final TreeSet<Integer> BRIGHTNESS_LEVELS = new TreeSet<>(); private final Context mContext; private final NativeInputManagerService mNative; // The PersistentDataStore should be locked before use. @GuardedBy("mDataStore") private final PersistentDataStore mDataStore; private final Handler mHandler; private final SparseArray<Light> mKeyboardBacklights = new SparseArray<>(); static { // Fixed brightness levels to avoid issues when converting back and forth from the // device brightness range to [0-255] // Levels are: 0, 25, 51, ..., 255 for (int i = 0; i <= NUM_BRIGHTNESS_CHANGE_STEPS; i++) { BRIGHTNESS_LEVELS.add( (int) Math.floor(((float) i * MAX_BRIGHTNESS) / NUM_BRIGHTNESS_CHANGE_STEPS)); } } KeyboardBacklightController(Context context, NativeInputManagerService nativeService, PersistentDataStore dataStore, Looper looper) { mContext = context; mNative = nativeService; mDataStore = dataStore; mHandler = new Handler(looper, this::handleMessage); } void systemRunning() { InputManager inputManager = Objects.requireNonNull( mContext.getSystemService(InputManager.class)); inputManager.registerInputDeviceListener(this, mHandler); // Circle through all the already added input devices for (int deviceId : inputManager.getInputDeviceIds()) { onInputDeviceAdded(deviceId); } } public void incrementKeyboardBacklight(int deviceId) { Message msg = Message.obtain(mHandler, MSG_INCREMENT_KEYBOARD_BACKLIGHT, deviceId); mHandler.sendMessage(msg); } public void decrementKeyboardBacklight(int deviceId) { Message msg = Message.obtain(mHandler, MSG_DECREMENT_KEYBOARD_BACKLIGHT, deviceId); mHandler.sendMessage(msg); } private void updateKeyboardBacklight(int deviceId, Direction direction) { InputDevice inputDevice = getInputDevice(deviceId); Light keyboardBacklight = mKeyboardBacklights.get(deviceId); if (inputDevice == null || keyboardBacklight == null) { return; } // Follow preset levels of brightness defined in BRIGHTNESS_LEVELS int currBrightness = BRIGHTNESS_LEVELS.floor(Color.alpha( mNative.getLightColor(deviceId, keyboardBacklight.getId()))); int newBrightness; if (direction == Direction.DIRECTION_UP) { newBrightness = currBrightness != MAX_BRIGHTNESS ? BRIGHTNESS_LEVELS.higher( currBrightness) : currBrightness; } else { newBrightness = currBrightness != 0 ? BRIGHTNESS_LEVELS.lower(currBrightness) : currBrightness; } @ColorInt int newColor = Color.argb(newBrightness, 0, 0, 0); mNative.setLightColor(deviceId, keyboardBacklight.getId(), newColor); if (DEBUG) { Slog.d(TAG, "Changing brightness from " + currBrightness + " to " + newBrightness); } synchronized (mDataStore) { try { mDataStore.setKeyboardBacklightBrightness(inputDevice.getDescriptor(), keyboardBacklight.getId(), newBrightness); } finally { mDataStore.saveIfNeeded(); } } } private void restoreBacklightBrightness(InputDevice inputDevice, Light keyboardBacklight) { OptionalInt brightness; synchronized (mDataStore) { brightness = mDataStore.getKeyboardBacklightBrightness( inputDevice.getDescriptor(), keyboardBacklight.getId()); } if (!brightness.isEmpty()) { mNative.setLightColor(inputDevice.getId(), keyboardBacklight.getId(), Color.argb(brightness.getAsInt(), 0, 0, 0)); if (DEBUG) { Slog.d(TAG, "Restoring brightness level " + brightness.getAsInt()); } } } private boolean handleMessage(Message msg) { switch (msg.what) { case MSG_INCREMENT_KEYBOARD_BACKLIGHT: updateKeyboardBacklight((int) msg.obj, Direction.DIRECTION_UP); return true; case MSG_DECREMENT_KEYBOARD_BACKLIGHT: updateKeyboardBacklight((int) msg.obj, Direction.DIRECTION_DOWN); return true; } return false; } @VisibleForTesting @Override public void onInputDeviceAdded(int deviceId) { onInputDeviceChanged(deviceId); } @VisibleForTesting @Override public void onInputDeviceRemoved(int deviceId) { mKeyboardBacklights.remove(deviceId); } @VisibleForTesting @Override public void onInputDeviceChanged(int deviceId) { InputDevice inputDevice = getInputDevice(deviceId); if (inputDevice == null) { return; } final Light keyboardBacklight = getKeyboardBacklight(inputDevice); if (keyboardBacklight == null) { mKeyboardBacklights.remove(deviceId); return; } final Light oldBacklight = mKeyboardBacklights.get(deviceId); if (oldBacklight != null && oldBacklight.getId() == keyboardBacklight.getId()) { return; } // The keyboard backlight was added or changed. mKeyboardBacklights.put(deviceId, keyboardBacklight); restoreBacklightBrightness(inputDevice, keyboardBacklight); } private InputDevice getInputDevice(int deviceId) { InputManager inputManager = mContext.getSystemService(InputManager.class); return inputManager != null ? inputManager.getInputDevice(deviceId) : null; } private Light getKeyboardBacklight(InputDevice inputDevice) { // Assuming each keyboard can have only single Light node for Keyboard backlight control // for simplicity. for (Light light : inputDevice.getLightsManager().getLights()) { if (light.getType() == Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT && light.hasBrightnessControl()) { return light; } } return null; } void dump(PrintWriter pw, String prefix) { pw.println(prefix + TAG + ": " + mKeyboardBacklights.size() + " keyboard backlights"); for (int i = 0; i < mKeyboardBacklights.size(); i++) { Light light = mKeyboardBacklights.get(i); pw.println(prefix + " " + i + ": { id: " + light.getId() + ", name: " + light.getName() + " }"); } } } services/core/java/com/android/server/input/PersistentDataStore.java +112 −37 Original line number Diff line number Diff line Loading @@ -16,40 +16,37 @@ package com.android.server.input; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import android.annotation.Nullable; import android.view.Surface; import android.hardware.input.TouchCalibration; import android.util.AtomicFile; import android.util.Slog; import android.util.SparseIntArray; import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; import android.util.Xml; import android.view.Surface; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.XmlUtils; import libcore.io.IoUtils; import org.xmlpull.v1.XmlPullParserException; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.OptionalInt; import java.util.Set; import libcore.io.IoUtils; /** * Manages persistent state recorded by the input manager service as an XML file. * Caller must acquire lock on the data store before accessing it. Loading @@ -69,7 +66,9 @@ final class PersistentDataStore { // Input device state by descriptor. private final HashMap<String, InputDeviceState> mInputDevices = new HashMap<String, InputDeviceState>(); private final AtomicFile mAtomicFile; // The interface for methods which should be replaced by the test harness. private Injector mInjector; // True if the data has been loaded. private boolean mLoaded; Loading @@ -78,8 +77,12 @@ final class PersistentDataStore { private boolean mDirty; public PersistentDataStore() { mAtomicFile = new AtomicFile(new File("/data/system/input-manager-state.xml"), "input-state"); this(new Injector()); } @VisibleForTesting PersistentDataStore(Injector injector) { mInjector = injector; } public void saveIfNeeded() { Loading @@ -90,7 +93,7 @@ final class PersistentDataStore { } public TouchCalibration getTouchCalibration(String inputDeviceDescriptor, int surfaceRotation) { InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false); InputDeviceState state = getInputDeviceState(inputDeviceDescriptor); if (state == null) { return TouchCalibration.IDENTITY; } Loading @@ -103,7 +106,7 @@ final class PersistentDataStore { } public boolean setTouchCalibration(String inputDeviceDescriptor, int surfaceRotation, TouchCalibration calibration) { InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true); InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor); if (state.setTouchCalibration(surfaceRotation, calibration)) { setDirty(); Loading @@ -114,13 +117,13 @@ final class PersistentDataStore { } public String getCurrentKeyboardLayout(String inputDeviceDescriptor) { InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false); InputDeviceState state = getInputDeviceState(inputDeviceDescriptor); return state != null ? state.getCurrentKeyboardLayout() : null; } public boolean setCurrentKeyboardLayout(String inputDeviceDescriptor, String keyboardLayoutDescriptor) { InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true); InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor); if (state.setCurrentKeyboardLayout(keyboardLayoutDescriptor)) { setDirty(); return true; Loading @@ -129,7 +132,7 @@ final class PersistentDataStore { } public String[] getKeyboardLayouts(String inputDeviceDescriptor) { InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false); InputDeviceState state = getInputDeviceState(inputDeviceDescriptor); if (state == null) { return (String[])ArrayUtils.emptyArray(String.class); } Loading @@ -138,7 +141,7 @@ final class PersistentDataStore { public boolean addKeyboardLayout(String inputDeviceDescriptor, String keyboardLayoutDescriptor) { InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true); InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor); if (state.addKeyboardLayout(keyboardLayoutDescriptor)) { setDirty(); return true; Loading @@ -148,7 +151,7 @@ final class PersistentDataStore { public boolean removeKeyboardLayout(String inputDeviceDescriptor, String keyboardLayoutDescriptor) { InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true); InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor); if (state.removeKeyboardLayout(keyboardLayoutDescriptor)) { setDirty(); return true; Loading @@ -157,7 +160,7 @@ final class PersistentDataStore { } public boolean switchKeyboardLayout(String inputDeviceDescriptor, int direction) { InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false); InputDeviceState state = getInputDeviceState(inputDeviceDescriptor); if (state != null && state.switchKeyboardLayout(direction)) { setDirty(); return true; Loading @@ -165,6 +168,24 @@ final class PersistentDataStore { return false; } public boolean setKeyboardBacklightBrightness(String inputDeviceDescriptor, int lightId, int brightness) { InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor); if (state.setKeyboardBacklightBrightness(lightId, brightness)) { setDirty(); return true; } return false; } public OptionalInt getKeyboardBacklightBrightness(String inputDeviceDescriptor, int lightId) { InputDeviceState state = getInputDeviceState(inputDeviceDescriptor); if (state == null) { return OptionalInt.empty(); } return state.getKeyboardBacklightBrightness(lightId); } public boolean removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts) { boolean changed = false; for (InputDeviceState state : mInputDevices.values()) { Loading @@ -179,11 +200,15 @@ final class PersistentDataStore { return false; } private InputDeviceState getInputDeviceState(String inputDeviceDescriptor, boolean createIfAbsent) { private InputDeviceState getInputDeviceState(String inputDeviceDescriptor) { loadIfNeeded(); return mInputDevices.get(inputDeviceDescriptor); } private InputDeviceState getOrCreateInputDeviceState(String inputDeviceDescriptor) { loadIfNeeded(); InputDeviceState state = mInputDevices.get(inputDeviceDescriptor); if (state == null && createIfAbsent) { if (state == null) { state = new InputDeviceState(); mInputDevices.put(inputDeviceDescriptor, state); setDirty(); Loading Loading @@ -211,7 +236,7 @@ final class PersistentDataStore { final InputStream is; try { is = mAtomicFile.openRead(); is = mInjector.openRead(); } catch (FileNotFoundException ex) { return; } Loading @@ -234,7 +259,7 @@ final class PersistentDataStore { private void save() { final FileOutputStream os; try { os = mAtomicFile.startWrite(); os = mInjector.startWrite(); boolean success = false; try { TypedXmlSerializer serializer = Xml.resolveSerializer(os); Loading @@ -242,11 +267,7 @@ final class PersistentDataStore { serializer.flush(); success = true; } finally { if (success) { mAtomicFile.finishWrite(os); } else { mAtomicFile.failWrite(os); } mInjector.finishWrite(os, success); } } catch (IOException ex) { Slog.w(InputManagerService.TAG, "Failed to save input manager persistent store data.", ex); Loading Loading @@ -307,10 +328,12 @@ final class PersistentDataStore { private static final String[] CALIBRATION_NAME = { "x_scale", "x_ymix", "x_offset", "y_xmix", "y_scale", "y_offset" }; private TouchCalibration[] mTouchCalibration = new TouchCalibration[4]; private static final int INVALID_VALUE = -1; private final TouchCalibration[] mTouchCalibration = new TouchCalibration[4]; @Nullable private String mCurrentKeyboardLayout; private ArrayList<String> mKeyboardLayouts = new ArrayList<String>(); private final ArrayList<String> mKeyboardLayouts = new ArrayList<String>(); private final SparseIntArray mKeyboardBacklightBrightnessMap = new SparseIntArray(); public TouchCalibration getTouchCalibration(int surfaceRotation) { try { Loading Loading @@ -377,6 +400,19 @@ final class PersistentDataStore { return true; } public boolean setKeyboardBacklightBrightness(int lightId, int brightness) { if (mKeyboardBacklightBrightnessMap.get(lightId, INVALID_VALUE) == brightness) { return false; } mKeyboardBacklightBrightnessMap.put(lightId, brightness); return true; } public OptionalInt getKeyboardBacklightBrightness(int lightId) { int brightness = mKeyboardBacklightBrightnessMap.get(lightId, INVALID_VALUE); return brightness == INVALID_VALUE ? OptionalInt.empty() : OptionalInt.of(brightness); } private void updateCurrentKeyboardLayoutIfRemoved( String removedKeyboardLayout, int removedIndex) { if (Objects.equals(mCurrentKeyboardLayout, removedKeyboardLayout)) { Loading Loading @@ -446,6 +482,10 @@ final class PersistentDataStore { } mCurrentKeyboardLayout = descriptor; } } else if (parser.getName().equals("light-info")) { int lightId = parser.getAttributeInt(null, "light-id"); int lightBrightness = parser.getAttributeInt(null, "light-brightness"); mKeyboardBacklightBrightnessMap.put(lightId, lightBrightness); } else if (parser.getName().equals("calibration")) { String format = parser.getAttributeValue(null, "format"); String rotation = parser.getAttributeValue(null, "rotation"); Loading Loading @@ -515,6 +555,15 @@ final class PersistentDataStore { serializer.endTag(null, "keyboard-layout"); } for (int i = 0; i < mKeyboardBacklightBrightnessMap.size(); i++) { int lightId = mKeyboardBacklightBrightnessMap.valueAt(i); serializer.startTag(null, "light-info"); serializer.attributeInt(null, "light-id", lightId); serializer.attributeInt(null, "light-brightness", mKeyboardBacklightBrightnessMap.get(lightId)); serializer.endTag(null, "light-info"); } for (int i = 0; i < mTouchCalibration.length; i++) { if (mTouchCalibration[i] != null) { String rotation = surfaceRotationToString(i); Loading Loading @@ -559,4 +608,30 @@ final class PersistentDataStore { throw new IllegalArgumentException("Unsupported surface rotation string '" + s + "'"); } } @VisibleForTesting static class Injector { private final AtomicFile mAtomicFile; Injector() { mAtomicFile = new AtomicFile(new File("/data/system/input-manager-state.xml"), "input-state"); } InputStream openRead() throws FileNotFoundException { return mAtomicFile.openRead(); } FileOutputStream startWrite() throws IOException { return mAtomicFile.startWrite(); } void finishWrite(FileOutputStream fos, boolean success) { if (success) { mAtomicFile.finishWrite(fos); } else { mAtomicFile.failWrite(fos); } } } } services/core/java/com/android/server/policy/PhoneWindowManager.java +9 −1 Original line number Diff line number Diff line Loading @@ -2931,9 +2931,17 @@ public class PhoneWindowManager implements WindowManagerPolicy { } return key_consumed; case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN: if (down) { mInputManagerInternal.decrementKeyboardBacklight(event.getDeviceId()); } return key_consumed; case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP: if (down) { mInputManagerInternal.incrementKeyboardBacklight(event.getDeviceId()); } return key_consumed; case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE: // TODO: Add logic to handle keyboard backlight controls (go/pk_backlight_control) // TODO: Add logic return key_consumed; case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: Loading Loading
services/core/java/com/android/server/input/InputManagerInternal.java +12 −0 Original line number Diff line number Diff line Loading @@ -140,4 +140,16 @@ public abstract class InputManagerInternal { * canceled for all other channels. */ public abstract void pilferPointers(IBinder token); /** * Increments keyboard backlight level if the device has an associated keyboard backlight * {@see Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT} */ public abstract void incrementKeyboardBacklight(int deviceId); /** * Decrements keyboard backlight level if the device has an associated keyboard backlight * {@see Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT} */ public abstract void decrementKeyboardBacklight(int deviceId); }
services/core/java/com/android/server/input/InputManagerService.java +17 −0 Original line number Diff line number Diff line Loading @@ -312,6 +312,9 @@ public class InputManagerService extends IInputManager.Stub // Manages battery state for input devices. private final BatteryController mBatteryController; // Manages Keyboard backlight private final KeyboardBacklightController mKeyboardBacklightController; // Maximum number of milliseconds to wait for input event injection. private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000; Loading Loading @@ -422,6 +425,8 @@ public class InputManagerService extends IInputManager.Stub mHandler = new InputManagerHandler(injector.getLooper()); mNative = injector.getNativeService(this); mBatteryController = new BatteryController(mContext, mNative, injector.getLooper()); mKeyboardBacklightController = new KeyboardBacklightController(mContext, mNative, mDataStore, injector.getLooper()); mUseDevInputEventForAudioJack = mContext.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack); Loading Loading @@ -563,6 +568,7 @@ public class InputManagerService extends IInputManager.Stub } mBatteryController.systemRunning(); mKeyboardBacklightController.systemRunning(); } private void reloadKeyboardLayouts() { Loading Loading @@ -2680,6 +2686,7 @@ public class InputManagerService extends IInputManager.Stub dumpSpyWindowGestureMonitors(pw, " " /*prefix*/); dumpDisplayInputPropertiesValues(pw, " " /*prefix*/); mBatteryController.dump(pw, " " /*prefix*/); mKeyboardBacklightController.dump(pw, " " /*prefix*/); } private void dumpAssociations(PrintWriter pw, String prefix) { Loading Loading @@ -3757,6 +3764,16 @@ public class InputManagerService extends IInputManager.Stub public void pilferPointers(IBinder token) { mNative.pilferPointers(token); } @Override public void incrementKeyboardBacklight(int deviceId) { mKeyboardBacklightController.incrementKeyboardBacklight(deviceId); } @Override public void decrementKeyboardBacklight(int deviceId) { mKeyboardBacklightController.decrementKeyboardBacklight(deviceId); } } @Override Loading
services/core/java/com/android/server/input/KeyboardBacklightController.java 0 → 100644 +227 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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.ColorInt; import android.content.Context; import android.graphics.Color; import android.hardware.input.InputManager; import android.hardware.lights.Light; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.view.InputDevice; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; import java.util.Objects; import java.util.OptionalInt; import java.util.TreeSet; /** * A thread-safe component of {@link InputManagerService} responsible for managing the keyboard * backlight for supported keyboards. */ final class KeyboardBacklightController implements InputManager.InputDeviceListener { 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); private enum Direction { DIRECTION_UP, DIRECTION_DOWN } private static final int MSG_INCREMENT_KEYBOARD_BACKLIGHT = 1; private static final int MSG_DECREMENT_KEYBOARD_BACKLIGHT = 2; private static final int MAX_BRIGHTNESS = 255; private static final int NUM_BRIGHTNESS_CHANGE_STEPS = 10; @VisibleForTesting static final TreeSet<Integer> BRIGHTNESS_LEVELS = new TreeSet<>(); private final Context mContext; private final NativeInputManagerService mNative; // The PersistentDataStore should be locked before use. @GuardedBy("mDataStore") private final PersistentDataStore mDataStore; private final Handler mHandler; private final SparseArray<Light> mKeyboardBacklights = new SparseArray<>(); static { // Fixed brightness levels to avoid issues when converting back and forth from the // device brightness range to [0-255] // Levels are: 0, 25, 51, ..., 255 for (int i = 0; i <= NUM_BRIGHTNESS_CHANGE_STEPS; i++) { BRIGHTNESS_LEVELS.add( (int) Math.floor(((float) i * MAX_BRIGHTNESS) / NUM_BRIGHTNESS_CHANGE_STEPS)); } } KeyboardBacklightController(Context context, NativeInputManagerService nativeService, PersistentDataStore dataStore, Looper looper) { mContext = context; mNative = nativeService; mDataStore = dataStore; mHandler = new Handler(looper, this::handleMessage); } void systemRunning() { InputManager inputManager = Objects.requireNonNull( mContext.getSystemService(InputManager.class)); inputManager.registerInputDeviceListener(this, mHandler); // Circle through all the already added input devices for (int deviceId : inputManager.getInputDeviceIds()) { onInputDeviceAdded(deviceId); } } public void incrementKeyboardBacklight(int deviceId) { Message msg = Message.obtain(mHandler, MSG_INCREMENT_KEYBOARD_BACKLIGHT, deviceId); mHandler.sendMessage(msg); } public void decrementKeyboardBacklight(int deviceId) { Message msg = Message.obtain(mHandler, MSG_DECREMENT_KEYBOARD_BACKLIGHT, deviceId); mHandler.sendMessage(msg); } private void updateKeyboardBacklight(int deviceId, Direction direction) { InputDevice inputDevice = getInputDevice(deviceId); Light keyboardBacklight = mKeyboardBacklights.get(deviceId); if (inputDevice == null || keyboardBacklight == null) { return; } // Follow preset levels of brightness defined in BRIGHTNESS_LEVELS int currBrightness = BRIGHTNESS_LEVELS.floor(Color.alpha( mNative.getLightColor(deviceId, keyboardBacklight.getId()))); int newBrightness; if (direction == Direction.DIRECTION_UP) { newBrightness = currBrightness != MAX_BRIGHTNESS ? BRIGHTNESS_LEVELS.higher( currBrightness) : currBrightness; } else { newBrightness = currBrightness != 0 ? BRIGHTNESS_LEVELS.lower(currBrightness) : currBrightness; } @ColorInt int newColor = Color.argb(newBrightness, 0, 0, 0); mNative.setLightColor(deviceId, keyboardBacklight.getId(), newColor); if (DEBUG) { Slog.d(TAG, "Changing brightness from " + currBrightness + " to " + newBrightness); } synchronized (mDataStore) { try { mDataStore.setKeyboardBacklightBrightness(inputDevice.getDescriptor(), keyboardBacklight.getId(), newBrightness); } finally { mDataStore.saveIfNeeded(); } } } private void restoreBacklightBrightness(InputDevice inputDevice, Light keyboardBacklight) { OptionalInt brightness; synchronized (mDataStore) { brightness = mDataStore.getKeyboardBacklightBrightness( inputDevice.getDescriptor(), keyboardBacklight.getId()); } if (!brightness.isEmpty()) { mNative.setLightColor(inputDevice.getId(), keyboardBacklight.getId(), Color.argb(brightness.getAsInt(), 0, 0, 0)); if (DEBUG) { Slog.d(TAG, "Restoring brightness level " + brightness.getAsInt()); } } } private boolean handleMessage(Message msg) { switch (msg.what) { case MSG_INCREMENT_KEYBOARD_BACKLIGHT: updateKeyboardBacklight((int) msg.obj, Direction.DIRECTION_UP); return true; case MSG_DECREMENT_KEYBOARD_BACKLIGHT: updateKeyboardBacklight((int) msg.obj, Direction.DIRECTION_DOWN); return true; } return false; } @VisibleForTesting @Override public void onInputDeviceAdded(int deviceId) { onInputDeviceChanged(deviceId); } @VisibleForTesting @Override public void onInputDeviceRemoved(int deviceId) { mKeyboardBacklights.remove(deviceId); } @VisibleForTesting @Override public void onInputDeviceChanged(int deviceId) { InputDevice inputDevice = getInputDevice(deviceId); if (inputDevice == null) { return; } final Light keyboardBacklight = getKeyboardBacklight(inputDevice); if (keyboardBacklight == null) { mKeyboardBacklights.remove(deviceId); return; } final Light oldBacklight = mKeyboardBacklights.get(deviceId); if (oldBacklight != null && oldBacklight.getId() == keyboardBacklight.getId()) { return; } // The keyboard backlight was added or changed. mKeyboardBacklights.put(deviceId, keyboardBacklight); restoreBacklightBrightness(inputDevice, keyboardBacklight); } private InputDevice getInputDevice(int deviceId) { InputManager inputManager = mContext.getSystemService(InputManager.class); return inputManager != null ? inputManager.getInputDevice(deviceId) : null; } private Light getKeyboardBacklight(InputDevice inputDevice) { // Assuming each keyboard can have only single Light node for Keyboard backlight control // for simplicity. for (Light light : inputDevice.getLightsManager().getLights()) { if (light.getType() == Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT && light.hasBrightnessControl()) { return light; } } return null; } void dump(PrintWriter pw, String prefix) { pw.println(prefix + TAG + ": " + mKeyboardBacklights.size() + " keyboard backlights"); for (int i = 0; i < mKeyboardBacklights.size(); i++) { Light light = mKeyboardBacklights.get(i); pw.println(prefix + " " + i + ": { id: " + light.getId() + ", name: " + light.getName() + " }"); } } }
services/core/java/com/android/server/input/PersistentDataStore.java +112 −37 Original line number Diff line number Diff line Loading @@ -16,40 +16,37 @@ package com.android.server.input; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import android.annotation.Nullable; import android.view.Surface; import android.hardware.input.TouchCalibration; import android.util.AtomicFile; import android.util.Slog; import android.util.SparseIntArray; import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; import android.util.Xml; import android.view.Surface; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.XmlUtils; import libcore.io.IoUtils; import org.xmlpull.v1.XmlPullParserException; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.OptionalInt; import java.util.Set; import libcore.io.IoUtils; /** * Manages persistent state recorded by the input manager service as an XML file. * Caller must acquire lock on the data store before accessing it. Loading @@ -69,7 +66,9 @@ final class PersistentDataStore { // Input device state by descriptor. private final HashMap<String, InputDeviceState> mInputDevices = new HashMap<String, InputDeviceState>(); private final AtomicFile mAtomicFile; // The interface for methods which should be replaced by the test harness. private Injector mInjector; // True if the data has been loaded. private boolean mLoaded; Loading @@ -78,8 +77,12 @@ final class PersistentDataStore { private boolean mDirty; public PersistentDataStore() { mAtomicFile = new AtomicFile(new File("/data/system/input-manager-state.xml"), "input-state"); this(new Injector()); } @VisibleForTesting PersistentDataStore(Injector injector) { mInjector = injector; } public void saveIfNeeded() { Loading @@ -90,7 +93,7 @@ final class PersistentDataStore { } public TouchCalibration getTouchCalibration(String inputDeviceDescriptor, int surfaceRotation) { InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false); InputDeviceState state = getInputDeviceState(inputDeviceDescriptor); if (state == null) { return TouchCalibration.IDENTITY; } Loading @@ -103,7 +106,7 @@ final class PersistentDataStore { } public boolean setTouchCalibration(String inputDeviceDescriptor, int surfaceRotation, TouchCalibration calibration) { InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true); InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor); if (state.setTouchCalibration(surfaceRotation, calibration)) { setDirty(); Loading @@ -114,13 +117,13 @@ final class PersistentDataStore { } public String getCurrentKeyboardLayout(String inputDeviceDescriptor) { InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false); InputDeviceState state = getInputDeviceState(inputDeviceDescriptor); return state != null ? state.getCurrentKeyboardLayout() : null; } public boolean setCurrentKeyboardLayout(String inputDeviceDescriptor, String keyboardLayoutDescriptor) { InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true); InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor); if (state.setCurrentKeyboardLayout(keyboardLayoutDescriptor)) { setDirty(); return true; Loading @@ -129,7 +132,7 @@ final class PersistentDataStore { } public String[] getKeyboardLayouts(String inputDeviceDescriptor) { InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false); InputDeviceState state = getInputDeviceState(inputDeviceDescriptor); if (state == null) { return (String[])ArrayUtils.emptyArray(String.class); } Loading @@ -138,7 +141,7 @@ final class PersistentDataStore { public boolean addKeyboardLayout(String inputDeviceDescriptor, String keyboardLayoutDescriptor) { InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true); InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor); if (state.addKeyboardLayout(keyboardLayoutDescriptor)) { setDirty(); return true; Loading @@ -148,7 +151,7 @@ final class PersistentDataStore { public boolean removeKeyboardLayout(String inputDeviceDescriptor, String keyboardLayoutDescriptor) { InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true); InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor); if (state.removeKeyboardLayout(keyboardLayoutDescriptor)) { setDirty(); return true; Loading @@ -157,7 +160,7 @@ final class PersistentDataStore { } public boolean switchKeyboardLayout(String inputDeviceDescriptor, int direction) { InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false); InputDeviceState state = getInputDeviceState(inputDeviceDescriptor); if (state != null && state.switchKeyboardLayout(direction)) { setDirty(); return true; Loading @@ -165,6 +168,24 @@ final class PersistentDataStore { return false; } public boolean setKeyboardBacklightBrightness(String inputDeviceDescriptor, int lightId, int brightness) { InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor); if (state.setKeyboardBacklightBrightness(lightId, brightness)) { setDirty(); return true; } return false; } public OptionalInt getKeyboardBacklightBrightness(String inputDeviceDescriptor, int lightId) { InputDeviceState state = getInputDeviceState(inputDeviceDescriptor); if (state == null) { return OptionalInt.empty(); } return state.getKeyboardBacklightBrightness(lightId); } public boolean removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts) { boolean changed = false; for (InputDeviceState state : mInputDevices.values()) { Loading @@ -179,11 +200,15 @@ final class PersistentDataStore { return false; } private InputDeviceState getInputDeviceState(String inputDeviceDescriptor, boolean createIfAbsent) { private InputDeviceState getInputDeviceState(String inputDeviceDescriptor) { loadIfNeeded(); return mInputDevices.get(inputDeviceDescriptor); } private InputDeviceState getOrCreateInputDeviceState(String inputDeviceDescriptor) { loadIfNeeded(); InputDeviceState state = mInputDevices.get(inputDeviceDescriptor); if (state == null && createIfAbsent) { if (state == null) { state = new InputDeviceState(); mInputDevices.put(inputDeviceDescriptor, state); setDirty(); Loading Loading @@ -211,7 +236,7 @@ final class PersistentDataStore { final InputStream is; try { is = mAtomicFile.openRead(); is = mInjector.openRead(); } catch (FileNotFoundException ex) { return; } Loading @@ -234,7 +259,7 @@ final class PersistentDataStore { private void save() { final FileOutputStream os; try { os = mAtomicFile.startWrite(); os = mInjector.startWrite(); boolean success = false; try { TypedXmlSerializer serializer = Xml.resolveSerializer(os); Loading @@ -242,11 +267,7 @@ final class PersistentDataStore { serializer.flush(); success = true; } finally { if (success) { mAtomicFile.finishWrite(os); } else { mAtomicFile.failWrite(os); } mInjector.finishWrite(os, success); } } catch (IOException ex) { Slog.w(InputManagerService.TAG, "Failed to save input manager persistent store data.", ex); Loading Loading @@ -307,10 +328,12 @@ final class PersistentDataStore { private static final String[] CALIBRATION_NAME = { "x_scale", "x_ymix", "x_offset", "y_xmix", "y_scale", "y_offset" }; private TouchCalibration[] mTouchCalibration = new TouchCalibration[4]; private static final int INVALID_VALUE = -1; private final TouchCalibration[] mTouchCalibration = new TouchCalibration[4]; @Nullable private String mCurrentKeyboardLayout; private ArrayList<String> mKeyboardLayouts = new ArrayList<String>(); private final ArrayList<String> mKeyboardLayouts = new ArrayList<String>(); private final SparseIntArray mKeyboardBacklightBrightnessMap = new SparseIntArray(); public TouchCalibration getTouchCalibration(int surfaceRotation) { try { Loading Loading @@ -377,6 +400,19 @@ final class PersistentDataStore { return true; } public boolean setKeyboardBacklightBrightness(int lightId, int brightness) { if (mKeyboardBacklightBrightnessMap.get(lightId, INVALID_VALUE) == brightness) { return false; } mKeyboardBacklightBrightnessMap.put(lightId, brightness); return true; } public OptionalInt getKeyboardBacklightBrightness(int lightId) { int brightness = mKeyboardBacklightBrightnessMap.get(lightId, INVALID_VALUE); return brightness == INVALID_VALUE ? OptionalInt.empty() : OptionalInt.of(brightness); } private void updateCurrentKeyboardLayoutIfRemoved( String removedKeyboardLayout, int removedIndex) { if (Objects.equals(mCurrentKeyboardLayout, removedKeyboardLayout)) { Loading Loading @@ -446,6 +482,10 @@ final class PersistentDataStore { } mCurrentKeyboardLayout = descriptor; } } else if (parser.getName().equals("light-info")) { int lightId = parser.getAttributeInt(null, "light-id"); int lightBrightness = parser.getAttributeInt(null, "light-brightness"); mKeyboardBacklightBrightnessMap.put(lightId, lightBrightness); } else if (parser.getName().equals("calibration")) { String format = parser.getAttributeValue(null, "format"); String rotation = parser.getAttributeValue(null, "rotation"); Loading Loading @@ -515,6 +555,15 @@ final class PersistentDataStore { serializer.endTag(null, "keyboard-layout"); } for (int i = 0; i < mKeyboardBacklightBrightnessMap.size(); i++) { int lightId = mKeyboardBacklightBrightnessMap.valueAt(i); serializer.startTag(null, "light-info"); serializer.attributeInt(null, "light-id", lightId); serializer.attributeInt(null, "light-brightness", mKeyboardBacklightBrightnessMap.get(lightId)); serializer.endTag(null, "light-info"); } for (int i = 0; i < mTouchCalibration.length; i++) { if (mTouchCalibration[i] != null) { String rotation = surfaceRotationToString(i); Loading Loading @@ -559,4 +608,30 @@ final class PersistentDataStore { throw new IllegalArgumentException("Unsupported surface rotation string '" + s + "'"); } } @VisibleForTesting static class Injector { private final AtomicFile mAtomicFile; Injector() { mAtomicFile = new AtomicFile(new File("/data/system/input-manager-state.xml"), "input-state"); } InputStream openRead() throws FileNotFoundException { return mAtomicFile.openRead(); } FileOutputStream startWrite() throws IOException { return mAtomicFile.startWrite(); } void finishWrite(FileOutputStream fos, boolean success) { if (success) { mAtomicFile.finishWrite(fos); } else { mAtomicFile.failWrite(fos); } } } }
services/core/java/com/android/server/policy/PhoneWindowManager.java +9 −1 Original line number Diff line number Diff line Loading @@ -2931,9 +2931,17 @@ public class PhoneWindowManager implements WindowManagerPolicy { } return key_consumed; case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN: if (down) { mInputManagerInternal.decrementKeyboardBacklight(event.getDeviceId()); } return key_consumed; case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP: if (down) { mInputManagerInternal.incrementKeyboardBacklight(event.getDeviceId()); } return key_consumed; case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE: // TODO: Add logic to handle keyboard backlight controls (go/pk_backlight_control) // TODO: Add logic return key_consumed; case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: Loading