diff options
7 files changed, 190 insertions, 24 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 0639d7273dda..0ee4a1aa2164 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -32064,6 +32064,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 2876775a334e..b3502f38dd53 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -552,6 +552,27 @@ public abstract class Vibrator { } /** + * 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. */ @RequiresPermission(android.Manifest.permission.VIBRATE) diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java index c7d66f0dccdc..d73469ca7e8e 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. + * + * <p>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 3a804641c3f9..9880f8c52971 100644 --- a/core/tests/coretests/src/android/os/VibratorInfoTest.java +++ b/core/tests/coretests/src/android/os/VibratorInfoTest.java @@ -100,6 +100,17 @@ public class VibratorInfoTest { } @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( TEST_VIBRATOR_ID).build().getDefaultBraking()); @@ -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 6213285b068a..8f9168b58184 100644 --- a/core/tests/coretests/src/android/os/VibratorTest.java +++ b/core/tests/coretests/src/android/os/VibratorTest.java @@ -80,6 +80,16 @@ public class VibratorTest { } @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); AudioAttributes audioAttributes = new AudioAttributes.Builder().setUsage( diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp index b7fa7960ea99..698e3f75d0ed 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<jint*>(primitives.data())); } + if (info.primitiveDurations.isOk()) { + std::vector<int32_t> durations; + for (auto duration : info.primitiveDurations.value()) { + durations.push_back(duration.count()); + } + primitiveDurations = env->NewIntArray(durations.size()); + env->SetIntArrayRegion(primitiveDurations, 0, durations.size(), + reinterpret_cast<jint*>(durations.data())); + } if (info.maxAmplitudes.isOk()) { std::vector<float> amplitudes = info.maxAmplitudes.value(); maxAmplitudes = env->NewFloatArray(amplitudes.size()); @@ -403,7 +413,7 @@ static jobject vibratorGetInfo(JNIEnv* env, jclass /* clazz */, jlong ptr, return env->NewObject(sVibratorInfoClass, sVibratorInfoCtor, wrapper->getVibratorId(), capabilities, supportedEffects, supportedBraking, supportedPrimitives, - qFactor, frequencyMapping); + primitiveDurations, qFactor, frequencyMapping); } static const JNINativeMethod method_table[] = { @@ -450,9 +460,10 @@ int register_android_server_vibrator_VibratorController(JavaVM* jvm, JNIEnv* env sFrequencyMappingCtor = GetMethodIDOrDie(env, sFrequencyMappingClass, "<init>", "(FFFF[F)V"); jclass vibratorInfoClass = FindClassOrDie(env, "android/os/VibratorInfo"); - sVibratorInfoClass = static_cast<jclass>(env->NewGlobalRef(vibratorInfoClass)); - sVibratorInfoCtor = GetMethodIDOrDie(env, sVibratorInfoClass, "<init>", - "(IJ[I[I[IFLandroid/os/VibratorInfo$FrequencyMapping;)V"); + sVibratorInfoClass = (jclass)env->NewGlobalRef(vibratorInfoClass); + sVibratorInfoCtor = + GetMethodIDOrDie(env, sVibratorInfoClass, "<init>", + "(IJ[I[I[I[IFLandroid/os/VibratorInfo$FrequencyMapping;)V"); return jniRegisterNativeMethods(env, "com/android/server/vibrator/VibratorController$NativeWrapper", diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java index 1d715c830fcf..058575817acf 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java @@ -156,7 +156,7 @@ final class FakeVibratorControllerProvider { mMinFrequency, mResonantFrequency, mFrequencyResolution, suggestedFrequencyRange, mMaxAmplitudes); return new VibratorInfo(vibratorId, mCapabilities, mSupportedEffects, mSupportedBraking, - mSupportedPrimitives, mQFactor, frequencyMapping); + mSupportedPrimitives, null, mQFactor, frequencyMapping); } private void applyLatency() { |