From 47c92077804aad2dc8504a90475bc2f4b7e72eb1 Mon Sep 17 00:00:00 2001 From: Vaibhav Devmurari Date: Mon, 19 Dec 2022 12:53:57 +0000 Subject: [PATCH] Add KeyboardBacklightListener APIs to listen to backlight changes API is going to be used by SysUI to show a popup UI, on user triggered backlight changes. Currently don't have ambient light based backlight changes supported but will be supported in Android U. SysUI pop up will be shown only when user triggered. Bug: 261570986 Test: atest KeyboardBacklightControlletTests Test: atest KeyboardBacklightListenerTest Change-Id: Iba74f10b903744081196e1f9b7c89ad606dc15f1 --- .../android/hardware/input/IInputManager.aidl | 12 ++ .../input/IKeyboardBacklightListener.aidl | 28 +++ .../input/IKeyboardBacklightState.aidl | 27 +++ .../android/hardware/input/InputManager.java | 150 ++++++++++++++++ .../input/KeyboardBacklightState.java | 41 +++++ core/res/AndroidManifest.xml | 6 + .../input/KeyboardBacklightListenerTest.kt | 163 ++++++++++++++++++ .../server/input/InputManagerService.java | 19 ++ .../input/KeyboardBacklightController.java | 114 ++++++++++++ .../input/KeyboardBacklightControllerTests.kt | 82 ++++++++- 10 files changed, 638 insertions(+), 4 deletions(-) create mode 100644 core/java/android/hardware/input/IKeyboardBacklightListener.aidl create mode 100644 core/java/android/hardware/input/IKeyboardBacklightState.aidl create mode 100644 core/java/android/hardware/input/KeyboardBacklightState.java create mode 100644 core/tests/coretests/src/android/hardware/input/KeyboardBacklightListenerTest.kt diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index 6314cabd298dd..222cf080379c1 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -22,6 +22,8 @@ import android.hardware.input.KeyboardLayout; import android.hardware.input.IInputDevicesChangedListener; import android.hardware.input.IInputDeviceBatteryListener; import android.hardware.input.IInputDeviceBatteryState; +import android.hardware.input.IKeyboardBacklightListener; +import android.hardware.input.IKeyboardBacklightState; import android.hardware.input.ITabletModeChangedListener; import android.hardware.input.TouchCalibration; import android.os.CombinedVibration; @@ -221,4 +223,14 @@ interface IInputManager { @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " + "android.Manifest.permission.MONITOR_INPUT)") void pilferPointers(IBinder inputChannelToken); + + @EnforcePermission("MONITOR_KEYBOARD_BACKLIGHT") + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " + + "android.Manifest.permission.MONITOR_KEYBOARD_BACKLIGHT)") + void registerKeyboardBacklightListener(IKeyboardBacklightListener listener); + + @EnforcePermission("MONITOR_KEYBOARD_BACKLIGHT") + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " + + "android.Manifest.permission.MONITOR_KEYBOARD_BACKLIGHT)") + void unregisterKeyboardBacklightListener(IKeyboardBacklightListener listener); } diff --git a/core/java/android/hardware/input/IKeyboardBacklightListener.aidl b/core/java/android/hardware/input/IKeyboardBacklightListener.aidl new file mode 100644 index 0000000000000..2c1f2520350d8 --- /dev/null +++ b/core/java/android/hardware/input/IKeyboardBacklightListener.aidl @@ -0,0 +1,28 @@ +/* + * 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 android.hardware.input; + +import android.hardware.input.IKeyboardBacklightState; + +/** @hide */ +oneway interface IKeyboardBacklightListener { + + /** + * Called when the keyboard backlight brightness is changed. + */ + void onBrightnessChanged(int deviceId, in IKeyboardBacklightState state, boolean isTriggeredByKeyPress); +} diff --git a/core/java/android/hardware/input/IKeyboardBacklightState.aidl b/core/java/android/hardware/input/IKeyboardBacklightState.aidl new file mode 100644 index 0000000000000..b04aa66d3e81d --- /dev/null +++ b/core/java/android/hardware/input/IKeyboardBacklightState.aidl @@ -0,0 +1,27 @@ +/* + * 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 android.hardware.input; + +/** @hide */ +@JavaDerive(equals=true) +parcelable IKeyboardBacklightState { + /** Current brightness level of the keyboard backlight in the range [0, maxBrightnessLevel]*/ + int brightnessLevel; + + /** Maximum brightness level of keyboard backlight */ + int maxBrightnessLevel; +} \ No newline at end of file diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index 655e5981971fa..fb201cfbbe38d 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -119,6 +119,12 @@ public final class InputManager { @GuardedBy("mBatteryListenersLock") private IInputDeviceBatteryListener mInputDeviceBatteryListener; + private final Object mKeyboardBacklightListenerLock = new Object(); + @GuardedBy("mKeyboardBacklightListenerLock") + private List mKeyboardBacklightListeners; + @GuardedBy("mKeyboardBacklightListenerLock") + private IKeyboardBacklightListener mKeyboardBacklightListener; + private InputDeviceSensorManager mInputDeviceSensorManager; /** * Broadcast Action: Query available keyboard layouts. @@ -2280,6 +2286,74 @@ public final class InputManager { // TODO: set the right setting } + /** + * Registers a Keyboard backlight change listener to be notified about {@link + * KeyboardBacklightState} changes for connected keyboard devices. + * + * @param executor an executor on which the callback will be called + * @param listener the {@link KeyboardBacklightListener} + * @hide + * @see #unregisterKeyboardBacklightListener(KeyboardBacklightListener) + * @throws IllegalArgumentException if {@code listener} has already been registered previously. + * @throws NullPointerException if {@code listener} or {@code executor} is null. + */ + @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_BACKLIGHT) + public void registerKeyboardBacklightListener(@NonNull Executor executor, + @NonNull KeyboardBacklightListener listener) throws IllegalArgumentException { + Objects.requireNonNull(executor, "executor should not be null"); + Objects.requireNonNull(listener, "listener should not be null"); + + synchronized (mKeyboardBacklightListenerLock) { + if (mKeyboardBacklightListener == null) { + mKeyboardBacklightListeners = new ArrayList<>(); + mKeyboardBacklightListener = new LocalKeyboardBacklightListener(); + + try { + mIm.registerKeyboardBacklightListener(mKeyboardBacklightListener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + for (KeyboardBacklightListenerDelegate delegate : mKeyboardBacklightListeners) { + if (delegate.mListener == listener) { + throw new IllegalArgumentException("Listener has already been registered!"); + } + } + KeyboardBacklightListenerDelegate delegate = + new KeyboardBacklightListenerDelegate(listener, executor); + mKeyboardBacklightListeners.add(delegate); + } + } + + /** + * Unregisters a previously added Keyboard backlight change listener. + * + * @param listener the {@link KeyboardBacklightListener} + * @see #registerKeyboardBacklightListener(Executor, KeyboardBacklightListener) + * @hide + */ + @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_BACKLIGHT) + public void unregisterKeyboardBacklightListener( + @NonNull KeyboardBacklightListener listener) { + Objects.requireNonNull(listener, "listener should not be null"); + + synchronized (mKeyboardBacklightListenerLock) { + if (mKeyboardBacklightListeners == null) { + return; + } + mKeyboardBacklightListeners.removeIf((delegate) -> delegate.mListener == listener); + if (mKeyboardBacklightListeners.isEmpty()) { + try { + mIm.unregisterKeyboardBacklightListener(mKeyboardBacklightListener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + mKeyboardBacklightListeners = null; + mKeyboardBacklightListener = null; + } + } + } + /** * A callback used to be notified about battery state changes for an input device. The * {@link #onBatteryStateChanged(int, long, BatteryState)} method will be called once after the @@ -2373,6 +2447,27 @@ public final class InputManager { void onTabletModeChanged(long whenNanos, boolean inTabletMode); } + /** + * A callback used to be notified about keyboard backlight state changes for keyboard device. + * The {@link #onKeyboardBacklightChanged(int, KeyboardBacklightState, boolean)} method + * will be called once after the listener is successfully registered to provide the initial + * keyboard backlight state of the device. + * @see #registerKeyboardBacklightListener(Executor, KeyboardBacklightListener) + * @see #unregisterKeyboardBacklightListener(KeyboardBacklightListener) + * @hide + */ + public interface KeyboardBacklightListener { + /** + * Called when the keyboard backlight brightness level changes. + * @param deviceId the keyboard for which the backlight brightness changed. + * @param state the new keyboard backlight state, never null. + * @param isTriggeredByKeyPress whether brightness change was triggered by the user + * pressing up/down key on the keyboard. + */ + void onKeyboardBacklightChanged( + int deviceId, @NonNull KeyboardBacklightState state, boolean isTriggeredByKeyPress); + } + private final class TabletModeChangedListener extends ITabletModeChangedListener.Stub { @Override public void onTabletModeChanged(long whenNanos, boolean inTabletMode) { @@ -2481,4 +2576,59 @@ public final class InputManager { } } } + + // Implementation of the android.hardware.input.KeyboardBacklightState interface used to report + // the keyboard backlight state via the KeyboardBacklightListener interfaces. + private static final class LocalKeyboardBacklightState extends KeyboardBacklightState { + + private final int mBrightnessLevel; + private final int mMaxBrightnessLevel; + + LocalKeyboardBacklightState(int brightnesslevel, int maxBrightnessLevel) { + mBrightnessLevel = brightnesslevel; + mMaxBrightnessLevel = maxBrightnessLevel; + } + + @Override + public int getBrightnessLevel() { + return mBrightnessLevel; + } + + @Override + public int getMaxBrightnessLevel() { + return mMaxBrightnessLevel; + } + } + + private static final class KeyboardBacklightListenerDelegate { + final KeyboardBacklightListener mListener; + final Executor mExecutor; + + KeyboardBacklightListenerDelegate(KeyboardBacklightListener listener, Executor executor) { + mListener = listener; + mExecutor = executor; + } + + void notifyKeyboardBacklightChange(int deviceId, IKeyboardBacklightState state, + boolean isTriggeredByKeyPress) { + mExecutor.execute(() -> + mListener.onKeyboardBacklightChanged(deviceId, + new LocalKeyboardBacklightState(state.brightnessLevel, + state.maxBrightnessLevel), isTriggeredByKeyPress)); + } + } + + private class LocalKeyboardBacklightListener extends IKeyboardBacklightListener.Stub { + + @Override + public void onBrightnessChanged(int deviceId, IKeyboardBacklightState state, + boolean isTriggeredByKeyPress) { + synchronized (mKeyboardBacklightListenerLock) { + if (mKeyboardBacklightListeners == null) return; + for (KeyboardBacklightListenerDelegate delegate : mKeyboardBacklightListeners) { + delegate.notifyKeyboardBacklightChange(deviceId, state, isTriggeredByKeyPress); + } + } + } + } } diff --git a/core/java/android/hardware/input/KeyboardBacklightState.java b/core/java/android/hardware/input/KeyboardBacklightState.java new file mode 100644 index 0000000000000..4beaf6d36d7fc --- /dev/null +++ b/core/java/android/hardware/input/KeyboardBacklightState.java @@ -0,0 +1,41 @@ +/* + * 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 android.hardware.input; + +/** + * The KeyboardBacklightState class is a representation of a keyboard backlight which is a + * single-colored backlight that illuminates all the keys on the keyboard. + * + * @hide + */ +public abstract class KeyboardBacklightState { + + /** + * Get the backlight brightness level in range [0, {@link #getMaxBrightnessLevel()}]. + * + * @return backlight brightness level + */ + public abstract int getBrightnessLevel(); + + /** + * Get the max backlight brightness level. + * + * @return max backlight brightness level + */ + public abstract int getMaxBrightnessLevel(); +} + diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 5f1a444d6cf62..fed812ebf3908 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -6855,6 +6855,12 @@ + + +