diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index 9598410d8f330..21c64876c24c7 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -410,9 +410,9 @@ public abstract class VibrationEffect implements Parcelable { * *

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

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) { @@ -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}. * *

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, @@ -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}. + * + *

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: + * + * + * 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(); + * + * + *

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: + * + * + * 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(); + * + * + *

When choosing to play a composed effect, you should check that individual components are + * supported by the device by using the appropriate vibrator method: + * + *

* * @see VibrationEffect#startComposition() */ @@ -1091,16 +1134,77 @@ public abstract class VibrationEffect implements Parcelable { * A builder for waveform haptic effects. * *

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. + * + *

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: + * + * + * 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(); + * + * + *

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. + * + *

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)}: * *

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

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)}. + *

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. + * + * + * 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(); + * + * + *

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: + * + * + * // 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(); + * * * @see VibrationEffect#startWaveform */ diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java index 104f077e5ad22..f7ca822c36e2a 100644 --- a/core/tests/coretests/src/android/os/VibrationEffectTest.java +++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java @@ -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)) @@ -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)); @@ -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) @@ -178,11 +213,28 @@ 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() - .repeatEffectIndefinitely(TEST_ONE_SHOT) + .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();