diff --git a/core/api/current.txt b/core/api/current.txt index ce0954466e616..f9f80f15c72f8 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -32081,6 +32081,7 @@ package android.os { method @NonNull public boolean[] arePrimitivesSupported(@NonNull int...); method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel(); method public int getId(); + method @NonNull public int[] getPrimitiveDurations(@NonNull int...); method public abstract boolean hasAmplitudeControl(); method public abstract boolean hasVibrator(); method @Deprecated @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(long); diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java index 2876775a334ef..b3502f38dd53c 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -551,6 +551,27 @@ public abstract class Vibrator { return true; } + /** + * Query the estimated durations of the given primitives. + * + * The returned array will be the same length as the query array and the value at a given index + * will contain the duration in milliseconds of the effect at the same index in the querying + * array. + * + * @param primitiveIds Which primitives to query for. + * @return The duration of each primitive, with zeroes for primitives that are not supported. + */ + @NonNull + public int[] getPrimitiveDurations( + @NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) { + VibratorInfo info = getInfo(); + int[] durations = new int[primitiveIds.length]; + for (int i = 0; i < primitiveIds.length; i++) { + durations[i] = info.getPrimitiveDuration(primitiveIds[i]); + } + return durations; + } + /** * Turn the vibrator off. */ diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java index c7d66f0dccdc5..d73469ca7e8e2 100644 --- a/core/java/android/os/VibratorInfo.java +++ b/core/java/android/os/VibratorInfo.java @@ -25,6 +25,7 @@ import android.util.Log; import android.util.MathUtils; import android.util.Range; import android.util.SparseBooleanArray; +import android.util.SparseIntArray; import java.util.ArrayList; import java.util.Arrays; @@ -51,7 +52,7 @@ public class VibratorInfo implements Parcelable { @Nullable private final SparseBooleanArray mSupportedBraking; @Nullable - private final SparseBooleanArray mSupportedPrimitives; + private final SparseIntArray mSupportedPrimitives; private final float mQFactor; private final FrequencyMapping mFrequencyMapping; @@ -60,19 +61,37 @@ public class VibratorInfo implements Parcelable { mCapabilities = in.readLong(); mSupportedEffects = in.readSparseBooleanArray(); mSupportedBraking = in.readSparseBooleanArray(); - mSupportedPrimitives = in.readSparseBooleanArray(); + mSupportedPrimitives = in.readSparseIntArray(); mQFactor = in.readFloat(); mFrequencyMapping = in.readParcelable(VibratorInfo.class.getClassLoader()); } - /** @hide */ + /** + * Default constructor. + * + * @param id The vibrator id. + * @param capabilities All capability flags of the vibrator, defined in IVibrator.CAP_*. + * @param supportedEffects All supported predefined effects, enum values from {@link + * android.hardware.vibrator.Effect}. + * @param supportedBraking All supported braking types, enum values from {@link Braking}. + * @param supportedPrimitives All supported primitive effects, enum values from {@link + * android.hardware.vibrator.CompositePrimitive}. + * @param primitiveDurations A mapping of primitive durations, where indexes are enum values + * from {@link android.hardware.vibrator.CompositePrimitive} and the + * values are estimated durations in milliseconds. + * @param qFactor The vibrator quality factor. + * @param frequencyMapping The description of the vibrator supported frequencies and max + * amplitude mappings. + * @hide + */ public VibratorInfo(int id, long capabilities, int[] supportedEffects, int[] supportedBraking, - int[] supportedPrimitives, float qFactor, @NonNull FrequencyMapping frequencyMapping) { + int[] supportedPrimitives, int[] primitiveDurations, float qFactor, + @NonNull FrequencyMapping frequencyMapping) { mId = id; mCapabilities = capabilities; mSupportedEffects = toSparseBooleanArray(supportedEffects); mSupportedBraking = toSparseBooleanArray(supportedBraking); - mSupportedPrimitives = toSparseBooleanArray(supportedPrimitives); + mSupportedPrimitives = toSparseIntArray(supportedPrimitives, primitiveDurations); mQFactor = qFactor; mFrequencyMapping = frequencyMapping; } @@ -100,7 +119,7 @@ public class VibratorInfo implements Parcelable { dest.writeLong(mCapabilities); dest.writeSparseBooleanArray(mSupportedEffects); dest.writeSparseBooleanArray(mSupportedBraking); - dest.writeSparseBooleanArray(mSupportedPrimitives); + dest.writeSparseIntArray(mSupportedPrimitives); dest.writeFloat(mQFactor); dest.writeParcelable(mFrequencyMapping, flags); } @@ -119,18 +138,41 @@ public class VibratorInfo implements Parcelable { return false; } VibratorInfo that = (VibratorInfo) o; + if (mSupportedPrimitives == null || that.mSupportedPrimitives == null) { + if (mSupportedPrimitives != that.mSupportedPrimitives) { + return false; + } + } else { + if (mSupportedPrimitives.size() != that.mSupportedPrimitives.size()) { + return false; + } + for (int i = 0; i < mSupportedPrimitives.size(); i++) { + if (mSupportedPrimitives.keyAt(i) != that.mSupportedPrimitives.keyAt(i)) { + return false; + } + if (mSupportedPrimitives.valueAt(i) != that.mSupportedPrimitives.valueAt(i)) { + return false; + } + } + } return mId == that.mId && mCapabilities == that.mCapabilities && Objects.equals(mSupportedEffects, that.mSupportedEffects) && Objects.equals(mSupportedBraking, that.mSupportedBraking) - && Objects.equals(mSupportedPrimitives, that.mSupportedPrimitives) && Objects.equals(mQFactor, that.mQFactor) && Objects.equals(mFrequencyMapping, that.mFrequencyMapping); } @Override public int hashCode() { - return Objects.hash(mId, mCapabilities, mSupportedEffects, mSupportedBraking, - mSupportedPrimitives, mQFactor, mFrequencyMapping); + int hashCode = Objects.hash(mId, mCapabilities, mSupportedEffects, mSupportedBraking, + mQFactor, mFrequencyMapping); + if (mSupportedPrimitives != null) { + for (int i = 0; i < mSupportedPrimitives.size(); i++) { + hashCode = 31 * hashCode + mSupportedPrimitives.keyAt(i); + hashCode = 31 * hashCode + mSupportedPrimitives.valueAt(i); + } + } + return hashCode; } @Override @@ -206,7 +248,19 @@ public class VibratorInfo implements Parcelable { public boolean isPrimitiveSupported( @VibrationEffect.Composition.PrimitiveType int primitiveId) { return hasCapability(IVibrator.CAP_COMPOSE_EFFECTS) && mSupportedPrimitives != null - && mSupportedPrimitives.get(primitiveId); + && (mSupportedPrimitives.indexOfKey(primitiveId) >= 0); + } + + /** + * Query the estimated duration of given primitive. + * + * @param primitiveId Which primitives to query for. + * @return The duration in milliseconds estimated for the primitive, or zero if primitive not + * supported. + */ + public int getPrimitiveDuration( + @VibrationEffect.Composition.PrimitiveType int primitiveId) { + return mSupportedPrimitives.get(primitiveId); } /** @@ -364,14 +418,37 @@ public class VibratorInfo implements Parcelable { return names; } + /** + * Create a {@link SparseBooleanArray} from given {@code supportedKeys} where each key is mapped + * to {@code true}. + */ @Nullable - private static SparseBooleanArray toSparseBooleanArray(int[] values) { - if (values == null) { + private static SparseBooleanArray toSparseBooleanArray(int[] supportedKeys) { + if (supportedKeys == null) { return null; } SparseBooleanArray array = new SparseBooleanArray(); - for (int value : values) { - array.put(value, true); + for (int key : supportedKeys) { + array.put(key, true); + } + return array; + } + + /** + * Create a {@link SparseIntArray} from given {@code supportedKeys} where each key is mapped + * to the value indexed by it. + * + *
If {@code values} is null or does not contain a given key as a index, then zero is stored
+ * to the sparse array so it can still be used to query the supported keys.
+ */
+ @Nullable
+ private static SparseIntArray toSparseIntArray(int[] supportedKeys, int[] values) {
+ if (supportedKeys == null) {
+ return null;
+ }
+ SparseIntArray array = new SparseIntArray();
+ for (int key : supportedKeys) {
+ array.put(key, (values == null || key >= values.length) ? 0 : values[key]);
}
return array;
}
@@ -419,7 +496,20 @@ public class VibratorInfo implements Parcelable {
in.createFloatArray());
}
- /** @hide */
+ /**
+ * Default constructor.
+ *
+ * @param minFrequencyHz Minimum supported frequency, in hertz.
+ * @param resonantFrequencyHz The vibrator resonant frequency, in hertz.
+ * @param frequencyResolutionHz The frequency resolution, in hertz, used by the max
+ * amplitudes mapping.
+ * @param suggestedSafeRangeHz The suggested range, in hertz, for the safe relative
+ * frequency range represented by [-1, 1].
+ * @param maxAmplitudes The max amplitude supported by each supported frequency,
+ * starting at minimum frequency with jumps of frequency
+ * resolution.
+ * @hide
+ */
public FrequencyMapping(float minFrequencyHz, float resonantFrequencyHz,
float frequencyResolutionHz, float suggestedSafeRangeHz, float[] maxAmplitudes) {
mMinFrequencyHz = minFrequencyHz;
@@ -547,8 +637,10 @@ public class VibratorInfo implements Parcelable {
@Override
public int hashCode() {
- return Objects.hash(mMinFrequencyHz, mFrequencyResolutionHz, mFrequencyResolutionHz,
- mSuggestedSafeRangeHz, mMaxAmplitudes);
+ int hashCode = Objects.hash(mMinFrequencyHz, mFrequencyResolutionHz,
+ mFrequencyResolutionHz, mSuggestedSafeRangeHz);
+ hashCode = 31 * hashCode + Arrays.hashCode(mMaxAmplitudes);
+ return hashCode;
}
@Override
@@ -587,6 +679,7 @@ public class VibratorInfo implements Parcelable {
private int[] mSupportedEffects = null;
private int[] mSupportedBraking = null;
private int[] mSupportedPrimitives = null;
+ private int[] mPrimitiveDurations = new int[0];
private float mQFactor = Float.NaN;
private FrequencyMapping mFrequencyMapping =
new FrequencyMapping(Float.NaN, Float.NaN, Float.NaN, Float.NaN, null);
@@ -627,6 +720,16 @@ public class VibratorInfo implements Parcelable {
return this;
}
+ /** Configure the duration of a {@link android.hardware.vibrator.CompositePrimitive}. */
+ @NonNull
+ public Builder setPrimitiveDuration(int primitiveId, int duration) {
+ if (mPrimitiveDurations.length <= primitiveId) {
+ mPrimitiveDurations = Arrays.copyOf(mPrimitiveDurations, primitiveId + 1);
+ }
+ mPrimitiveDurations[primitiveId] = duration;
+ return this;
+ }
+
/** Configure the vibrator quality factor. */
@NonNull
public Builder setQFactor(float qFactor) {
@@ -645,7 +748,7 @@ public class VibratorInfo implements Parcelable {
@NonNull
public VibratorInfo build() {
return new VibratorInfo(mId, mCapabilities, mSupportedEffects, mSupportedBraking,
- mSupportedPrimitives, mQFactor, mFrequencyMapping);
+ mSupportedPrimitives, mPrimitiveDurations, mQFactor, mFrequencyMapping);
}
}
diff --git a/core/tests/coretests/src/android/os/VibratorInfoTest.java b/core/tests/coretests/src/android/os/VibratorInfoTest.java
index 3a804641c3f9a..9880f8c52971e 100644
--- a/core/tests/coretests/src/android/os/VibratorInfoTest.java
+++ b/core/tests/coretests/src/android/os/VibratorInfoTest.java
@@ -99,6 +99,17 @@ public class VibratorInfoTest {
assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
}
+ @Test
+ public void testGetPrimitiveDuration() {
+ VibratorInfo info = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .setPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
+ .build();
+ assertEquals(20, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK));
+ assertEquals(0, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_TICK));
+ }
+
@Test
public void testGetDefaultBraking_returnsFirstSupportedBraking() {
assertEquals(Braking.NONE, new VibratorInfo.Builder(
@@ -251,12 +262,14 @@ public class VibratorInfoTest {
.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
.setSupportedEffects(VibrationEffect.EFFECT_CLICK)
.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .setPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
.setQFactor(2f)
.setFrequencyMapping(TEST_FREQUENCY_MAPPING);
VibratorInfo complete = completeBuilder.build();
assertEquals(complete, complete);
assertEquals(complete, completeBuilder.build());
+ assertEquals(complete.hashCode(), completeBuilder.build().hashCode());
VibratorInfo completeWithComposeControl = completeBuilder
.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
@@ -279,6 +292,11 @@ public class VibratorInfoTest {
.build();
assertNotEquals(complete, completeWithUnknownPrimitives);
+ VibratorInfo completeWithDifferentPrimitiveDuration = completeBuilder
+ .setPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
+ .build();
+ assertNotEquals(complete, completeWithDifferentPrimitiveDuration);
+
VibratorInfo completeWithDifferentFrequencyMapping = completeBuilder
.setFrequencyMapping(new VibratorInfo.FrequencyMapping(TEST_MIN_FREQUENCY + 10,
TEST_RESONANT_FREQUENCY + 20, TEST_FREQUENCY_RESOLUTION + 5,
@@ -314,7 +332,8 @@ public class VibratorInfoTest {
VibratorInfo original = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
.setSupportedEffects(VibrationEffect.EFFECT_CLICK)
- .setSupportedPrimitives(null)
+ .setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .setPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
.setQFactor(Float.NaN)
.setFrequencyMapping(TEST_FREQUENCY_MAPPING)
.build();
@@ -324,5 +343,6 @@ public class VibratorInfoTest {
parcel.setDataPosition(0);
VibratorInfo restored = VibratorInfo.CREATOR.createFromParcel(parcel);
assertEquals(original, restored);
+ assertEquals(original.hashCode(), restored.hashCode());
}
}
diff --git a/core/tests/coretests/src/android/os/VibratorTest.java b/core/tests/coretests/src/android/os/VibratorTest.java
index 6213285b068ad..8f9168b581842 100644
--- a/core/tests/coretests/src/android/os/VibratorTest.java
+++ b/core/tests/coretests/src/android/os/VibratorTest.java
@@ -79,6 +79,16 @@ public class VibratorTest {
VibrationEffect.Composition.PRIMITIVE_QUICK_RISE}).length);
}
+ @Test
+ public void getPrimitivesDurations_returnsArrayOfSameSize() {
+ assertEquals(0, mVibratorSpy.getPrimitiveDurations(new int[0]).length);
+ assertEquals(1, mVibratorSpy.getPrimitiveDurations(
+ new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK}).length);
+ assertEquals(2, mVibratorSpy.getPrimitiveDurations(
+ new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK,
+ VibrationEffect.Composition.PRIMITIVE_QUICK_RISE}).length);
+ }
+
@Test
public void vibrate_withAudioAttributes_createsVibrationAttributesWithSameUsage() {
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index b7fa7960ea994..698e3f75d0ed4 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -370,6 +370,7 @@ static jobject vibratorGetInfo(JNIEnv* env, jclass /* clazz */, jlong ptr,
jintArray supportedEffects = nullptr;
jintArray supportedBraking = nullptr;
jintArray supportedPrimitives = nullptr;
+ jintArray primitiveDurations = nullptr;
jfloatArray maxAmplitudes = nullptr;
if (info.supportedEffects.isOk()) {
@@ -390,6 +391,15 @@ static jobject vibratorGetInfo(JNIEnv* env, jclass /* clazz */, jlong ptr,
env->SetIntArrayRegion(supportedPrimitives, 0, primitives.size(),
reinterpret_cast