Expose vibration primitives durations in Vibrator

Fix: 182477149
Test: VibratorTest
Change-Id: I4dd500fe6551645454ac9af24499098a74fd866e
This commit is contained in:
Lais Andrade
2021-03-31 17:59:10 +00:00
parent 1eb043a982
commit 8231960159
7 changed files with 190 additions and 24 deletions

View File

@@ -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);

View File

@@ -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.
*/

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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);

View File

@@ -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",

View File

@@ -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() {