Merge "Add InputDeviceBatteryListener to InputManager" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
666111dfa7
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* 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 */
|
||||||
|
oneway interface IInputDeviceBatteryListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when there is a change in battery state for a monitored device. This will be called
|
||||||
|
* immediately after the listener is successfully registered for a new device via IInputManager.
|
||||||
|
* The parameters are values exposed through {@link android.hardware.BatteryState}.
|
||||||
|
*/
|
||||||
|
void onBatteryStateChanged(int deviceId, boolean isBatteryPresent, int status, float capacity,
|
||||||
|
long eventTime);
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ import android.graphics.Rect;
|
|||||||
import android.hardware.input.InputDeviceIdentifier;
|
import android.hardware.input.InputDeviceIdentifier;
|
||||||
import android.hardware.input.KeyboardLayout;
|
import android.hardware.input.KeyboardLayout;
|
||||||
import android.hardware.input.IInputDevicesChangedListener;
|
import android.hardware.input.IInputDevicesChangedListener;
|
||||||
|
import android.hardware.input.IInputDeviceBatteryListener;
|
||||||
import android.hardware.input.ITabletModeChangedListener;
|
import android.hardware.input.ITabletModeChangedListener;
|
||||||
import android.hardware.input.TouchCalibration;
|
import android.hardware.input.TouchCalibration;
|
||||||
import android.os.CombinedVibration;
|
import android.os.CombinedVibration;
|
||||||
@@ -155,4 +156,8 @@ interface IInputManager {
|
|||||||
void closeLightSession(int deviceId, in IBinder token);
|
void closeLightSession(int deviceId, in IBinder token);
|
||||||
|
|
||||||
void cancelCurrentTouch();
|
void cancelCurrentTouch();
|
||||||
|
|
||||||
|
void registerBatteryListener(int deviceId, IInputDeviceBatteryListener listener);
|
||||||
|
|
||||||
|
void unregisterBatteryListener(int deviceId, IInputDeviceBatteryListener listener);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import android.app.ActivityThread;
|
|||||||
import android.compat.annotation.ChangeId;
|
import android.compat.annotation.ChangeId;
|
||||||
import android.compat.annotation.UnsupportedAppUsage;
|
import android.compat.annotation.UnsupportedAppUsage;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.hardware.BatteryState;
|
||||||
import android.hardware.SensorManager;
|
import android.hardware.SensorManager;
|
||||||
import android.hardware.lights.Light;
|
import android.hardware.lights.Light;
|
||||||
import android.hardware.lights.LightState;
|
import android.hardware.lights.LightState;
|
||||||
@@ -66,6 +67,7 @@ import android.view.PointerIcon;
|
|||||||
import android.view.VerifiedInputEvent;
|
import android.view.VerifiedInputEvent;
|
||||||
import android.view.WindowManager.LayoutParams;
|
import android.view.WindowManager.LayoutParams;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.GuardedBy;
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.internal.os.SomeArgs;
|
import com.android.internal.os.SomeArgs;
|
||||||
import com.android.internal.util.ArrayUtils;
|
import com.android.internal.util.ArrayUtils;
|
||||||
@@ -74,6 +76,8 @@ import java.lang.annotation.Retention;
|
|||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides information about input devices and available key layouts.
|
* Provides information about input devices and available key layouts.
|
||||||
@@ -111,6 +115,14 @@ public final class InputManager {
|
|||||||
private TabletModeChangedListener mTabletModeChangedListener;
|
private TabletModeChangedListener mTabletModeChangedListener;
|
||||||
private List<OnTabletModeChangedListenerDelegate> mOnTabletModeChangedListeners;
|
private List<OnTabletModeChangedListenerDelegate> mOnTabletModeChangedListeners;
|
||||||
|
|
||||||
|
private final Object mBatteryListenersLock = new Object();
|
||||||
|
// Maps a deviceId whose battery is currently being monitored to an entry containing the
|
||||||
|
// registered listeners for that device.
|
||||||
|
@GuardedBy("mBatteryListenersLock")
|
||||||
|
private SparseArray<RegisteredBatteryListeners> mBatteryListeners;
|
||||||
|
@GuardedBy("mBatteryListenersLock")
|
||||||
|
private IInputDeviceBatteryListener mInputDeviceBatteryListener;
|
||||||
|
|
||||||
private InputDeviceSensorManager mInputDeviceSensorManager;
|
private InputDeviceSensorManager mInputDeviceSensorManager;
|
||||||
/**
|
/**
|
||||||
* Broadcast Action: Query available keyboard layouts.
|
* Broadcast Action: Query available keyboard layouts.
|
||||||
@@ -1751,6 +1763,129 @@ public final class InputManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a battery listener to be notified about {@link BatteryState} changes for an input
|
||||||
|
* device. The same listener can be registered for multiple input devices.
|
||||||
|
* The listener will be notified of the initial battery state of the device after it is
|
||||||
|
* successfully registered.
|
||||||
|
* @param deviceId the input device that should be monitored
|
||||||
|
* @param executor an executor on which the callback will be called
|
||||||
|
* @param listener the {@link InputDeviceBatteryListener}
|
||||||
|
* @see #removeInputDeviceBatteryListener(int, InputDeviceBatteryListener)
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public void addInputDeviceBatteryListener(int deviceId, @NonNull Executor executor,
|
||||||
|
@NonNull InputDeviceBatteryListener listener) {
|
||||||
|
Objects.requireNonNull(executor, "executor should not be null");
|
||||||
|
Objects.requireNonNull(listener, "listener should not be null");
|
||||||
|
|
||||||
|
synchronized (mBatteryListenersLock) {
|
||||||
|
if (mBatteryListeners == null) {
|
||||||
|
mBatteryListeners = new SparseArray<>();
|
||||||
|
mInputDeviceBatteryListener = new LocalInputDeviceBatteryListener();
|
||||||
|
}
|
||||||
|
RegisteredBatteryListeners listenersForDevice = mBatteryListeners.get(deviceId);
|
||||||
|
if (listenersForDevice == null) {
|
||||||
|
// The deviceId is currently not being monitored for battery changes.
|
||||||
|
// Start monitoring the device.
|
||||||
|
listenersForDevice = new RegisteredBatteryListeners();
|
||||||
|
mBatteryListeners.put(deviceId, listenersForDevice);
|
||||||
|
try {
|
||||||
|
mIm.registerBatteryListener(deviceId, mInputDeviceBatteryListener);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
throw e.rethrowFromSystemServer();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The deviceId is already being monitored for battery changes.
|
||||||
|
// Ensure that the listener is not already registered.
|
||||||
|
for (InputDeviceBatteryListenerDelegate delegate : listenersForDevice.mDelegates) {
|
||||||
|
if (Objects.equals(listener, delegate.mListener)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Attempting to register an InputDeviceBatteryListener that has "
|
||||||
|
+ "already been registered for deviceId: "
|
||||||
|
+ deviceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final InputDeviceBatteryListenerDelegate delegate =
|
||||||
|
new InputDeviceBatteryListenerDelegate(listener, executor);
|
||||||
|
listenersForDevice.mDelegates.add(delegate);
|
||||||
|
|
||||||
|
// Notify the listener immediately if we already have the latest battery state.
|
||||||
|
if (listenersForDevice.mLatestBatteryState != null) {
|
||||||
|
delegate.notifyBatteryStateChanged(listenersForDevice.mLatestBatteryState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a previously registered battery listener for an input device.
|
||||||
|
* @see #addInputDeviceBatteryListener(int, Executor, InputDeviceBatteryListener)
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public void removeInputDeviceBatteryListener(int deviceId,
|
||||||
|
@NonNull InputDeviceBatteryListener listener) {
|
||||||
|
Objects.requireNonNull(listener, "listener should not be null");
|
||||||
|
|
||||||
|
synchronized (mBatteryListenersLock) {
|
||||||
|
if (mBatteryListeners == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RegisteredBatteryListeners listenersForDevice = mBatteryListeners.get(deviceId);
|
||||||
|
if (listenersForDevice == null) {
|
||||||
|
// The deviceId is not currently being monitored.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final List<InputDeviceBatteryListenerDelegate> delegates =
|
||||||
|
listenersForDevice.mDelegates;
|
||||||
|
for (int i = 0; i < delegates.size();) {
|
||||||
|
if (Objects.equals(listener, delegates.get(i).mListener)) {
|
||||||
|
delegates.remove(i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (!delegates.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// There are no more battery listeners for this deviceId. Stop monitoring this device.
|
||||||
|
mBatteryListeners.remove(deviceId);
|
||||||
|
try {
|
||||||
|
mIm.unregisterBatteryListener(deviceId, mInputDeviceBatteryListener);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
throw e.rethrowFromSystemServer();
|
||||||
|
}
|
||||||
|
if (mBatteryListeners.size() == 0) {
|
||||||
|
// There are no more devices being monitored, so the registered
|
||||||
|
// IInputDeviceBatteryListener will be automatically dropped by the server.
|
||||||
|
mBatteryListeners = null;
|
||||||
|
mInputDeviceBatteryListener = 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
|
||||||
|
* listener is successfully registered to provide the initial battery state of the device.
|
||||||
|
* @see InputDevice#getBatteryState()
|
||||||
|
* @see #addInputDeviceBatteryListener(int, Executor, InputDeviceBatteryListener)
|
||||||
|
* @see #removeInputDeviceBatteryListener(int, InputDeviceBatteryListener)
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public interface InputDeviceBatteryListener {
|
||||||
|
/**
|
||||||
|
* Called when the battery state of an input device changes.
|
||||||
|
* @param deviceId the input device for which the battery changed.
|
||||||
|
* @param eventTimeMillis the time (in ms) when the battery change took place.
|
||||||
|
* This timestamp is in the {@link SystemClock#uptimeMillis()} time base.
|
||||||
|
* @param batteryState the new battery state, never null.
|
||||||
|
*/
|
||||||
|
void onBatteryStateChanged(
|
||||||
|
int deviceId, long eventTimeMillis, @NonNull BatteryState batteryState);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listens for changes in input devices.
|
* Listens for changes in input devices.
|
||||||
*/
|
*/
|
||||||
@@ -1861,4 +1996,76 @@ public final class InputManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class LocalBatteryState extends BatteryState {
|
||||||
|
final int mDeviceId;
|
||||||
|
final boolean mIsPresent;
|
||||||
|
final int mStatus;
|
||||||
|
final float mCapacity;
|
||||||
|
final long mEventTime;
|
||||||
|
|
||||||
|
LocalBatteryState(int deviceId, boolean isPresent, int status, float capacity,
|
||||||
|
long eventTime) {
|
||||||
|
mDeviceId = deviceId;
|
||||||
|
mIsPresent = isPresent;
|
||||||
|
mStatus = status;
|
||||||
|
mCapacity = capacity;
|
||||||
|
mEventTime = eventTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPresent() {
|
||||||
|
return mIsPresent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getStatus() {
|
||||||
|
return mStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getCapacity() {
|
||||||
|
return mCapacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class RegisteredBatteryListeners {
|
||||||
|
final List<InputDeviceBatteryListenerDelegate> mDelegates = new ArrayList<>();
|
||||||
|
LocalBatteryState mLatestBatteryState;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class InputDeviceBatteryListenerDelegate {
|
||||||
|
final InputDeviceBatteryListener mListener;
|
||||||
|
final Executor mExecutor;
|
||||||
|
|
||||||
|
InputDeviceBatteryListenerDelegate(InputDeviceBatteryListener listener, Executor executor) {
|
||||||
|
mListener = listener;
|
||||||
|
mExecutor = executor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void notifyBatteryStateChanged(LocalBatteryState batteryState) {
|
||||||
|
mExecutor.execute(() ->
|
||||||
|
mListener.onBatteryStateChanged(batteryState.mDeviceId, batteryState.mEventTime,
|
||||||
|
batteryState));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LocalInputDeviceBatteryListener extends IInputDeviceBatteryListener.Stub {
|
||||||
|
@Override
|
||||||
|
public void onBatteryStateChanged(int deviceId, boolean isBatteryPresent, int status,
|
||||||
|
float capacity, long eventTime) {
|
||||||
|
synchronized (mBatteryListenersLock) {
|
||||||
|
if (mBatteryListeners == null) return;
|
||||||
|
final RegisteredBatteryListeners entry = mBatteryListeners.get(deviceId);
|
||||||
|
if (entry == null) return;
|
||||||
|
|
||||||
|
entry.mLatestBatteryState =
|
||||||
|
new LocalBatteryState(
|
||||||
|
deviceId, isBatteryPresent, status, capacity, eventTime);
|
||||||
|
for (InputDeviceBatteryListenerDelegate delegate : entry.mDelegates) {
|
||||||
|
delegate.notifyBatteryStateChanged(entry.mLatestBatteryState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1021,6 +1021,15 @@ public final class InputDevice implements Parcelable {
|
|||||||
InputManager.getInstance().setCustomPointerIcon(icon);
|
InputManager.getInstance().setCustomPointerIcon(icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reports whether the device has a battery.
|
||||||
|
* @return true if the device has a battery, false otherwise.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public boolean hasBattery() {
|
||||||
|
return mHasBattery;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides information about the range of values for a particular {@link MotionEvent} axis.
|
* Provides information about the range of values for a particular {@link MotionEvent} axis.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,227 @@
|
|||||||
|
/*
|
||||||
|
* 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.BatteryState
|
||||||
|
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 java.util.concurrent.Executor
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
import kotlin.test.fail
|
||||||
|
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.anyInt
|
||||||
|
import org.mockito.Mockito.doAnswer
|
||||||
|
import org.mockito.junit.MockitoJUnit
|
||||||
|
import org.mockito.junit.MockitoJUnitRunner
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for [InputManager.InputDeviceBatteryListener].
|
||||||
|
*
|
||||||
|
* Build/Install/Run:
|
||||||
|
* atest FrameworksCoreTests:InputDeviceBatteryListenerTest
|
||||||
|
*/
|
||||||
|
@Presubmit
|
||||||
|
@RunWith(MockitoJUnitRunner::class)
|
||||||
|
class InputDeviceBatteryListenerTest {
|
||||||
|
@get:Rule
|
||||||
|
val rule = MockitoJUnit.rule()!!
|
||||||
|
|
||||||
|
private lateinit var testLooper: TestLooper
|
||||||
|
private var registeredListener: IInputDeviceBatteryListener? = null
|
||||||
|
private val monitoredDevices = mutableListOf<Int>()
|
||||||
|
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
|
||||||
|
monitoredDevices.clear()
|
||||||
|
inputManager = InputManager.resetInstance(iInputManagerMock)
|
||||||
|
|
||||||
|
// Handle battery listener registration.
|
||||||
|
doAnswer {
|
||||||
|
val deviceId = it.getArgument(0) as Int
|
||||||
|
val listener = it.getArgument(1) as IInputDeviceBatteryListener
|
||||||
|
if (registeredListener != null &&
|
||||||
|
registeredListener!!.asBinder() != listener.asBinder()) {
|
||||||
|
// There can only be one registered battery listener per process.
|
||||||
|
fail("Trying to register a new listener when one already exists")
|
||||||
|
}
|
||||||
|
if (monitoredDevices.contains(deviceId)) {
|
||||||
|
fail("Trying to start monitoring a device that was already being monitored")
|
||||||
|
}
|
||||||
|
monitoredDevices.add(deviceId)
|
||||||
|
registeredListener = listener
|
||||||
|
null
|
||||||
|
}.`when`(iInputManagerMock).registerBatteryListener(anyInt(), any())
|
||||||
|
|
||||||
|
// Handle battery listener being unregistered.
|
||||||
|
doAnswer {
|
||||||
|
val deviceId = it.getArgument(0) as Int
|
||||||
|
val listener = it.getArgument(1) as IInputDeviceBatteryListener
|
||||||
|
if (registeredListener == null ||
|
||||||
|
registeredListener!!.asBinder() != listener.asBinder()) {
|
||||||
|
fail("Trying to unregister a listener that is not registered")
|
||||||
|
}
|
||||||
|
if (!monitoredDevices.remove(deviceId)) {
|
||||||
|
fail("Trying to stop monitoring a device that is not being monitored")
|
||||||
|
}
|
||||||
|
if (monitoredDevices.isEmpty()) {
|
||||||
|
registeredListener = null
|
||||||
|
}
|
||||||
|
}.`when`(iInputManagerMock).unregisterBatteryListener(anyInt(), any())
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
InputManager.clearInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun notifyBatteryStateChanged(
|
||||||
|
deviceId: Int,
|
||||||
|
isPresent: Boolean = true,
|
||||||
|
status: Int = BatteryState.STATUS_FULL,
|
||||||
|
capacity: Float = 1.0f,
|
||||||
|
eventTime: Long = 12345L
|
||||||
|
) {
|
||||||
|
registeredListener!!.onBatteryStateChanged(deviceId, isPresent, status, capacity, eventTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testListenerIsNotifiedCorrectly() {
|
||||||
|
var callbackCount = 0
|
||||||
|
|
||||||
|
// Add a battery listener to monitor battery changes.
|
||||||
|
inputManager.addInputDeviceBatteryListener(1 /*deviceId*/, executor) {
|
||||||
|
deviceId: Int, eventTime: Long, batteryState: BatteryState ->
|
||||||
|
callbackCount++
|
||||||
|
assertEquals(1, deviceId)
|
||||||
|
assertEquals(true, batteryState.isPresent)
|
||||||
|
assertEquals(BatteryState.STATUS_DISCHARGING, batteryState.status)
|
||||||
|
assertEquals(0.5f, batteryState.capacity)
|
||||||
|
assertEquals(8675309L, eventTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adding the listener should register the callback with InputManagerService.
|
||||||
|
assertNotNull(registeredListener)
|
||||||
|
assertTrue(monitoredDevices.contains(1))
|
||||||
|
|
||||||
|
// Notifying battery change for a different device should not trigger the listener.
|
||||||
|
notifyBatteryStateChanged(deviceId = 2)
|
||||||
|
testLooper.dispatchAll()
|
||||||
|
assertEquals(0, callbackCount)
|
||||||
|
|
||||||
|
// Notifying battery change for the registered device will notify the listener.
|
||||||
|
notifyBatteryStateChanged(1 /*deviceId*/, true /*isPresent*/,
|
||||||
|
BatteryState.STATUS_DISCHARGING, 0.5f /*capacity*/, 8675309L /*eventTime*/)
|
||||||
|
testLooper.dispatchNext()
|
||||||
|
assertEquals(1, callbackCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMultipleListeners() {
|
||||||
|
// Set up two callbacks.
|
||||||
|
var callbackCount1 = 0
|
||||||
|
var callbackCount2 = 0
|
||||||
|
val callback1 = InputManager.InputDeviceBatteryListener { _, _, _ -> callbackCount1++ }
|
||||||
|
val callback2 = InputManager.InputDeviceBatteryListener { _, _, _ -> callbackCount2++ }
|
||||||
|
|
||||||
|
// Monitor battery changes for three devices. The first callback monitors devices 1 and 3,
|
||||||
|
// while the second callback monitors devices 2 and 3.
|
||||||
|
inputManager.addInputDeviceBatteryListener(1 /*deviceId*/, executor, callback1)
|
||||||
|
assertEquals(1, monitoredDevices.size)
|
||||||
|
inputManager.addInputDeviceBatteryListener(2 /*deviceId*/, executor, callback2)
|
||||||
|
assertEquals(2, monitoredDevices.size)
|
||||||
|
inputManager.addInputDeviceBatteryListener(3 /*deviceId*/, executor, callback1)
|
||||||
|
assertEquals(3, monitoredDevices.size)
|
||||||
|
inputManager.addInputDeviceBatteryListener(3 /*deviceId*/, executor, callback2)
|
||||||
|
assertEquals(3, monitoredDevices.size)
|
||||||
|
|
||||||
|
// Notifying battery change for each of the devices should trigger the registered callbacks.
|
||||||
|
notifyBatteryStateChanged(deviceId = 1)
|
||||||
|
testLooper.dispatchNext()
|
||||||
|
assertEquals(1, callbackCount1)
|
||||||
|
assertEquals(0, callbackCount2)
|
||||||
|
|
||||||
|
notifyBatteryStateChanged(deviceId = 2)
|
||||||
|
testLooper.dispatchNext()
|
||||||
|
assertEquals(1, callbackCount1)
|
||||||
|
assertEquals(1, callbackCount2)
|
||||||
|
|
||||||
|
notifyBatteryStateChanged(deviceId = 3)
|
||||||
|
testLooper.dispatchNext()
|
||||||
|
testLooper.dispatchNext()
|
||||||
|
assertEquals(2, callbackCount1)
|
||||||
|
assertEquals(2, callbackCount2)
|
||||||
|
|
||||||
|
// Stop monitoring devices 1 and 2.
|
||||||
|
inputManager.removeInputDeviceBatteryListener(1 /*deviceId*/, callback1)
|
||||||
|
assertEquals(2, monitoredDevices.size)
|
||||||
|
inputManager.removeInputDeviceBatteryListener(2 /*deviceId*/, callback2)
|
||||||
|
assertEquals(1, monitoredDevices.size)
|
||||||
|
|
||||||
|
// Ensure device 3 continues to be monitored.
|
||||||
|
notifyBatteryStateChanged(deviceId = 3)
|
||||||
|
testLooper.dispatchNext()
|
||||||
|
testLooper.dispatchNext()
|
||||||
|
assertEquals(3, callbackCount1)
|
||||||
|
assertEquals(3, callbackCount2)
|
||||||
|
|
||||||
|
// Stop monitoring all devices.
|
||||||
|
inputManager.removeInputDeviceBatteryListener(3 /*deviceId*/, callback1)
|
||||||
|
assertEquals(1, monitoredDevices.size)
|
||||||
|
inputManager.removeInputDeviceBatteryListener(3 /*deviceId*/, callback2)
|
||||||
|
assertEquals(0, monitoredDevices.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testAdditionalListenersNotifiedImmediately() {
|
||||||
|
var callbackCount1 = 0
|
||||||
|
var callbackCount2 = 0
|
||||||
|
val callback1 = InputManager.InputDeviceBatteryListener { _, _, _ -> callbackCount1++ }
|
||||||
|
val callback2 = InputManager.InputDeviceBatteryListener { _, _, _ -> callbackCount2++ }
|
||||||
|
|
||||||
|
// Add a battery listener and send the latest battery state.
|
||||||
|
inputManager.addInputDeviceBatteryListener(1 /*deviceId*/, executor, callback1)
|
||||||
|
assertEquals(1, monitoredDevices.size)
|
||||||
|
notifyBatteryStateChanged(deviceId = 1)
|
||||||
|
testLooper.dispatchNext()
|
||||||
|
assertEquals(1, callbackCount1)
|
||||||
|
|
||||||
|
// Add a second listener for the same device that already has the latest battery state.
|
||||||
|
inputManager.addInputDeviceBatteryListener(1 /*deviceId*/, executor, callback2)
|
||||||
|
assertEquals(1, monitoredDevices.size)
|
||||||
|
|
||||||
|
// Ensure that this listener is notified immediately.
|
||||||
|
testLooper.dispatchNext()
|
||||||
|
assertEquals(1, callbackCount2)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.android.server.input;
|
||||||
|
|
||||||
|
import android.annotation.BinderThread;
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.hardware.input.IInputDeviceBatteryListener;
|
||||||
|
import android.hardware.input.InputManager;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.util.ArrayMap;
|
||||||
|
import android.util.ArraySet;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.Slog;
|
||||||
|
import android.view.InputDevice;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.GuardedBy;
|
||||||
|
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A thread-safe component of {@link InputManagerService} responsible for managing the battery state
|
||||||
|
* of input devices.
|
||||||
|
*/
|
||||||
|
final class BatteryController {
|
||||||
|
private static final String TAG = BatteryController.class.getSimpleName();
|
||||||
|
|
||||||
|
// To enable these logs, run:
|
||||||
|
// 'adb shell setprop log.tag.BatteryController DEBUG' (requires restart)
|
||||||
|
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
||||||
|
|
||||||
|
private final Object mLock = new Object();
|
||||||
|
private final Context mContext;
|
||||||
|
private final NativeInputManagerService mNative;
|
||||||
|
|
||||||
|
// Maps a pid to the registered listener record for that process. There can only be one battery
|
||||||
|
// listener per process.
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private final ArrayMap<Integer, ListenerRecord> mListenerRecords = new ArrayMap<>();
|
||||||
|
|
||||||
|
BatteryController(Context context, NativeInputManagerService nativeService) {
|
||||||
|
mContext = context;
|
||||||
|
mNative = nativeService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the battery listener for the given input device and start monitoring its battery
|
||||||
|
* state.
|
||||||
|
*/
|
||||||
|
@BinderThread
|
||||||
|
void registerBatteryListener(int deviceId, @NonNull IInputDeviceBatteryListener listener,
|
||||||
|
int pid) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
ListenerRecord listenerRecord = mListenerRecords.get(pid);
|
||||||
|
|
||||||
|
if (listenerRecord == null) {
|
||||||
|
listenerRecord = new ListenerRecord(pid, listener);
|
||||||
|
try {
|
||||||
|
listener.asBinder().linkToDeath(listenerRecord.mDeathRecipient, 0);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Slog.i(TAG, "Client died before battery listener could be registered.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mListenerRecords.put(pid, listenerRecord);
|
||||||
|
if (DEBUG) Slog.d(TAG, "Battery listener added for pid " + pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listenerRecord.mListener.asBinder() != listener.asBinder()) {
|
||||||
|
throw new SecurityException(
|
||||||
|
"Cannot register a new battery listener when there is already another "
|
||||||
|
+ "registered listener for pid "
|
||||||
|
+ pid);
|
||||||
|
}
|
||||||
|
if (!listenerRecord.mMonitoredDevices.add(deviceId)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"The battery listener for pid " + pid
|
||||||
|
+ " is already monitoring deviceId " + deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
Slog.d(TAG, "Battery listener for pid " + pid
|
||||||
|
+ " is monitoring deviceId " + deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyBatteryListener(deviceId, listenerRecord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyBatteryListener(int deviceId, ListenerRecord record) {
|
||||||
|
final long eventTime = SystemClock.uptimeMillis();
|
||||||
|
try {
|
||||||
|
record.mListener.onBatteryStateChanged(
|
||||||
|
deviceId,
|
||||||
|
hasBattery(deviceId),
|
||||||
|
mNative.getBatteryStatus(deviceId),
|
||||||
|
mNative.getBatteryCapacity(deviceId) / 100.f,
|
||||||
|
eventTime);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Slog.e(TAG, "Failed to notify listener", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasBattery(int deviceId) {
|
||||||
|
final InputDevice device =
|
||||||
|
Objects.requireNonNull(mContext.getSystemService(InputManager.class))
|
||||||
|
.getInputDevice(deviceId);
|
||||||
|
return device != null && device.hasBattery();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister the battery listener for the given input device and stop monitoring its battery
|
||||||
|
* state. If there are no other input devices that this listener is monitoring, the listener is
|
||||||
|
* removed.
|
||||||
|
*/
|
||||||
|
@BinderThread
|
||||||
|
void unregisterBatteryListener(int deviceId, @NonNull IInputDeviceBatteryListener listener,
|
||||||
|
int pid) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
final ListenerRecord listenerRecord = mListenerRecords.get(pid);
|
||||||
|
if (listenerRecord == null) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Cannot unregister battery callback: No listener registered for pid "
|
||||||
|
+ pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listenerRecord.mListener.asBinder() != listener.asBinder()) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Cannot unregister battery callback: The listener is not the one that "
|
||||||
|
+ "is registered for pid "
|
||||||
|
+ pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!listenerRecord.mMonitoredDevices.contains(deviceId)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Cannot unregister battery callback: The device is not being "
|
||||||
|
+ "monitored for deviceId " + deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
unregisterRecordLocked(listenerRecord, deviceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private void unregisterRecordLocked(ListenerRecord listenerRecord, int deviceId) {
|
||||||
|
final int pid = listenerRecord.mPid;
|
||||||
|
|
||||||
|
if (!listenerRecord.mMonitoredDevices.remove(deviceId)) {
|
||||||
|
throw new IllegalStateException("Cannot unregister battery callback: The deviceId "
|
||||||
|
+ deviceId
|
||||||
|
+ " is not being monitored by pid "
|
||||||
|
+ pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listenerRecord.mMonitoredDevices.isEmpty()) {
|
||||||
|
// There are no more devices being monitored by this listener.
|
||||||
|
listenerRecord.mListener.asBinder().unlinkToDeath(listenerRecord.mDeathRecipient, 0);
|
||||||
|
mListenerRecords.remove(pid);
|
||||||
|
if (DEBUG) Slog.d(TAG, "Battery listener removed for pid " + pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleListeningProcessDied(int pid) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
final ListenerRecord listenerRecord = mListenerRecords.get(pid);
|
||||||
|
if (listenerRecord == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (DEBUG) {
|
||||||
|
Slog.d(TAG,
|
||||||
|
"Removing battery listener for pid " + pid + " because the process died");
|
||||||
|
}
|
||||||
|
for (final int deviceId : listenerRecord.mMonitoredDevices) {
|
||||||
|
unregisterRecordLocked(listenerRecord, deviceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void dump(PrintWriter pw, String prefix) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
pw.println(prefix + TAG + ": " + mListenerRecords.size()
|
||||||
|
+ " battery listeners");
|
||||||
|
for (int i = 0; i < mListenerRecords.size(); i++) {
|
||||||
|
pw.println(prefix + " " + i + ": " + mListenerRecords.valueAt(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
void monitor() {
|
||||||
|
synchronized (mLock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A record of a registered battery listener from one process.
|
||||||
|
private class ListenerRecord {
|
||||||
|
final int mPid;
|
||||||
|
final IInputDeviceBatteryListener mListener;
|
||||||
|
final IBinder.DeathRecipient mDeathRecipient;
|
||||||
|
// The set of deviceIds that are currently being monitored by this listener.
|
||||||
|
final Set<Integer> mMonitoredDevices;
|
||||||
|
|
||||||
|
ListenerRecord(int pid, IInputDeviceBatteryListener listener) {
|
||||||
|
mPid = pid;
|
||||||
|
mListener = listener;
|
||||||
|
mMonitoredDevices = new ArraySet<>();
|
||||||
|
mDeathRecipient = () -> handleListeningProcessDied(pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "pid=" + mPid
|
||||||
|
+ ", monitored devices=" + Arrays.toString(mMonitoredDevices.toArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -47,6 +47,7 @@ import android.hardware.SensorPrivacyManager.Sensors;
|
|||||||
import android.hardware.SensorPrivacyManagerInternal;
|
import android.hardware.SensorPrivacyManagerInternal;
|
||||||
import android.hardware.display.DisplayManager;
|
import android.hardware.display.DisplayManager;
|
||||||
import android.hardware.display.DisplayViewport;
|
import android.hardware.display.DisplayViewport;
|
||||||
|
import android.hardware.input.IInputDeviceBatteryListener;
|
||||||
import android.hardware.input.IInputDevicesChangedListener;
|
import android.hardware.input.IInputDevicesChangedListener;
|
||||||
import android.hardware.input.IInputManager;
|
import android.hardware.input.IInputManager;
|
||||||
import android.hardware.input.IInputSensorEventListener;
|
import android.hardware.input.IInputSensorEventListener;
|
||||||
@@ -318,6 +319,9 @@ public class InputManagerService extends IInputManager.Stub
|
|||||||
@GuardedBy("mInputMonitors")
|
@GuardedBy("mInputMonitors")
|
||||||
final Map<IBinder, GestureMonitorSpyWindow> mInputMonitors = new HashMap<>();
|
final Map<IBinder, GestureMonitorSpyWindow> mInputMonitors = new HashMap<>();
|
||||||
|
|
||||||
|
// Manages battery state for input devices.
|
||||||
|
private final BatteryController mBatteryController;
|
||||||
|
|
||||||
// Maximum number of milliseconds to wait for input event injection.
|
// Maximum number of milliseconds to wait for input event injection.
|
||||||
private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000;
|
private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000;
|
||||||
|
|
||||||
@@ -425,6 +429,7 @@ public class InputManagerService extends IInputManager.Stub
|
|||||||
mContext = injector.getContext();
|
mContext = injector.getContext();
|
||||||
mHandler = new InputManagerHandler(injector.getLooper());
|
mHandler = new InputManagerHandler(injector.getLooper());
|
||||||
mNative = injector.getNativeService(this);
|
mNative = injector.getNativeService(this);
|
||||||
|
mBatteryController = new BatteryController(mContext, mNative);
|
||||||
|
|
||||||
mUseDevInputEventForAudioJack =
|
mUseDevInputEventForAudioJack =
|
||||||
mContext.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
|
mContext.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
|
||||||
@@ -2673,6 +2678,18 @@ public class InputManagerService extends IInputManager.Stub
|
|||||||
mNative.cancelCurrentTouch();
|
mNative.cancelCurrentTouch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerBatteryListener(int deviceId, IInputDeviceBatteryListener listener) {
|
||||||
|
Objects.requireNonNull(listener);
|
||||||
|
mBatteryController.registerBatteryListener(deviceId, listener, Binder.getCallingPid());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unregisterBatteryListener(int deviceId, IInputDeviceBatteryListener listener) {
|
||||||
|
Objects.requireNonNull(listener);
|
||||||
|
mBatteryController.unregisterBatteryListener(deviceId, listener, Binder.getCallingPid());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
||||||
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
|
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
|
||||||
@@ -2686,7 +2703,8 @@ public class InputManagerService extends IInputManager.Stub
|
|||||||
pw.println("Input Manager Service (Java) State:");
|
pw.println("Input Manager Service (Java) State:");
|
||||||
dumpAssociations(pw, " " /*prefix*/);
|
dumpAssociations(pw, " " /*prefix*/);
|
||||||
dumpSpyWindowGestureMonitors(pw, " " /*prefix*/);
|
dumpSpyWindowGestureMonitors(pw, " " /*prefix*/);
|
||||||
dumpDisplayInputPropertiesValues(pw, " " /* prefix */);
|
dumpDisplayInputPropertiesValues(pw, " " /*prefix*/);
|
||||||
|
mBatteryController.dump(pw, " " /*prefix*/);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void dumpAssociations(PrintWriter pw, String prefix) {
|
private void dumpAssociations(PrintWriter pw, String prefix) {
|
||||||
@@ -2797,6 +2815,7 @@ public class InputManagerService extends IInputManager.Stub
|
|||||||
synchronized (mLidSwitchLock) { /* Test if blocked by lid switch lock. */ }
|
synchronized (mLidSwitchLock) { /* Test if blocked by lid switch lock. */ }
|
||||||
synchronized (mInputMonitors) { /* Test if blocked by input monitor lock. */ }
|
synchronized (mInputMonitors) { /* Test if blocked by input monitor lock. */ }
|
||||||
synchronized (mAdditionalDisplayInputPropertiesLock) { /* Test if blocked by props lock */ }
|
synchronized (mAdditionalDisplayInputPropertiesLock) { /* Test if blocked by props lock */ }
|
||||||
|
mBatteryController.monitor();
|
||||||
mNative.monitor();
|
mNative.monitor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.android.server.input
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.ContextWrapper
|
||||||
|
import android.hardware.BatteryState.STATUS_CHARGING
|
||||||
|
import android.hardware.BatteryState.STATUS_FULL
|
||||||
|
import android.hardware.input.IInputDeviceBatteryListener
|
||||||
|
import android.hardware.input.IInputManager
|
||||||
|
import android.hardware.input.InputManager
|
||||||
|
import android.os.Binder
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.platform.test.annotations.Presubmit
|
||||||
|
import android.view.InputDevice
|
||||||
|
import androidx.test.InstrumentationRegistry
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Assert.fail
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mockito.ArgumentCaptor
|
||||||
|
import org.mockito.ArgumentMatchers.notNull
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito.anyInt
|
||||||
|
import org.mockito.Mockito.anyLong
|
||||||
|
import org.mockito.Mockito.eq
|
||||||
|
import org.mockito.Mockito.mock
|
||||||
|
import org.mockito.Mockito.never
|
||||||
|
import org.mockito.Mockito.spy
|
||||||
|
import org.mockito.Mockito.times
|
||||||
|
import org.mockito.Mockito.verify
|
||||||
|
import org.mockito.Mockito.`when`
|
||||||
|
import org.mockito.junit.MockitoJUnit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link InputDeviceBatteryController}.
|
||||||
|
*
|
||||||
|
* Build/Install/Run:
|
||||||
|
* atest FrameworksServicesTests:InputDeviceBatteryControllerTests
|
||||||
|
*/
|
||||||
|
@Presubmit
|
||||||
|
class BatteryControllerTests {
|
||||||
|
companion object {
|
||||||
|
const val PID = 42
|
||||||
|
const val DEVICE_ID = 13
|
||||||
|
const val SECOND_DEVICE_ID = 11
|
||||||
|
}
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
val rule = MockitoJUnit.rule()!!
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var native: NativeInputManagerService
|
||||||
|
@Mock
|
||||||
|
private lateinit var iInputManager: IInputManager
|
||||||
|
|
||||||
|
private lateinit var batteryController: BatteryController
|
||||||
|
private lateinit var context: Context
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
context = spy(ContextWrapper(InstrumentationRegistry.getContext()))
|
||||||
|
val inputManager = InputManager.resetInstance(iInputManager)
|
||||||
|
`when`(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager)
|
||||||
|
`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID, SECOND_DEVICE_ID))
|
||||||
|
`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(createInputDevice(DEVICE_ID))
|
||||||
|
`when`(iInputManager.getInputDevice(SECOND_DEVICE_ID))
|
||||||
|
.thenReturn(createInputDevice(SECOND_DEVICE_ID))
|
||||||
|
|
||||||
|
batteryController = BatteryController(context, native)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createInputDevice(deviceId: Int): InputDevice =
|
||||||
|
InputDevice(deviceId, 0 /*generation*/, 0 /*controllerNumber*/,
|
||||||
|
"Device $deviceId" /*name*/, 0 /*vendorId*/, 0 /*productId*/, "descriptor$deviceId",
|
||||||
|
true /*isExternal*/, 0 /*sources*/, 0 /*keyboardType*/, null /*keyCharacterMap*/,
|
||||||
|
false /*hasVibrator*/, false /*hasMicrophone*/, false /*hasButtonUnderPad*/,
|
||||||
|
false /*hasSensor*/, true /*hasBattery*/)
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
InputManager.clearInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createMockListener(): IInputDeviceBatteryListener {
|
||||||
|
val listener = mock(IInputDeviceBatteryListener::class.java)
|
||||||
|
val binder = mock(Binder::class.java)
|
||||||
|
`when`(listener.asBinder()).thenReturn(binder)
|
||||||
|
return listener
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testRegisterAndUnregisterBinderLifecycle() {
|
||||||
|
val listener = createMockListener()
|
||||||
|
// Ensure the binder lifecycle is tracked when registering a listener.
|
||||||
|
batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
|
||||||
|
verify(listener.asBinder()).linkToDeath(notNull(), anyInt())
|
||||||
|
batteryController.registerBatteryListener(SECOND_DEVICE_ID, listener, PID)
|
||||||
|
verify(listener.asBinder(), times(1)).linkToDeath(notNull(), anyInt())
|
||||||
|
|
||||||
|
// Ensure the binder lifecycle stops being tracked when all devices stopped being monitored.
|
||||||
|
batteryController.unregisterBatteryListener(SECOND_DEVICE_ID, listener, PID)
|
||||||
|
verify(listener.asBinder(), never()).unlinkToDeath(notNull(), anyInt())
|
||||||
|
batteryController.unregisterBatteryListener(DEVICE_ID, listener, PID)
|
||||||
|
verify(listener.asBinder()).unlinkToDeath(notNull(), anyInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testOneListenerPerProcess() {
|
||||||
|
val listener1 = createMockListener()
|
||||||
|
batteryController.registerBatteryListener(DEVICE_ID, listener1, PID)
|
||||||
|
verify(listener1.asBinder()).linkToDeath(notNull(), anyInt())
|
||||||
|
|
||||||
|
// If a second listener is added for the same process, a security exception is thrown.
|
||||||
|
val listener2 = createMockListener()
|
||||||
|
try {
|
||||||
|
batteryController.registerBatteryListener(DEVICE_ID, listener2, PID)
|
||||||
|
fail("Expected security exception when registering more than one listener per process")
|
||||||
|
} catch (ignored: SecurityException) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testProcessDeathRemovesListener() {
|
||||||
|
val deathRecipient = ArgumentCaptor.forClass(IBinder.DeathRecipient::class.java)
|
||||||
|
val listener = createMockListener()
|
||||||
|
batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
|
||||||
|
verify(listener.asBinder()).linkToDeath(deathRecipient.capture(), anyInt())
|
||||||
|
|
||||||
|
// When the binder dies, the callback is unregistered.
|
||||||
|
deathRecipient.value!!.binderDied(listener.asBinder())
|
||||||
|
verify(listener.asBinder()).unlinkToDeath(notNull(), anyInt())
|
||||||
|
|
||||||
|
// It is now possible to register the same listener again.
|
||||||
|
batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
|
||||||
|
verify(listener.asBinder(), times(2)).linkToDeath(notNull(), anyInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testRegisteringListenerNotifiesStateImmediately() {
|
||||||
|
`when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_FULL)
|
||||||
|
`when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(100)
|
||||||
|
val listener = createMockListener()
|
||||||
|
batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
|
||||||
|
verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/),
|
||||||
|
eq(STATUS_FULL), eq(1f), anyLong())
|
||||||
|
|
||||||
|
`when`(native.getBatteryStatus(SECOND_DEVICE_ID)).thenReturn(STATUS_CHARGING)
|
||||||
|
`when`(native.getBatteryCapacity(SECOND_DEVICE_ID)).thenReturn(78)
|
||||||
|
batteryController.registerBatteryListener(SECOND_DEVICE_ID, listener, PID)
|
||||||
|
verify(listener).onBatteryStateChanged(eq(SECOND_DEVICE_ID), eq(true /*isPresent*/),
|
||||||
|
eq(STATUS_CHARGING), eq(0.78f), anyLong())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user