From a1167c2a064a0e5260e4d8cfa6161a3a79e76bd2 Mon Sep 17 00:00:00 2001 From: Lais Andrade Date: Thu, 3 Jun 2021 17:17:27 +0100 Subject: Split long vibration composition using HAL limits Load composition and pwle limits from the HAL and apply them on VibrationThread to split long vibrations in multiple calls. This ensures that long vibration will be performed by the HAL. Bug: 188431691 Fix: 183517124 Test: VibrationThreadTest & VibrationInfoTest Change-Id: I09a04c7b1de331320d453899f59b01e4bbb42ac3 --- .../hardware/input/InputDeviceVibrator.java | 8 +- core/java/android/os/VibratorInfo.java | 275 ++++++++++++--------- .../coretests/src/android/os/VibratorInfoTest.java | 63 +++-- .../android/server/vibrator/VibrationThread.java | 14 +- .../server/vibrator/VibratorController.java | 38 ++- ..._android_server_vibrator_VibratorController.cpp | 139 ++++++++--- .../vibrator/FakeVibratorControllerProvider.java | 34 ++- .../server/vibrator/VibrationThreadTest.java | 69 +++++- .../server/vibrator/VibratorControllerTest.java | 12 +- 9 files changed, 439 insertions(+), 213 deletions(-) diff --git a/core/java/android/hardware/input/InputDeviceVibrator.java b/core/java/android/hardware/input/InputDeviceVibrator.java index d8150e483fd6..653c622386d6 100644 --- a/core/java/android/hardware/input/InputDeviceVibrator.java +++ b/core/java/android/hardware/input/InputDeviceVibrator.java @@ -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(); } diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java index 597df0811e20..486f9f139e0a 100644 --- a/core/java/android/os/VibratorInfo.java +++ b/core/java/android/os/VibratorInfo.java @@ -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. - * - *

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 setPwlePrimitiveDurationMax(int pwlePrimitiveDurationMax) { + mPwlePrimitiveDurationMax = pwlePrimitiveDurationMax; + return this; + } + + /** Configure maximum number of primitives supported in a single PWLE composed effect. */ @NonNull - public Builder setSupportedPrimitives(int... supportedPrimitives) { - mSupportedPrimitives = supportedPrimitives; + 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; } } diff --git a/core/tests/coretests/src/android/os/VibratorInfoTest.java b/core/tests/coretests/src/android/os/VibratorInfoTest.java index 8c7d10c7a5ef..6e07fa264c1c 100644 --- a/core/tests/coretests/src/android/os/VibratorInfoTest.java +++ b/core/tests/coretests/src/android/os/VibratorInfoTest.java @@ -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(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(empty, emptyWithKnownSupport); + 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(); diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java index 150fde99b706..45d511159e11 100644 --- a/services/core/java/com/android/server/vibrator/VibrationThread.java +++ b/services/core/java/com/android/server/vibrator/VibrationThread.java @@ -844,7 +844,12 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { public List 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 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 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 pwles = new ArrayList<>(); for (int i = segmentIndex; i < segmentCount; i++) { VibrationEffectSegment segment = effect.getSegments().get(i); diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java index 5dceac2d066c..001d5c440d58 100644 --- a/services/core/java/com/android/server/vibrator/VibratorController.java +++ b/services/core/java/com/android/server/vibrator/VibratorController.java @@ -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 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); } } } diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp index 698e3f75d0ed..9029fe7cca66 100644 --- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp +++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp @@ -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(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(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(info.capabilities.valueOr(vibrator::Capabilities::NONE)); - jfloat minFrequency = static_cast(info.minFrequency.valueOr(NAN)); - jfloat resonantFrequency = static_cast(info.resonantFrequency.valueOr(NAN)); - jfloat frequencyResolution = static_cast(info.frequencyResolution.valueOr(NAN)); - jfloat qFactor = static_cast(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(info.capabilities.value())); + } if (info.supportedEffects.isOk()) { std::vector effects = info.supportedEffects.value(); - supportedEffects = env->NewIntArray(effects.size()); + jintArray supportedEffects = env->NewIntArray(effects.size()); env->SetIntArrayRegion(supportedEffects, 0, effects.size(), reinterpret_cast(effects.data())); + env->CallObjectMethod(vibratorInfoBuilder, + sVibratorInfoBuilderClassInfo.setSupportedEffects, supportedEffects); } if (info.supportedBraking.isOk()) { std::vector braking = info.supportedBraking.value(); - supportedBraking = env->NewIntArray(braking.size()); + jintArray supportedBraking = env->NewIntArray(braking.size()); env->SetIntArrayRegion(supportedBraking, 0, braking.size(), reinterpret_cast(braking.data())); + env->CallObjectMethod(vibratorInfoBuilder, + sVibratorInfoBuilderClassInfo.setSupportedBraking, supportedBraking); } - if (info.supportedPrimitives.isOk()) { - std::vector primitives = info.supportedPrimitives.value(); - supportedPrimitives = env->NewIntArray(primitives.size()); - env->SetIntArrayRegion(supportedPrimitives, 0, primitives.size(), - reinterpret_cast(primitives.data())); + if (info.pwlePrimitiveDurationMax.isOk()) { + env->CallObjectMethod(vibratorInfoBuilder, + sVibratorInfoBuilderClassInfo.setPwlePrimitiveDurationMax, + static_cast(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(info.pwleSizeMax.value() - 1)); } - if (info.primitiveDurations.isOk()) { - std::vector durations; - for (auto duration : info.primitiveDurations.value()) { - durations.push_back(duration.count()); + if (info.supportedPrimitives.isOk()) { + auto durations = info.primitiveDurations.valueOr({}); + for (auto& primitive : info.supportedPrimitives.value()) { + auto primitiveIdx = static_cast(primitive); + auto duration = durations.size() > primitiveIdx ? durations[primitiveIdx].count() : 0; + env->CallObjectMethod(vibratorInfoBuilder, + sVibratorInfoBuilderClassInfo.setSupportedPrimitive, + static_cast(primitive), static_cast(duration)); } - primitiveDurations = env->NewIntArray(durations.size()); - env->SetIntArrayRegion(primitiveDurations, 0, durations.size(), - reinterpret_cast(durations.data())); } + if (info.primitiveDelayMax.isOk()) { + env->CallObjectMethod(vibratorInfoBuilder, + sVibratorInfoBuilderClassInfo.setPrimitiveDelayMax, + static_cast(info.primitiveDelayMax.value().count())); + } + if (info.compositionSizeMax.isOk()) { + env->CallObjectMethod(vibratorInfoBuilder, + sVibratorInfoBuilderClassInfo.setCompositionSizeMax, + static_cast(info.compositionSizeMax.value())); + } + if (info.qFactor.isOk()) { + env->CallObjectMethod(vibratorInfoBuilder, sVibratorInfoBuilderClassInfo.setQFactor, + static_cast(info.qFactor.value())); + } + + jfloat minFrequency = static_cast(info.minFrequency.valueOr(NAN)); + jfloat resonantFrequency = static_cast(info.resonantFrequency.valueOr(NAN)); + jfloat frequencyResolution = static_cast(info.frequencyResolution.valueOr(NAN)); + jfloatArray maxAmplitudes = nullptr; if (info.maxAmplitudes.isOk()) { std::vector amplitudes = info.maxAmplitudes.value(); maxAmplitudes = env->NewFloatArray(amplitudes.size()); env->SetFloatArrayRegion(maxAmplitudes, 0, amplitudes.size(), reinterpret_cast(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(env->NewGlobalRef(frequencyMappingClass)); sFrequencyMappingCtor = GetMethodIDOrDie(env, sFrequencyMappingClass, "", "(FFFF[F)V"); - jclass vibratorInfoClass = FindClassOrDie(env, "android/os/VibratorInfo"); - sVibratorInfoClass = (jclass)env->NewGlobalRef(vibratorInfoClass); - sVibratorInfoCtor = - GetMethodIDOrDie(env, sVibratorInfoClass, "", - "(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", diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java index 058575817acf..0449e4450d06 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java @@ -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; diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java index f02e2f081e3b..b8fdb552e453 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java @@ -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 diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java index 9e98e7d0410c..a732bd18676a 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java @@ -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) { -- cgit v1.2.3-59-g8ed1b