Merge "Add KeyboardBacklightListener APIs to listen to backlight changes"
This commit is contained in:
committed by
Android (Google) Code Review
commit
8bdd5306a1
@@ -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