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:
Vaibhav Devmurari
2022-12-19 12:53:57 +00:00
parent 84f91603c0
commit 47c9207780
10 changed files with 638 additions and 4 deletions

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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);
}
}
}
}
}

View 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();
}

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -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;

View File

@@ -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
+ "}";
}
}
}

View File

@@ -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
)
}