Merge "Split long vibration composition using HAL limits" into sc-dev am: 55a8db6450 am: 4391344e4f

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/14807584

Change-Id: Ie7282dc3d3b916b1410fc8110d9bba1e7dd6deee
This commit is contained in:
TreeHugger Robot
2021-06-22 16:45:01 +00:00
committed by Automerger Merge Worker
9 changed files with 439 additions and 213 deletions

View File

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

View File

@@ -51,8 +51,11 @@ public class VibratorInfo implements Parcelable {
private final SparseBooleanArray mSupportedEffects;
@Nullable
private final SparseBooleanArray mSupportedBraking;
@Nullable
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 FrequencyMapping mFrequencyMapping;
@@ -62,6 +65,10 @@ public class VibratorInfo implements Parcelable {
mSupportedEffects = in.readSparseBooleanArray();
mSupportedBraking = in.readSparseBooleanArray();
mSupportedPrimitives = in.readSparseIntArray();
mPrimitiveDelayMax = in.readInt();
mCompositionSizeMax = in.readInt();
mPwlePrimitiveDurationMax = in.readInt();
mPwleSizeMax = in.readInt();
mQFactor = in.readFloat();
mFrequencyMapping = in.readParcelable(VibratorInfo.class.getClassLoader());
}
@@ -69,48 +76,50 @@ public class VibratorInfo implements Parcelable {
/**
* 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.
* @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, key are enum values from
* {@link android.hardware.vibrator.CompositePrimitive} and
* 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 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, int[] primitiveDurations, float qFactor,
@NonNull FrequencyMapping frequencyMapping) {
public VibratorInfo(int id, long capabilities, @Nullable SparseBooleanArray supportedEffects,
@Nullable SparseBooleanArray supportedBraking,
@NonNull SparseIntArray supportedPrimitives, int primitiveDelayMax,
int compositionSizeMax, int pwlePrimitiveDurationMax, int pwleSizeMax,
float qFactor, @NonNull FrequencyMapping frequencyMapping) {
mId = id;
mCapabilities = capabilities;
mSupportedEffects = toSparseBooleanArray(supportedEffects);
mSupportedBraking = toSparseBooleanArray(supportedBraking);
mSupportedPrimitives = toSparseIntArray(supportedPrimitives, primitiveDurations);
mSupportedEffects = supportedEffects == null ? null : supportedEffects.clone();
mSupportedBraking = supportedBraking == null ? null : supportedBraking.clone();
mSupportedPrimitives = supportedPrimitives.clone();
mPrimitiveDelayMax = primitiveDelayMax;
mCompositionSizeMax = compositionSizeMax;
mPwlePrimitiveDurationMax = pwlePrimitiveDurationMax;
mPwleSizeMax = pwleSizeMax;
mQFactor = qFactor;
mFrequencyMapping = frequencyMapping;
}
protected VibratorInfo(int id, int capabilities, VibratorInfo baseVibrator) {
mId = id;
mCapabilities = capabilities;
mSupportedEffects = baseVibrator.mSupportedEffects == null ? null :
baseVibrator.mSupportedEffects.clone();
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);
this(id, capabilities, baseVibrator.mSupportedEffects, baseVibrator.mSupportedBraking,
baseVibrator.mSupportedPrimitives, baseVibrator.mPrimitiveDelayMax,
baseVibrator.mCompositionSizeMax, baseVibrator.mPwlePrimitiveDurationMax,
baseVibrator.mPwleSizeMax, baseVibrator.mQFactor, baseVibrator.mFrequencyMapping);
}
@Override
@@ -120,6 +129,10 @@ public class VibratorInfo implements Parcelable {
dest.writeSparseBooleanArray(mSupportedEffects);
dest.writeSparseBooleanArray(mSupportedBraking);
dest.writeSparseIntArray(mSupportedPrimitives);
dest.writeInt(mPrimitiveDelayMax);
dest.writeInt(mCompositionSizeMax);
dest.writeInt(mPwlePrimitiveDurationMax);
dest.writeInt(mPwleSizeMax);
dest.writeFloat(mQFactor);
dest.writeParcelable(mFrequencyMapping, flags);
}
@@ -138,24 +151,23 @@ public class VibratorInfo implements Parcelable {
return false;
}
VibratorInfo that = (VibratorInfo) o;
if (mSupportedPrimitives == null || that.mSupportedPrimitives == null) {
if (mSupportedPrimitives != that.mSupportedPrimitives) {
int supportedPrimitivesCount = mSupportedPrimitives.size();
if (supportedPrimitivesCount != that.mSupportedPrimitives.size()) {
return false;
}
for (int i = 0; i < supportedPrimitivesCount; i++) {
if (mSupportedPrimitives.keyAt(i) != that.mSupportedPrimitives.keyAt(i)) {
return false;
}
} else {
if (mSupportedPrimitives.size() != that.mSupportedPrimitives.size()) {
if (mSupportedPrimitives.valueAt(i) != that.mSupportedPrimitives.valueAt(i)) {
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
&& mPrimitiveDelayMax == that.mPrimitiveDelayMax
&& mCompositionSizeMax == that.mCompositionSizeMax
&& mPwlePrimitiveDurationMax == that.mPwlePrimitiveDurationMax
&& mPwleSizeMax == that.mPwleSizeMax
&& Objects.equals(mSupportedEffects, that.mSupportedEffects)
&& Objects.equals(mSupportedBraking, that.mSupportedBraking)
&& Objects.equals(mQFactor, that.mQFactor)
@@ -166,11 +178,9 @@ public class VibratorInfo implements Parcelable {
public int hashCode() {
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);
}
for (int i = 0; i < mSupportedPrimitives.size(); i++) {
hashCode = 31 * hashCode + mSupportedPrimitives.keyAt(i);
hashCode = 31 * hashCode + mSupportedPrimitives.valueAt(i);
}
return hashCode;
}
@@ -184,6 +194,10 @@ public class VibratorInfo implements Parcelable {
+ ", mSupportedEffects=" + Arrays.toString(getSupportedEffectsNames())
+ ", mSupportedBraking=" + Arrays.toString(getSupportedBrakingNames())
+ ", mSupportedPrimitives=" + Arrays.toString(getSupportedPrimitivesNames())
+ ", mPrimitiveDelayMax=" + mPrimitiveDelayMax
+ ", mCompositionSizeMax=" + mCompositionSizeMax
+ ", mPwlePrimitiveDurationMax=" + mPwlePrimitiveDurationMax
+ ", mPwleSizeMax=" + mPwleSizeMax
+ ", mQFactor=" + mQFactor
+ ", mFrequencyMapping=" + mFrequencyMapping
+ '}';
@@ -247,7 +261,7 @@ public class VibratorInfo implements Parcelable {
*/
public boolean isPrimitiveSupported(
@VibrationEffect.Composition.PrimitiveType int primitiveId) {
return hasCapability(IVibrator.CAP_COMPOSE_EFFECTS) && mSupportedPrimitives != null
return hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)
&& (mSupportedPrimitives.indexOfKey(primitiveId) >= 0);
}
@@ -260,7 +274,43 @@ public class VibratorInfo implements Parcelable {
*/
public int getPrimitiveDuration(
@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() {
if (mSupportedPrimitives == null) {
return new String[0];
}
String[] names = new String[mSupportedPrimitives.size()];
for (int i = 0; i < mSupportedPrimitives.size(); i++) {
int supportedPrimitivesCount = mSupportedPrimitives.size();
String[] names = new String[supportedPrimitivesCount];
for (int i = 0; i < supportedPrimitivesCount; i++) {
names[i] = VibrationEffect.Composition.primitiveToString(mSupportedPrimitives.keyAt(i));
}
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}.
*
@@ -675,11 +688,14 @@ public class VibratorInfo implements Parcelable {
/** @hide */
public static final class Builder {
private final int mId;
private int mCapabilities = 0;
private int[] mSupportedEffects = null;
private int[] mSupportedBraking = null;
private int[] mSupportedPrimitives = null;
private int[] mPrimitiveDurations = new int[0];
private long mCapabilities;
private SparseBooleanArray mSupportedEffects;
private SparseBooleanArray mSupportedBraking;
private SparseIntArray mSupportedPrimitives = new SparseIntArray();
private int mPrimitiveDelayMax;
private int mCompositionSizeMax;
private int mPwlePrimitiveDurationMax;
private int mPwleSizeMax;
private float mQFactor = Float.NaN;
private FrequencyMapping mFrequencyMapping =
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. */
@NonNull
public Builder setCapabilities(int capabilities) {
public Builder setCapabilities(long capabilities) {
mCapabilities = capabilities;
return this;
}
@@ -699,34 +715,49 @@ public class VibratorInfo implements Parcelable {
/** Configure the effects supported with {@link android.hardware.vibrator.Effect} values. */
@NonNull
public Builder setSupportedEffects(int... supportedEffects) {
mSupportedEffects = supportedEffects;
mSupportedEffects = toSparseBooleanArray(supportedEffects);
return this;
}
/** Configure braking supported with {@link android.hardware.vibrator.Braking} values. */
@NonNull
public Builder setSupportedBraking(int... supportedBraking) {
mSupportedBraking = supportedBraking;
mSupportedBraking = toSparseBooleanArray(supportedBraking);
return this;
}
/**
* Configure the primitives supported with
* {@link android.hardware.vibrator.CompositePrimitive} values.
*/
/** Configure maximum duration, in milliseconds, of a PWLE primitive. */
@NonNull
public Builder setSupportedPrimitives(int... supportedPrimitives) {
mSupportedPrimitives = supportedPrimitives;
public Builder setPwlePrimitiveDurationMax(int pwlePrimitiveDurationMax) {
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;
}
/** 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;
public Builder setSupportedPrimitive(int primitiveId, int duration) {
mSupportedPrimitives.put(primitiveId, duration);
return this;
}
/** 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;
}
@@ -748,7 +779,25 @@ public class VibratorInfo implements Parcelable {
@NonNull
public VibratorInfo build() {
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() {
VibratorInfo info = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK)
.setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
.build();
assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK));
// Returns false when there is no compose capability.
info = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK)
.setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
.build();
assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
}
@@ -103,8 +103,7 @@ public class VibratorInfoTest {
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)
.setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
.build();
assertEquals(20, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK));
assertEquals(0, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_TICK));
@@ -112,6 +111,26 @@ public class VibratorInfoTest {
.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
public void testGetDefaultBraking_returnsFirstSupportedBraking() {
assertEquals(Braking.NONE, new VibratorInfo.Builder(
@@ -263,8 +282,12 @@ public class VibratorInfoTest {
VibratorInfo.Builder completeBuilder = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
.setSupportedEffects(VibrationEffect.EFFECT_CLICK)
.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK)
.setPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
.setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
.setPrimitiveDelayMax(100)
.setCompositionSizeMax(10)
.setSupportedBraking(Braking.CLAB)
.setPwlePrimitiveDurationMax(50)
.setPwleSizeMax(20)
.setQFactor(2f)
.setFrequencyMapping(TEST_FREQUENCY_MAPPING);
VibratorInfo complete = completeBuilder.build();
@@ -279,8 +302,7 @@ public class VibratorInfoTest {
assertNotEquals(complete, completeWithComposeControl);
VibratorInfo completeWithNoEffects = completeBuilder
.setSupportedEffects()
.setSupportedPrimitives()
.setSupportedEffects(new int[0])
.build();
assertNotEquals(complete, completeWithNoEffects);
@@ -289,13 +311,8 @@ public class VibratorInfoTest {
.build();
assertNotEquals(complete, completeWithUnknownEffects);
VibratorInfo completeWithUnknownPrimitives = completeBuilder
.setSupportedPrimitives(null)
.build();
assertNotEquals(complete, completeWithUnknownPrimitives);
VibratorInfo completeWithDifferentPrimitiveDuration = completeBuilder
.setPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
.setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
.build();
assertNotEquals(complete, completeWithDifferentPrimitiveDuration);
@@ -321,12 +338,17 @@ public class VibratorInfoTest {
.build();
assertNotEquals(complete, completeWithDifferentQFactor);
VibratorInfo empty = new VibratorInfo.Builder(TEST_VIBRATOR_ID).build();
VibratorInfo emptyWithKnownSupport = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
.setSupportedEffects()
.setSupportedPrimitives()
VibratorInfo unknownEffectSupport = new VibratorInfo.Builder(TEST_VIBRATOR_ID).build();
VibratorInfo knownEmptyEffectSupport = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
.setSupportedEffects(new int[0])
.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
@@ -334,8 +356,7 @@ public class VibratorInfoTest {
VibratorInfo original = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
.setSupportedEffects(VibrationEffect.EFFECT_CLICK)
.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK)
.setPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
.setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
.setQFactor(Float.NaN)
.setFrequencyMapping(TEST_FREQUENCY_MAPPING)
.build();

View File

@@ -844,7 +844,12 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
public List<Step> play() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePrimitivesStep");
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<>();
for (int i = segmentIndex; i < segmentCount; i++) {
VibrationEffectSegment segment = effect.getSegments().get(i);
@@ -896,7 +901,12 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
public List<Step> play() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePwleStep");
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<>();
for (int i = segmentIndex; i < segmentCount; 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.VisibleForTesting;
import com.android.internal.util.Preconditions;
import libcore.util.NativeAllocationRegistry;
/** Controls a single vibrator. */
final class 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 NativeWrapper mNativeWrapper;
private final VibratorInfo mVibratorInfo;
private final VibratorInfo.Builder mVibratorInfoBuilder;
@GuardedBy("mLock")
private VibratorInfo mVibratorInfo;
@GuardedBy("mLock")
private boolean mVibratorInfoLoaded;
@GuardedBy("mLock")
private final RemoteCallbackList<IVibratorStateListener> mVibratorStateListeners =
new RemoteCallbackList<>();
@@ -66,10 +71,10 @@ final class VibratorController {
NativeWrapper nativeWrapper) {
mNativeWrapper = nativeWrapper;
mNativeWrapper.init(vibratorId, listener);
// TODO(b/167947076): load suggested range from config
mVibratorInfo = mNativeWrapper.getInfo(/* suggestedFrequencyRange= */ 200);
Preconditions.checkNotNull(mVibratorInfo, "Failed to retrieve data for vibrator %d",
vibratorId);
mVibratorInfoBuilder = new VibratorInfo.Builder(vibratorId);
mVibratorInfoLoaded = mNativeWrapper.getInfo(SUGGESTED_FREQUENCY_SAFE_RANGE,
mVibratorInfoBuilder);
mVibratorInfo = mVibratorInfoBuilder.build();
}
/** Register state listener for this vibrator. */
@@ -103,7 +108,15 @@ final class VibratorController {
/** Return the {@link VibratorInfo} representing the vibrator controlled by this instance. */
public VibratorInfo getVibratorInfo() {
return mVibratorInfo;
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;
}
}
/**
@@ -361,7 +374,8 @@ final class VibratorController {
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;
@@ -428,9 +442,11 @@ final class VibratorController {
alwaysOnDisable(mNativePtr, id);
}
/** Return device vibrator metadata. */
public VibratorInfo getInfo(float suggestedFrequencyRange) {
return getInfo(mNativePtr, suggestedFrequencyRange);
/**
* Loads device vibrator metadata and returns true if all metadata was loaded successfully.
*/
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 jclass sFrequencyMappingClass;
static jmethodID sFrequencyMappingCtor;
static jclass sVibratorInfoClass;
static jmethodID sVibratorInfoCtor;
static struct {
jmethodID setCapabilities;
jmethodID setSupportedEffects;
jmethodID setSupportedBraking;
jmethodID setPwlePrimitiveDurationMax;
jmethodID setPwleSizeMax;
jmethodID setSupportedPrimitive;
jmethodID setPrimitiveDelayMax;
jmethodID setCompositionSizeMax;
jmethodID setQFactor;
jmethodID setFrequencyMapping;
} sVibratorInfoBuilderClassInfo;
static struct {
jfieldID id;
jfieldID scale;
@@ -352,68 +362,88 @@ static void vibratorAlwaysOnDisable(JNIEnv* env, jclass /* clazz */, jlong ptr,
wrapper->halCall<void>(alwaysOnDisableFn, "alwaysOnDisable");
}
static jobject vibratorGetInfo(JNIEnv* env, jclass /* clazz */, jlong ptr,
jfloat suggestedSafeRange) {
static jboolean vibratorGetInfo(JNIEnv* env, jclass /* clazz */, jlong ptr,
jfloat suggestedSafeRange, jobject vibratorInfoBuilder) {
VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
if (wrapper == nullptr) {
ALOGE("vibratorGetInfo failed because native wrapper was not initialized");
return nullptr;
return JNI_FALSE;
}
vibrator::Info info = wrapper->getVibratorInfo();
jlong capabilities =
static_cast<jlong>(info.capabilities.valueOr(vibrator::Capabilities::NONE));
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));
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.capabilities.isOk()) {
env->CallObjectMethod(vibratorInfoBuilder, sVibratorInfoBuilderClassInfo.setCapabilities,
static_cast<jlong>(info.capabilities.value()));
}
if (info.supportedEffects.isOk()) {
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(),
reinterpret_cast<jint*>(effects.data()));
env->CallObjectMethod(vibratorInfoBuilder,
sVibratorInfoBuilderClassInfo.setSupportedEffects, supportedEffects);
}
if (info.supportedBraking.isOk()) {
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(),
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()) {
std::vector<aidl::CompositePrimitive> primitives = info.supportedPrimitives.value();
supportedPrimitives = env->NewIntArray(primitives.size());
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());
auto durations = info.primitiveDurations.valueOr({});
for (auto& primitive : info.supportedPrimitives.value()) {
auto primitiveIdx = static_cast<size_t>(primitive);
auto duration = durations.size() > primitiveIdx ? durations[primitiveIdx].count() : 0;
env->CallObjectMethod(vibratorInfoBuilder,
sVibratorInfoBuilderClassInfo.setSupportedPrimitive,
static_cast<jint>(primitive), static_cast<jint>(duration));
}
primitiveDurations = env->NewIntArray(durations.size());
env->SetIntArrayRegion(primitiveDurations, 0, durations.size(),
reinterpret_cast<jint*>(durations.data()));
}
if (info.primitiveDelayMax.isOk()) {
env->CallObjectMethod(vibratorInfoBuilder,
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()) {
std::vector<float> amplitudes = info.maxAmplitudes.value();
maxAmplitudes = env->NewFloatArray(amplitudes.size());
env->SetFloatArrayRegion(maxAmplitudes, 0, amplitudes.size(),
reinterpret_cast<jfloat*>(amplitudes.data()));
}
jobject frequencyMapping = env->NewObject(sFrequencyMappingClass, sFrequencyMappingCtor,
minFrequency, resonantFrequency, frequencyResolution,
suggestedSafeRange, maxAmplitudes);
env->CallObjectMethod(vibratorInfoBuilder, sVibratorInfoBuilderClassInfo.setFrequencyMapping,
frequencyMapping);
return env->NewObject(sVibratorInfoClass, sVibratorInfoCtor, wrapper->getVibratorId(),
capabilities, supportedEffects, supportedBraking, supportedPrimitives,
primitiveDurations, qFactor, frequencyMapping);
return info.checkAndLogFailure("vibratorGetInfo") ? JNI_FALSE : JNI_TRUE;
}
static const JNINativeMethod method_table[] = {
@@ -433,7 +463,7 @@ static const JNINativeMethod method_table[] = {
{"setExternalControl", "(JZ)V", (void*)vibratorSetExternalControl},
{"alwaysOnEnable", "(JJJJ)V", (void*)vibratorAlwaysOnEnable},
{"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) {
@@ -459,11 +489,38 @@ int register_android_server_vibrator_VibratorController(JavaVM* jvm, JNIEnv* env
sFrequencyMappingClass = static_cast<jclass>(env->NewGlobalRef(frequencyMappingClass));
sFrequencyMappingCtor = GetMethodIDOrDie(env, sFrequencyMappingClass, "<init>", "(FFFF[F)V");
jclass vibratorInfoClass = FindClassOrDie(env, "android/os/VibratorInfo");
sVibratorInfoClass = (jclass)env->NewGlobalRef(vibratorInfoClass);
sVibratorInfoCtor =
GetMethodIDOrDie(env, sVibratorInfoClass, "<init>",
"(IJ[I[I[I[IFLandroid/os/VibratorInfo$FrequencyMapping;)V");
jclass vibratorInfoBuilderClass = FindClassOrDie(env, "android/os/VibratorInfo$Builder");
sVibratorInfoBuilderClassInfo.setCapabilities =
GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setCapabilities",
"(J)Landroid/os/VibratorInfo$Builder;");
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,
"com/android/server/vibrator/VibratorController$NativeWrapper",

View File

@@ -56,6 +56,8 @@ final class FakeVibratorControllerProvider {
private int[] mSupportedEffects;
private int[] mSupportedBraking;
private int[] mSupportedPrimitives;
private int mCompositionSizeMax;
private int mPwleSizeMax;
private float mMinFrequency = Float.NaN;
private float mResonantFrequency = Float.NaN;
private float mFrequencyResolution = Float.NaN;
@@ -151,12 +153,22 @@ final class FakeVibratorControllerProvider {
}
@Override
public VibratorInfo getInfo(float suggestedFrequencyRange) {
VibratorInfo.FrequencyMapping frequencyMapping = new VibratorInfo.FrequencyMapping(
mMinFrequency, mResonantFrequency, mFrequencyResolution,
suggestedFrequencyRange, mMaxAmplitudes);
return new VibratorInfo(vibratorId, mCapabilities, mSupportedEffects, mSupportedBraking,
mSupportedPrimitives, null, mQFactor, frequencyMapping);
public boolean getInfo(float suggestedFrequencyRange, VibratorInfo.Builder infoBuilder) {
infoBuilder.setCapabilities(mCapabilities);
infoBuilder.setSupportedBraking(mSupportedBraking);
infoBuilder.setPwleSizeMax(mPwleSizeMax);
infoBuilder.setSupportedEffects(mSupportedEffects);
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() {
@@ -236,6 +248,16 @@ final class FakeVibratorControllerProvider {
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. */
public void setResonantFrequency(float frequencyHz) {
mResonantFrequency = frequencyHz;

View File

@@ -356,7 +356,8 @@ public class VibrationThreadTest {
@Test
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;
VibrationEffect effect = VibrationEffect.startComposition()
@@ -374,7 +375,7 @@ public class VibrationThreadTest {
assertEquals(Arrays.asList(
expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0),
expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0)),
mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
fakeVibrator.getEffectSegments());
}
@Test
@@ -394,6 +395,27 @@ public class VibrationThreadTest {
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
public void vibrate_singleVibratorComposedEffects_runsDifferentVibrations() throws Exception {
mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
@@ -432,12 +454,13 @@ public class VibrationThreadTest {
@Test
public void vibrate_singleVibratorPwle_runsComposePwle() throws Exception {
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
mVibratorProviders.get(VIBRATOR_ID).setSupportedBraking(Braking.CLAB);
mVibratorProviders.get(VIBRATOR_ID).setMinFrequency(100);
mVibratorProviders.get(VIBRATOR_ID).setResonantFrequency(150);
mVibratorProviders.get(VIBRATOR_ID).setFrequencyResolution(50);
mVibratorProviders.get(VIBRATOR_ID).setMaxAmplitudes(
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
fakeVibrator.setSupportedBraking(Braking.CLAB);
fakeVibrator.setMinFrequency(100);
fakeVibrator.setResonantFrequency(150);
fakeVibrator.setFrequencyResolution(50);
fakeVibrator.setMaxAmplitudes(
0.5f /* 100Hz*/, 1 /* 150Hz */, 0.6f /* 200Hz */);
long vibrationId = 1;
@@ -462,8 +485,34 @@ public class VibrationThreadTest {
expectedRamp(/* amplitude= */ 0.6f, /* frequency= */ 200, /* duration= */ 30),
expectedRamp(/* StartAmplitude= */ 0.6f, /* endAmplitude= */ 0.5f,
/* startFrequency= */ 200, /* endFrequency= */ 100, /* duration= */ 40)),
mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
assertEquals(Arrays.asList(Braking.CLAB), mVibratorProviders.get(VIBRATOR_ID).getBraking());
fakeVibrator.getEffectSegments());
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

View File

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