From 8605f83e2888449489b68ab321b13be75827796a Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 13 Sep 2022 20:36:12 +0000 Subject: [PATCH 1/2] 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 --- .../input/IInputDeviceBatteryListener.aidl | 6 +- .../input/IInputDeviceBatteryState.aidl | 40 +++++ .../android/hardware/input/IInputManager.aidl | 5 +- .../input/InputDeviceBatteryState.java | 65 -------- .../android/hardware/input/InputManager.java | 81 ++++------ core/java/android/view/InputDevice.java | 8 +- .../input/InputDeviceBatteryListenerTest.kt | 8 +- .../server/input/BatteryController.java | 145 ++++++++++++----- .../server/input/InputManagerService.java | 11 +- .../server/input/BatteryControllerTests.kt | 146 +++++++++++++----- 10 files changed, 302 insertions(+), 213 deletions(-) create mode 100644 core/java/android/hardware/input/IInputDeviceBatteryState.aidl delete mode 100644 core/java/android/hardware/input/InputDeviceBatteryState.java diff --git a/core/java/android/hardware/input/IInputDeviceBatteryListener.aidl b/core/java/android/hardware/input/IInputDeviceBatteryListener.aidl index dc5a96684606b..89324352ca34d 100644 --- a/core/java/android/hardware/input/IInputDeviceBatteryListener.aidl +++ b/core/java/android/hardware/input/IInputDeviceBatteryListener.aidl @@ -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); } diff --git a/core/java/android/hardware/input/IInputDeviceBatteryState.aidl b/core/java/android/hardware/input/IInputDeviceBatteryState.aidl new file mode 100644 index 0000000000000..561286cbabd6e --- /dev/null +++ b/core/java/android/hardware/input/IInputDeviceBatteryState.aidl @@ -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; +} \ No newline at end of file diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index 36297b9919120..f213224b981e1 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -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); diff --git a/core/java/android/hardware/input/InputDeviceBatteryState.java b/core/java/android/hardware/input/InputDeviceBatteryState.java deleted file mode 100644 index d069eb0ddb812..0000000000000 --- a/core/java/android/hardware/input/InputDeviceBatteryState.java +++ /dev/null @@ -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; - } -} diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index 8960d2a336b15..8d4aac4bba885 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -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 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); } } } diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java index 42c37fd97213f..9fd8ecb1727f9 100644 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -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); } /** diff --git a/core/tests/coretests/src/android/hardware/input/InputDeviceBatteryListenerTest.kt b/core/tests/coretests/src/android/hardware/input/InputDeviceBatteryListenerTest.kt index e3b3ea7492c5b..4f27e9997cf62 100644 --- a/core/tests/coretests/src/android/hardware/input/InputDeviceBatteryListenerTest.kt +++ b/core/tests/coretests/src/android/hardware/input/InputDeviceBatteryListenerTest.kt @@ -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 diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java index 32f17dc18033a..2f369ccc51b1b 100644 --- a/services/core/java/com/android/server/input/BatteryController.java +++ b/services/core/java/com/android/server/input/BatteryController.java @@ -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 + + " }"; + } + } } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 71feb952cb005..beb524ee1ed91 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -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 diff --git a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt index 40757182890c8..5f3f3d7714ef7 100644 --- a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt +++ b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt @@ -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 memberMatcher( + member: String, + memberProvider: (T) -> U, + match: Matcher +): TypeSafeMatcher = + object : TypeSafeMatcher() { + + 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 { + val batteryStateMatchers = mutableListOf>( + 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) } } From b92ff23bd845b8b19e5b2b1a89dd7af7c9b9ff1f Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 13 Sep 2022 21:04:39 +0000 Subject: [PATCH 2/2] Rename MonitoredDeviceState to DeviceMonitor Also: - Don't make InputDevicesChangedListener part of the BatteryController interface, since it's not used for testing. - Report a copy of the battery state when notifying components outside of BatteryController, becuase reporting a member object would leak the internal state to other components. - Explicitly mark all interface methods as public. Bug: 243005009 Test: atest FrameworkServicesTests Test: atest FrameworkCoreTests Change-Id: Iefa08f4f1b52b8dd48dc273e90e90c92d15d4ed4 --- .../server/input/BatteryController.java | 172 +++++++++--------- 1 file changed, 90 insertions(+), 82 deletions(-) diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java index 2f369ccc51b1b..696b6047137a6 100644 --- a/services/core/java/com/android/server/input/BatteryController.java +++ b/services/core/java/com/android/server/input/BatteryController.java @@ -48,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: @@ -70,10 +75,9 @@ final class BatteryController implements InputManager.InputDeviceListener { @GuardedBy("mLock") private final ArrayMap 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 mMonitoredDeviceStates = new ArrayMap<>(); + private final ArrayMap mDeviceMonitors = new ArrayMap<>(); @GuardedBy("mLock") private boolean mIsPolling = false; @@ -93,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); } /** @@ -103,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); @@ -132,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) { @@ -145,37 +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( - new State(deviceState.mState)); + 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 " + deviceState.mState.deviceId); + + " of state of deviceId " + state.deviceId); } } @GuardedBy("mLock") - private void notifyAllListenersForDeviceLocked(MonitoredDeviceState deviceState) { - if (DEBUG) Slog.d(TAG, "Notifying all listeners of battery state: " + 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.mState.deviceId)) { - 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); @@ -198,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); } @@ -209,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) { @@ -249,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()) { @@ -290,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()); } } } @@ -309,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()); } } }); @@ -322,31 +323,32 @@ final class BatteryController implements InputManager.InputDeviceListener { } /** Gets the current battery state of an input device. */ - IInputDeviceBatteryState getBatteryState(int deviceId) { + public IInputDeviceBatteryState getBatteryState(int deviceId) { synchronized (mLock) { final long updateTime = SystemClock.uptimeMillis(); - final MonitoredDeviceState deviceState = mMonitoredDeviceStates.get(deviceId); - if (deviceState == null) { + 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); - } else { - // Force the battery state to update, and notify listeners if necessary. - if (deviceState.updateBatteryState(updateTime)) { - notifyAllListenersForDeviceLocked(deviceState); - } - return new State(deviceState.mState); } + // 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; } } - void onInteractiveChanged(boolean interactive) { + 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) { final String indent = prefix + " "; final String indent2 = indent + " "; @@ -360,50 +362,50 @@ final class BatteryController implements InputManager.InputDeviceListener { 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)); + 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 mMonitoredDevices; + public final Set mMonitoredDevices; ListenerRecord(int pid, IInputDeviceBatteryListener listener) { mPid = pid; @@ -431,14 +433,14 @@ final class BatteryController implements InputManager.InputDeviceListener { } // Holds the state of an InputDevice for which battery changes are currently being monitored. - private class MonitoredDeviceState { + private class DeviceMonitor { @NonNull private State mState; @Nullable private UEventListener mUEventListener; - MonitoredDeviceState(int deviceId) { + DeviceMonitor(int deviceId) { mState = new State(deviceId); // Load the initial battery state and start monitoring. @@ -447,7 +449,7 @@ final class BatteryController implements InputManager.InputDeviceListener { } // Returns true if the battery state changed since the last time it was updated. - boolean updateBatteryState(long updateTime) { + public boolean updateBatteryState(long updateTime) { mState.updateTime = updateTime; final State updatedState = queryBatteryStateFromNative(mState.deviceId, updateTime); @@ -470,23 +472,29 @@ final class BatteryController implements InputManager.InputDeviceListener { if (batteryPath == null) { return; } + final int deviceId = mState.deviceId; mUEventListener = new UEventListener() { @Override - void onUEvent(long eventTime) { - handleBatteryChangeNotification(mState.deviceId, 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 @@ -513,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) {