Loading services/core/java/com/android/server/input/KeyboardBacklightController.java +44 −0 Original line number Diff line number Diff line Loading @@ -29,6 +29,8 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.SystemClock; import android.os.UEventObserver; import android.text.TextUtils; import android.util.IndentingPrintWriter; import android.util.Log; import android.util.Slog; Loading Loading @@ -69,6 +71,8 @@ final class KeyboardBacklightController implements private static final int MAX_BRIGHTNESS = 255; private static final int NUM_BRIGHTNESS_CHANGE_STEPS = 10; private static final String UEVENT_KEYBOARD_BACKLIGHT_TAG = "kbd_backlight"; @VisibleForTesting static final long USER_INACTIVITY_THRESHOLD_MILLIS = Duration.ofSeconds(30).toMillis(); Loading Loading @@ -120,6 +124,18 @@ final class KeyboardBacklightController implements Message msg = Message.obtain(mHandler, MSG_UPDATE_EXISTING_DEVICES, inputManager.getInputDeviceIds()); mHandler.sendMessage(msg); // Observe UEvents for "kbd_backlight" sysfs nodes. // We want to observe creation of such LED nodes since they might be created after device // FD created and InputDevice creation logic doesn't initialize LED nodes which leads to // backlight not working. UEventObserver observer = new UEventObserver() { @Override public void onUEvent(UEvent event) { onKeyboardBacklightUEvent(event); } }; observer.startObserving(UEVENT_KEYBOARD_BACKLIGHT_TAG); } @Override Loading Loading @@ -386,6 +402,34 @@ final class KeyboardBacklightController implements } } @VisibleForTesting public void onKeyboardBacklightUEvent(UEventObserver.UEvent event) { if ("ADD".equalsIgnoreCase(event.get("ACTION")) && "LEDS".equalsIgnoreCase( event.get("SUBSYSTEM"))) { final String devPath = event.get("DEVPATH"); if (isValidBacklightNodePath(devPath)) { mNative.sysfsNodeChanged("/sys" + devPath); } } } private static boolean isValidBacklightNodePath(String devPath) { if (TextUtils.isEmpty(devPath)) { return false; } int index = devPath.lastIndexOf('/'); if (index < 0) { return false; } String backlightNode = devPath.substring(index + 1); devPath = devPath.substring(0, index); if (!devPath.endsWith("leds") || !backlightNode.contains("kbd_backlight")) { return false; } index = devPath.lastIndexOf('/'); return index >= 0; } @Override public void dump(PrintWriter pw) { IndentingPrintWriter ipw = new IndentingPrintWriter(pw); Loading services/core/java/com/android/server/input/NativeInputManagerService.java +9 −0 Original line number Diff line number Diff line Loading @@ -237,6 +237,12 @@ interface NativeInputManagerService { /** Set whether showing a pointer icon for styluses is enabled. */ void setStylusPointerIconEnabled(boolean enabled); /** * Report sysfs node changes. This may result in recreation of the corresponding InputDevice. * The recreated device may contain new associated peripheral devices like Light, Battery, etc. */ void sysfsNodeChanged(String sysfsNodePath); /** The native implementation of InputManagerService methods. */ class NativeImpl implements NativeInputManagerService { /** Pointer to native input manager service object, used by native code. */ Loading Loading @@ -484,5 +490,8 @@ interface NativeInputManagerService { @Override public native void setStylusPointerIconEnabled(boolean enabled); @Override public native void sysfsNodeChanged(String sysfsNodePath); } } services/core/jni/com_android_server_input_InputManagerService.cpp +9 −0 Original line number Diff line number Diff line Loading @@ -2336,6 +2336,14 @@ static void nativeReloadDeviceAliases(JNIEnv* env, jobject nativeImplObj) { InputReaderConfiguration::CHANGE_DEVICE_ALIAS); } static void nativeSysfsNodeChanged(JNIEnv* env, jobject nativeImplObj, jstring path) { ScopedUtfChars sysfsNodePathChars(env, path); const std::string sysfsNodePath = sysfsNodePathChars.c_str(); NativeInputManager* im = getNativeInputManager(env, nativeImplObj); im->getInputManager()->getReader().sysfsNodeChanged(sysfsNodePath); } static std::string dumpInputProperties() { std::string out = "Input properties:\n"; const std::string strategy = Loading Loading @@ -2651,6 +2659,7 @@ static const JNINativeMethod gInputManagerMethods[] = { {"getBatteryDevicePath", "(I)Ljava/lang/String;", (void*)nativeGetBatteryDevicePath}, {"reloadKeyboardLayouts", "()V", (void*)nativeReloadKeyboardLayouts}, {"reloadDeviceAliases", "()V", (void*)nativeReloadDeviceAliases}, {"sysfsNodeChanged", "(Ljava/lang/String;)V", (void*)nativeSysfsNodeChanged}, {"dump", "()Ljava/lang/String;", (void*)nativeDump}, {"monitor", "()V", (void*)nativeMonitor}, {"isInputDeviceEnabled", "(I)Z", (void*)nativeIsInputDeviceEnabled}, Loading services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt +64 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import android.hardware.input.IKeyboardBacklightListener import android.hardware.input.IKeyboardBacklightState import android.hardware.input.InputManager import android.hardware.lights.Light import android.os.UEventObserver import android.os.test.TestLooper import android.platform.test.annotations.Presubmit import android.view.InputDevice Loading @@ -39,6 +40,7 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.mockito.Mock import org.mockito.Mockito.any import org.mockito.Mockito.anyInt import org.mockito.Mockito.eq import org.mockito.Mockito.spy Loading Loading @@ -95,6 +97,7 @@ class KeyboardBacklightControllerTests { private lateinit var testLooper: TestLooper private var lightColorMap: HashMap<Int, Int> = HashMap() private var lastBacklightState: KeyboardBacklightState? = null private var sysfsNodeChanges = 0 @Before fun setup() { Loading @@ -121,6 +124,9 @@ class KeyboardBacklightControllerTests { lightColorMap.put(args[1] as Int, args[2] as Int) } lightColorMap.clear() `when`(native.sysfsNodeChanged(any())).then { sysfsNodeChanges++ } } @After Loading Loading @@ -393,6 +399,64 @@ class KeyboardBacklightControllerTests { ) } @Test fun testKeyboardBacklightSysfsNodeAdded_AfterInputDeviceAdded() { var counter = sysfsNodeChanges keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent( "ACTION=add\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/leds/abc::no_backlight\u0000" )) assertEquals( "Should not reload sysfs node if UEvent path doesn't contain kbd_backlight", counter, sysfsNodeChanges ) keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent( "ACTION=add\u0000SUBSYSTEM=power\u0000DEVPATH=/xyz/leds/abc::kbd_backlight\u0000" )) assertEquals( "Should not reload sysfs node if UEvent doesn't belong to subsystem LED", counter, sysfsNodeChanges ) keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent( "ACTION=remove\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/leds/abc::kbd_backlight\u0000" )) assertEquals( "Should not reload sysfs node if UEvent doesn't have ACTION(add)", counter, sysfsNodeChanges ) keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent( "ACTION=add\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/pqr/abc::kbd_backlight\u0000" )) assertEquals( "Should not reload sysfs node if UEvent path doesn't belong to leds/ directory", counter, sysfsNodeChanges ) keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent( "ACTION=add\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/leds/abc::kbd_backlight\u0000" )) assertEquals( "Should reload sysfs node if a valid Keyboard backlight LED UEvent occurs", ++counter, sysfsNodeChanges ) keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent( "ACTION=add\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/leds/abc:kbd_backlight:red\u0000" )) assertEquals( "Should reload sysfs node if a valid Keyboard backlight LED UEvent occurs", ++counter, sysfsNodeChanges ) } inner class KeyboardBacklightListener : IKeyboardBacklightListener.Stub() { override fun onBrightnessChanged( deviceId: Int, Loading Loading
services/core/java/com/android/server/input/KeyboardBacklightController.java +44 −0 Original line number Diff line number Diff line Loading @@ -29,6 +29,8 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.SystemClock; import android.os.UEventObserver; import android.text.TextUtils; import android.util.IndentingPrintWriter; import android.util.Log; import android.util.Slog; Loading Loading @@ -69,6 +71,8 @@ final class KeyboardBacklightController implements private static final int MAX_BRIGHTNESS = 255; private static final int NUM_BRIGHTNESS_CHANGE_STEPS = 10; private static final String UEVENT_KEYBOARD_BACKLIGHT_TAG = "kbd_backlight"; @VisibleForTesting static final long USER_INACTIVITY_THRESHOLD_MILLIS = Duration.ofSeconds(30).toMillis(); Loading Loading @@ -120,6 +124,18 @@ final class KeyboardBacklightController implements Message msg = Message.obtain(mHandler, MSG_UPDATE_EXISTING_DEVICES, inputManager.getInputDeviceIds()); mHandler.sendMessage(msg); // Observe UEvents for "kbd_backlight" sysfs nodes. // We want to observe creation of such LED nodes since they might be created after device // FD created and InputDevice creation logic doesn't initialize LED nodes which leads to // backlight not working. UEventObserver observer = new UEventObserver() { @Override public void onUEvent(UEvent event) { onKeyboardBacklightUEvent(event); } }; observer.startObserving(UEVENT_KEYBOARD_BACKLIGHT_TAG); } @Override Loading Loading @@ -386,6 +402,34 @@ final class KeyboardBacklightController implements } } @VisibleForTesting public void onKeyboardBacklightUEvent(UEventObserver.UEvent event) { if ("ADD".equalsIgnoreCase(event.get("ACTION")) && "LEDS".equalsIgnoreCase( event.get("SUBSYSTEM"))) { final String devPath = event.get("DEVPATH"); if (isValidBacklightNodePath(devPath)) { mNative.sysfsNodeChanged("/sys" + devPath); } } } private static boolean isValidBacklightNodePath(String devPath) { if (TextUtils.isEmpty(devPath)) { return false; } int index = devPath.lastIndexOf('/'); if (index < 0) { return false; } String backlightNode = devPath.substring(index + 1); devPath = devPath.substring(0, index); if (!devPath.endsWith("leds") || !backlightNode.contains("kbd_backlight")) { return false; } index = devPath.lastIndexOf('/'); return index >= 0; } @Override public void dump(PrintWriter pw) { IndentingPrintWriter ipw = new IndentingPrintWriter(pw); Loading
services/core/java/com/android/server/input/NativeInputManagerService.java +9 −0 Original line number Diff line number Diff line Loading @@ -237,6 +237,12 @@ interface NativeInputManagerService { /** Set whether showing a pointer icon for styluses is enabled. */ void setStylusPointerIconEnabled(boolean enabled); /** * Report sysfs node changes. This may result in recreation of the corresponding InputDevice. * The recreated device may contain new associated peripheral devices like Light, Battery, etc. */ void sysfsNodeChanged(String sysfsNodePath); /** The native implementation of InputManagerService methods. */ class NativeImpl implements NativeInputManagerService { /** Pointer to native input manager service object, used by native code. */ Loading Loading @@ -484,5 +490,8 @@ interface NativeInputManagerService { @Override public native void setStylusPointerIconEnabled(boolean enabled); @Override public native void sysfsNodeChanged(String sysfsNodePath); } }
services/core/jni/com_android_server_input_InputManagerService.cpp +9 −0 Original line number Diff line number Diff line Loading @@ -2336,6 +2336,14 @@ static void nativeReloadDeviceAliases(JNIEnv* env, jobject nativeImplObj) { InputReaderConfiguration::CHANGE_DEVICE_ALIAS); } static void nativeSysfsNodeChanged(JNIEnv* env, jobject nativeImplObj, jstring path) { ScopedUtfChars sysfsNodePathChars(env, path); const std::string sysfsNodePath = sysfsNodePathChars.c_str(); NativeInputManager* im = getNativeInputManager(env, nativeImplObj); im->getInputManager()->getReader().sysfsNodeChanged(sysfsNodePath); } static std::string dumpInputProperties() { std::string out = "Input properties:\n"; const std::string strategy = Loading Loading @@ -2651,6 +2659,7 @@ static const JNINativeMethod gInputManagerMethods[] = { {"getBatteryDevicePath", "(I)Ljava/lang/String;", (void*)nativeGetBatteryDevicePath}, {"reloadKeyboardLayouts", "()V", (void*)nativeReloadKeyboardLayouts}, {"reloadDeviceAliases", "()V", (void*)nativeReloadDeviceAliases}, {"sysfsNodeChanged", "(Ljava/lang/String;)V", (void*)nativeSysfsNodeChanged}, {"dump", "()Ljava/lang/String;", (void*)nativeDump}, {"monitor", "()V", (void*)nativeMonitor}, {"isInputDeviceEnabled", "(I)Z", (void*)nativeIsInputDeviceEnabled}, Loading
services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt +64 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import android.hardware.input.IKeyboardBacklightListener import android.hardware.input.IKeyboardBacklightState import android.hardware.input.InputManager import android.hardware.lights.Light import android.os.UEventObserver import android.os.test.TestLooper import android.platform.test.annotations.Presubmit import android.view.InputDevice Loading @@ -39,6 +40,7 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.mockito.Mock import org.mockito.Mockito.any import org.mockito.Mockito.anyInt import org.mockito.Mockito.eq import org.mockito.Mockito.spy Loading Loading @@ -95,6 +97,7 @@ class KeyboardBacklightControllerTests { private lateinit var testLooper: TestLooper private var lightColorMap: HashMap<Int, Int> = HashMap() private var lastBacklightState: KeyboardBacklightState? = null private var sysfsNodeChanges = 0 @Before fun setup() { Loading @@ -121,6 +124,9 @@ class KeyboardBacklightControllerTests { lightColorMap.put(args[1] as Int, args[2] as Int) } lightColorMap.clear() `when`(native.sysfsNodeChanged(any())).then { sysfsNodeChanges++ } } @After Loading Loading @@ -393,6 +399,64 @@ class KeyboardBacklightControllerTests { ) } @Test fun testKeyboardBacklightSysfsNodeAdded_AfterInputDeviceAdded() { var counter = sysfsNodeChanges keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent( "ACTION=add\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/leds/abc::no_backlight\u0000" )) assertEquals( "Should not reload sysfs node if UEvent path doesn't contain kbd_backlight", counter, sysfsNodeChanges ) keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent( "ACTION=add\u0000SUBSYSTEM=power\u0000DEVPATH=/xyz/leds/abc::kbd_backlight\u0000" )) assertEquals( "Should not reload sysfs node if UEvent doesn't belong to subsystem LED", counter, sysfsNodeChanges ) keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent( "ACTION=remove\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/leds/abc::kbd_backlight\u0000" )) assertEquals( "Should not reload sysfs node if UEvent doesn't have ACTION(add)", counter, sysfsNodeChanges ) keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent( "ACTION=add\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/pqr/abc::kbd_backlight\u0000" )) assertEquals( "Should not reload sysfs node if UEvent path doesn't belong to leds/ directory", counter, sysfsNodeChanges ) keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent( "ACTION=add\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/leds/abc::kbd_backlight\u0000" )) assertEquals( "Should reload sysfs node if a valid Keyboard backlight LED UEvent occurs", ++counter, sysfsNodeChanges ) keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent( "ACTION=add\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/leds/abc:kbd_backlight:red\u0000" )) assertEquals( "Should reload sysfs node if a valid Keyboard backlight LED UEvent occurs", ++counter, sysfsNodeChanges ) } inner class KeyboardBacklightListener : IKeyboardBacklightListener.Stub() { override fun onBrightnessChanged( deviceId: Int, Loading