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
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -119,6 +119,12 @@ public final class InputManager {
|
||||
@GuardedBy("mBatteryListenersLock")
|
||||
private IInputDeviceBatteryListener mInputDeviceBatteryListener;
|
||||
|
||||
private final Object mKeyboardBacklightListenerLock = new Object();
|
||||
@GuardedBy("mKeyboardBacklightListenerLock")
|
||||
private List<KeyboardBacklightListenerDelegate> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
41
core/java/android/hardware/input/KeyboardBacklightState.java
Normal file
41
core/java/android/hardware/input/KeyboardBacklightState.java
Normal file
@@ -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();
|
||||
}
|
||||
|
||||
@@ -6855,6 +6855,12 @@
|
||||
<permission android:name="android.permission.REMAP_MODIFIER_KEYS"
|
||||
android:protectionLevel="signature" />
|
||||
|
||||
<!-- Allows low-level access to monitor keyboard backlight changes.
|
||||
<p>Not for use by third-party applications.
|
||||
@hide -->
|
||||
<permission android:name="android.permission.MONITOR_KEYBOARD_BACKLIGHT"
|
||||
android:protectionLevel="signature" />
|
||||
|
||||
<uses-permission android:name="android.permission.HANDLE_QUERY_PACKAGE_RESTART" />
|
||||
|
||||
<!-- Allows financed device kiosk apps to perform actions on the Device Lock service
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* 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.os.Handler
|
||||
import android.os.HandlerExecutor
|
||||
import android.os.test.TestLooper
|
||||
import android.platform.test.annotations.Presubmit
|
||||
import com.android.server.testutils.any
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.doAnswer
|
||||
import org.mockito.junit.MockitoJUnit
|
||||
import org.mockito.junit.MockitoJUnitRunner
|
||||
import java.util.concurrent.Executor
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.fail
|
||||
|
||||
/**
|
||||
* Tests for [InputManager.KeyboardBacklightListener].
|
||||
*
|
||||
* Build/Install/Run:
|
||||
* atest FrameworksCoreTests:KeyboardBacklightListenerTest
|
||||
*/
|
||||
@Presubmit
|
||||
@RunWith(MockitoJUnitRunner::class)
|
||||
class KeyboardBacklightListenerTest {
|
||||
@get:Rule
|
||||
val rule = MockitoJUnit.rule()!!
|
||||
|
||||
private lateinit var testLooper: TestLooper
|
||||
private var registeredListener: IKeyboardBacklightListener? = null
|
||||
private lateinit var executor: Executor
|
||||
private lateinit var inputManager: InputManager
|
||||
|
||||
@Mock
|
||||
private lateinit var iInputManagerMock: IInputManager
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
testLooper = TestLooper()
|
||||
executor = HandlerExecutor(Handler(testLooper.looper))
|
||||
registeredListener = null
|
||||
inputManager = InputManager.resetInstance(iInputManagerMock)
|
||||
|
||||
// Handle keyboard backlight listener registration.
|
||||
doAnswer {
|
||||
val listener = it.getArgument(0) as IKeyboardBacklightListener
|
||||
if (registeredListener != null &&
|
||||
registeredListener!!.asBinder() != listener.asBinder()) {
|
||||
// There can only be one registered keyboard backlight listener per process.
|
||||
fail("Trying to register a new listener when one already exists")
|
||||
}
|
||||
registeredListener = listener
|
||||
null
|
||||
}.`when`(iInputManagerMock).registerKeyboardBacklightListener(any())
|
||||
|
||||
// Handle keyboard backlight listener being unregistered.
|
||||
doAnswer {
|
||||
val listener = it.getArgument(0) as IKeyboardBacklightListener
|
||||
if (registeredListener == null ||
|
||||
registeredListener!!.asBinder() != listener.asBinder()) {
|
||||
fail("Trying to unregister a listener that is not registered")
|
||||
}
|
||||
registeredListener = null
|
||||
null
|
||||
}.`when`(iInputManagerMock).unregisterKeyboardBacklightListener(any())
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
InputManager.clearInstance()
|
||||
}
|
||||
|
||||
private fun notifyKeyboardBacklightChanged(
|
||||
deviceId: Int,
|
||||
brightnessLevel: Int,
|
||||
maxBrightnessLevel: Int = 10,
|
||||
isTriggeredByKeyPress: Boolean = true
|
||||
) {
|
||||
registeredListener!!.onBrightnessChanged(deviceId, IKeyboardBacklightState().apply {
|
||||
this.brightnessLevel = brightnessLevel
|
||||
this.maxBrightnessLevel = maxBrightnessLevel
|
||||
}, isTriggeredByKeyPress)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testListenerIsNotifiedCorrectly() {
|
||||
var callbackCount = 0
|
||||
|
||||
// Add a keyboard backlight listener
|
||||
inputManager.registerKeyboardBacklightListener(executor) {
|
||||
deviceId: Int,
|
||||
keyboardBacklightState: KeyboardBacklightState,
|
||||
isTriggeredByKeyPress: Boolean ->
|
||||
callbackCount++
|
||||
assertEquals(1, deviceId)
|
||||
assertEquals(2, keyboardBacklightState.brightnessLevel)
|
||||
assertEquals(10, keyboardBacklightState.maxBrightnessLevel)
|
||||
assertEquals(true, isTriggeredByKeyPress)
|
||||
}
|
||||
|
||||
// Adding the listener should register the callback with InputManagerService.
|
||||
assertNotNull(registeredListener)
|
||||
|
||||
// Notifying keyboard backlight change will notify the listener.
|
||||
notifyKeyboardBacklightChanged(1 /*deviceId*/, 2 /* brightnessLevel */)
|
||||
testLooper.dispatchNext()
|
||||
assertEquals(1, callbackCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMultipleListeners() {
|
||||
// Set up two callbacks.
|
||||
var callbackCount1 = 0
|
||||
var callbackCount2 = 0
|
||||
val callback1 = InputManager.KeyboardBacklightListener { _, _, _ -> callbackCount1++ }
|
||||
val callback2 = InputManager.KeyboardBacklightListener { _, _, _ -> callbackCount2++ }
|
||||
|
||||
// Add both keyboard backlight listeners
|
||||
inputManager.registerKeyboardBacklightListener(executor, callback1)
|
||||
inputManager.registerKeyboardBacklightListener(executor, callback2)
|
||||
|
||||
// Adding the listeners should register the callback with InputManagerService.
|
||||
assertNotNull(registeredListener)
|
||||
|
||||
// Notifying keyboard backlight change trigger the both callbacks.
|
||||
notifyKeyboardBacklightChanged(1 /*deviceId*/, 1 /* brightnessLevel */)
|
||||
testLooper.dispatchAll()
|
||||
assertEquals(1, callbackCount1)
|
||||
assertEquals(1, callbackCount2)
|
||||
|
||||
inputManager.unregisterKeyboardBacklightListener(callback2)
|
||||
// Notifying keyboard backlight change should still trigger callback1.
|
||||
notifyKeyboardBacklightChanged(1 /*deviceId*/, 2 /* brightnessLevel */)
|
||||
testLooper.dispatchAll()
|
||||
assertEquals(2, callbackCount1)
|
||||
|
||||
// Unregister all listeners, should remove registered listener from InputManagerService
|
||||
inputManager.unregisterKeyboardBacklightListener(callback1)
|
||||
assertNull(registeredListener)
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@ import android.hardware.input.IInputDeviceBatteryState;
|
||||
import android.hardware.input.IInputDevicesChangedListener;
|
||||
import android.hardware.input.IInputManager;
|
||||
import android.hardware.input.IInputSensorEventListener;
|
||||
import android.hardware.input.IKeyboardBacklightListener;
|
||||
import android.hardware.input.ITabletModeChangedListener;
|
||||
import android.hardware.input.InputDeviceIdentifier;
|
||||
import android.hardware.input.InputManager;
|
||||
@@ -2226,6 +2227,24 @@ public class InputManagerService extends IInputManager.Stub
|
||||
mNative.pilferPointers(inputChannelToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
@EnforcePermission(Manifest.permission.MONITOR_KEYBOARD_BACKLIGHT)
|
||||
public void registerKeyboardBacklightListener(IKeyboardBacklightListener listener) {
|
||||
super.registerKeyboardBacklightListener_enforcePermission();
|
||||
Objects.requireNonNull(listener);
|
||||
mKeyboardBacklightController.registerKeyboardBacklightListener(listener,
|
||||
Binder.getCallingPid());
|
||||
}
|
||||
|
||||
@Override
|
||||
@EnforcePermission(Manifest.permission.MONITOR_KEYBOARD_BACKLIGHT)
|
||||
public void unregisterKeyboardBacklightListener(IKeyboardBacklightListener listener) {
|
||||
super.unregisterKeyboardBacklightListener_enforcePermission();
|
||||
Objects.requireNonNull(listener);
|
||||
mKeyboardBacklightController.unregisterKeyboardBacklightListener(listener,
|
||||
Binder.getCallingPid());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
||||
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
|
||||
|
||||
@@ -16,14 +16,19 @@
|
||||
|
||||
package com.android.server.input;
|
||||
|
||||
import android.annotation.BinderThread;
|
||||
import android.annotation.ColorInt;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.hardware.input.IKeyboardBacklightListener;
|
||||
import android.hardware.input.IKeyboardBacklightState;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.hardware.lights.Light;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
@@ -68,6 +73,11 @@ final class KeyboardBacklightController implements InputManager.InputDeviceListe
|
||||
private final Handler mHandler;
|
||||
private final SparseArray<Light> mKeyboardBacklights = new SparseArray<>();
|
||||
|
||||
// List of currently registered keyboard backlight listeners
|
||||
@GuardedBy("mKeyboardBacklightListenerRecords")
|
||||
private final SparseArray<KeyboardBacklightListenerRecord> mKeyboardBacklightListenerRecords =
|
||||
new SparseArray<>();
|
||||
|
||||
static {
|
||||
// Fixed brightness levels to avoid issues when converting back and forth from the
|
||||
// device brightness range to [0-255]
|
||||
@@ -129,6 +139,9 @@ final class KeyboardBacklightController implements InputManager.InputDeviceListe
|
||||
Slog.d(TAG, "Changing brightness from " + currBrightness + " to " + newBrightness);
|
||||
}
|
||||
|
||||
notifyKeyboardBacklightChanged(deviceId, BRIGHTNESS_LEVELS.headSet(newBrightness).size(),
|
||||
true/* isTriggeredByKeyPress */);
|
||||
|
||||
synchronized (mDataStore) {
|
||||
try {
|
||||
mDataStore.setKeyboardBacklightBrightness(inputDevice.getDescriptor(),
|
||||
@@ -217,6 +230,62 @@ final class KeyboardBacklightController implements InputManager.InputDeviceListe
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Register the keyboard backlight listener for a process. */
|
||||
@BinderThread
|
||||
public void registerKeyboardBacklightListener(IKeyboardBacklightListener listener,
|
||||
int pid) {
|
||||
synchronized (mKeyboardBacklightListenerRecords) {
|
||||
if (mKeyboardBacklightListenerRecords.get(pid) != null) {
|
||||
throw new IllegalStateException("The calling process has already registered "
|
||||
+ "a KeyboardBacklightListener.");
|
||||
}
|
||||
KeyboardBacklightListenerRecord record = new KeyboardBacklightListenerRecord(pid,
|
||||
listener);
|
||||
try {
|
||||
listener.asBinder().linkToDeath(record, 0);
|
||||
} catch (RemoteException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
mKeyboardBacklightListenerRecords.put(pid, record);
|
||||
}
|
||||
}
|
||||
|
||||
/** Unregister the keyboard backlight listener for a process. */
|
||||
@BinderThread
|
||||
public void unregisterKeyboardBacklightListener(IKeyboardBacklightListener listener,
|
||||
int pid) {
|
||||
synchronized (mKeyboardBacklightListenerRecords) {
|
||||
KeyboardBacklightListenerRecord record = mKeyboardBacklightListenerRecords.get(pid);
|
||||
if (record == null) {
|
||||
throw new IllegalStateException("The calling process has no registered "
|
||||
+ "KeyboardBacklightListener.");
|
||||
}
|
||||
if (record.mListener != listener) {
|
||||
throw new IllegalStateException("The calling process has a different registered "
|
||||
+ "KeyboardBacklightListener.");
|
||||
}
|
||||
record.mListener.asBinder().unlinkToDeath(record, 0);
|
||||
mKeyboardBacklightListenerRecords.remove(pid);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyKeyboardBacklightChanged(int deviceId, int currentBacklightLevel,
|
||||
boolean isTriggeredByKeyPress) {
|
||||
synchronized (mKeyboardBacklightListenerRecords) {
|
||||
for (int i = 0; i < mKeyboardBacklightListenerRecords.size(); i++) {
|
||||
mKeyboardBacklightListenerRecords.valueAt(i).notifyKeyboardBacklightChanged(
|
||||
deviceId, new KeyboardBacklightState(currentBacklightLevel),
|
||||
isTriggeredByKeyPress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onKeyboardBacklightListenerDied(int pid) {
|
||||
synchronized (mKeyboardBacklightListenerRecords) {
|
||||
mKeyboardBacklightListenerRecords.remove(pid);
|
||||
}
|
||||
}
|
||||
|
||||
void dump(PrintWriter pw) {
|
||||
IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
|
||||
ipw.println(TAG + ": " + mKeyboardBacklights.size() + " keyboard backlights");
|
||||
@@ -227,4 +296,49 @@ final class KeyboardBacklightController implements InputManager.InputDeviceListe
|
||||
}
|
||||
ipw.decreaseIndent();
|
||||
}
|
||||
|
||||
// A record of a registered Keyboard backlight listener from one process.
|
||||
private class KeyboardBacklightListenerRecord implements IBinder.DeathRecipient {
|
||||
public final int mPid;
|
||||
public final IKeyboardBacklightListener mListener;
|
||||
|
||||
KeyboardBacklightListenerRecord(int pid, IKeyboardBacklightListener listener) {
|
||||
mPid = pid;
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binderDied() {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Keyboard backlight listener for pid " + mPid + " died.");
|
||||
}
|
||||
onKeyboardBacklightListenerDied(mPid);
|
||||
}
|
||||
|
||||
public void notifyKeyboardBacklightChanged(int deviceId, IKeyboardBacklightState state,
|
||||
boolean isTriggeredByKeyPress) {
|
||||
try {
|
||||
mListener.onBrightnessChanged(deviceId, state, isTriggeredByKeyPress);
|
||||
} catch (RemoteException ex) {
|
||||
Slog.w(TAG, "Failed to notify process " + mPid
|
||||
+ " that keyboard backlight changed, assuming it died.", ex);
|
||||
binderDied();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class KeyboardBacklightState extends IKeyboardBacklightState {
|
||||
|
||||
KeyboardBacklightState(int brightnessLevel) {
|
||||
this.brightnessLevel = brightnessLevel;
|
||||
this.maxBrightnessLevel = NUM_BRIGHTNESS_CHANGE_STEPS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "KeyboardBacklightState{brightnessLevel=" + brightnessLevel
|
||||
+ ", maxBrightnessLevel=" + maxBrightnessLevel
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.graphics.Color
|
||||
import android.hardware.input.IInputManager
|
||||
import android.hardware.input.IKeyboardBacklightListener
|
||||
import android.hardware.input.IKeyboardBacklightState
|
||||
import android.hardware.input.InputManager
|
||||
import android.hardware.lights.Light
|
||||
import android.os.test.TestLooper
|
||||
@@ -27,10 +29,6 @@ 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
|
||||
@@ -45,6 +43,10 @@ import org.mockito.Mockito.eq
|
||||
import org.mockito.Mockito.spy
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.junit.MockitoJUnit
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
private fun createKeyboard(deviceId: Int): InputDevice =
|
||||
InputDevice.Builder()
|
||||
@@ -90,6 +92,7 @@ class KeyboardBacklightControllerTests {
|
||||
private lateinit var dataStore: PersistentDataStore
|
||||
private lateinit var testLooper: TestLooper
|
||||
private var lightColorMap: HashMap<Int, Int> = HashMap()
|
||||
private var lastBacklightState: KeyboardBacklightState? = null
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
@@ -310,4 +313,75 @@ class KeyboardBacklightControllerTests {
|
||||
lightColorMap[LIGHT_ID]
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testKeyboardBacklightT_registerUnregisterListener() {
|
||||
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)
|
||||
|
||||
// Register backlight listener
|
||||
val listener = KeyboardBacklightListener()
|
||||
keyboardBacklightController.registerKeyboardBacklightListener(listener, 0)
|
||||
|
||||
lastBacklightState = null
|
||||
keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
|
||||
testLooper.dispatchNext()
|
||||
|
||||
assertEquals(
|
||||
"Backlight state device Id should be $DEVICE_ID",
|
||||
DEVICE_ID,
|
||||
lastBacklightState!!.deviceId
|
||||
)
|
||||
assertEquals(
|
||||
"Backlight state brightnessLevel should be " + 1,
|
||||
1,
|
||||
lastBacklightState!!.brightnessLevel
|
||||
)
|
||||
assertEquals(
|
||||
"Backlight state maxBrightnessLevel should be " + (BRIGHTNESS_LEVELS.size - 1),
|
||||
(BRIGHTNESS_LEVELS.size - 1),
|
||||
lastBacklightState!!.maxBrightnessLevel
|
||||
)
|
||||
assertEquals(
|
||||
"Backlight state isTriggeredByKeyPress should be true",
|
||||
true,
|
||||
lastBacklightState!!.isTriggeredByKeyPress
|
||||
)
|
||||
|
||||
// Unregister listener
|
||||
keyboardBacklightController.unregisterKeyboardBacklightListener(listener, 0)
|
||||
|
||||
lastBacklightState = null
|
||||
keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
|
||||
testLooper.dispatchNext()
|
||||
|
||||
assertNull("Listener should not receive any updates", lastBacklightState)
|
||||
}
|
||||
|
||||
inner class KeyboardBacklightListener : IKeyboardBacklightListener.Stub() {
|
||||
override fun onBrightnessChanged(
|
||||
deviceId: Int,
|
||||
state: IKeyboardBacklightState,
|
||||
isTriggeredByKeyPress: Boolean
|
||||
) {
|
||||
lastBacklightState = KeyboardBacklightState(
|
||||
deviceId,
|
||||
state.brightnessLevel,
|
||||
state.maxBrightnessLevel,
|
||||
isTriggeredByKeyPress
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class KeyboardBacklightState(
|
||||
val deviceId: Int,
|
||||
val brightnessLevel: Int,
|
||||
val maxBrightnessLevel: Int,
|
||||
val isTriggeredByKeyPress: Boolean
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user