diff --git a/services/core/java/com/android/server/input/KeyboardBacklightController.java b/services/core/java/com/android/server/input/KeyboardBacklightController.java index 403323803fcae..048308e75e804 100644 --- a/services/core/java/com/android/server/input/KeyboardBacklightController.java +++ b/services/core/java/com/android/server/input/KeyboardBacklightController.java @@ -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; @@ -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(); @@ -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 @@ -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); diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java index a0918e4f7ea74..aeb2477f88906 100644 --- a/services/core/java/com/android/server/input/NativeInputManagerService.java +++ b/services/core/java/com/android/server/input/NativeInputManagerService.java @@ -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. */ @@ -484,5 +490,8 @@ interface NativeInputManagerService { @Override public native void setStylusPointerIconEnabled(boolean enabled); + + @Override + public native void sysfsNodeChanged(String sysfsNodePath); } } diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 075dcd52f487e..71acbb439c887 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -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 = @@ -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}, diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt index 1d23e12d0a103..00eb80d21f474 100644 --- a/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt +++ b/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt @@ -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 @@ -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 @@ -95,6 +97,7 @@ class KeyboardBacklightControllerTests { private lateinit var testLooper: TestLooper private var lightColorMap: HashMap = HashMap() private var lastBacklightState: KeyboardBacklightState? = null + private var sysfsNodeChanges = 0 @Before fun setup() { @@ -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 @@ -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,