Merge "Add keyboard backlight controller"
This commit is contained in:
committed by
Android (Google) Code Review
commit
b1ab4249bc
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
@@ -563,6 +568,7 @@ public class InputManagerService extends IInputManager.Stub
|
||||
}
|
||||
|
||||
mBatteryController.systemRunning();
|
||||
mKeyboardBacklightController.systemRunning();
|
||||
}
|
||||
|
||||
private void reloadKeyboardLayouts() {
|
||||
@@ -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) {
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* 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()
|
||||
+ " }");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
@@ -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() {
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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()) {
|
||||
@@ -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();
|
||||
@@ -211,7 +236,7 @@ final class PersistentDataStore {
|
||||
|
||||
final InputStream is;
|
||||
try {
|
||||
is = mAtomicFile.openRead();
|
||||
is = mInjector.openRead();
|
||||
} catch (FileNotFoundException ex) {
|
||||
return;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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 {
|
||||
@@ -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)) {
|
||||
@@ -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");
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
* 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.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.graphics.Color
|
||||
import android.hardware.input.IInputManager
|
||||
import android.hardware.input.InputManager
|
||||
import android.hardware.lights.Light
|
||||
import android.os.test.TestLooper
|
||||
import android.platform.test.annotations.Presubmit
|
||||
import android.view.InputDevice
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.android.server.input.KeyboardBacklightController.BRIGHTNESS_LEVELS
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.anyInt
|
||||
import org.mockito.Mockito.eq
|
||||
import org.mockito.Mockito.spy
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.junit.MockitoJUnit
|
||||
|
||||
private fun createKeyboard(deviceId: Int): InputDevice =
|
||||
InputDevice.Builder()
|
||||
.setId(deviceId)
|
||||
.setName("Device $deviceId")
|
||||
.setDescriptor("descriptor $deviceId")
|
||||
.setSources(InputDevice.SOURCE_KEYBOARD)
|
||||
.setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC)
|
||||
.setExternal(true)
|
||||
.build()
|
||||
|
||||
private fun createLight(lightId: Int, lightType: Int): Light =
|
||||
Light(
|
||||
lightId,
|
||||
"Light $lightId",
|
||||
1,
|
||||
lightType,
|
||||
Light.LIGHT_CAPABILITY_BRIGHTNESS
|
||||
)
|
||||
/**
|
||||
* Tests for {@link KeyboardBacklightController}.
|
||||
*
|
||||
* Build/Install/Run:
|
||||
* atest FrameworksServicesTests:KeyboardBacklightControllerTests
|
||||
*/
|
||||
@Presubmit
|
||||
class KeyboardBacklightControllerTests {
|
||||
companion object {
|
||||
const val DEVICE_ID = 1
|
||||
const val LIGHT_ID = 2
|
||||
const val SECOND_LIGHT_ID = 3
|
||||
}
|
||||
|
||||
@get:Rule
|
||||
val rule = MockitoJUnit.rule()!!
|
||||
|
||||
@Mock
|
||||
private lateinit var iInputManager: IInputManager
|
||||
@Mock
|
||||
private lateinit var native: NativeInputManagerService
|
||||
private lateinit var keyboardBacklightController: KeyboardBacklightController
|
||||
private lateinit var context: Context
|
||||
private lateinit var dataStore: PersistentDataStore
|
||||
private lateinit var testLooper: TestLooper
|
||||
private var lightColorMap: HashMap<Int, Int> = HashMap()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
context = spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
|
||||
dataStore = PersistentDataStore(object : PersistentDataStore.Injector() {
|
||||
override fun openRead(): InputStream? {
|
||||
throw FileNotFoundException()
|
||||
}
|
||||
|
||||
override fun startWrite(): FileOutputStream? {
|
||||
throw IOException()
|
||||
}
|
||||
|
||||
override fun finishWrite(fos: FileOutputStream?, success: Boolean) {}
|
||||
})
|
||||
testLooper = TestLooper()
|
||||
keyboardBacklightController =
|
||||
KeyboardBacklightController(context, native, dataStore, testLooper.looper)
|
||||
val inputManager = InputManager.resetInstance(iInputManager)
|
||||
`when`(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager)
|
||||
`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
|
||||
`when`(native.setLightColor(anyInt(), anyInt(), anyInt())).then {
|
||||
val args = it.arguments
|
||||
lightColorMap.put(args[1] as Int, args[2] as Int)
|
||||
}
|
||||
`when`(native.getLightColor(anyInt(), anyInt())).then {
|
||||
val args = it.arguments
|
||||
lightColorMap.getOrDefault(args[1] as Int, -1)
|
||||
}
|
||||
lightColorMap.clear()
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
InputManager.clearInstance()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testKeyboardBacklightIncrementDecrement() {
|
||||
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
|
||||
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
|
||||
`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
|
||||
`when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
|
||||
keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
|
||||
// Initially backlight is at min
|
||||
lightColorMap[LIGHT_ID] = Color.argb(BRIGHTNESS_LEVELS.first(), 0, 0, 0)
|
||||
|
||||
val brightnessLevelsArray = BRIGHTNESS_LEVELS.toTypedArray()
|
||||
for (level in 1 until brightnessLevelsArray.size) {
|
||||
keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
|
||||
testLooper.dispatchNext()
|
||||
assertEquals(
|
||||
"Light value for level $level mismatched",
|
||||
Color.argb(brightnessLevelsArray[level], 0, 0, 0),
|
||||
lightColorMap[LIGHT_ID]
|
||||
)
|
||||
assertEquals(
|
||||
"Light value for level $level must be correctly stored in the datastore",
|
||||
brightnessLevelsArray[level],
|
||||
dataStore.getKeyboardBacklightBrightness(
|
||||
keyboardWithBacklight.descriptor,
|
||||
LIGHT_ID
|
||||
).asInt
|
||||
)
|
||||
}
|
||||
|
||||
for (level in brightnessLevelsArray.size - 2 downTo 0) {
|
||||
keyboardBacklightController.decrementKeyboardBacklight(DEVICE_ID)
|
||||
testLooper.dispatchNext()
|
||||
assertEquals(
|
||||
"Light value for level $level mismatched",
|
||||
Color.argb(brightnessLevelsArray[level], 0, 0, 0),
|
||||
lightColorMap[LIGHT_ID]
|
||||
)
|
||||
assertEquals(
|
||||
"Light value for level $level must be correctly stored in the datastore",
|
||||
brightnessLevelsArray[level],
|
||||
dataStore.getKeyboardBacklightBrightness(
|
||||
keyboardWithBacklight.descriptor,
|
||||
LIGHT_ID
|
||||
).asInt
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testKeyboardBacklightIncrementAboveMaxLevel() {
|
||||
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
|
||||
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
|
||||
`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
|
||||
`when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
|
||||
keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
|
||||
// Initially backlight is at max
|
||||
lightColorMap[LIGHT_ID] = Color.argb(BRIGHTNESS_LEVELS.last(), 0, 0, 0)
|
||||
|
||||
keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
|
||||
testLooper.dispatchNext()
|
||||
assertEquals(
|
||||
"Light value for max level mismatched",
|
||||
Color.argb(BRIGHTNESS_LEVELS.last(), 0, 0, 0),
|
||||
lightColorMap[LIGHT_ID]
|
||||
)
|
||||
assertEquals(
|
||||
"Light value for max level must be correctly stored in the datastore",
|
||||
BRIGHTNESS_LEVELS.last(),
|
||||
dataStore.getKeyboardBacklightBrightness(
|
||||
keyboardWithBacklight.descriptor,
|
||||
LIGHT_ID
|
||||
).asInt
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testKeyboardBacklightDecrementBelowMin() {
|
||||
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
|
||||
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
|
||||
`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
|
||||
`when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
|
||||
keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
|
||||
// Initially backlight is at min
|
||||
lightColorMap[LIGHT_ID] = Color.argb(BRIGHTNESS_LEVELS.first(), 0, 0, 0)
|
||||
|
||||
keyboardBacklightController.decrementKeyboardBacklight(DEVICE_ID)
|
||||
testLooper.dispatchNext()
|
||||
assertEquals(
|
||||
"Light value for min level mismatched",
|
||||
Color.argb(BRIGHTNESS_LEVELS.first(), 0, 0, 0),
|
||||
lightColorMap[LIGHT_ID]
|
||||
)
|
||||
assertEquals(
|
||||
"Light value for min level must be correctly stored in the datastore",
|
||||
BRIGHTNESS_LEVELS.first(),
|
||||
dataStore.getKeyboardBacklightBrightness(
|
||||
keyboardWithBacklight.descriptor,
|
||||
LIGHT_ID
|
||||
).asInt
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testKeyboardWithoutBacklight() {
|
||||
val keyboardWithoutBacklight = createKeyboard(DEVICE_ID)
|
||||
val keyboardInputLight = createLight(LIGHT_ID, Light.LIGHT_TYPE_INPUT)
|
||||
`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithoutBacklight)
|
||||
`when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardInputLight))
|
||||
keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
|
||||
|
||||
keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
|
||||
assertTrue("Non Keyboard backlights should not change", lightColorMap.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testKeyboardWithMultipleLight() {
|
||||
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
|
||||
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
|
||||
val keyboardInputLight = createLight(SECOND_LIGHT_ID, Light.LIGHT_TYPE_INPUT)
|
||||
`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
|
||||
`when`(iInputManager.getLights(DEVICE_ID)).thenReturn(
|
||||
listOf(
|
||||
keyboardBacklight,
|
||||
keyboardInputLight
|
||||
)
|
||||
)
|
||||
keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
|
||||
|
||||
keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
|
||||
testLooper.dispatchNext()
|
||||
assertEquals("Only keyboard backlights should change", 1, lightColorMap.size)
|
||||
assertNotNull("Keyboard backlight should change", lightColorMap[LIGHT_ID])
|
||||
assertNull("Input lights should not change", lightColorMap[SECOND_LIGHT_ID])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRestoreBacklightOnInputDeviceAdded() {
|
||||
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
|
||||
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
|
||||
`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
|
||||
`when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
|
||||
|
||||
dataStore.setKeyboardBacklightBrightness(
|
||||
keyboardWithBacklight.descriptor,
|
||||
LIGHT_ID,
|
||||
BRIGHTNESS_LEVELS.last()
|
||||
)
|
||||
lightColorMap.clear()
|
||||
|
||||
keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
|
||||
assertEquals(
|
||||
"Keyboard backlight level should be restored to the level saved in the data store",
|
||||
Color.argb(BRIGHTNESS_LEVELS.last(), 0, 0, 0),
|
||||
lightColorMap[LIGHT_ID]
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRestoreBacklightOnInputDeviceChanged() {
|
||||
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
|
||||
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
|
||||
`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
|
||||
dataStore.setKeyboardBacklightBrightness(
|
||||
keyboardWithBacklight.descriptor,
|
||||
LIGHT_ID,
|
||||
BRIGHTNESS_LEVELS.last()
|
||||
)
|
||||
lightColorMap.clear()
|
||||
|
||||
keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
|
||||
assertTrue(
|
||||
"Keyboard backlight should not be changed until its added",
|
||||
lightColorMap.isEmpty()
|
||||
)
|
||||
|
||||
`when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
|
||||
keyboardBacklightController.onInputDeviceChanged(DEVICE_ID)
|
||||
assertEquals(
|
||||
"Keyboard backlight level should be restored to the level saved in the data store",
|
||||
Color.argb(BRIGHTNESS_LEVELS.last(), 0, 0, 0),
|
||||
lightColorMap[LIGHT_ID]
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user