Merge "Split long vibration composition using HAL limits" into sc-dev

This commit is contained in:
TreeHugger Robot
2021-06-22 16:08:22 +00:00
committed by Android (Google) Code Review
9 changed files with 439 additions and 213 deletions

View File

@@ -56,10 +56,10 @@ final class InputDeviceVibrator extends Vibrator {
mDeviceId = deviceId; mDeviceId = deviceId;
mVibratorInfo = new VibratorInfo.Builder(vibratorId) mVibratorInfo = new VibratorInfo.Builder(vibratorId)
.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL) .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
// Set predefined support to empty as we know input devices do not support them. // The supported effect and braking lists are known to be empty for input devices,
.setSupportedEffects() // which is different from not being set (that means the device support is unknown).
.setSupportedPrimitives() .setSupportedEffects(new int[0])
.setSupportedBraking() .setSupportedBraking(new int[0])
.build(); .build();
mToken = new Binder(); mToken = new Binder();
} }

View File

@@ -51,8 +51,11 @@ public class VibratorInfo implements Parcelable {
private final SparseBooleanArray mSupportedEffects; private final SparseBooleanArray mSupportedEffects;
@Nullable @Nullable
private final SparseBooleanArray mSupportedBraking; private final SparseBooleanArray mSupportedBraking;
@Nullable
private final SparseIntArray mSupportedPrimitives; private final SparseIntArray mSupportedPrimitives;
private final int mPrimitiveDelayMax;
private final int mCompositionSizeMax;
private final int mPwlePrimitiveDurationMax;
private final int mPwleSizeMax;
private final float mQFactor; private final float mQFactor;
private final FrequencyMapping mFrequencyMapping; private final FrequencyMapping mFrequencyMapping;
@@ -62,6 +65,10 @@ public class VibratorInfo implements Parcelable {
mSupportedEffects = in.readSparseBooleanArray(); mSupportedEffects = in.readSparseBooleanArray();
mSupportedBraking = in.readSparseBooleanArray(); mSupportedBraking = in.readSparseBooleanArray();
mSupportedPrimitives = in.readSparseIntArray(); mSupportedPrimitives = in.readSparseIntArray();
mPrimitiveDelayMax = in.readInt();
mCompositionSizeMax = in.readInt();
mPwlePrimitiveDurationMax = in.readInt();
mPwleSizeMax = in.readInt();
mQFactor = in.readFloat(); mQFactor = in.readFloat();
mFrequencyMapping = in.readParcelable(VibratorInfo.class.getClassLoader()); mFrequencyMapping = in.readParcelable(VibratorInfo.class.getClassLoader());
} }
@@ -70,47 +77,49 @@ public class VibratorInfo implements Parcelable {
* Default constructor. * Default constructor.
* *
* @param id The vibrator id. * @param id The vibrator id.
* @param capabilities All capability flags of the vibrator, defined in IVibrator.CAP_*. * @param capabilities All capability flags of the vibrator, defined in
* @param supportedEffects All supported predefined effects, enum values from {@link * IVibrator.CAP_*.
* android.hardware.vibrator.Effect}. * @param supportedEffects All supported predefined effects, enum values from
* @param supportedBraking All supported braking types, enum values from {@link Braking}. * {@link android.hardware.vibrator.Effect}.
* @param supportedPrimitives All supported primitive effects, enum values from {@link * @param supportedBraking All supported braking types, enum values from {@link
* android.hardware.vibrator.CompositePrimitive}. * Braking}.
* @param primitiveDurations A mapping of primitive durations, where indexes are enum values * @param supportedPrimitives All supported primitive effects, key are enum values from
* from {@link android.hardware.vibrator.CompositePrimitive} and the * {@link android.hardware.vibrator.CompositePrimitive} and
* values are estimated durations in milliseconds. * values are estimated durations in milliseconds.
* @param primitiveDelayMax The maximum delay that can be set to a composition primitive
* in milliseconds.
* @param compositionSizeMax The maximum number of primitives supported by a composition.
* @param pwlePrimitiveDurationMax The maximum duration of a PWLE primitive in milliseconds.
* @param pwleSizeMax The maximum number of primitives supported by a PWLE
* composition.
* @param qFactor The vibrator quality factor. * @param qFactor The vibrator quality factor.
* @param frequencyMapping The description of the vibrator supported frequencies and max * @param frequencyMapping The description of the vibrator supported frequencies and max
* amplitude mappings. * amplitude mappings.
* @hide * @hide
*/ */
public VibratorInfo(int id, long capabilities, int[] supportedEffects, int[] supportedBraking, public VibratorInfo(int id, long capabilities, @Nullable SparseBooleanArray supportedEffects,
int[] supportedPrimitives, int[] primitiveDurations, float qFactor, @Nullable SparseBooleanArray supportedBraking,
@NonNull FrequencyMapping frequencyMapping) { @NonNull SparseIntArray supportedPrimitives, int primitiveDelayMax,
int compositionSizeMax, int pwlePrimitiveDurationMax, int pwleSizeMax,
float qFactor, @NonNull FrequencyMapping frequencyMapping) {
mId = id; mId = id;
mCapabilities = capabilities; mCapabilities = capabilities;
mSupportedEffects = toSparseBooleanArray(supportedEffects); mSupportedEffects = supportedEffects == null ? null : supportedEffects.clone();
mSupportedBraking = toSparseBooleanArray(supportedBraking); mSupportedBraking = supportedBraking == null ? null : supportedBraking.clone();
mSupportedPrimitives = toSparseIntArray(supportedPrimitives, primitiveDurations); mSupportedPrimitives = supportedPrimitives.clone();
mPrimitiveDelayMax = primitiveDelayMax;
mCompositionSizeMax = compositionSizeMax;
mPwlePrimitiveDurationMax = pwlePrimitiveDurationMax;
mPwleSizeMax = pwleSizeMax;
mQFactor = qFactor; mQFactor = qFactor;
mFrequencyMapping = frequencyMapping; mFrequencyMapping = frequencyMapping;
} }
protected VibratorInfo(int id, int capabilities, VibratorInfo baseVibrator) { protected VibratorInfo(int id, int capabilities, VibratorInfo baseVibrator) {
mId = id; this(id, capabilities, baseVibrator.mSupportedEffects, baseVibrator.mSupportedBraking,
mCapabilities = capabilities; baseVibrator.mSupportedPrimitives, baseVibrator.mPrimitiveDelayMax,
mSupportedEffects = baseVibrator.mSupportedEffects == null ? null : baseVibrator.mCompositionSizeMax, baseVibrator.mPwlePrimitiveDurationMax,
baseVibrator.mSupportedEffects.clone(); baseVibrator.mPwleSizeMax, baseVibrator.mQFactor, baseVibrator.mFrequencyMapping);
mSupportedBraking = baseVibrator.mSupportedBraking == null ? null :
baseVibrator.mSupportedBraking.clone();
mSupportedPrimitives = baseVibrator.mSupportedPrimitives == null ? null :
baseVibrator.mSupportedPrimitives.clone();
mQFactor = baseVibrator.mQFactor;
mFrequencyMapping = new FrequencyMapping(baseVibrator.mFrequencyMapping.mMinFrequencyHz,
baseVibrator.mFrequencyMapping.mResonantFrequencyHz,
baseVibrator.mFrequencyMapping.mFrequencyResolutionHz,
baseVibrator.mFrequencyMapping.mSuggestedSafeRangeHz,
baseVibrator.mFrequencyMapping.mMaxAmplitudes);
} }
@Override @Override
@@ -120,6 +129,10 @@ public class VibratorInfo implements Parcelable {
dest.writeSparseBooleanArray(mSupportedEffects); dest.writeSparseBooleanArray(mSupportedEffects);
dest.writeSparseBooleanArray(mSupportedBraking); dest.writeSparseBooleanArray(mSupportedBraking);
dest.writeSparseIntArray(mSupportedPrimitives); dest.writeSparseIntArray(mSupportedPrimitives);
dest.writeInt(mPrimitiveDelayMax);
dest.writeInt(mCompositionSizeMax);
dest.writeInt(mPwlePrimitiveDurationMax);
dest.writeInt(mPwleSizeMax);
dest.writeFloat(mQFactor); dest.writeFloat(mQFactor);
dest.writeParcelable(mFrequencyMapping, flags); dest.writeParcelable(mFrequencyMapping, flags);
} }
@@ -138,15 +151,11 @@ public class VibratorInfo implements Parcelable {
return false; return false;
} }
VibratorInfo that = (VibratorInfo) o; VibratorInfo that = (VibratorInfo) o;
if (mSupportedPrimitives == null || that.mSupportedPrimitives == null) { int supportedPrimitivesCount = mSupportedPrimitives.size();
if (mSupportedPrimitives != that.mSupportedPrimitives) { if (supportedPrimitivesCount != that.mSupportedPrimitives.size()) {
return false; return false;
} }
} else { for (int i = 0; i < supportedPrimitivesCount; i++) {
if (mSupportedPrimitives.size() != that.mSupportedPrimitives.size()) {
return false;
}
for (int i = 0; i < mSupportedPrimitives.size(); i++) {
if (mSupportedPrimitives.keyAt(i) != that.mSupportedPrimitives.keyAt(i)) { if (mSupportedPrimitives.keyAt(i) != that.mSupportedPrimitives.keyAt(i)) {
return false; return false;
} }
@@ -154,8 +163,11 @@ public class VibratorInfo implements Parcelable {
return false; return false;
} }
} }
}
return mId == that.mId && mCapabilities == that.mCapabilities return mId == that.mId && mCapabilities == that.mCapabilities
&& mPrimitiveDelayMax == that.mPrimitiveDelayMax
&& mCompositionSizeMax == that.mCompositionSizeMax
&& mPwlePrimitiveDurationMax == that.mPwlePrimitiveDurationMax
&& mPwleSizeMax == that.mPwleSizeMax
&& Objects.equals(mSupportedEffects, that.mSupportedEffects) && Objects.equals(mSupportedEffects, that.mSupportedEffects)
&& Objects.equals(mSupportedBraking, that.mSupportedBraking) && Objects.equals(mSupportedBraking, that.mSupportedBraking)
&& Objects.equals(mQFactor, that.mQFactor) && Objects.equals(mQFactor, that.mQFactor)
@@ -166,12 +178,10 @@ public class VibratorInfo implements Parcelable {
public int hashCode() { public int hashCode() {
int hashCode = Objects.hash(mId, mCapabilities, mSupportedEffects, mSupportedBraking, int hashCode = Objects.hash(mId, mCapabilities, mSupportedEffects, mSupportedBraking,
mQFactor, mFrequencyMapping); mQFactor, mFrequencyMapping);
if (mSupportedPrimitives != null) {
for (int i = 0; i < mSupportedPrimitives.size(); i++) { for (int i = 0; i < mSupportedPrimitives.size(); i++) {
hashCode = 31 * hashCode + mSupportedPrimitives.keyAt(i); hashCode = 31 * hashCode + mSupportedPrimitives.keyAt(i);
hashCode = 31 * hashCode + mSupportedPrimitives.valueAt(i); hashCode = 31 * hashCode + mSupportedPrimitives.valueAt(i);
} }
}
return hashCode; return hashCode;
} }
@@ -184,6 +194,10 @@ public class VibratorInfo implements Parcelable {
+ ", mSupportedEffects=" + Arrays.toString(getSupportedEffectsNames()) + ", mSupportedEffects=" + Arrays.toString(getSupportedEffectsNames())
+ ", mSupportedBraking=" + Arrays.toString(getSupportedBrakingNames()) + ", mSupportedBraking=" + Arrays.toString(getSupportedBrakingNames())
+ ", mSupportedPrimitives=" + Arrays.toString(getSupportedPrimitivesNames()) + ", mSupportedPrimitives=" + Arrays.toString(getSupportedPrimitivesNames())
+ ", mPrimitiveDelayMax=" + mPrimitiveDelayMax
+ ", mCompositionSizeMax=" + mCompositionSizeMax
+ ", mPwlePrimitiveDurationMax=" + mPwlePrimitiveDurationMax
+ ", mPwleSizeMax=" + mPwleSizeMax
+ ", mQFactor=" + mQFactor + ", mQFactor=" + mQFactor
+ ", mFrequencyMapping=" + mFrequencyMapping + ", mFrequencyMapping=" + mFrequencyMapping
+ '}'; + '}';
@@ -247,7 +261,7 @@ public class VibratorInfo implements Parcelable {
*/ */
public boolean isPrimitiveSupported( public boolean isPrimitiveSupported(
@VibrationEffect.Composition.PrimitiveType int primitiveId) { @VibrationEffect.Composition.PrimitiveType int primitiveId) {
return hasCapability(IVibrator.CAP_COMPOSE_EFFECTS) && mSupportedPrimitives != null return hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)
&& (mSupportedPrimitives.indexOfKey(primitiveId) >= 0); && (mSupportedPrimitives.indexOfKey(primitiveId) >= 0);
} }
@@ -260,7 +274,43 @@ public class VibratorInfo implements Parcelable {
*/ */
public int getPrimitiveDuration( public int getPrimitiveDuration(
@VibrationEffect.Composition.PrimitiveType int primitiveId) { @VibrationEffect.Composition.PrimitiveType int primitiveId) {
return mSupportedPrimitives != null ? mSupportedPrimitives.get(primitiveId) : 0; return mSupportedPrimitives.get(primitiveId);
}
/**
* Query the maximum delay supported for a primitive in a composed effect.
*
* @return The max delay in milliseconds, or zero if unlimited.
*/
public int getPrimitiveDelayMax() {
return mPrimitiveDelayMax;
}
/**
* Query the maximum number of primitives supported in a composed effect.
*
* @return The max number of primitives supported, or zero if unlimited.
*/
public int getCompositionSizeMax() {
return mCompositionSizeMax;
}
/**
* Query the maximum duration supported for a primitive in a PWLE composition.
*
* @return The max duration in milliseconds, or zero if unlimited.
*/
public int getPwlePrimitiveDurationMax() {
return mPwlePrimitiveDurationMax;
}
/**
* Query the maximum number of primitives supported in a PWLE composition.
*
* @return The max number of primitives supported, or zero if unlimited.
*/
public int getPwleSizeMax() {
return mPwleSizeMax;
} }
/** /**
@@ -408,51 +458,14 @@ public class VibratorInfo implements Parcelable {
} }
private String[] getSupportedPrimitivesNames() { private String[] getSupportedPrimitivesNames() {
if (mSupportedPrimitives == null) { int supportedPrimitivesCount = mSupportedPrimitives.size();
return new String[0]; String[] names = new String[supportedPrimitivesCount];
} for (int i = 0; i < supportedPrimitivesCount; i++) {
String[] names = new String[mSupportedPrimitives.size()];
for (int i = 0; i < mSupportedPrimitives.size(); i++) {
names[i] = VibrationEffect.Composition.primitiveToString(mSupportedPrimitives.keyAt(i)); names[i] = VibrationEffect.Composition.primitiveToString(mSupportedPrimitives.keyAt(i));
} }
return names; return names;
} }
/**
* Create a {@link SparseBooleanArray} from given {@code supportedKeys} where each key is mapped
* to {@code true}.
*/
@Nullable
private static SparseBooleanArray toSparseBooleanArray(int[] supportedKeys) {
if (supportedKeys == null) {
return null;
}
SparseBooleanArray array = new SparseBooleanArray();
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;
}
/** /**
* Describes how frequency should be mapped to absolute values for a specific {@link Vibrator}. * Describes how frequency should be mapped to absolute values for a specific {@link Vibrator}.
* *
@@ -675,11 +688,14 @@ public class VibratorInfo implements Parcelable {
/** @hide */ /** @hide */
public static final class Builder { public static final class Builder {
private final int mId; private final int mId;
private int mCapabilities = 0; private long mCapabilities;
private int[] mSupportedEffects = null; private SparseBooleanArray mSupportedEffects;
private int[] mSupportedBraking = null; private SparseBooleanArray mSupportedBraking;
private int[] mSupportedPrimitives = null; private SparseIntArray mSupportedPrimitives = new SparseIntArray();
private int[] mPrimitiveDurations = new int[0]; private int mPrimitiveDelayMax;
private int mCompositionSizeMax;
private int mPwlePrimitiveDurationMax;
private int mPwleSizeMax;
private float mQFactor = Float.NaN; private float mQFactor = Float.NaN;
private FrequencyMapping mFrequencyMapping = private FrequencyMapping mFrequencyMapping =
new FrequencyMapping(Float.NaN, Float.NaN, Float.NaN, Float.NaN, null); new FrequencyMapping(Float.NaN, Float.NaN, Float.NaN, Float.NaN, null);
@@ -691,7 +707,7 @@ public class VibratorInfo implements Parcelable {
/** Configure the vibrator capabilities with a combination of IVibrator.CAP_* values. */ /** Configure the vibrator capabilities with a combination of IVibrator.CAP_* values. */
@NonNull @NonNull
public Builder setCapabilities(int capabilities) { public Builder setCapabilities(long capabilities) {
mCapabilities = capabilities; mCapabilities = capabilities;
return this; return this;
} }
@@ -699,34 +715,49 @@ public class VibratorInfo implements Parcelable {
/** Configure the effects supported with {@link android.hardware.vibrator.Effect} values. */ /** Configure the effects supported with {@link android.hardware.vibrator.Effect} values. */
@NonNull @NonNull
public Builder setSupportedEffects(int... supportedEffects) { public Builder setSupportedEffects(int... supportedEffects) {
mSupportedEffects = supportedEffects; mSupportedEffects = toSparseBooleanArray(supportedEffects);
return this; return this;
} }
/** Configure braking supported with {@link android.hardware.vibrator.Braking} values. */ /** Configure braking supported with {@link android.hardware.vibrator.Braking} values. */
@NonNull @NonNull
public Builder setSupportedBraking(int... supportedBraking) { public Builder setSupportedBraking(int... supportedBraking) {
mSupportedBraking = supportedBraking; mSupportedBraking = toSparseBooleanArray(supportedBraking);
return this; return this;
} }
/** /** Configure maximum duration, in milliseconds, of a PWLE primitive. */
* Configure the primitives supported with
* {@link android.hardware.vibrator.CompositePrimitive} values.
*/
@NonNull @NonNull
public Builder setSupportedPrimitives(int... supportedPrimitives) { public Builder setPwlePrimitiveDurationMax(int pwlePrimitiveDurationMax) {
mSupportedPrimitives = supportedPrimitives; mPwlePrimitiveDurationMax = pwlePrimitiveDurationMax;
return this;
}
/** Configure maximum number of primitives supported in a single PWLE composed effect. */
@NonNull
public Builder setPwleSizeMax(int pwleSizeMax) {
mPwleSizeMax = pwleSizeMax;
return this; return this;
} }
/** Configure the duration of a {@link android.hardware.vibrator.CompositePrimitive}. */ /** Configure the duration of a {@link android.hardware.vibrator.CompositePrimitive}. */
@NonNull @NonNull
public Builder setPrimitiveDuration(int primitiveId, int duration) { public Builder setSupportedPrimitive(int primitiveId, int duration) {
if (mPrimitiveDurations.length <= primitiveId) { mSupportedPrimitives.put(primitiveId, duration);
mPrimitiveDurations = Arrays.copyOf(mPrimitiveDurations, primitiveId + 1); return this;
} }
mPrimitiveDurations[primitiveId] = duration;
/** Configure maximum delay, in milliseconds, supported in a composed effect primitive. */
@NonNull
public Builder setPrimitiveDelayMax(int primitiveDelayMax) {
mPrimitiveDelayMax = primitiveDelayMax;
return this;
}
/** Configure maximum number of primitives supported in a single composed effect. */
@NonNull
public Builder setCompositionSizeMax(int compositionSizeMax) {
mCompositionSizeMax = compositionSizeMax;
return this; return this;
} }
@@ -748,7 +779,25 @@ public class VibratorInfo implements Parcelable {
@NonNull @NonNull
public VibratorInfo build() { public VibratorInfo build() {
return new VibratorInfo(mId, mCapabilities, mSupportedEffects, mSupportedBraking, return new VibratorInfo(mId, mCapabilities, mSupportedEffects, mSupportedBraking,
mSupportedPrimitives, mPrimitiveDurations, mQFactor, mFrequencyMapping); mSupportedPrimitives, mPrimitiveDelayMax, mCompositionSizeMax,
mPwlePrimitiveDurationMax, mPwleSizeMax, mQFactor, mFrequencyMapping);
}
/**
* Create a {@link SparseBooleanArray} from given {@code supportedKeys} where each key is
* mapped
* to {@code true}.
*/
@Nullable
private static SparseBooleanArray toSparseBooleanArray(int[] supportedKeys) {
if (supportedKeys == null) {
return null;
}
SparseBooleanArray array = new SparseBooleanArray();
for (int key : supportedKeys) {
array.put(key, true);
}
return array;
} }
} }

View File

@@ -87,14 +87,14 @@ public class VibratorInfoTest {
public void testIsPrimitiveSupported() { public void testIsPrimitiveSupported() {
VibratorInfo info = new VibratorInfo.Builder(TEST_VIBRATOR_ID) VibratorInfo info = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS) .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK) .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
.build(); .build();
assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK)); assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK)); assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK));
// Returns false when there is no compose capability. // Returns false when there is no compose capability.
info = new VibratorInfo.Builder(TEST_VIBRATOR_ID) info = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK) .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
.build(); .build();
assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK)); assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
} }
@@ -103,8 +103,7 @@ public class VibratorInfoTest {
public void testGetPrimitiveDuration() { public void testGetPrimitiveDuration() {
VibratorInfo info = new VibratorInfo.Builder(TEST_VIBRATOR_ID) VibratorInfo info = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS) .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK) .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
.setPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
.build(); .build();
assertEquals(20, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK)); assertEquals(20, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK));
assertEquals(0, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_TICK)); assertEquals(0, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_TICK));
@@ -112,6 +111,26 @@ public class VibratorInfoTest {
.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_TICK)); .getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_TICK));
} }
@Test
public void testCompositionLimits() {
VibratorInfo info = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
.setPrimitiveDelayMax(100)
.setCompositionSizeMax(10)
.setPwlePrimitiveDurationMax(50)
.setPwleSizeMax(20)
.build();
assertEquals(100, info.getPrimitiveDelayMax());
assertEquals(10, info.getCompositionSizeMax());
assertEquals(50, info.getPwlePrimitiveDurationMax());
assertEquals(20, info.getPwleSizeMax());
VibratorInfo emptyInfo = new VibratorInfo.Builder(TEST_VIBRATOR_ID).build();
assertEquals(0, emptyInfo.getPrimitiveDelayMax());
assertEquals(0, emptyInfo.getCompositionSizeMax());
assertEquals(0, emptyInfo.getPwlePrimitiveDurationMax());
assertEquals(0, emptyInfo.getPwleSizeMax());
}
@Test @Test
public void testGetDefaultBraking_returnsFirstSupportedBraking() { public void testGetDefaultBraking_returnsFirstSupportedBraking() {
assertEquals(Braking.NONE, new VibratorInfo.Builder( assertEquals(Braking.NONE, new VibratorInfo.Builder(
@@ -263,8 +282,12 @@ public class VibratorInfoTest {
VibratorInfo.Builder completeBuilder = new VibratorInfo.Builder(TEST_VIBRATOR_ID) VibratorInfo.Builder completeBuilder = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL) .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
.setSupportedEffects(VibrationEffect.EFFECT_CLICK) .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK) .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
.setPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK, 20) .setPrimitiveDelayMax(100)
.setCompositionSizeMax(10)
.setSupportedBraking(Braking.CLAB)
.setPwlePrimitiveDurationMax(50)
.setPwleSizeMax(20)
.setQFactor(2f) .setQFactor(2f)
.setFrequencyMapping(TEST_FREQUENCY_MAPPING); .setFrequencyMapping(TEST_FREQUENCY_MAPPING);
VibratorInfo complete = completeBuilder.build(); VibratorInfo complete = completeBuilder.build();
@@ -279,8 +302,7 @@ public class VibratorInfoTest {
assertNotEquals(complete, completeWithComposeControl); assertNotEquals(complete, completeWithComposeControl);
VibratorInfo completeWithNoEffects = completeBuilder VibratorInfo completeWithNoEffects = completeBuilder
.setSupportedEffects() .setSupportedEffects(new int[0])
.setSupportedPrimitives()
.build(); .build();
assertNotEquals(complete, completeWithNoEffects); assertNotEquals(complete, completeWithNoEffects);
@@ -289,13 +311,8 @@ public class VibratorInfoTest {
.build(); .build();
assertNotEquals(complete, completeWithUnknownEffects); assertNotEquals(complete, completeWithUnknownEffects);
VibratorInfo completeWithUnknownPrimitives = completeBuilder
.setSupportedPrimitives(null)
.build();
assertNotEquals(complete, completeWithUnknownPrimitives);
VibratorInfo completeWithDifferentPrimitiveDuration = completeBuilder VibratorInfo completeWithDifferentPrimitiveDuration = completeBuilder
.setPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK, 10) .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
.build(); .build();
assertNotEquals(complete, completeWithDifferentPrimitiveDuration); assertNotEquals(complete, completeWithDifferentPrimitiveDuration);
@@ -321,12 +338,17 @@ public class VibratorInfoTest {
.build(); .build();
assertNotEquals(complete, completeWithDifferentQFactor); assertNotEquals(complete, completeWithDifferentQFactor);
VibratorInfo empty = new VibratorInfo.Builder(TEST_VIBRATOR_ID).build(); VibratorInfo unknownEffectSupport = new VibratorInfo.Builder(TEST_VIBRATOR_ID).build();
VibratorInfo emptyWithKnownSupport = new VibratorInfo.Builder(TEST_VIBRATOR_ID) VibratorInfo knownEmptyEffectSupport = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
.setSupportedEffects() .setSupportedEffects(new int[0])
.setSupportedPrimitives()
.build(); .build();
assertNotEquals(empty, emptyWithKnownSupport); assertNotEquals(unknownEffectSupport, knownEmptyEffectSupport);
VibratorInfo unknownBrakingSupport = new VibratorInfo.Builder(TEST_VIBRATOR_ID).build();
VibratorInfo knownEmptyBrakingSupport = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
.setSupportedBraking(new int[0])
.build();
assertNotEquals(unknownBrakingSupport, knownEmptyBrakingSupport);
} }
@Test @Test
@@ -334,8 +356,7 @@ public class VibratorInfoTest {
VibratorInfo original = new VibratorInfo.Builder(TEST_VIBRATOR_ID) VibratorInfo original = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS) .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
.setSupportedEffects(VibrationEffect.EFFECT_CLICK) .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK) .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
.setPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
.setQFactor(Float.NaN) .setQFactor(Float.NaN)
.setFrequencyMapping(TEST_FREQUENCY_MAPPING) .setFrequencyMapping(TEST_FREQUENCY_MAPPING)
.build(); .build();

View File

@@ -844,7 +844,12 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
public List<Step> play() { public List<Step> play() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePrimitivesStep"); Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePrimitivesStep");
try { try {
int segmentCount = effect.getSegments().size(); // Load the next PrimitiveSegments to create a single compose call to the vibrator,
// limited to the vibrator composition maximum size.
int limit = controller.getVibratorInfo().getCompositionSizeMax();
int segmentCount = limit > 0
? Math.min(effect.getSegments().size(), segmentIndex + limit)
: effect.getSegments().size();
List<PrimitiveSegment> primitives = new ArrayList<>(); List<PrimitiveSegment> primitives = new ArrayList<>();
for (int i = segmentIndex; i < segmentCount; i++) { for (int i = segmentIndex; i < segmentCount; i++) {
VibrationEffectSegment segment = effect.getSegments().get(i); VibrationEffectSegment segment = effect.getSegments().get(i);
@@ -896,7 +901,12 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
public List<Step> play() { public List<Step> play() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePwleStep"); Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePwleStep");
try { try {
int segmentCount = effect.getSegments().size(); // Load the next RampSegments to create a single composePwle call to the vibrator,
// limited to the vibrator PWLE maximum size.
int limit = controller.getVibratorInfo().getPwleSizeMax();
int segmentCount = limit > 0
? Math.min(effect.getSegments().size(), segmentIndex + limit)
: effect.getSegments().size();
List<RampSegment> pwles = new ArrayList<>(); List<RampSegment> pwles = new ArrayList<>();
for (int i = segmentIndex; i < segmentCount; i++) { for (int i = segmentIndex; i < segmentCount; i++) {
VibrationEffectSegment segment = effect.getSegments().get(i); VibrationEffectSegment segment = effect.getSegments().get(i);

View File

@@ -30,18 +30,23 @@ import android.util.Slog;
import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import libcore.util.NativeAllocationRegistry; import libcore.util.NativeAllocationRegistry;
/** Controls a single vibrator. */ /** Controls a single vibrator. */
final class VibratorController { final class VibratorController {
private static final String TAG = "VibratorController"; private static final String TAG = "VibratorController";
// TODO(b/167947076): load suggested range from config
private static final int SUGGESTED_FREQUENCY_SAFE_RANGE = 200;
private final Object mLock = new Object(); private final Object mLock = new Object();
private final NativeWrapper mNativeWrapper; private final NativeWrapper mNativeWrapper;
private final VibratorInfo mVibratorInfo; private final VibratorInfo.Builder mVibratorInfoBuilder;
@GuardedBy("mLock")
private VibratorInfo mVibratorInfo;
@GuardedBy("mLock")
private boolean mVibratorInfoLoaded;
@GuardedBy("mLock") @GuardedBy("mLock")
private final RemoteCallbackList<IVibratorStateListener> mVibratorStateListeners = private final RemoteCallbackList<IVibratorStateListener> mVibratorStateListeners =
new RemoteCallbackList<>(); new RemoteCallbackList<>();
@@ -66,10 +71,10 @@ final class VibratorController {
NativeWrapper nativeWrapper) { NativeWrapper nativeWrapper) {
mNativeWrapper = nativeWrapper; mNativeWrapper = nativeWrapper;
mNativeWrapper.init(vibratorId, listener); mNativeWrapper.init(vibratorId, listener);
// TODO(b/167947076): load suggested range from config mVibratorInfoBuilder = new VibratorInfo.Builder(vibratorId);
mVibratorInfo = mNativeWrapper.getInfo(/* suggestedFrequencyRange= */ 200); mVibratorInfoLoaded = mNativeWrapper.getInfo(SUGGESTED_FREQUENCY_SAFE_RANGE,
Preconditions.checkNotNull(mVibratorInfo, "Failed to retrieve data for vibrator %d", mVibratorInfoBuilder);
vibratorId); mVibratorInfo = mVibratorInfoBuilder.build();
} }
/** Register state listener for this vibrator. */ /** Register state listener for this vibrator. */
@@ -103,8 +108,16 @@ final class VibratorController {
/** Return the {@link VibratorInfo} representing the vibrator controlled by this instance. */ /** Return the {@link VibratorInfo} representing the vibrator controlled by this instance. */
public VibratorInfo getVibratorInfo() { public VibratorInfo getVibratorInfo() {
synchronized (mLock) {
if (!mVibratorInfoLoaded) {
// Try to load the vibrator metadata that has failed in the last attempt.
mVibratorInfoLoaded = mNativeWrapper.getInfo(SUGGESTED_FREQUENCY_SAFE_RANGE,
mVibratorInfoBuilder);
mVibratorInfo = mVibratorInfoBuilder.build();
}
return mVibratorInfo; return mVibratorInfo;
} }
}
/** /**
* Return {@code true} is this vibrator is currently vibrating, false otherwise. * Return {@code true} is this vibrator is currently vibrating, false otherwise.
@@ -361,7 +374,8 @@ final class VibratorController {
private static native void alwaysOnDisable(long nativePtr, long id); private static native void alwaysOnDisable(long nativePtr, long id);
private static native VibratorInfo getInfo(long nativePtr, float suggestedFrequencyRange); private static native boolean getInfo(long nativePtr, float suggestedFrequencyRange,
VibratorInfo.Builder infoBuilder);
private long mNativePtr = 0; private long mNativePtr = 0;
@@ -428,9 +442,11 @@ final class VibratorController {
alwaysOnDisable(mNativePtr, id); alwaysOnDisable(mNativePtr, id);
} }
/** Return device vibrator metadata. */ /**
public VibratorInfo getInfo(float suggestedFrequencyRange) { * Loads device vibrator metadata and returns true if all metadata was loaded successfully.
return getInfo(mNativePtr, suggestedFrequencyRange); */
public boolean getInfo(float suggestedFrequencyRange, VibratorInfo.Builder infoBuilder) {
return getInfo(mNativePtr, suggestedFrequencyRange, infoBuilder);
} }
} }
} }

View File

@@ -41,8 +41,18 @@ static JavaVM* sJvm = nullptr;
static jmethodID sMethodIdOnComplete; static jmethodID sMethodIdOnComplete;
static jclass sFrequencyMappingClass; static jclass sFrequencyMappingClass;
static jmethodID sFrequencyMappingCtor; static jmethodID sFrequencyMappingCtor;
static jclass sVibratorInfoClass; static struct {
static jmethodID sVibratorInfoCtor; jmethodID setCapabilities;
jmethodID setSupportedEffects;
jmethodID setSupportedBraking;
jmethodID setPwlePrimitiveDurationMax;
jmethodID setPwleSizeMax;
jmethodID setSupportedPrimitive;
jmethodID setPrimitiveDelayMax;
jmethodID setCompositionSizeMax;
jmethodID setQFactor;
jmethodID setFrequencyMapping;
} sVibratorInfoBuilderClassInfo;
static struct { static struct {
jfieldID id; jfieldID id;
jfieldID scale; jfieldID scale;
@@ -352,68 +362,88 @@ static void vibratorAlwaysOnDisable(JNIEnv* env, jclass /* clazz */, jlong ptr,
wrapper->halCall<void>(alwaysOnDisableFn, "alwaysOnDisable"); wrapper->halCall<void>(alwaysOnDisableFn, "alwaysOnDisable");
} }
static jobject vibratorGetInfo(JNIEnv* env, jclass /* clazz */, jlong ptr, static jboolean vibratorGetInfo(JNIEnv* env, jclass /* clazz */, jlong ptr,
jfloat suggestedSafeRange) { jfloat suggestedSafeRange, jobject vibratorInfoBuilder) {
VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr); VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
if (wrapper == nullptr) { if (wrapper == nullptr) {
ALOGE("vibratorGetInfo failed because native wrapper was not initialized"); ALOGE("vibratorGetInfo failed because native wrapper was not initialized");
return nullptr; return JNI_FALSE;
} }
vibrator::Info info = wrapper->getVibratorInfo(); vibrator::Info info = wrapper->getVibratorInfo();
jlong capabilities = if (info.capabilities.isOk()) {
static_cast<jlong>(info.capabilities.valueOr(vibrator::Capabilities::NONE)); env->CallObjectMethod(vibratorInfoBuilder, sVibratorInfoBuilderClassInfo.setCapabilities,
jfloat minFrequency = static_cast<jfloat>(info.minFrequency.valueOr(NAN)); static_cast<jlong>(info.capabilities.value()));
jfloat resonantFrequency = static_cast<jfloat>(info.resonantFrequency.valueOr(NAN)); }
jfloat frequencyResolution = static_cast<jfloat>(info.frequencyResolution.valueOr(NAN));
jfloat qFactor = static_cast<jfloat>(info.qFactor.valueOr(NAN));
jintArray supportedEffects = nullptr;
jintArray supportedBraking = nullptr;
jintArray supportedPrimitives = nullptr;
jintArray primitiveDurations = nullptr;
jfloatArray maxAmplitudes = nullptr;
if (info.supportedEffects.isOk()) { if (info.supportedEffects.isOk()) {
std::vector<aidl::Effect> effects = info.supportedEffects.value(); std::vector<aidl::Effect> effects = info.supportedEffects.value();
supportedEffects = env->NewIntArray(effects.size()); jintArray supportedEffects = env->NewIntArray(effects.size());
env->SetIntArrayRegion(supportedEffects, 0, effects.size(), env->SetIntArrayRegion(supportedEffects, 0, effects.size(),
reinterpret_cast<jint*>(effects.data())); reinterpret_cast<jint*>(effects.data()));
env->CallObjectMethod(vibratorInfoBuilder,
sVibratorInfoBuilderClassInfo.setSupportedEffects, supportedEffects);
} }
if (info.supportedBraking.isOk()) { if (info.supportedBraking.isOk()) {
std::vector<aidl::Braking> braking = info.supportedBraking.value(); std::vector<aidl::Braking> braking = info.supportedBraking.value();
supportedBraking = env->NewIntArray(braking.size()); jintArray supportedBraking = env->NewIntArray(braking.size());
env->SetIntArrayRegion(supportedBraking, 0, braking.size(), env->SetIntArrayRegion(supportedBraking, 0, braking.size(),
reinterpret_cast<jint*>(braking.data())); reinterpret_cast<jint*>(braking.data()));
env->CallObjectMethod(vibratorInfoBuilder,
sVibratorInfoBuilderClassInfo.setSupportedBraking, supportedBraking);
}
if (info.pwlePrimitiveDurationMax.isOk()) {
env->CallObjectMethod(vibratorInfoBuilder,
sVibratorInfoBuilderClassInfo.setPwlePrimitiveDurationMax,
static_cast<jint>(info.pwlePrimitiveDurationMax.value().count()));
}
if (info.pwleSizeMax.isOk()) {
// Use (pwleMaxSize - 1) to account for a possible extra braking segment added by the
// vibratorPerformPwleEffect method.
env->CallObjectMethod(vibratorInfoBuilder, sVibratorInfoBuilderClassInfo.setPwleSizeMax,
static_cast<jint>(info.pwleSizeMax.value() - 1));
} }
if (info.supportedPrimitives.isOk()) { if (info.supportedPrimitives.isOk()) {
std::vector<aidl::CompositePrimitive> primitives = info.supportedPrimitives.value(); auto durations = info.primitiveDurations.valueOr({});
supportedPrimitives = env->NewIntArray(primitives.size()); for (auto& primitive : info.supportedPrimitives.value()) {
env->SetIntArrayRegion(supportedPrimitives, 0, primitives.size(), auto primitiveIdx = static_cast<size_t>(primitive);
reinterpret_cast<jint*>(primitives.data())); auto duration = durations.size() > primitiveIdx ? durations[primitiveIdx].count() : 0;
env->CallObjectMethod(vibratorInfoBuilder,
sVibratorInfoBuilderClassInfo.setSupportedPrimitive,
static_cast<jint>(primitive), static_cast<jint>(duration));
} }
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()); if (info.primitiveDelayMax.isOk()) {
env->SetIntArrayRegion(primitiveDurations, 0, durations.size(), env->CallObjectMethod(vibratorInfoBuilder,
reinterpret_cast<jint*>(durations.data())); sVibratorInfoBuilderClassInfo.setPrimitiveDelayMax,
static_cast<jint>(info.primitiveDelayMax.value().count()));
} }
if (info.compositionSizeMax.isOk()) {
env->CallObjectMethod(vibratorInfoBuilder,
sVibratorInfoBuilderClassInfo.setCompositionSizeMax,
static_cast<jint>(info.compositionSizeMax.value()));
}
if (info.qFactor.isOk()) {
env->CallObjectMethod(vibratorInfoBuilder, sVibratorInfoBuilderClassInfo.setQFactor,
static_cast<jfloat>(info.qFactor.value()));
}
jfloat minFrequency = static_cast<jfloat>(info.minFrequency.valueOr(NAN));
jfloat resonantFrequency = static_cast<jfloat>(info.resonantFrequency.valueOr(NAN));
jfloat frequencyResolution = static_cast<jfloat>(info.frequencyResolution.valueOr(NAN));
jfloatArray maxAmplitudes = nullptr;
if (info.maxAmplitudes.isOk()) { if (info.maxAmplitudes.isOk()) {
std::vector<float> amplitudes = info.maxAmplitudes.value(); std::vector<float> amplitudes = info.maxAmplitudes.value();
maxAmplitudes = env->NewFloatArray(amplitudes.size()); maxAmplitudes = env->NewFloatArray(amplitudes.size());
env->SetFloatArrayRegion(maxAmplitudes, 0, amplitudes.size(), env->SetFloatArrayRegion(maxAmplitudes, 0, amplitudes.size(),
reinterpret_cast<jfloat*>(amplitudes.data())); reinterpret_cast<jfloat*>(amplitudes.data()));
} }
jobject frequencyMapping = env->NewObject(sFrequencyMappingClass, sFrequencyMappingCtor, jobject frequencyMapping = env->NewObject(sFrequencyMappingClass, sFrequencyMappingCtor,
minFrequency, resonantFrequency, frequencyResolution, minFrequency, resonantFrequency, frequencyResolution,
suggestedSafeRange, maxAmplitudes); suggestedSafeRange, maxAmplitudes);
env->CallObjectMethod(vibratorInfoBuilder, sVibratorInfoBuilderClassInfo.setFrequencyMapping,
frequencyMapping);
return env->NewObject(sVibratorInfoClass, sVibratorInfoCtor, wrapper->getVibratorId(), return info.checkAndLogFailure("vibratorGetInfo") ? JNI_FALSE : JNI_TRUE;
capabilities, supportedEffects, supportedBraking, supportedPrimitives,
primitiveDurations, qFactor, frequencyMapping);
} }
static const JNINativeMethod method_table[] = { static const JNINativeMethod method_table[] = {
@@ -433,7 +463,7 @@ static const JNINativeMethod method_table[] = {
{"setExternalControl", "(JZ)V", (void*)vibratorSetExternalControl}, {"setExternalControl", "(JZ)V", (void*)vibratorSetExternalControl},
{"alwaysOnEnable", "(JJJJ)V", (void*)vibratorAlwaysOnEnable}, {"alwaysOnEnable", "(JJJJ)V", (void*)vibratorAlwaysOnEnable},
{"alwaysOnDisable", "(JJ)V", (void*)vibratorAlwaysOnDisable}, {"alwaysOnDisable", "(JJ)V", (void*)vibratorAlwaysOnDisable},
{"getInfo", "(JF)Landroid/os/VibratorInfo;", (void*)vibratorGetInfo}, {"getInfo", "(JFLandroid/os/VibratorInfo$Builder;)Z", (void*)vibratorGetInfo},
}; };
int register_android_server_vibrator_VibratorController(JavaVM* jvm, JNIEnv* env) { int register_android_server_vibrator_VibratorController(JavaVM* jvm, JNIEnv* env) {
@@ -459,11 +489,38 @@ int register_android_server_vibrator_VibratorController(JavaVM* jvm, JNIEnv* env
sFrequencyMappingClass = static_cast<jclass>(env->NewGlobalRef(frequencyMappingClass)); sFrequencyMappingClass = static_cast<jclass>(env->NewGlobalRef(frequencyMappingClass));
sFrequencyMappingCtor = GetMethodIDOrDie(env, sFrequencyMappingClass, "<init>", "(FFFF[F)V"); sFrequencyMappingCtor = GetMethodIDOrDie(env, sFrequencyMappingClass, "<init>", "(FFFF[F)V");
jclass vibratorInfoClass = FindClassOrDie(env, "android/os/VibratorInfo"); jclass vibratorInfoBuilderClass = FindClassOrDie(env, "android/os/VibratorInfo$Builder");
sVibratorInfoClass = (jclass)env->NewGlobalRef(vibratorInfoClass); sVibratorInfoBuilderClassInfo.setCapabilities =
sVibratorInfoCtor = GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setCapabilities",
GetMethodIDOrDie(env, sVibratorInfoClass, "<init>", "(J)Landroid/os/VibratorInfo$Builder;");
"(IJ[I[I[I[IFLandroid/os/VibratorInfo$FrequencyMapping;)V"); sVibratorInfoBuilderClassInfo.setSupportedEffects =
GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setSupportedEffects",
"([I)Landroid/os/VibratorInfo$Builder;");
sVibratorInfoBuilderClassInfo.setSupportedBraking =
GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setSupportedBraking",
"([I)Landroid/os/VibratorInfo$Builder;");
sVibratorInfoBuilderClassInfo.setPwlePrimitiveDurationMax =
GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setPwlePrimitiveDurationMax",
"(I)Landroid/os/VibratorInfo$Builder;");
sVibratorInfoBuilderClassInfo.setPwleSizeMax =
GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setPwleSizeMax",
"(I)Landroid/os/VibratorInfo$Builder;");
sVibratorInfoBuilderClassInfo.setSupportedPrimitive =
GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setSupportedPrimitive",
"(II)Landroid/os/VibratorInfo$Builder;");
sVibratorInfoBuilderClassInfo.setPrimitiveDelayMax =
GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setPrimitiveDelayMax",
"(I)Landroid/os/VibratorInfo$Builder;");
sVibratorInfoBuilderClassInfo.setCompositionSizeMax =
GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setCompositionSizeMax",
"(I)Landroid/os/VibratorInfo$Builder;");
sVibratorInfoBuilderClassInfo.setQFactor =
GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setQFactor",
"(F)Landroid/os/VibratorInfo$Builder;");
sVibratorInfoBuilderClassInfo.setFrequencyMapping =
GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setFrequencyMapping",
"(Landroid/os/VibratorInfo$FrequencyMapping;)"
"Landroid/os/VibratorInfo$Builder;");
return jniRegisterNativeMethods(env, return jniRegisterNativeMethods(env,
"com/android/server/vibrator/VibratorController$NativeWrapper", "com/android/server/vibrator/VibratorController$NativeWrapper",

View File

@@ -56,6 +56,8 @@ final class FakeVibratorControllerProvider {
private int[] mSupportedEffects; private int[] mSupportedEffects;
private int[] mSupportedBraking; private int[] mSupportedBraking;
private int[] mSupportedPrimitives; private int[] mSupportedPrimitives;
private int mCompositionSizeMax;
private int mPwleSizeMax;
private float mMinFrequency = Float.NaN; private float mMinFrequency = Float.NaN;
private float mResonantFrequency = Float.NaN; private float mResonantFrequency = Float.NaN;
private float mFrequencyResolution = Float.NaN; private float mFrequencyResolution = Float.NaN;
@@ -151,12 +153,22 @@ final class FakeVibratorControllerProvider {
} }
@Override @Override
public VibratorInfo getInfo(float suggestedFrequencyRange) { public boolean getInfo(float suggestedFrequencyRange, VibratorInfo.Builder infoBuilder) {
VibratorInfo.FrequencyMapping frequencyMapping = new VibratorInfo.FrequencyMapping( infoBuilder.setCapabilities(mCapabilities);
mMinFrequency, mResonantFrequency, mFrequencyResolution, infoBuilder.setSupportedBraking(mSupportedBraking);
suggestedFrequencyRange, mMaxAmplitudes); infoBuilder.setPwleSizeMax(mPwleSizeMax);
return new VibratorInfo(vibratorId, mCapabilities, mSupportedEffects, mSupportedBraking, infoBuilder.setSupportedEffects(mSupportedEffects);
mSupportedPrimitives, null, mQFactor, frequencyMapping); if (mSupportedPrimitives != null) {
for (int primitive : mSupportedPrimitives) {
infoBuilder.setSupportedPrimitive(primitive, EFFECT_DURATION);
}
}
infoBuilder.setCompositionSizeMax(mCompositionSizeMax);
infoBuilder.setQFactor(mQFactor);
infoBuilder.setFrequencyMapping(new VibratorInfo.FrequencyMapping(mMinFrequency,
mResonantFrequency, mFrequencyResolution, suggestedFrequencyRange,
mMaxAmplitudes));
return true;
} }
private void applyLatency() { private void applyLatency() {
@@ -236,6 +248,16 @@ final class FakeVibratorControllerProvider {
mSupportedPrimitives = primitives; mSupportedPrimitives = primitives;
} }
/** Set the max number of primitives allowed in a composition by the fake vibrator hardware. */
public void setCompositionSizeMax(int compositionSizeMax) {
mCompositionSizeMax = compositionSizeMax;
}
/** Set the max number of PWLEs allowed in a composition by the fake vibrator hardware. */
public void setPwleSizeMax(int pwleSizeMax) {
mPwleSizeMax = pwleSizeMax;
}
/** Set the resonant frequency of the fake vibrator hardware. */ /** Set the resonant frequency of the fake vibrator hardware. */
public void setResonantFrequency(float frequencyHz) { public void setResonantFrequency(float frequencyHz) {
mResonantFrequency = frequencyHz; mResonantFrequency = frequencyHz;

View File

@@ -356,7 +356,8 @@ public class VibrationThreadTest {
@Test @Test
public void vibrate_singleVibratorComposed_runsVibration() throws Exception { public void vibrate_singleVibratorComposed_runsVibration() throws Exception {
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
long vibrationId = 1; long vibrationId = 1;
VibrationEffect effect = VibrationEffect.startComposition() VibrationEffect effect = VibrationEffect.startComposition()
@@ -374,7 +375,7 @@ public class VibrationThreadTest {
assertEquals(Arrays.asList( assertEquals(Arrays.asList(
expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0), expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0),
expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0)), expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0)),
mVibratorProviders.get(VIBRATOR_ID).getEffectSegments()); fakeVibrator.getEffectSegments());
} }
@Test @Test
@@ -394,6 +395,27 @@ public class VibrationThreadTest {
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty()); assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty());
} }
@Test
public void vibrate_singleVibratorLargeComposition_splitsVibratorComposeCalls() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
fakeVibrator.setCompositionSizeMax(2);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.8f)
.compose();
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
waitForCompletion(thread);
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED));
// Vibrator compose called twice.
verify(mControllerCallbacks, times(2)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
assertEquals(3, fakeVibrator.getEffectSegments().size());
}
@Test @Test
public void vibrate_singleVibratorComposedEffects_runsDifferentVibrations() throws Exception { public void vibrate_singleVibratorComposedEffects_runsDifferentVibrations() throws Exception {
mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects(VibrationEffect.EFFECT_CLICK); mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
@@ -432,12 +454,13 @@ public class VibrationThreadTest {
@Test @Test
public void vibrate_singleVibratorPwle_runsComposePwle() throws Exception { public void vibrate_singleVibratorPwle_runsComposePwle() throws Exception {
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS); FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
mVibratorProviders.get(VIBRATOR_ID).setSupportedBraking(Braking.CLAB); fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
mVibratorProviders.get(VIBRATOR_ID).setMinFrequency(100); fakeVibrator.setSupportedBraking(Braking.CLAB);
mVibratorProviders.get(VIBRATOR_ID).setResonantFrequency(150); fakeVibrator.setMinFrequency(100);
mVibratorProviders.get(VIBRATOR_ID).setFrequencyResolution(50); fakeVibrator.setResonantFrequency(150);
mVibratorProviders.get(VIBRATOR_ID).setMaxAmplitudes( fakeVibrator.setFrequencyResolution(50);
fakeVibrator.setMaxAmplitudes(
0.5f /* 100Hz*/, 1 /* 150Hz */, 0.6f /* 200Hz */); 0.5f /* 100Hz*/, 1 /* 150Hz */, 0.6f /* 200Hz */);
long vibrationId = 1; long vibrationId = 1;
@@ -462,8 +485,34 @@ public class VibrationThreadTest {
expectedRamp(/* amplitude= */ 0.6f, /* frequency= */ 200, /* duration= */ 30), expectedRamp(/* amplitude= */ 0.6f, /* frequency= */ 200, /* duration= */ 30),
expectedRamp(/* StartAmplitude= */ 0.6f, /* endAmplitude= */ 0.5f, expectedRamp(/* StartAmplitude= */ 0.6f, /* endAmplitude= */ 0.5f,
/* startFrequency= */ 200, /* endFrequency= */ 100, /* duration= */ 40)), /* startFrequency= */ 200, /* endFrequency= */ 100, /* duration= */ 40)),
mVibratorProviders.get(VIBRATOR_ID).getEffectSegments()); fakeVibrator.getEffectSegments());
assertEquals(Arrays.asList(Braking.CLAB), mVibratorProviders.get(VIBRATOR_ID).getBraking()); assertEquals(Arrays.asList(Braking.CLAB), fakeVibrator.getBraking());
}
@Test
public void vibrate_singleVibratorLargePwle_splitsVibratorComposeCalls() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
fakeVibrator.setMinFrequency(100);
fakeVibrator.setResonantFrequency(150);
fakeVibrator.setFrequencyResolution(50);
fakeVibrator.setMaxAmplitudes(1, 1, 1);
fakeVibrator.setPwleSizeMax(2);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.startWaveform()
.addStep(1, 10)
.addRamp(0, 20)
.addStep(0.8f, 1, 30)
.addRamp(0.6f, -1, 40)
.build();
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
waitForCompletion(thread);
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED));
// Vibrator compose called twice.
verify(mControllerCallbacks, times(2)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
assertEquals(4, fakeVibrator.getEffectSegments().size());
} }
@Test @Test

View File

@@ -298,11 +298,13 @@ public class VibratorControllerTest {
private void mockVibratorCapabilities(int capabilities) { private void mockVibratorCapabilities(int capabilities) {
VibratorInfo.FrequencyMapping frequencyMapping = new VibratorInfo.FrequencyMapping( VibratorInfo.FrequencyMapping frequencyMapping = new VibratorInfo.FrequencyMapping(
Float.NaN, Float.NaN, Float.NaN, Float.NaN, null); Float.NaN, Float.NaN, Float.NaN, Float.NaN, null);
when(mNativeWrapperMock.getInfo(anyFloat())).thenReturn( when(mNativeWrapperMock.getInfo(anyFloat(), any(VibratorInfo.Builder.class)))
new VibratorInfo.Builder(VIBRATOR_ID) .then(invocation -> {
((VibratorInfo.Builder) invocation.getArgument(1))
.setCapabilities(capabilities) .setCapabilities(capabilities)
.setFrequencyMapping(frequencyMapping) .setFrequencyMapping(frequencyMapping);
.build()); return true;
});
} }
private PrebakedSegment createPrebaked(int effectId, int effectStrength) { private PrebakedSegment createPrebaked(int effectId, int effectStrength) {