Merge changes from topic "aidl-iinputdevicebatterystate"

* changes:
  Rename MonitoredDeviceState to DeviceMonitor
  Add AIDL definiton for IInputDeviceBatteryState
This commit is contained in:
Prabir Pradhan
2022-09-30 12:40:39 +00:00
committed by Android (Google) Code Review
10 changed files with 374 additions and 277 deletions

View File

@@ -16,14 +16,14 @@
package android.hardware.input;
import android.hardware.input.IInputDeviceBatteryState;
/** @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);
void onBatteryStateChanged(in IInputDeviceBatteryState batteryState);
}

View File

@@ -0,0 +1,40 @@
/*
* 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 IInputDeviceBatteryState {
/** The deviceId of the input device that this battery state is associated with. */
int deviceId;
/**
* The timestamp of the last time the battery state was updated, in the
* {@link SystemClock.uptimeMillis()} time base.
*/
long updateTime;
/** Whether the input device has a battery. */
boolean isPresent;
/** The battery status for this input device. */
@JavaPassthrough(annotation="@android.hardware.BatteryState.BatteryStatus")
int status;
/** The battery capacity for this input device, in a range between 0 and 1. */
float capacity;
}

View File

@@ -21,6 +21,7 @@ import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.KeyboardLayout;
import android.hardware.input.IInputDevicesChangedListener;
import android.hardware.input.IInputDeviceBatteryListener;
import android.hardware.input.IInputDeviceBatteryState;
import android.hardware.input.ITabletModeChangedListener;
import android.hardware.input.TouchCalibration;
import android.os.CombinedVibration;
@@ -110,9 +111,7 @@ interface IInputManager {
boolean registerVibratorStateListener(int deviceId, in IVibratorStateListener listener);
boolean unregisterVibratorStateListener(int deviceId, in IVibratorStateListener listener);
// Input device battery query.
int getBatteryStatus(int deviceId);
int getBatteryCapacity(int deviceId);
IInputDeviceBatteryState getBatteryState(int deviceId);
void setPointerIconType(int typeId);
void setCustomPointerIcon(in PointerIcon icon);

View File

@@ -1,65 +0,0 @@
/*
* Copyright (C) 2021 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 static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
import static android.os.IInputConstants.INVALID_BATTERY_CAPACITY;
import android.hardware.BatteryState;
/**
* Battery implementation for input devices.
*
* @hide
*/
public final class InputDeviceBatteryState extends BatteryState {
private static final float NULL_BATTERY_CAPACITY = Float.NaN;
private final InputManager mInputManager;
private final int mDeviceId;
private final boolean mHasBattery;
InputDeviceBatteryState(InputManager inputManager, int deviceId, boolean hasBattery) {
mInputManager = inputManager;
mDeviceId = deviceId;
mHasBattery = hasBattery;
}
@Override
public boolean isPresent() {
return mHasBattery;
}
@Override
public int getStatus() {
if (!mHasBattery) {
return BATTERY_STATUS_UNKNOWN;
}
return mInputManager.getBatteryStatus(mDeviceId);
}
@Override
public float getCapacity() {
if (mHasBattery) {
int capacity = mInputManager.getBatteryCapacity(mDeviceId);
if (capacity != INVALID_BATTERY_CAPACITY) {
return (float) capacity / 100.0f;
}
}
return NULL_BATTERY_CAPACITY;
}
}

View File

@@ -1307,32 +1307,6 @@ public final class InputManager {
}
}
/**
* Get the battery status of the input device
* @param deviceId The input device ID
* @hide
*/
public int getBatteryStatus(int deviceId) {
try {
return mIm.getBatteryStatus(deviceId);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
/**
* Get the remaining battery capacity of the input device
* @param deviceId The input device ID
* @hide
*/
public int getBatteryCapacity(int deviceId) {
try {
return mIm.getBatteryCapacity(deviceId);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
/**
* Add a runtime association between the input port and the display port. This overrides any
* static associations.
@@ -1622,8 +1596,17 @@ public final class InputManager {
* @return The battery, never null.
* @hide
*/
public InputDeviceBatteryState getInputDeviceBatteryState(int deviceId, boolean hasBattery) {
return new InputDeviceBatteryState(this, deviceId, hasBattery);
@NonNull
public BatteryState getInputDeviceBatteryState(int deviceId, boolean hasBattery) {
if (!hasBattery) {
return new LocalBatteryState();
}
try {
final IInputDeviceBatteryState state = mIm.getBatteryState(deviceId);
return new LocalBatteryState(state.isPresent, state.status, state.capacity);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
/**
@@ -1767,8 +1750,8 @@ public final class InputManager {
listenersForDevice.mDelegates.add(delegate);
// Notify the listener immediately if we already have the latest battery state.
if (listenersForDevice.mLatestBatteryState != null) {
delegate.notifyBatteryStateChanged(listenersForDevice.mLatestBatteryState);
if (listenersForDevice.mInputDeviceBatteryState != null) {
delegate.notifyBatteryStateChanged(listenersForDevice.mInputDeviceBatteryState);
}
}
}
@@ -1952,20 +1935,21 @@ public final class InputManager {
}
}
// Implementation of the android.hardware.BatteryState interface used to report the battery
// state via the InputDevice#getBatteryState() and InputDeviceBatteryListener interfaces.
private static final class LocalBatteryState extends BatteryState {
final int mDeviceId;
final boolean mIsPresent;
final int mStatus;
final float mCapacity;
final long mEventTime;
private final boolean mIsPresent;
private final int mStatus;
private final float mCapacity;
LocalBatteryState(int deviceId, boolean isPresent, int status, float capacity,
long eventTime) {
mDeviceId = deviceId;
LocalBatteryState() {
this(false /*isPresent*/, BatteryState.STATUS_UNKNOWN, Float.NaN /*capacity*/);
}
LocalBatteryState(boolean isPresent, int status, float capacity) {
mIsPresent = isPresent;
mStatus = status;
mCapacity = capacity;
mEventTime = eventTime;
}
@Override
@@ -1986,7 +1970,7 @@ public final class InputManager {
private static final class RegisteredBatteryListeners {
final List<InputDeviceBatteryListenerDelegate> mDelegates = new ArrayList<>();
LocalBatteryState mLatestBatteryState;
IInputDeviceBatteryState mInputDeviceBatteryState;
}
private static final class InputDeviceBatteryListenerDelegate {
@@ -1998,27 +1982,24 @@ public final class InputManager {
mExecutor = executor;
}
void notifyBatteryStateChanged(LocalBatteryState batteryState) {
void notifyBatteryStateChanged(IInputDeviceBatteryState state) {
mExecutor.execute(() ->
mListener.onBatteryStateChanged(batteryState.mDeviceId, batteryState.mEventTime,
batteryState));
mListener.onBatteryStateChanged(state.deviceId, state.updateTime,
new LocalBatteryState(state.isPresent, state.status, state.capacity)));
}
}
private class LocalInputDeviceBatteryListener extends IInputDeviceBatteryListener.Stub {
@Override
public void onBatteryStateChanged(int deviceId, boolean isBatteryPresent, int status,
float capacity, long eventTime) {
public void onBatteryStateChanged(IInputDeviceBatteryState state) {
synchronized (mBatteryListenersLock) {
if (mBatteryListeners == null) return;
final RegisteredBatteryListeners entry = mBatteryListeners.get(deviceId);
final RegisteredBatteryListeners entry = mBatteryListeners.get(state.deviceId);
if (entry == null) return;
entry.mLatestBatteryState =
new LocalBatteryState(
deviceId, isBatteryPresent, status, capacity, eventTime);
entry.mInputDeviceBatteryState = state;
for (InputDeviceBatteryListenerDelegate delegate : entry.mDelegates) {
delegate.notifyBatteryStateChanged(entry.mLatestBatteryState);
delegate.notifyBatteryStateChanged(entry.mInputDeviceBatteryState);
}
}
}

View File

@@ -91,9 +91,6 @@ public final class InputDevice implements Parcelable {
@GuardedBy("mMotionRanges")
private SensorManager mSensorManager;
@GuardedBy("mMotionRanges")
private BatteryState mBatteryState;
@GuardedBy("mMotionRanges")
private LightsManager mLightsManager;
@@ -1058,10 +1055,7 @@ public final class InputDevice implements Parcelable {
*/
@NonNull
public BatteryState getBatteryState() {
if (mBatteryState == null) {
mBatteryState = InputManager.getInstance().getInputDeviceBatteryState(mId, mHasBattery);
}
return mBatteryState;
return InputManager.getInstance().getInputDeviceBatteryState(mId, mHasBattery);
}
/**

View File

@@ -112,7 +112,13 @@ class InputDeviceBatteryListenerTest {
capacity: Float = 1.0f,
eventTime: Long = 12345L
) {
registeredListener!!.onBatteryStateChanged(deviceId, isPresent, status, capacity, eventTime)
registeredListener!!.onBatteryStateChanged(IInputDeviceBatteryState().apply {
this.deviceId = deviceId
this.updateTime = eventTime
this.isPresent = isPresent
this.status = status
this.capacity = capacity
})
}
@Test

View File

@@ -22,6 +22,7 @@ import android.annotation.Nullable;
import android.content.Context;
import android.hardware.BatteryState;
import android.hardware.input.IInputDeviceBatteryListener;
import android.hardware.input.IInputDeviceBatteryState;
import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.IBinder;
@@ -47,8 +48,13 @@ import java.util.Set;
/**
* A thread-safe component of {@link InputManagerService} responsible for managing the battery state
* of input devices.
*
* Interactions with BatteryController can happen on several threads, including Binder threads, the
* {@link UEventObserver}'s thread, or its own Handler thread, among others. All public methods, and
* private methods prefixed with "handle-" (e.g. {@link #handleListeningProcessDied(int)}),
* serve as entry points for these threads.
*/
final class BatteryController implements InputManager.InputDeviceListener {
final class BatteryController {
private static final String TAG = BatteryController.class.getSimpleName();
// To enable these logs, run:
@@ -69,10 +75,9 @@ final class BatteryController implements InputManager.InputDeviceListener {
@GuardedBy("mLock")
private final ArrayMap<Integer, ListenerRecord> mListenerRecords = new ArrayMap<>();
// Maps a deviceId that is being monitored to the battery state for the device.
// This must be kept in sync with {@link #mListenerRecords}.
// Maps a deviceId that is being monitored to the monitor for the battery state of the device.
@GuardedBy("mLock")
private final ArrayMap<Integer, MonitoredDeviceState> mMonitoredDeviceStates = new ArrayMap<>();
private final ArrayMap<Integer, DeviceMonitor> mDeviceMonitors = new ArrayMap<>();
@GuardedBy("mLock")
private boolean mIsPolling = false;
@@ -92,9 +97,9 @@ final class BatteryController implements InputManager.InputDeviceListener {
mUEventManager = uEventManager;
}
void systemRunning() {
public void systemRunning() {
Objects.requireNonNull(mContext.getSystemService(InputManager.class))
.registerInputDeviceListener(this, mHandler);
.registerInputDeviceListener(mInputDeviceListener, mHandler);
}
/**
@@ -102,7 +107,7 @@ final class BatteryController implements InputManager.InputDeviceListener {
* state.
*/
@BinderThread
void registerBatteryListener(int deviceId, @NonNull IInputDeviceBatteryListener listener,
public void registerBatteryListener(int deviceId, @NonNull IInputDeviceBatteryListener listener,
int pid) {
synchronized (mLock) {
ListenerRecord listenerRecord = mListenerRecords.get(pid);
@@ -131,11 +136,11 @@ final class BatteryController implements InputManager.InputDeviceListener {
+ " is already monitoring deviceId " + deviceId);
}
MonitoredDeviceState deviceState = mMonitoredDeviceStates.get(deviceId);
if (deviceState == null) {
DeviceMonitor monitor = mDeviceMonitors.get(deviceId);
if (monitor == null) {
// This is the first listener that is monitoring this device.
deviceState = new MonitoredDeviceState(deviceId);
mMonitoredDeviceStates.put(deviceId, deviceState);
monitor = new DeviceMonitor(deviceId);
mDeviceMonitors.put(deviceId, monitor);
}
if (DEBUG) {
@@ -144,36 +149,35 @@ final class BatteryController implements InputManager.InputDeviceListener {
}
updatePollingLocked(true /*delayStart*/);
notifyBatteryListener(listenerRecord, deviceState);
notifyBatteryListener(listenerRecord, monitor.getBatteryStateForReporting());
}
}
private static void notifyBatteryListener(ListenerRecord listenerRecord,
MonitoredDeviceState deviceState) {
private static void notifyBatteryListener(ListenerRecord listenerRecord, State state) {
try {
listenerRecord.mListener.onBatteryStateChanged(
deviceState.mDeviceId,
deviceState.mHasBattery,
deviceState.mBatteryStatus,
deviceState.mBatteryCapacity,
deviceState.mLastUpdateTime);
listenerRecord.mListener.onBatteryStateChanged(state);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to notify listener", e);
}
if (DEBUG) {
Slog.d(TAG, "Notified battery listener from pid " + listenerRecord.mPid
+ " of state of deviceId " + state.deviceId);
}
}
@GuardedBy("mLock")
private void notifyAllListenersForDeviceLocked(MonitoredDeviceState deviceState) {
private void notifyAllListenersForDeviceLocked(State state) {
if (DEBUG) Slog.d(TAG, "Notifying all listeners of battery state: " + state);
mListenerRecords.forEach((pid, listenerRecord) -> {
if (listenerRecord.mMonitoredDevices.contains(deviceState.mDeviceId)) {
notifyBatteryListener(listenerRecord, deviceState);
if (listenerRecord.mMonitoredDevices.contains(state.deviceId)) {
notifyBatteryListener(listenerRecord, state);
}
});
}
@GuardedBy("mLock")
private void updatePollingLocked(boolean delayStart) {
if (mMonitoredDeviceStates.isEmpty() || !mIsInteractive) {
if (mDeviceMonitors.isEmpty() || !mIsInteractive) {
// Stop polling.
mIsPolling = false;
mHandler.removeCallbacks(this::handlePollEvent);
@@ -196,8 +200,8 @@ final class BatteryController implements InputManager.InputDeviceListener {
}
@GuardedBy("mLock")
private MonitoredDeviceState getDeviceStateOrThrowLocked(int deviceId) {
return Objects.requireNonNull(mMonitoredDeviceStates.get(deviceId),
private DeviceMonitor getDeviceMonitorOrThrowLocked(int deviceId) {
return Objects.requireNonNull(mDeviceMonitors.get(deviceId),
"Maps are out of sync: Cannot find device state for deviceId " + deviceId);
}
@@ -207,8 +211,8 @@ final class BatteryController implements InputManager.InputDeviceListener {
* removed.
*/
@BinderThread
void unregisterBatteryListener(int deviceId, @NonNull IInputDeviceBatteryListener listener,
int pid) {
public void unregisterBatteryListener(int deviceId,
@NonNull IInputDeviceBatteryListener listener, int pid) {
synchronized (mLock) {
final ListenerRecord listenerRecord = mListenerRecords.get(pid);
if (listenerRecord == null) {
@@ -247,9 +251,9 @@ final class BatteryController implements InputManager.InputDeviceListener {
if (!hasRegisteredListenerForDeviceLocked(deviceId)) {
// There are no more listeners monitoring this device.
final MonitoredDeviceState deviceState = getDeviceStateOrThrowLocked(deviceId);
deviceState.stopMonitoring();
mMonitoredDeviceStates.remove(deviceId);
final DeviceMonitor monitor = getDeviceMonitorOrThrowLocked(deviceId);
monitor.stopMonitoring();
mDeviceMonitors.remove(deviceId);
}
if (listenerRecord.mMonitoredDevices.isEmpty()) {
@@ -288,15 +292,14 @@ final class BatteryController implements InputManager.InputDeviceListener {
}
}
// Query the battery state for the device and notify all listeners if there is a change.
private void handleBatteryChangeNotification(int deviceId, long eventTime) {
private void handleUEventNotification(int deviceId, long eventTime) {
synchronized (mLock) {
final MonitoredDeviceState deviceState = mMonitoredDeviceStates.get(deviceId);
if (deviceState == null) {
final DeviceMonitor monitor = mDeviceMonitors.get(deviceId);
if (monitor == null) {
return;
}
if (deviceState.updateBatteryState(eventTime)) {
notifyAllListenersForDeviceLocked(deviceState);
if (monitor.updateBatteryState(eventTime)) {
notifyAllListenersForDeviceLocked(monitor.getBatteryStateForReporting());
}
}
}
@@ -307,11 +310,11 @@ final class BatteryController implements InputManager.InputDeviceListener {
return;
}
final long eventTime = SystemClock.uptimeMillis();
mMonitoredDeviceStates.forEach((deviceId, deviceState) -> {
mDeviceMonitors.forEach((deviceId, monitor) -> {
// Re-acquire lock in the lambda to silence error-prone build warnings.
synchronized (mLock) {
if (deviceState.updateBatteryState(eventTime)) {
notifyAllListenersForDeviceLocked(deviceState);
if (monitor.updateBatteryState(eventTime)) {
notifyAllListenersForDeviceLocked(monitor.getBatteryStateForReporting());
}
}
});
@@ -319,62 +322,90 @@ final class BatteryController implements InputManager.InputDeviceListener {
}
}
void onInteractiveChanged(boolean interactive) {
/** Gets the current battery state of an input device. */
public IInputDeviceBatteryState getBatteryState(int deviceId) {
synchronized (mLock) {
final long updateTime = SystemClock.uptimeMillis();
final DeviceMonitor monitor = mDeviceMonitors.get(deviceId);
if (monitor == null) {
// The input device's battery is not being monitored by any listener.
return queryBatteryStateFromNative(deviceId, updateTime);
}
// Force the battery state to update, and notify listeners if necessary.
final boolean stateChanged = monitor.updateBatteryState(updateTime);
final State state = monitor.getBatteryStateForReporting();
if (stateChanged) {
notifyAllListenersForDeviceLocked(state);
}
return state;
}
}
public void onInteractiveChanged(boolean interactive) {
synchronized (mLock) {
mIsInteractive = interactive;
updatePollingLocked(false /*delayStart*/);
}
}
void dump(PrintWriter pw, String prefix) {
public void dump(PrintWriter pw, String prefix) {
synchronized (mLock) {
pw.println(prefix + TAG + ": "
+ mListenerRecords.size() + " battery listeners"
+ ", Polling = " + mIsPolling
final String indent = prefix + " ";
final String indent2 = indent + " ";
pw.println(prefix + TAG + ":");
pw.println(indent + "State: Polling = " + mIsPolling
+ ", Interactive = " + mIsInteractive);
pw.println(indent + "Listeners: " + mListenerRecords.size() + " battery listeners");
for (int i = 0; i < mListenerRecords.size(); i++) {
pw.println(prefix + " " + i + ": " + mListenerRecords.valueAt(i));
pw.println(indent2 + i + ": " + mListenerRecords.valueAt(i));
}
pw.println(indent + "Device Monitors: " + mDeviceMonitors.size() + " monitors");
for (int i = 0; i < mDeviceMonitors.size(); i++) {
pw.println(indent2 + i + ": " + mDeviceMonitors.valueAt(i));
}
}
}
@SuppressWarnings("all")
void monitor() {
public void monitor() {
synchronized (mLock) {
return;
}
}
@VisibleForTesting
@Override
public void onInputDeviceAdded(int deviceId) {}
private final InputManager.InputDeviceListener mInputDeviceListener =
new InputManager.InputDeviceListener() {
@Override
public void onInputDeviceAdded(int deviceId) {}
@VisibleForTesting
@Override
public void onInputDeviceRemoved(int deviceId) {}
@Override
public void onInputDeviceRemoved(int deviceId) {}
@VisibleForTesting
@Override
public void onInputDeviceChanged(int deviceId) {
synchronized (mLock) {
final MonitoredDeviceState deviceState = mMonitoredDeviceStates.get(deviceId);
if (deviceState == null) {
return;
}
final long eventTime = SystemClock.uptimeMillis();
if (deviceState.updateBatteryState(eventTime)) {
notifyAllListenersForDeviceLocked(deviceState);
@Override
public void onInputDeviceChanged(int deviceId) {
synchronized (mLock) {
final DeviceMonitor monitor = mDeviceMonitors.get(deviceId);
if (monitor == null) {
return;
}
final long eventTime = SystemClock.uptimeMillis();
if (monitor.updateBatteryState(eventTime)) {
notifyAllListenersForDeviceLocked(monitor.getBatteryStateForReporting());
}
}
}
}
};
// A record of a registered battery listener from one process.
private class ListenerRecord {
final int mPid;
final IInputDeviceBatteryListener mListener;
final IBinder.DeathRecipient mDeathRecipient;
public final int mPid;
public final IInputDeviceBatteryListener mListener;
public final IBinder.DeathRecipient mDeathRecipient;
// The set of deviceIds that are currently being monitored by this listener.
final Set<Integer> mMonitoredDevices;
public final Set<Integer> mMonitoredDevices;
ListenerRecord(int pid, IInputDeviceBatteryListener listener) {
mPid = pid;
@@ -390,21 +421,27 @@ final class BatteryController implements InputManager.InputDeviceListener {
}
}
// Holds the state of an InputDevice for which battery changes are currently being monitored.
private class MonitoredDeviceState {
private final int mDeviceId;
// Queries the battery state of an input device from native code.
private State queryBatteryStateFromNative(int deviceId, long updateTime) {
final boolean isPresent = hasBattery(deviceId);
return new State(
deviceId,
updateTime,
isPresent,
isPresent ? mNative.getBatteryStatus(deviceId) : BatteryState.STATUS_UNKNOWN,
isPresent ? mNative.getBatteryCapacity(deviceId) / 100.f : Float.NaN);
}
private long mLastUpdateTime = 0;
private boolean mHasBattery = false;
@BatteryState.BatteryStatus
private int mBatteryStatus = BatteryState.STATUS_UNKNOWN;
private float mBatteryCapacity = Float.NaN;
// Holds the state of an InputDevice for which battery changes are currently being monitored.
private class DeviceMonitor {
@NonNull
private State mState;
@Nullable
private UEventListener mUEventListener;
MonitoredDeviceState(int deviceId) {
mDeviceId = deviceId;
DeviceMonitor(int deviceId) {
mState = new State(deviceId);
// Load the initial battery state and start monitoring.
final long eventTime = SystemClock.uptimeMillis();
@@ -412,56 +449,57 @@ final class BatteryController implements InputManager.InputDeviceListener {
}
// Returns true if the battery state changed since the last time it was updated.
boolean updateBatteryState(long eventTime) {
mLastUpdateTime = eventTime;
public boolean updateBatteryState(long updateTime) {
mState.updateTime = updateTime;
final boolean batteryPresenceChanged = mHasBattery != hasBattery(mDeviceId);
if (batteryPresenceChanged) {
mHasBattery = !mHasBattery;
if (mHasBattery) {
final State updatedState = queryBatteryStateFromNative(mState.deviceId, updateTime);
if (mState.equals(updatedState)) {
return false;
}
if (mState.isPresent != updatedState.isPresent) {
if (updatedState.isPresent) {
startMonitoring();
} else {
stopMonitoring();
}
}
final int oldStatus = mBatteryStatus;
final float oldCapacity = mBatteryCapacity;
if (mHasBattery) {
mBatteryStatus = mNative.getBatteryStatus(mDeviceId);
mBatteryCapacity = mNative.getBatteryCapacity(mDeviceId) / 100.f;
} else {
mBatteryStatus = BatteryState.STATUS_UNKNOWN;
mBatteryCapacity = Float.NaN;
}
return batteryPresenceChanged
|| mBatteryStatus != oldStatus
|| mBatteryCapacity != oldCapacity;
mState = updatedState;
return true;
}
private void startMonitoring() {
final String batteryPath = mNative.getBatteryDevicePath(mDeviceId);
final String batteryPath = mNative.getBatteryDevicePath(mState.deviceId);
if (batteryPath == null) {
return;
}
final int deviceId = mState.deviceId;
mUEventListener = new UEventListener() {
@Override
void onUEvent(long eventTime) {
handleBatteryChangeNotification(mDeviceId, eventTime);
public void onUEvent(long eventTime) {
handleUEventNotification(deviceId, eventTime);
}
};
mUEventManager.addListener(mUEventListener, "DEVPATH=" + batteryPath);
}
// This must be called when the device is no longer being monitored.
void stopMonitoring() {
public void stopMonitoring() {
if (mUEventListener != null) {
mUEventManager.removeListener(mUEventListener);
mUEventListener = null;
}
}
// Returns the current battery state that can be used to notify listeners BatteryController.
public State getBatteryStateForReporting() {
return new State(mState);
}
@Override
public String toString() {
return "state=" + mState
+ ", uEventListener=" + (mUEventListener != null ? "added" : "none");
}
}
// An interface used to change the API of UEventObserver to a more test-friendly format.
@@ -483,7 +521,7 @@ final class BatteryController implements InputManager.InputDeviceListener {
}
};
abstract void onUEvent(long eventTime);
public abstract void onUEvent(long eventTime);
}
default void addListener(UEventListener listener, String match) {
@@ -494,4 +532,37 @@ final class BatteryController implements InputManager.InputDeviceListener {
listener.mObserver.stopObserving();
}
}
// Helper class that adds copying and printing functionality to IInputDeviceBatteryState.
private static class State extends IInputDeviceBatteryState {
State(int deviceId) {
initialize(deviceId, 0 /*updateTime*/, false /*isPresent*/, BatteryState.STATUS_UNKNOWN,
Float.NaN /*capacity*/);
}
State(IInputDeviceBatteryState s) {
initialize(s.deviceId, s.updateTime, s.isPresent, s.status, s.capacity);
}
State(int deviceId, long updateTime, boolean isPresent, int status, float capacity) {
initialize(deviceId, updateTime, isPresent, status, capacity);
}
private void initialize(int deviceId, long updateTime, boolean isPresent, int status,
float capacity) {
this.deviceId = deviceId;
this.updateTime = updateTime;
this.isPresent = isPresent;
this.status = status;
this.capacity = capacity;
}
@Override
public String toString() {
return "BatteryState{deviceId=" + deviceId + ", updateTime=" + updateTime
+ ", isPresent=" + isPresent + ", status=" + status + ", capacity=" + capacity
+ " }";
}
}
}

View File

@@ -49,6 +49,7 @@ import android.hardware.SensorPrivacyManagerInternal;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayViewport;
import android.hardware.input.IInputDeviceBatteryListener;
import android.hardware.input.IInputDeviceBatteryState;
import android.hardware.input.IInputDevicesChangedListener;
import android.hardware.input.IInputManager;
import android.hardware.input.IInputSensorEventListener;
@@ -2305,14 +2306,8 @@ public class InputManagerService extends IInputManager.Stub
// Binder call
@Override
public int getBatteryStatus(int deviceId) {
return mNative.getBatteryStatus(deviceId);
}
// Binder call
@Override
public int getBatteryCapacity(int deviceId) {
return mNative.getBatteryCapacity(deviceId);
public IInputDeviceBatteryState getBatteryState(int deviceId) {
return mBatteryController.getBatteryState(deviceId);
}
// Binder call

View File

@@ -22,6 +22,7 @@ import android.hardware.BatteryState.STATUS_CHARGING
import android.hardware.BatteryState.STATUS_FULL
import android.hardware.BatteryState.STATUS_UNKNOWN
import android.hardware.input.IInputDeviceBatteryListener
import android.hardware.input.IInputDeviceBatteryState
import android.hardware.input.IInputDevicesChangedListener
import android.hardware.input.IInputManager
import android.hardware.input.InputManager
@@ -32,6 +33,12 @@ import android.platform.test.annotations.Presubmit
import android.view.InputDevice
import androidx.test.InstrumentationRegistry
import com.android.server.input.BatteryController.UEventManager
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers
import org.hamcrest.TypeSafeMatcher
import org.hamcrest.core.IsEqual.equalTo
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.fail
@@ -42,7 +49,6 @@ 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.clearInvocations
import org.mockito.Mockito.eq
import org.mockito.Mockito.mock
@@ -52,7 +58,9 @@ import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when`
import org.mockito.hamcrest.MockitoHamcrest
import org.mockito.junit.MockitoJUnit
import org.mockito.verification.VerificationMode
private fun createInputDevice(deviceId: Int, hasBattery: Boolean = true): InputDevice =
InputDevice.Builder()
@@ -64,6 +72,64 @@ private fun createInputDevice(deviceId: Int, hasBattery: Boolean = true): InputD
.setGeneration(0)
.build()
// Returns a matcher that helps match member variables of a class.
private fun <T, U> memberMatcher(
member: String,
memberProvider: (T) -> U,
match: Matcher<U>
): TypeSafeMatcher<T> =
object : TypeSafeMatcher<T>() {
override fun matchesSafely(item: T?): Boolean {
return match.matches(memberProvider(item!!))
}
override fun describeMismatchSafely(item: T?, mismatchDescription: Description?) {
match.describeMismatch(item, mismatchDescription)
}
override fun describeTo(description: Description?) {
match.describeTo(description?.appendText("matches member $member"))
}
}
// Returns a matcher for IInputDeviceBatteryState that optionally matches some arguments.
private fun matchesState(
deviceId: Int,
isPresent: Boolean = true,
status: Int? = null,
capacity: Float? = null,
eventTime: Long? = null
): Matcher<IInputDeviceBatteryState> {
val batteryStateMatchers = mutableListOf<Matcher<IInputDeviceBatteryState>>(
memberMatcher("deviceId", { it.deviceId }, equalTo(deviceId)),
memberMatcher("isPresent", { it.isPresent }, equalTo(isPresent))
)
if (eventTime != null) {
batteryStateMatchers.add(memberMatcher("updateTime", { it.updateTime }, equalTo(eventTime)))
}
if (status != null) {
batteryStateMatchers.add(memberMatcher("status", { it.status }, equalTo(status)))
}
if (capacity != null) {
batteryStateMatchers.add(memberMatcher("capacity", { it.capacity }, equalTo(capacity)))
}
return Matchers.allOf(batteryStateMatchers)
}
// Helper used to verify interactions with a mocked battery listener.
private fun IInputDeviceBatteryListener.verifyNotified(
deviceId: Int,
mode: VerificationMode = times(1),
isPresent: Boolean = true,
status: Int? = null,
capacity: Float? = null,
eventTime: Long? = null
) {
verify(this, mode).onBatteryStateChanged(
MockitoHamcrest.argThat(matchesState(deviceId, isPresent, status, capacity, eventTime)))
}
/**
* Tests for {@link InputDeviceBatteryController}.
*
@@ -184,14 +250,12 @@ class BatteryControllerTests {
`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())
listener.verifyNotified(DEVICE_ID, status = STATUS_FULL, capacity = 1.0f)
`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())
listener.verifyNotified(SECOND_DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f)
}
@Test
@@ -203,14 +267,13 @@ class BatteryControllerTests {
val uEventListener = ArgumentCaptor.forClass(UEventManager.UEventListener::class.java)
batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
verify(uEventManager).addListener(uEventListener.capture(), eq("DEVPATH=/test/device1"))
verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/),
eq(STATUS_CHARGING), eq(0.78f), anyLong())
listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f)
// If the battery state has changed when an UEvent is sent, the listeners are notified.
`when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(80)
uEventListener.value!!.onUEvent(TIMESTAMP)
verify(listener).onBatteryStateChanged(DEVICE_ID, true /*isPresent*/, STATUS_CHARGING,
0.80f, TIMESTAMP)
listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.80f,
eventTime = TIMESTAMP)
// If the battery state has not changed when an UEvent is sent, the listeners are not
// notified.
@@ -233,20 +296,15 @@ class BatteryControllerTests {
val uEventListener = ArgumentCaptor.forClass(UEventManager.UEventListener::class.java)
batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
verify(uEventManager).addListener(uEventListener.capture(), eq("DEVPATH=/test/device1"))
verify(listener).onBatteryStateChanged(
eq(DEVICE_ID), eq(true /*isPresent*/),
eq(STATUS_CHARGING), eq(0.78f), anyLong()
)
listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f)
// If the battery presence for the InputDevice changes, the listener is notified.
`when`(iInputManager.getInputDevice(DEVICE_ID))
.thenReturn(createInputDevice(DEVICE_ID, hasBattery = false))
notifyDeviceChanged(DEVICE_ID)
testLooper.dispatchNext()
verify(listener).onBatteryStateChanged(
eq(DEVICE_ID), eq(false /*isPresent*/),
eq(STATUS_UNKNOWN), eq(Float.NaN), anyLong()
)
listener.verifyNotified(DEVICE_ID, isPresent = false, status = STATUS_UNKNOWN,
capacity = Float.NaN)
// Since the battery is no longer present, the UEventListener should be removed.
verify(uEventManager).removeListener(uEventListener.value)
@@ -255,36 +313,32 @@ class BatteryControllerTests {
.thenReturn(createInputDevice(DEVICE_ID, hasBattery = true))
notifyDeviceChanged(DEVICE_ID)
testLooper.dispatchNext()
verify(listener, times(2)).onBatteryStateChanged(
eq(DEVICE_ID), eq(true /*isPresent*/),
eq(STATUS_CHARGING), eq(0.78f), anyLong()
)
listener.verifyNotified(DEVICE_ID, mode = times(2), status = STATUS_CHARGING,
capacity = 0.78f)
// Ensure that a new UEventListener was added.
verify(uEventManager, times(2))
.addListener(uEventListener.capture(), eq("DEVPATH=/test/device1"))
}
@Test
fun testStartPollingWhenListenerIsRegistered() {
val listener = createMockListener()
`when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78)
batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/), anyInt(),
eq(0.78f), anyLong())
listener.verifyNotified(DEVICE_ID, capacity = 0.78f)
// Assume there is a change in the battery state. Ensure the listener is not notified
// while the polling period has not elapsed.
`when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(80)
testLooper.moveTimeForward(1)
testLooper.dispatchAll()
verify(listener, never()).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/),
anyInt(), eq(0.80f), anyLong())
listener.verifyNotified(DEVICE_ID, mode = never(), capacity = 0.80f)
// Move the time forward so that the polling period has elapsed.
// The listener should be notified.
testLooper.moveTimeForward(BatteryController.POLLING_PERIOD_MILLIS - 1)
testLooper.dispatchNext()
verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/), anyInt(),
eq(0.80f), anyLong())
listener.verifyNotified(DEVICE_ID, capacity = 0.80f)
}
@Test
@@ -294,28 +348,50 @@ class BatteryControllerTests {
val listener = createMockListener()
`when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78)
batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/), anyInt(),
eq(0.78f), anyLong())
listener.verifyNotified(DEVICE_ID, capacity = 0.78f)
// The battery state changed, but we should not be polling for battery changes when the
// device is not interactive.
`when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(80)
testLooper.moveTimeForward(BatteryController.POLLING_PERIOD_MILLIS)
testLooper.dispatchAll()
verify(listener, never()).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/),
anyInt(), eq(0.80f), anyLong())
listener.verifyNotified(DEVICE_ID, mode = never(), capacity = 0.80f)
// The device is now interactive. Battery state polling begins immediately.
batteryController.onInteractiveChanged(true /*interactive*/)
testLooper.dispatchNext()
verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/),
anyInt(), eq(0.80f), anyLong())
listener.verifyNotified(DEVICE_ID, capacity = 0.80f)
// Ensure that we continue to poll for battery changes.
`when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(90)
testLooper.moveTimeForward(BatteryController.POLLING_PERIOD_MILLIS)
testLooper.dispatchNext()
verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/),
anyInt(), eq(0.90f), anyLong())
listener.verifyNotified(DEVICE_ID, capacity = 0.90f)
}
@Test
fun testGetBatteryState() {
`when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_CHARGING)
`when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78)
val batteryState = batteryController.getBatteryState(DEVICE_ID)
assertThat("battery state matches", batteryState,
matchesState(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f))
}
@Test
fun testGetBatteryStateWithListener() {
val listener = createMockListener()
`when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_CHARGING)
`when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78)
batteryController.registerBatteryListener(DEVICE_ID, listener, PID)
listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f)
// If getBatteryState() is called when a listener is monitoring the device and there is a
// change in the battery state, the listener is also notified.
`when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(80)
val batteryState = batteryController.getBatteryState(DEVICE_ID)
assertThat("battery matches state", batteryState,
matchesState(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.80f))
listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.80f)
}
}