summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt5
-rw-r--r--core/java/android/hardware/input/InputManager.java19
-rw-r--r--core/java/android/view/InputDevice.java120
-rw-r--r--core/jni/android_view_InputDevice.cpp22
-rw-r--r--tests/Input/src/com/android/test/input/InputDeviceTest.java13
5 files changed, 172 insertions, 7 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index 8fd50cc7c1ef..9e3cc96c1711 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -20268,6 +20268,7 @@ package android.hardware.input {
method @Nullable public android.hardware.input.HostUsiVersion getHostUsiVersion(@NonNull android.view.Display);
method @Nullable public android.view.InputDevice getInputDevice(int);
method public int[] getInputDeviceIds();
+ method @FlaggedApi("com.android.input.flags.input_device_view_behavior_api") @Nullable public android.view.InputDevice.ViewBehavior getInputDeviceViewBehavior(int);
method @FloatRange(from=0, to=1) public float getMaximumObscuringOpacityForTouch();
method public boolean isStylusPointerIconEnabled();
method public void registerInputDeviceListener(android.hardware.input.InputManager.InputDeviceListener, android.os.Handler);
@@ -50448,6 +50449,10 @@ package android.view {
method public boolean isFromSource(int);
}
+ @FlaggedApi("com.android.input.flags.input_device_view_behavior_api") public static final class InputDevice.ViewBehavior {
+ method @FlaggedApi("com.android.input.flags.input_device_view_behavior_api") public boolean shouldSmoothScroll(int, int);
+ }
+
public abstract class InputEvent implements android.os.Parcelable {
method public int describeContents();
method public final android.view.InputDevice getDevice();
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 4ebbde732747..db992cdd20db 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -16,9 +16,11 @@
package android.hardware.input;
+import static com.android.input.flags.Flags.FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API;
import static com.android.hardware.input.Flags.keyboardLayoutPreviewFlag;
import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -294,6 +296,23 @@ public final class InputManager {
}
/**
+ * Gets the {@link InputDevice.ViewBehavior} of the input device with a given {@code id}.
+ *
+ * <p>Use this API to query a fresh view behavior instance whenever the input device
+ * changes.
+ *
+ * @param deviceId the id of the input device whose view behavior is being requested.
+ * @return the view behavior of the input device with the provided id, or {@code null} if there
+ * is not input device with the provided id.
+ */
+ @FlaggedApi(FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API)
+ @Nullable
+ public InputDevice.ViewBehavior getInputDeviceViewBehavior(int deviceId) {
+ InputDevice device = getInputDevice(deviceId);
+ return device == null ? null : device.getViewBehavior();
+ }
+
+ /**
* Gets information about the input device with the specified descriptor.
* @param descriptor The input device descriptor.
* @return The input device or null if not found.
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index f2c3abc8edb4..891e2a2d4b20 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -16,7 +16,10 @@
package android.view;
+import static com.android.input.flags.Flags.FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API;
+
import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -28,6 +31,7 @@ import android.hardware.BatteryState;
import android.hardware.SensorManager;
import android.hardware.input.HostUsiVersion;
import android.hardware.input.InputDeviceIdentifier;
+import android.hardware.input.InputManager;
import android.hardware.input.InputManagerGlobal;
import android.hardware.lights.LightsManager;
import android.icu.util.ULocale;
@@ -90,6 +94,8 @@ public final class InputDevice implements Parcelable {
private final int mAssociatedDisplayId;
private final ArrayList<MotionRange> mMotionRanges = new ArrayList<MotionRange>();
+ private final ViewBehavior mViewBehavior = new ViewBehavior(this);
+
@GuardedBy("mMotionRanges")
private Vibrator mVibrator; // guarded by mMotionRanges during initialization
@@ -539,6 +545,8 @@ public final class InputDevice implements Parcelable {
addMotionRange(in.readInt(), in.readInt(), in.readFloat(), in.readFloat(),
in.readFloat(), in.readFloat(), in.readFloat());
}
+
+ mViewBehavior.mShouldSmoothScroll = in.readBoolean();
}
/**
@@ -571,6 +579,7 @@ public final class InputDevice implements Parcelable {
private int mUsiVersionMinor = -1;
private int mAssociatedDisplayId = Display.INVALID_DISPLAY;
private List<MotionRange> mMotionRanges = new ArrayList<>();
+ private boolean mShouldSmoothScroll;
/** @see InputDevice#getId() */
public Builder setId(int id) {
@@ -706,6 +715,16 @@ public final class InputDevice implements Parcelable {
return this;
}
+ /**
+ * Sets the view behavior for smooth scrolling ({@code false} by default).
+ *
+ * @see ViewBehavior#shouldSmoothScroll(int, int)
+ */
+ public Builder setShouldSmoothScroll(boolean shouldSmoothScroll) {
+ mShouldSmoothScroll = shouldSmoothScroll;
+ return this;
+ }
+
/** Build {@link InputDevice}. */
public InputDevice build() {
InputDevice device = new InputDevice(
@@ -745,6 +764,8 @@ public final class InputDevice implements Parcelable {
range.getResolution());
}
+ device.setShouldSmoothScroll(mShouldSmoothScroll);
+
return device;
}
}
@@ -1123,6 +1144,22 @@ public final class InputDevice implements Parcelable {
return mMotionRanges;
}
+ /**
+ * Provides the {@link ViewBehavior} for the device.
+ *
+ * <p>This behavior is designed to be obtained using the
+ * {@link InputManager#getInputDeviceViewBehavior(int)} API, to allow associating the behavior
+ * with a {@link Context} (since input device is not associated with a context).
+ * The ability to associate the behavior with a context opens capabilities like linking the
+ * behavior to user settings, for example.
+ *
+ * @hide
+ */
+ @NonNull
+ public ViewBehavior getViewBehavior() {
+ return mViewBehavior;
+ }
+
// Called from native code.
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private void addMotionRange(int axis, int source,
@@ -1130,6 +1167,11 @@ public final class InputDevice implements Parcelable {
mMotionRanges.add(new MotionRange(axis, source, min, max, flat, fuzz, resolution));
}
+ // Called from native code.
+ private void setShouldSmoothScroll(boolean shouldSmoothScroll) {
+ mViewBehavior.mShouldSmoothScroll = shouldSmoothScroll;
+ }
+
/**
* Returns the Bluetooth address of this input device, if known.
*
@@ -1447,6 +1489,82 @@ public final class InputDevice implements Parcelable {
}
}
+ /**
+ * Provides information on how views processing {@link MotionEvent}s generated by this input
+ * device should respond to the events. Use {@link InputManager#getInputDeviceViewBehavior(int)}
+ * to get an instance of the view behavior for an input device.
+ *
+ * <p>See an example below how a {@link View} can use this class to determine and apply the
+ * scrolling behavior for a generic {@link MotionEvent}.
+ *
+ * <pre>{@code
+ * public boolean onGenericMotionEvent(MotionEvent event) {
+ * InputManager manager = context.getSystemService(InputManager.class);
+ * ViewBehavior viewBehavior = manager.getInputDeviceViewBehavior(event.getDeviceId());
+ * // Assume a helper function that tells us which axis to use for scrolling purpose.
+ * int axis = getScrollAxisForGenericMotionEvent(event);
+ * int source = event.getSource();
+ *
+ * boolean shouldSmoothScroll =
+ * viewBehavior != null && viewBehavior.shouldSmoothScroll(axis, source);
+ * // Proceed to running the scrolling logic...
+ * }
+ * }</pre>
+ *
+ * @see InputManager#getInputDeviceViewBehavior(int)
+ */
+ @FlaggedApi(FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API)
+ public static final class ViewBehavior {
+ private static final boolean DEFAULT_SHOULD_SMOOTH_SCROLL = false;
+
+ private final InputDevice mInputDevice;
+
+ // TODO(b/246946631): implement support for InputDevices to adjust this configuration
+ // by axis and source. When implemented, the axis/source specific config will take
+ // precedence over this global config.
+ /** A global smooth scroll configuration applying to all motion axis and input source. */
+ private boolean mShouldSmoothScroll = DEFAULT_SHOULD_SMOOTH_SCROLL;
+
+ /** @hide */
+ public ViewBehavior(@NonNull InputDevice inputDevice) {
+ mInputDevice = inputDevice;
+ }
+
+ /**
+ * Returns whether a view should smooth scroll when scrolling due to a {@link MotionEvent}
+ * generated by the input device.
+ *
+ * <p>Smooth scroll in this case refers to a scroll that animates the transition between
+ * the starting and ending positions of the scroll. When this method returns {@code true},
+ * views should try to animate a scroll generated by this device at the given axis and with
+ * the given source to produce a good scroll user experience. If this method returns
+ * {@code false}, animating scrolls is not necessary.
+ *
+ * <p>If the input device does not have a {@link MotionRange} with the provided axis and
+ * source, this method returns {@code false}.
+ *
+ * @param axis the {@link MotionEvent} axis whose value is used to get the scroll extent.
+ * @param source the {link InputDevice} source from which the {@link MotionEvent} that
+ * triggers the scroll came.
+ * @return {@code true} if smooth scrolling should be used for the scroll, or {@code false}
+ * if smooth scrolling is not necessary, or if the provided axis and source combination
+ * is not available for the input device.
+ */
+ @FlaggedApi(FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API)
+ public boolean shouldSmoothScroll(int axis, int source) {
+ // Note: although we currently do not use axis and source in computing the return value,
+ // we will keep the API params to avoid further public API changes when we start
+ // supporting axis/source configuration. Also, having these params lets OEMs provide
+ // their custom implementation of the API that depends on axis and source.
+
+ // TODO(b/246946631): speed up computation using caching of results.
+ if (mInputDevice.getMotionRange(axis, source) == null) {
+ return false;
+ }
+ return mShouldSmoothScroll;
+ }
+ }
+
@Override
public void writeToParcel(Parcel out, int flags) {
mKeyCharacterMap.writeToParcel(out, flags);
@@ -1484,6 +1602,8 @@ public final class InputDevice implements Parcelable {
out.writeFloat(range.mFuzz);
out.writeFloat(range.mResolution);
}
+
+ out.writeBoolean(mViewBehavior.mShouldSmoothScroll);
}
@Override
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index 239c6260800b..aae0da9006a2 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -14,17 +14,16 @@
* limitations under the License.
*/
-#include <input/Input.h>
+#include "android_view_InputDevice.h"
#include <android_runtime/AndroidRuntime.h>
+#include <com_android_input_flags.h>
+#include <input/Input.h>
#include <jni.h>
#include <nativehelper/JNIHelp.h>
-
#include <nativehelper/ScopedLocalRef.h>
-#include "android_view_InputDevice.h"
#include "android_view_KeyCharacterMap.h"
-
#include "core_jni_helpers.h"
namespace android {
@@ -34,6 +33,7 @@ static struct {
jmethodID ctor;
jmethodID addMotionRange;
+ jmethodID setShouldSmoothScroll;
} gInputDeviceClassInfo;
jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& deviceInfo) {
@@ -103,6 +103,18 @@ jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& devi
}
}
+ if (com::android::input::flags::input_device_view_behavior_api()) {
+ const InputDeviceViewBehavior& viewBehavior = deviceInfo.getViewBehavior();
+ std::optional<bool> defaultSmoothScroll = viewBehavior.shouldSmoothScroll;
+ if (defaultSmoothScroll.has_value()) {
+ env->CallVoidMethod(inputDeviceObj.get(), gInputDeviceClassInfo.setShouldSmoothScroll,
+ *defaultSmoothScroll);
+ if (env->ExceptionCheck()) {
+ return NULL;
+ }
+ }
+ }
+
return env->NewLocalRef(inputDeviceObj.get());
}
@@ -118,6 +130,8 @@ int register_android_view_InputDevice(JNIEnv* env)
gInputDeviceClassInfo.addMotionRange =
GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "addMotionRange", "(IIFFFFF)V");
+ gInputDeviceClassInfo.setShouldSmoothScroll =
+ GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "setShouldSmoothScroll", "(Z)V");
return 0;
}
diff --git a/tests/Input/src/com/android/test/input/InputDeviceTest.java b/tests/Input/src/com/android/test/input/InputDeviceTest.java
index 5434c82b07bd..5f1bc8748db8 100644
--- a/tests/Input/src/com/android/test/input/InputDeviceTest.java
+++ b/tests/Input/src/com/android/test/input/InputDeviceTest.java
@@ -67,8 +67,14 @@ public class InputDeviceTest {
assertEquals("keyCharacterMap not equal", keyCharacterMap, outKeyCharacterMap);
for (int j = 0; j < device.getMotionRanges().size(); j++) {
- assertMotionRangeEquals(device.getMotionRanges().get(j),
- outDevice.getMotionRanges().get(j));
+ InputDevice.MotionRange motionRange = device.getMotionRanges().get(j);
+ assertMotionRangeEquals(motionRange, outDevice.getMotionRanges().get(j));
+
+ int axis = motionRange.getAxis();
+ int source = motionRange.getSource();
+ assertEquals(
+ device.getViewBehavior().shouldSmoothScroll(axis, source),
+ outDevice.getViewBehavior().shouldSmoothScroll(axis, source));
}
}
@@ -93,7 +99,8 @@ public class InputDeviceTest {
.setHasBattery(true)
.setKeyboardLanguageTag("en-US")
.setKeyboardLayoutType("qwerty")
- .setUsiVersion(new HostUsiVersion(2, 0));
+ .setUsiVersion(new HostUsiVersion(2, 0))
+ .setShouldSmoothScroll(true);
for (int i = 0; i < 30; i++) {
deviceBuilder.addMotionRange(