diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java index 9fd8ecb1727f9..9b1d8673390b9 100644 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -80,6 +80,7 @@ public final class InputDevice implements Parcelable { private final boolean mHasButtonUnderPad; private final boolean mHasSensor; private final boolean mHasBattery; + private final boolean mSupportsUsi; private final ArrayList mMotionRanges = new ArrayList(); @GuardedBy("mMotionRanges") @@ -462,7 +463,7 @@ public final class InputDevice implements Parcelable { int productId, String descriptor, boolean isExternal, int sources, int keyboardType, KeyCharacterMap keyCharacterMap, @InputDeviceCountryCode int countryCode, boolean hasVibrator, boolean hasMicrophone, boolean hasButtonUnderPad, - boolean hasSensor, boolean hasBattery) { + boolean hasSensor, boolean hasBattery, boolean supportsUsi) { mId = id; mGeneration = generation; mControllerNumber = controllerNumber; @@ -481,6 +482,7 @@ public final class InputDevice implements Parcelable { mHasSensor = hasSensor; mHasBattery = hasBattery; mIdentifier = new InputDeviceIdentifier(descriptor, vendorId, productId); + mSupportsUsi = supportsUsi; } private InputDevice(Parcel in) { @@ -501,6 +503,7 @@ public final class InputDevice implements Parcelable { mHasButtonUnderPad = in.readInt() != 0; mHasSensor = in.readInt() != 0; mHasBattery = in.readInt() != 0; + mSupportsUsi = in.readInt() != 0; mIdentifier = new InputDeviceIdentifier(mDescriptor, mVendorId, mProductId); int numRanges = in.readInt(); @@ -538,6 +541,7 @@ public final class InputDevice implements Parcelable { private boolean mHasBattery = false; @InputDeviceCountryCode private int mCountryCode = InputDeviceCountryCode.INVALID; + private boolean mSupportsUsi = false; /** @see InputDevice#getId() */ public Builder setId(int id) { @@ -641,12 +645,18 @@ public final class InputDevice implements Parcelable { return this; } + /** @see InputDevice#supportsUsi() () */ + public Builder setSupportsUsi(boolean supportsUsi) { + mSupportsUsi = supportsUsi; + return this; + } + /** Build {@link InputDevice}. */ public InputDevice build() { return new InputDevice(mId, mGeneration, mControllerNumber, mName, mVendorId, mProductId, mDescriptor, mIsExternal, mSources, mKeyboardType, mKeyCharacterMap, mCountryCode, mHasVibrator, mHasMicrophone, mHasButtonUnderPad, mHasSensor, - mHasBattery); + mHasBattery, mSupportsUsi); } } @@ -1178,6 +1188,15 @@ public final class InputDevice implements Parcelable { return mHasBattery; } + /** + * Reports whether the device supports the Universal Stylus Initiative (USI) protocol for + * styluses. + * @hide + */ + public boolean supportsUsi() { + return mSupportsUsi; + } + /** * Provides information about the range of values for a particular {@link MotionEvent} axis. * @@ -1308,6 +1327,7 @@ public final class InputDevice implements Parcelable { out.writeInt(mHasButtonUnderPad ? 1 : 0); out.writeInt(mHasSensor ? 1 : 0); out.writeInt(mHasBattery ? 1 : 0); + out.writeInt(mSupportsUsi ? 1 : 0); final int numRanges = mMotionRanges.size(); out.writeInt(numRanges); @@ -1361,6 +1381,8 @@ public final class InputDevice implements Parcelable { description.append(" Has mic: ").append(mHasMicrophone).append("\n"); + description.append(" Supports USI: ").append(mSupportsUsi).append("\n"); + description.append(" Sources: 0x").append(Integer.toHexString(mSources)).append(" ("); appendSourceDescriptionIfApplicable(description, SOURCE_KEYBOARD, "keyboard"); appendSourceDescriptionIfApplicable(description, SOURCE_DPAD, "dpad"); diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp index aece8c35a9e6a..39ec0374dc5ee 100644 --- a/core/jni/android_view_InputDevice.cpp +++ b/core/jni/android_view_InputDevice.cpp @@ -57,9 +57,6 @@ jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& devi const InputDeviceIdentifier& ident = deviceInfo.getIdentifier(); - // Not sure why, but JNI is complaining when I pass this through directly. - jboolean hasMic = deviceInfo.hasMic() ? JNI_TRUE : JNI_FALSE; - ScopedLocalRef inputDeviceObj(env, env->NewObject(gInputDeviceClassInfo.clazz, gInputDeviceClassInfo.ctor, @@ -70,8 +67,9 @@ jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& devi deviceInfo.isExternal(), deviceInfo.getSources(), deviceInfo.getKeyboardType(), kcmObj.get(), deviceInfo.getCountryCode(), deviceInfo.hasVibrator(), - hasMic, deviceInfo.hasButtonUnderPad(), - deviceInfo.hasSensor(), deviceInfo.hasBattery())); + deviceInfo.hasMic(), deviceInfo.hasButtonUnderPad(), + deviceInfo.hasSensor(), deviceInfo.hasBattery(), + deviceInfo.supportsUsi())); const std::vector& ranges = deviceInfo.getMotionRanges(); for (const InputDeviceInfo::MotionRange& range: ranges) { @@ -94,7 +92,7 @@ int register_android_view_InputDevice(JNIEnv* env) gInputDeviceClassInfo.ctor = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "", "(IIILjava/lang/String;IILjava/lang/" - "String;ZIILandroid/view/KeyCharacterMap;IZZZZZ)V"); + "String;ZIILandroid/view/KeyCharacterMap;IZZZZZZ)V"); gInputDeviceClassInfo.addMotionRange = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "addMotionRange", "(IIFFFFF)V"); diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java index 696b6047137a6..324eefc809e82 100644 --- a/services/core/java/com/android/server/input/BatteryController.java +++ b/services/core/java/com/android/server/input/BatteryController.java @@ -38,7 +38,7 @@ import android.view.InputDevice; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.input.BatteryController.UEventManager.UEventListener; +import com.android.server.input.BatteryController.UEventManager.UEventBatteryListener; import java.io.PrintWriter; import java.util.Arrays; @@ -438,7 +438,7 @@ final class BatteryController { private State mState; @Nullable - private UEventListener mUEventListener; + private UEventBatteryListener mUEventBatteryListener; DeviceMonitor(int deviceId) { mState = new State(deviceId); @@ -473,20 +473,26 @@ final class BatteryController { return; } final int deviceId = mState.deviceId; - mUEventListener = new UEventListener() { + mUEventBatteryListener = new UEventBatteryListener() { @Override - public void onUEvent(long eventTime) { + public void onBatteryUEvent(long eventTime) { handleUEventNotification(deviceId, eventTime); } }; - mUEventManager.addListener(mUEventListener, "DEVPATH=" + batteryPath); + mUEventManager.addListener( + mUEventBatteryListener, "DEVPATH=" + formatDevPath(batteryPath)); + } + + private String formatDevPath(String path) { + // Remove the "/sys" prefix if it has one. + return path.startsWith("/sys") ? path.substring(4) : path; } // This must be called when the device is no longer being monitored. public void stopMonitoring() { - if (mUEventListener != null) { - mUEventManager.removeListener(mUEventListener); - mUEventListener = null; + if (mUEventBatteryListener != null) { + mUEventManager.removeListener(mUEventBatteryListener); + mUEventBatteryListener = null; } } @@ -498,7 +504,7 @@ final class BatteryController { @Override public String toString() { return "state=" + mState - + ", uEventListener=" + (mUEventListener != null ? "added" : "none"); + + ", uEventListener=" + (mUEventBatteryListener != null ? "added" : "none"); } } @@ -507,7 +513,7 @@ final class BatteryController { interface UEventManager { @VisibleForTesting - abstract class UEventListener { + abstract class UEventBatteryListener { private final UEventObserver mObserver = new UEventObserver() { @Override public void onUEvent(UEvent event) { @@ -517,18 +523,23 @@ final class BatteryController { "UEventListener: Received UEvent: " + event + " eventTime: " + eventTime); } - UEventListener.this.onUEvent(eventTime); + if (!"CHANGE".equalsIgnoreCase(event.get("ACTION")) + || !"POWER_SUPPLY".equalsIgnoreCase(event.get("SUBSYSTEM"))) { + // Disregard any UEvents that do not correspond to battery changes. + return; + } + UEventBatteryListener.this.onBatteryUEvent(eventTime); } }; - public abstract void onUEvent(long eventTime); + public abstract void onBatteryUEvent(long eventTime); } - default void addListener(UEventListener listener, String match) { + default void addListener(UEventBatteryListener listener, String match) { listener.mObserver.startObserving(match); } - default void removeListener(UEventListener listener) { + default void removeListener(UEventBatteryListener listener) { listener.mObserver.stopObserving(); } } 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 5f3f3d7714ef7..65076a372b0b9 100644 --- a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt +++ b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt @@ -33,6 +33,7 @@ import android.platform.test.annotations.Presubmit import android.view.InputDevice import androidx.test.InstrumentationRegistry import com.android.server.input.BatteryController.UEventManager +import com.android.server.input.BatteryController.UEventManager.UEventBatteryListener import org.hamcrest.Description import org.hamcrest.Matcher import org.hamcrest.MatcherAssert.assertThat @@ -260,25 +261,28 @@ class BatteryControllerTests { @Test fun testListenersNotifiedOnUEventNotification() { - `when`(native.getBatteryDevicePath(DEVICE_ID)).thenReturn("/test/device1") + `when`(native.getBatteryDevicePath(DEVICE_ID)).thenReturn("/sys/dev/test/device1") `when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_CHARGING) `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78) val listener = createMockListener() - val uEventListener = ArgumentCaptor.forClass(UEventManager.UEventListener::class.java) + val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java) batteryController.registerBatteryListener(DEVICE_ID, listener, PID) - verify(uEventManager).addListener(uEventListener.capture(), eq("DEVPATH=/test/device1")) + // The device paths for UEvent notifications do not include the "/sys" prefix, so verify + // that the added listener is configured to match the path without that prefix. + verify(uEventManager) + .addListener(uEventListener.capture(), eq("DEVPATH=/dev/test/device1")) 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) + uEventListener.value!!.onBatteryUEvent(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. clearInvocations(listener) - uEventListener.value!!.onUEvent(TIMESTAMP + 1) + uEventListener.value!!.onBatteryUEvent(TIMESTAMP + 1) verifyNoMoreInteractions(listener) batteryController.unregisterBatteryListener(DEVICE_ID, listener, PID) @@ -293,7 +297,7 @@ class BatteryControllerTests { `when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_CHARGING) `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78) val listener = createMockListener() - val uEventListener = ArgumentCaptor.forClass(UEventManager.UEventListener::class.java) + val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java) batteryController.registerBatteryListener(DEVICE_ID, listener, PID) verify(uEventManager).addListener(uEventListener.capture(), eq("DEVPATH=/test/device1")) listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f) diff --git a/tests/Input/src/com/android/test/input/InputDeviceTest.java b/tests/Input/src/com/android/test/input/InputDeviceTest.java index 06a96dfb8176e..8aaf18af06644 100644 --- a/tests/Input/src/com/android/test/input/InputDeviceTest.java +++ b/tests/Input/src/com/android/test/input/InputDeviceTest.java @@ -87,6 +87,7 @@ public class InputDeviceTest { .setHasSensor(true) .setHasBattery(true) .setCountryCode(InputDeviceCountryCode.INTERNATIONAL) + .setSupportsUsi(true) .build(); Parcel parcel = Parcel.obtain();