Loading core/java/android/os/VibrationEffect.java +120 −16 Original line number Diff line number Diff line Loading @@ -410,9 +410,9 @@ public abstract class VibrationEffect implements Parcelable { * * <p>The waveform will start the first transition from the vibrator off state, with the * resonant frequency by default. To provide an initial state, use * {@link #startWaveform(VibrationParameter)}. * {@link #startWaveform(VibrationEffect.VibrationParameter)}. * * @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters. * @see VibrationEffect.WaveformBuilder */ @NonNull public static WaveformBuilder startWaveform() { Loading @@ -421,14 +421,16 @@ public abstract class VibrationEffect implements Parcelable { /** * Start building a waveform vibration with an initial state specified by a * {@link VibrationParameter}. * {@link VibrationEffect.VibrationParameter}. * * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing * control over vibration amplitude and frequency via smooth transitions between values. * * @param initialParameter The initial {@link VibrationParameter} value to be applied at the * beginning of the vibration. * @param initialParameter The initial {@link VibrationEffect.VibrationParameter} value to be * applied at the beginning of the vibration. * @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters. * * @see VibrationEffect.WaveformBuilder */ @NonNull public static WaveformBuilder startWaveform(@NonNull VibrationParameter initialParameter) { Loading @@ -439,17 +441,19 @@ public abstract class VibrationEffect implements Parcelable { /** * Start building a waveform vibration with an initial state specified by two * {@link VibrationParameter VibrationParameters}. * {@link VibrationEffect.VibrationParameter VibrationParameters}. * * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing * control over vibration amplitude and frequency via smooth transitions between values. * * @param initialParameter1 The initial {@link VibrationParameter} value to be applied at the * beginning of the vibration. * @param initialParameter2 The initial {@link VibrationParameter} value to be applied at the * beginning of the vibration, must be a different type of parameter * than the one specified by the first argument. * @param initialParameter1 The initial {@link VibrationEffect.VibrationParameter} value to be * applied at the beginning of the vibration. * @param initialParameter2 The initial {@link VibrationEffect.VibrationParameter} value to be * applied at the beginning of the vibration, must be a different type * of parameter than the one specified by the first argument. * @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters. * * @see VibrationEffect.WaveformBuilder */ @NonNull public static WaveformBuilder startWaveform(@NonNull VibrationParameter initialParameter1, Loading Loading @@ -805,7 +809,46 @@ public abstract class VibrationEffect implements Parcelable { } /** * A composition of haptic primitives that, when combined, create a single haptic effect. * A composition of haptic elements that are combined to be playable as a single * {@link VibrationEffect}. * * <p>The haptic primitives are available as {@code Composition.PRIMITIVE_*} constants and * can be added to a composition to create a custom vibration effect. Here is an example of an * effect that grows in intensity and then dies off, with a longer rising portion for emphasis * and an extra tick 100ms after: * * <code> * VibrationEffect effect = VibrationEffect.startComposition() * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE, 0.5f) * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, 0.5f) * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1.0f, 100) * .compose(); * </code> * * <p>Composition elements can also be {@link VibrationEffect} instances, including other * compositions, and off durations, which are periods of time when the vibrator will be * turned off. Here is an example of a composition that "warms up" with a light tap, * a stronger double tap, then repeats a vibration pattern indefinitely: * * <code> * VibrationEffect repeatingEffect = VibrationEffect.startComposition() * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK) * .addOffDuration(Duration.ofMillis(10)) * .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_DOUBLE_CLICK)) * .addOffDuration(Duration.ofMillis(50)) * .addEffect(VibrationEffect.createWaveform(pattern, repeatIndex)) * .compose(); * </code> * * <p>When choosing to play a composed effect, you should check that individual components are * supported by the device by using the appropriate vibrator method: * * <ul> * <li>Primitive support can be checked using {@link Vibrator#arePrimitivesSupported}. * <li>Effect support can be checked using {@link Vibrator#areEffectsSupported}. * <li>Amplitude control for one-shot and waveforms with amplitude values can be checked * using {@link Vibrator#hasAmplitudeControl}. * </ul> * * @see VibrationEffect#startComposition() */ Loading Loading @@ -1091,16 +1134,77 @@ public abstract class VibrationEffect implements Parcelable { * A builder for waveform haptic effects. * * <p>Waveform vibrations constitute of one or more timed transitions to new sets of vibration * parameters. These parameters can be the vibration amplitude or frequency, for example. * parameters. These parameters can be the vibration amplitude, frequency, or both. * * <p>The following example ramps a vibrator turned off to full amplitude at 120Hz, over 100ms * starting at 60Hz, then holds that state for 200ms and ramps back down again over 100ms: * * <code> * import static android.os.VibrationEffect.VibrationParameter.targetAmplitude; * import static android.os.VibrationEffect.VibrationParameter.targetFrequency; * * VibrationEffect effect = VibrationEffect.startWaveform(targetFrequency(60)) * .addTransition(Duration.ofMillis(100), targetAmplitude(1), targetFrequency(120)) * .addSustain(Duration.ofMillis(200)) * .addTransition(Duration.ofMillis(100), targetAmplitude(0), targetFrequency(60)) * .build(); * </code> * * <p>The initial state of the waveform can be set via * {@link VibrationEffect#startWaveform(VibrationParameter)} or * {@link VibrationEffect#startWaveform(VibrationParameter, VibrationParameter)}. If the initial * parameters are not set then the {@link WaveformBuilder} will start with the vibrator off, * represented by zero amplitude, at the vibrator's resonant frequency. * * <p>Repeating waveforms can be created by building the repeating block separately and adding * it to the end of a composition with * {@link Composition#repeatEffectIndefinitely(VibrationEffect)}: * * <p>Note that physical vibration actuators have different reaction times for changing * amplitude and frequency. Durations specified here represent a timeline for the target * parameters, and quality of effects may be improved if the durations allow time for a * transition to be smoothly applied. * * <p>Repeating waveforms can be built by constructing the repeating block separately and adding * it to the end of a composition using * {@link Composition#repeatEffectIndefinitely(VibrationEffect)}. * <p>The following example illustrates both an initial state and a repeating section, using * a {@link VibrationEffect.Composition}. The resulting effect will have a tick followed by a * repeated beating effect with a rise that stretches out and a sharp finish. * * <code> * VibrationEffect patternToBeRepeated = VibrationEffect.startWaveform(targetAmplitude(0.2f)) * .addSustain(Duration.ofMillis(10)) * .addTransition(Duration.ofMillis(20), targetAmplitude(0.4f)) * .addSustain(Duration.ofMillis(30)) * .addTransition(Duration.ofMillis(40), targetAmplitude(0.8f)) * .addSustain(Duration.ofMillis(50)) * .addTransition(Duration.ofMillis(60), targetAmplitude(0.2f)) * .build(); * * VibrationEffect effect = VibrationEffect.startComposition() * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK) * .addOffDuration(Duration.ofMillis(20)) * .repeatEffectIndefinitely(patternToBeRepeated) * .compose(); * </code> * * <p>The amplitude step waveforms that can be created via * {@link VibrationEffect#createWaveform(long[], int[], int)} can also be created with * {@link WaveformBuilder} by adding zero duration transitions: * * <code> * // These two effects are the same * VibrationEffect waveform = VibrationEffect.createWaveform( * new long[] { 10, 20, 30 }, // timings in milliseconds * new int[] { 51, 102, 204 }, // amplitudes in [0,255] * -1); // repeat index * * VibrationEffect sameWaveform = VibrationEffect.startWaveform(targetAmplitude(0.2f)) * .addSustain(Duration.ofMillis(10)) * .addTransition(Duration.ZERO, targetAmplitude(0.4f)) * .addSustain(Duration.ofMillis(20)) * .addTransition(Duration.ZERO, targetAmplitude(0.8f)) * .addSustain(Duration.ofMillis(30)) * .build(); * </code> * * @see VibrationEffect#startWaveform */ Loading core/tests/coretests/src/android/os/VibrationEffectTest.java +53 −1 Original line number Diff line number Diff line Loading @@ -146,6 +146,7 @@ public class VibrationEffectTest { @Test public void testValidateWaveformBuilder() { // Cover builder methods VibrationEffect.startWaveform(targetAmplitude(1)) .addTransition(Duration.ofSeconds(1), targetAmplitude(0.5f), targetFrequency(100)) .addTransition(Duration.ZERO, targetAmplitude(0f), targetFrequency(200)) Loading @@ -158,6 +159,39 @@ public class VibrationEffectTest { .build() .validate(); // Make sure class summary javadoc examples compile and are valid. // NOTE: IF THIS IS UPDATED, PLEASE ALSO UPDATE WaveformBuilder javadocs. VibrationEffect.startWaveform(targetFrequency(60)) .addTransition(Duration.ofMillis(100), targetAmplitude(1), targetFrequency(120)) .addSustain(Duration.ofMillis(200)) .addTransition(Duration.ofMillis(100), targetAmplitude(0), targetFrequency(60)) .build() .validate(); VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK) .addOffDuration(Duration.ofMillis(20)) .repeatEffectIndefinitely( VibrationEffect.startWaveform(targetAmplitude(0.2f)) .addSustain(Duration.ofMillis(10)) .addTransition(Duration.ofMillis(20), targetAmplitude(0.4f)) .addSustain(Duration.ofMillis(30)) .addTransition(Duration.ofMillis(40), targetAmplitude(0.8f)) .addSustain(Duration.ofMillis(50)) .addTransition(Duration.ofMillis(60), targetAmplitude(0.2f)) .build()) .compose() .validate(); VibrationEffect.createWaveform(new long[]{10, 20, 30}, new int[]{51, 102, 204}, -1) .validate(); VibrationEffect.startWaveform(targetAmplitude(0.2f)) .addSustain(Duration.ofMillis(10)) .addTransition(Duration.ZERO, targetAmplitude(0.4f)) .addSustain(Duration.ofMillis(20)) .addTransition(Duration.ZERO, targetAmplitude(0.8f)) .addSustain(Duration.ofMillis(30)) .build() .validate(); assertThrows(IllegalStateException.class, () -> VibrationEffect.startWaveform().build().validate()); assertThrows(IllegalArgumentException.class, () -> targetAmplitude(-2)); Loading @@ -171,6 +205,7 @@ public class VibrationEffectTest { @Test public void testValidateComposed() { // Cover builder methods VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) .addEffect(TEST_ONE_SHOT) Loading @@ -178,14 +213,31 @@ public class VibrationEffectTest { .addOffDuration(Duration.ofMillis(100)) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10) .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) .addEffect(VibrationEffect.createWaveform(new long[]{10, 20}, /* repeat= */ 0)) .compose() .validate(); VibrationEffect.startComposition() .repeatEffectIndefinitely(TEST_ONE_SHOT) .compose() .validate(); // Make sure class summary javadoc examples compile and are valid. // NOTE: IF THIS IS UPDATED, PLEASE ALSO UPDATE Composition javadocs. VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE, 0.5f) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, 0.5f) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1.0f, 100) .compose() .validate(); VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK) .addOffDuration(Duration.ofMillis(10)) .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_DOUBLE_CLICK)) .addOffDuration(Duration.ofMillis(50)) .addEffect(VibrationEffect.createWaveform(new long[]{10, 20}, /* repeat= */ 0)) .compose() .validate(); assertThrows(IllegalStateException.class, () -> VibrationEffect.startComposition().compose().validate()); assertThrows(IllegalStateException.class, Loading Loading
core/java/android/os/VibrationEffect.java +120 −16 Original line number Diff line number Diff line Loading @@ -410,9 +410,9 @@ public abstract class VibrationEffect implements Parcelable { * * <p>The waveform will start the first transition from the vibrator off state, with the * resonant frequency by default. To provide an initial state, use * {@link #startWaveform(VibrationParameter)}. * {@link #startWaveform(VibrationEffect.VibrationParameter)}. * * @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters. * @see VibrationEffect.WaveformBuilder */ @NonNull public static WaveformBuilder startWaveform() { Loading @@ -421,14 +421,16 @@ public abstract class VibrationEffect implements Parcelable { /** * Start building a waveform vibration with an initial state specified by a * {@link VibrationParameter}. * {@link VibrationEffect.VibrationParameter}. * * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing * control over vibration amplitude and frequency via smooth transitions between values. * * @param initialParameter The initial {@link VibrationParameter} value to be applied at the * beginning of the vibration. * @param initialParameter The initial {@link VibrationEffect.VibrationParameter} value to be * applied at the beginning of the vibration. * @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters. * * @see VibrationEffect.WaveformBuilder */ @NonNull public static WaveformBuilder startWaveform(@NonNull VibrationParameter initialParameter) { Loading @@ -439,17 +441,19 @@ public abstract class VibrationEffect implements Parcelable { /** * Start building a waveform vibration with an initial state specified by two * {@link VibrationParameter VibrationParameters}. * {@link VibrationEffect.VibrationParameter VibrationParameters}. * * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing * control over vibration amplitude and frequency via smooth transitions between values. * * @param initialParameter1 The initial {@link VibrationParameter} value to be applied at the * beginning of the vibration. * @param initialParameter2 The initial {@link VibrationParameter} value to be applied at the * beginning of the vibration, must be a different type of parameter * than the one specified by the first argument. * @param initialParameter1 The initial {@link VibrationEffect.VibrationParameter} value to be * applied at the beginning of the vibration. * @param initialParameter2 The initial {@link VibrationEffect.VibrationParameter} value to be * applied at the beginning of the vibration, must be a different type * of parameter than the one specified by the first argument. * @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters. * * @see VibrationEffect.WaveformBuilder */ @NonNull public static WaveformBuilder startWaveform(@NonNull VibrationParameter initialParameter1, Loading Loading @@ -805,7 +809,46 @@ public abstract class VibrationEffect implements Parcelable { } /** * A composition of haptic primitives that, when combined, create a single haptic effect. * A composition of haptic elements that are combined to be playable as a single * {@link VibrationEffect}. * * <p>The haptic primitives are available as {@code Composition.PRIMITIVE_*} constants and * can be added to a composition to create a custom vibration effect. Here is an example of an * effect that grows in intensity and then dies off, with a longer rising portion for emphasis * and an extra tick 100ms after: * * <code> * VibrationEffect effect = VibrationEffect.startComposition() * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE, 0.5f) * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, 0.5f) * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1.0f, 100) * .compose(); * </code> * * <p>Composition elements can also be {@link VibrationEffect} instances, including other * compositions, and off durations, which are periods of time when the vibrator will be * turned off. Here is an example of a composition that "warms up" with a light tap, * a stronger double tap, then repeats a vibration pattern indefinitely: * * <code> * VibrationEffect repeatingEffect = VibrationEffect.startComposition() * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK) * .addOffDuration(Duration.ofMillis(10)) * .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_DOUBLE_CLICK)) * .addOffDuration(Duration.ofMillis(50)) * .addEffect(VibrationEffect.createWaveform(pattern, repeatIndex)) * .compose(); * </code> * * <p>When choosing to play a composed effect, you should check that individual components are * supported by the device by using the appropriate vibrator method: * * <ul> * <li>Primitive support can be checked using {@link Vibrator#arePrimitivesSupported}. * <li>Effect support can be checked using {@link Vibrator#areEffectsSupported}. * <li>Amplitude control for one-shot and waveforms with amplitude values can be checked * using {@link Vibrator#hasAmplitudeControl}. * </ul> * * @see VibrationEffect#startComposition() */ Loading Loading @@ -1091,16 +1134,77 @@ public abstract class VibrationEffect implements Parcelable { * A builder for waveform haptic effects. * * <p>Waveform vibrations constitute of one or more timed transitions to new sets of vibration * parameters. These parameters can be the vibration amplitude or frequency, for example. * parameters. These parameters can be the vibration amplitude, frequency, or both. * * <p>The following example ramps a vibrator turned off to full amplitude at 120Hz, over 100ms * starting at 60Hz, then holds that state for 200ms and ramps back down again over 100ms: * * <code> * import static android.os.VibrationEffect.VibrationParameter.targetAmplitude; * import static android.os.VibrationEffect.VibrationParameter.targetFrequency; * * VibrationEffect effect = VibrationEffect.startWaveform(targetFrequency(60)) * .addTransition(Duration.ofMillis(100), targetAmplitude(1), targetFrequency(120)) * .addSustain(Duration.ofMillis(200)) * .addTransition(Duration.ofMillis(100), targetAmplitude(0), targetFrequency(60)) * .build(); * </code> * * <p>The initial state of the waveform can be set via * {@link VibrationEffect#startWaveform(VibrationParameter)} or * {@link VibrationEffect#startWaveform(VibrationParameter, VibrationParameter)}. If the initial * parameters are not set then the {@link WaveformBuilder} will start with the vibrator off, * represented by zero amplitude, at the vibrator's resonant frequency. * * <p>Repeating waveforms can be created by building the repeating block separately and adding * it to the end of a composition with * {@link Composition#repeatEffectIndefinitely(VibrationEffect)}: * * <p>Note that physical vibration actuators have different reaction times for changing * amplitude and frequency. Durations specified here represent a timeline for the target * parameters, and quality of effects may be improved if the durations allow time for a * transition to be smoothly applied. * * <p>Repeating waveforms can be built by constructing the repeating block separately and adding * it to the end of a composition using * {@link Composition#repeatEffectIndefinitely(VibrationEffect)}. * <p>The following example illustrates both an initial state and a repeating section, using * a {@link VibrationEffect.Composition}. The resulting effect will have a tick followed by a * repeated beating effect with a rise that stretches out and a sharp finish. * * <code> * VibrationEffect patternToBeRepeated = VibrationEffect.startWaveform(targetAmplitude(0.2f)) * .addSustain(Duration.ofMillis(10)) * .addTransition(Duration.ofMillis(20), targetAmplitude(0.4f)) * .addSustain(Duration.ofMillis(30)) * .addTransition(Duration.ofMillis(40), targetAmplitude(0.8f)) * .addSustain(Duration.ofMillis(50)) * .addTransition(Duration.ofMillis(60), targetAmplitude(0.2f)) * .build(); * * VibrationEffect effect = VibrationEffect.startComposition() * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK) * .addOffDuration(Duration.ofMillis(20)) * .repeatEffectIndefinitely(patternToBeRepeated) * .compose(); * </code> * * <p>The amplitude step waveforms that can be created via * {@link VibrationEffect#createWaveform(long[], int[], int)} can also be created with * {@link WaveformBuilder} by adding zero duration transitions: * * <code> * // These two effects are the same * VibrationEffect waveform = VibrationEffect.createWaveform( * new long[] { 10, 20, 30 }, // timings in milliseconds * new int[] { 51, 102, 204 }, // amplitudes in [0,255] * -1); // repeat index * * VibrationEffect sameWaveform = VibrationEffect.startWaveform(targetAmplitude(0.2f)) * .addSustain(Duration.ofMillis(10)) * .addTransition(Duration.ZERO, targetAmplitude(0.4f)) * .addSustain(Duration.ofMillis(20)) * .addTransition(Duration.ZERO, targetAmplitude(0.8f)) * .addSustain(Duration.ofMillis(30)) * .build(); * </code> * * @see VibrationEffect#startWaveform */ Loading
core/tests/coretests/src/android/os/VibrationEffectTest.java +53 −1 Original line number Diff line number Diff line Loading @@ -146,6 +146,7 @@ public class VibrationEffectTest { @Test public void testValidateWaveformBuilder() { // Cover builder methods VibrationEffect.startWaveform(targetAmplitude(1)) .addTransition(Duration.ofSeconds(1), targetAmplitude(0.5f), targetFrequency(100)) .addTransition(Duration.ZERO, targetAmplitude(0f), targetFrequency(200)) Loading @@ -158,6 +159,39 @@ public class VibrationEffectTest { .build() .validate(); // Make sure class summary javadoc examples compile and are valid. // NOTE: IF THIS IS UPDATED, PLEASE ALSO UPDATE WaveformBuilder javadocs. VibrationEffect.startWaveform(targetFrequency(60)) .addTransition(Duration.ofMillis(100), targetAmplitude(1), targetFrequency(120)) .addSustain(Duration.ofMillis(200)) .addTransition(Duration.ofMillis(100), targetAmplitude(0), targetFrequency(60)) .build() .validate(); VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK) .addOffDuration(Duration.ofMillis(20)) .repeatEffectIndefinitely( VibrationEffect.startWaveform(targetAmplitude(0.2f)) .addSustain(Duration.ofMillis(10)) .addTransition(Duration.ofMillis(20), targetAmplitude(0.4f)) .addSustain(Duration.ofMillis(30)) .addTransition(Duration.ofMillis(40), targetAmplitude(0.8f)) .addSustain(Duration.ofMillis(50)) .addTransition(Duration.ofMillis(60), targetAmplitude(0.2f)) .build()) .compose() .validate(); VibrationEffect.createWaveform(new long[]{10, 20, 30}, new int[]{51, 102, 204}, -1) .validate(); VibrationEffect.startWaveform(targetAmplitude(0.2f)) .addSustain(Duration.ofMillis(10)) .addTransition(Duration.ZERO, targetAmplitude(0.4f)) .addSustain(Duration.ofMillis(20)) .addTransition(Duration.ZERO, targetAmplitude(0.8f)) .addSustain(Duration.ofMillis(30)) .build() .validate(); assertThrows(IllegalStateException.class, () -> VibrationEffect.startWaveform().build().validate()); assertThrows(IllegalArgumentException.class, () -> targetAmplitude(-2)); Loading @@ -171,6 +205,7 @@ public class VibrationEffectTest { @Test public void testValidateComposed() { // Cover builder methods VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) .addEffect(TEST_ONE_SHOT) Loading @@ -178,14 +213,31 @@ public class VibrationEffectTest { .addOffDuration(Duration.ofMillis(100)) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10) .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) .addEffect(VibrationEffect.createWaveform(new long[]{10, 20}, /* repeat= */ 0)) .compose() .validate(); VibrationEffect.startComposition() .repeatEffectIndefinitely(TEST_ONE_SHOT) .compose() .validate(); // Make sure class summary javadoc examples compile and are valid. // NOTE: IF THIS IS UPDATED, PLEASE ALSO UPDATE Composition javadocs. VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE, 0.5f) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, 0.5f) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1.0f, 100) .compose() .validate(); VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK) .addOffDuration(Duration.ofMillis(10)) .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_DOUBLE_CLICK)) .addOffDuration(Duration.ofMillis(50)) .addEffect(VibrationEffect.createWaveform(new long[]{10, 20}, /* repeat= */ 0)) .compose() .validate(); assertThrows(IllegalStateException.class, () -> VibrationEffect.startComposition().compose().validate()); assertThrows(IllegalStateException.class, Loading