Add AIDL definiton for IInputDeviceBatteryState

Create an AIDL parcelable class to represent the battery state of an
input device.

Instead of separately querying the battery status and capacity from an
app-local implmentation of "InputDeviceBatteryState" that acts as a
"manager", we combine them into one method that queries the battery
state from BatteryController.

Bug: 243005009
Test: atest BatteryControllerTests
Test: atest InputDeviceBatteryListenerTests
Change-Id: I2b77aeca6a2d062c8b04a16e38d3acf6c996c134
This commit is contained in:
Prabir Pradhan
2022-09-13 20:36:12 +00:00
parent fdfde64c82
commit 8605f83e28
10 changed files with 302 additions and 213 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;
@@ -152,20 +153,21 @@ final class BatteryController implements InputManager.InputDeviceListener {
MonitoredDeviceState deviceState) {
try {
listenerRecord.mListener.onBatteryStateChanged(
deviceState.mDeviceId,
deviceState.mHasBattery,
deviceState.mBatteryStatus,
deviceState.mBatteryCapacity,
deviceState.mLastUpdateTime);
new State(deviceState.mState));
} 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 " + deviceState.mState.deviceId);
}
}
@GuardedBy("mLock")
private void notifyAllListenersForDeviceLocked(MonitoredDeviceState deviceState) {
if (DEBUG) Slog.d(TAG, "Notifying all listeners of battery state: " + deviceState);
mListenerRecords.forEach((pid, listenerRecord) -> {
if (listenerRecord.mMonitoredDevices.contains(deviceState.mDeviceId)) {
if (listenerRecord.mMonitoredDevices.contains(deviceState.mState.deviceId)) {
notifyBatteryListener(listenerRecord, deviceState);
}
});
@@ -319,6 +321,24 @@ final class BatteryController implements InputManager.InputDeviceListener {
}
}
/** Gets the current battery state of an input device. */
IInputDeviceBatteryState getBatteryState(int deviceId) {
synchronized (mLock) {
final long updateTime = SystemClock.uptimeMillis();
final MonitoredDeviceState deviceState = mMonitoredDeviceStates.get(deviceId);
if (deviceState == null) {
// The input device's battery is not being monitored by any listener.
return queryBatteryStateFromNative(deviceId, updateTime);
} else {
// Force the battery state to update, and notify listeners if necessary.
if (deviceState.updateBatteryState(updateTime)) {
notifyAllListenersForDeviceLocked(deviceState);
}
return new State(deviceState.mState);
}
}
}
void onInteractiveChanged(boolean interactive) {
synchronized (mLock) {
mIsInteractive = interactive;
@@ -328,12 +348,21 @@ final class BatteryController implements InputManager.InputDeviceListener {
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 + "Monitored devices: " + mMonitoredDeviceStates.size() + " devices");
for (int i = 0; i < mMonitoredDeviceStates.size(); i++) {
pw.println(indent2 + i + ": " + mMonitoredDeviceStates.valueAt(i));
}
}
}
@@ -390,21 +419,27 @@ final class BatteryController implements InputManager.InputDeviceListener {
}
}
// 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);
}
// Holds the state of an InputDevice for which battery changes are currently being monitored.
private class MonitoredDeviceState {
private final int mDeviceId;
private long mLastUpdateTime = 0;
private boolean mHasBattery = false;
@BatteryState.BatteryStatus
private int mBatteryStatus = BatteryState.STATUS_UNKNOWN;
private float mBatteryCapacity = Float.NaN;
@NonNull
private State mState;
@Nullable
private UEventListener mUEventListener;
MonitoredDeviceState(int deviceId) {
mDeviceId = deviceId;
mState = new State(deviceId);
// Load the initial battery state and start monitoring.
final long eventTime = SystemClock.uptimeMillis();
@@ -412,44 +447,33 @@ 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;
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;
}
mUEventListener = new UEventListener() {
@Override
void onUEvent(long eventTime) {
handleBatteryChangeNotification(mDeviceId, eventTime);
handleBatteryChangeNotification(mState.deviceId, eventTime);
}
};
mUEventManager.addListener(mUEventListener, "DEVPATH=" + batteryPath);
@@ -462,6 +486,12 @@ final class BatteryController implements InputManager.InputDeviceListener {
mUEventListener = null;
}
}
@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.
@@ -494,4 +524,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)
}
}