Merge "Make VibrationEffect.WaveformBuilder public"

This commit is contained in:
TreeHugger Robot
2022-01-26 12:04:23 +00:00
committed by Android (Google) Code Review
7 changed files with 487 additions and 236 deletions

View File

@@ -31928,6 +31928,9 @@ package android.os {
method public static android.os.VibrationEffect createWaveform(long[], int[], int);
method public int describeContents();
method @NonNull public static android.os.VibrationEffect.Composition startComposition();
method @NonNull public static android.os.VibrationEffect.WaveformBuilder startWaveform();
method @NonNull public static android.os.VibrationEffect.WaveformBuilder startWaveform(@NonNull android.os.VibrationEffect.VibrationParameter);
method @NonNull public static android.os.VibrationEffect.WaveformBuilder startWaveform(@NonNull android.os.VibrationEffect.VibrationParameter, @NonNull android.os.VibrationEffect.VibrationParameter);
field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
field public static final int EFFECT_CLICK = 0; // 0x0
@@ -31937,10 +31940,13 @@ package android.os {
}
public static final class VibrationEffect.Composition {
method @NonNull public android.os.VibrationEffect.Composition addEffect(@NonNull android.os.VibrationEffect);
method @NonNull public android.os.VibrationEffect.Composition addOffDuration(@NonNull java.time.Duration);
method @NonNull public android.os.VibrationEffect.Composition addPrimitive(int);
method @NonNull public android.os.VibrationEffect.Composition addPrimitive(int, @FloatRange(from=0.0f, to=1.0f) float);
method @NonNull public android.os.VibrationEffect.Composition addPrimitive(int, @FloatRange(from=0.0f, to=1.0f) float, @IntRange(from=0) int);
method @NonNull public android.os.VibrationEffect compose();
method @NonNull public android.os.VibrationEffect.Composition repeatEffectIndefinitely(@NonNull android.os.VibrationEffect);
field public static final int PRIMITIVE_CLICK = 1; // 0x1
field public static final int PRIMITIVE_LOW_TICK = 8; // 0x8
field public static final int PRIMITIVE_QUICK_FALL = 6; // 0x6
@@ -31951,6 +31957,21 @@ package android.os {
field public static final int PRIMITIVE_TICK = 7; // 0x7
}
public static final class VibrationEffect.Composition.UnreachableAfterRepeatingIndefinitelyException extends java.lang.IllegalStateException {
}
public static class VibrationEffect.VibrationParameter {
method @NonNull public static android.os.VibrationEffect.VibrationParameter targetAmplitude(@FloatRange(from=0, to=1) float);
method @NonNull public static android.os.VibrationEffect.VibrationParameter targetFrequency(@FloatRange(from=1) float);
}
public static final class VibrationEffect.WaveformBuilder {
method @NonNull public android.os.VibrationEffect.WaveformBuilder addSustain(@NonNull java.time.Duration);
method @NonNull public android.os.VibrationEffect.WaveformBuilder addTransition(@NonNull java.time.Duration, @NonNull android.os.VibrationEffect.VibrationParameter);
method @NonNull public android.os.VibrationEffect.WaveformBuilder addTransition(@NonNull java.time.Duration, @NonNull android.os.VibrationEffect.VibrationParameter, @NonNull android.os.VibrationEffect.VibrationParameter);
method @NonNull public android.os.VibrationEffect build();
}
public abstract class Vibrator {
method public final int areAllEffectsSupported(@NonNull int...);
method public final boolean areAllPrimitivesSupported(@NonNull int...);

View File

@@ -1793,7 +1793,6 @@ package android.os {
method public static android.os.VibrationEffect get(int, boolean);
method @Nullable public static android.os.VibrationEffect get(android.net.Uri, android.content.Context);
method public abstract long getDuration();
method @NonNull public static android.os.VibrationEffect.WaveformBuilder startWaveform();
field public static final int EFFECT_POP = 4; // 0x4
field public static final int EFFECT_STRENGTH_LIGHT = 0; // 0x0
field public static final int EFFECT_STRENGTH_MEDIUM = 1; // 0x1
@@ -1811,20 +1810,6 @@ package android.os {
field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.Composed> CREATOR;
}
public static final class VibrationEffect.Composition {
method @NonNull public android.os.VibrationEffect.Composition addEffect(@NonNull android.os.VibrationEffect);
method @NonNull public android.os.VibrationEffect.Composition addEffect(@NonNull android.os.VibrationEffect, @IntRange(from=0) int);
}
public static final class VibrationEffect.WaveformBuilder {
method @NonNull public android.os.VibrationEffect.WaveformBuilder addRamp(@FloatRange(from=0.0f, to=1.0f) float, @IntRange(from=0) int);
method @NonNull public android.os.VibrationEffect.WaveformBuilder addRamp(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=1.0f) float, @IntRange(from=0) int);
method @NonNull public android.os.VibrationEffect.WaveformBuilder addStep(@FloatRange(from=0.0f, to=1.0f) float, @IntRange(from=0) int);
method @NonNull public android.os.VibrationEffect.WaveformBuilder addStep(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=1.0f) float, @IntRange(from=0) int);
method @NonNull public android.os.VibrationEffect build();
method @NonNull public android.os.VibrationEffect build(int);
}
public abstract class Vibrator {
method public int getDefaultVibrationIntensity(int);
field public static final int VIBRATION_INTENSITY_HIGH = 3; // 0x3

View File

@@ -21,7 +21,6 @@ import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
@@ -40,6 +39,7 @@ import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -196,20 +196,20 @@ public abstract class VibrationEffect implements Parcelable {
/**
* Create a waveform vibration.
*
* Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
* <p>Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
* each pair, the value in the amplitude array determines the strength of the vibration and the
* value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
* vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
* <p>
* The amplitude array of the generated waveform will be the same size as the given
*
* <p>The amplitude array of the generated waveform will be the same size as the given
* timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE},
* starting with 0. Therefore the first timing value will be the period to wait before turning
* the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE}
* strength, etc.
* </p><p>
* To cause the pattern to repeat, pass the index into the timings array at which to start the
* repetition, or -1 to disable repeating.
* </p>
*
* <p>To cause the pattern to repeat, pass the index into the timings array at which to start
* the repetition, or -1 to disable repeating. Repeating effects will be played indefinitely
* and should be cancelled via {@link Vibrator#cancel()}.
*
* @param timings The pattern of alternating on-off timings, starting with off. Timing values
* of 0 will cause the timing / amplitude pair to be ignored.
@@ -229,15 +229,15 @@ public abstract class VibrationEffect implements Parcelable {
/**
* Create a waveform vibration.
*
* Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
* <p>Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
* each pair, the value in the amplitude array determines the strength of the vibration and the
* value in the timing array determines how long it vibrates for, in milliseconds. Amplitude
* values must be between 0 and 255, and an amplitude of 0 implies no vibration (i.e. off). Any
* pairs with a timing value of 0 will be ignored.
* </p><p>
* To cause the pattern to repeat, pass the index into the timings array at which to start the
* repetition, or -1 to disable repeating.
* </p>
*
* <p>To cause the pattern to repeat, pass the index into the timings array at which to start
* the repetition, or -1 to disable repeating. Repeating effects will be played indefinitely
* and should be cancelled via {@link Vibrator#cancel()}.
*
* @param timings The timing values, in milliseconds, of the timing / amplitude pairs. Timing
* values of 0 will cause the pair to be ignored.
@@ -407,20 +407,59 @@ public abstract class VibrationEffect implements Parcelable {
* Start building a waveform vibration.
*
* <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing
* control over vibration frequency and ramping up or down the vibration amplitude, frequency or
* both.
* control over vibration amplitude and frequency via smooth transitions between values.
*
* <p>For simpler waveform patterns see {@link #createWaveform} methods.
* <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)}.
*
* @hide
* @see VibrationEffect.WaveformBuilder
* @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters.
*/
@TestApi
@NonNull
public static WaveformBuilder startWaveform() {
return new WaveformBuilder();
}
/**
* Start building a waveform vibration with an initial state specified by a
* {@link 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.
* @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters.
*/
@NonNull
public static WaveformBuilder startWaveform(@NonNull VibrationParameter initialParameter) {
WaveformBuilder builder = startWaveform();
builder.addTransition(Duration.ZERO, initialParameter);
return builder;
}
/**
* Start building a waveform vibration with an initial state specified by two
* {@link 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.
* @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters.
*/
@NonNull
public static WaveformBuilder startWaveform(@NonNull VibrationParameter initialParameter1,
@NonNull VibrationParameter initialParameter2) {
WaveformBuilder builder = startWaveform();
builder.addTransition(Duration.ZERO, initialParameter1, initialParameter2);
return builder;
}
@Override
public int describeContents() {
return 0;
@@ -784,10 +823,23 @@ public abstract class VibrationEffect implements Parcelable {
PRIMITIVE_LOW_TICK,
})
@Retention(RetentionPolicy.SOURCE)
public @interface PrimitiveType {}
public @interface PrimitiveType {
}
/**
* Exception thrown when adding an element to a {@link Composition} that already ends in an
* indefinitely repeating effect.
*/
public static final class UnreachableAfterRepeatingIndefinitelyException
extends IllegalStateException {
UnreachableAfterRepeatingIndefinitelyException() {
super("Compositions ending in an indefinitely repeating effect can't be extended");
}
}
/**
* No haptic effect. Used to generate extended delays between primitives.
*
* @hide
*/
public static final int PRIMITIVE_NOOP = 0;
@@ -837,41 +889,76 @@ public abstract class VibrationEffect implements Parcelable {
Composition() {}
/**
* Add a haptic effect to the end of the current composition.
* Adds a time duration to the current composition, during which the vibrator will be
* turned off
*
* <p>Similar to {@link #addEffect(VibrationEffect, int)} , but with no delay applied.
* @param duration The length of time the vibrator should be off. Value must be non-negative
* and will be truncated to milliseconds.
* @return This {@link Composition} object to enable adding multiple elements in one chain.
*
* @param effect The effect to add to this composition as a primitive
* @return The {@link Composition} object to enable adding multiple primitives in one chain.
* @hide
* @throws UnreachableAfterRepeatingIndefinitelyException if the composition is currently
* ending with a repeating effect.
*/
@TestApi
@NonNull
public Composition addEffect(@NonNull VibrationEffect effect) {
return addEffect(effect, /* delay= */ 0);
public Composition addOffDuration(@NonNull Duration duration) {
int durationMs = (int) duration.toMillis();
Preconditions.checkArgumentNonnegative(durationMs, "Off period must be non-negative");
if (durationMs > 0) {
// Created a segment sustaining the zero amplitude to represent the delay.
addSegment(new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0,
(int) duration.toMillis()));
}
return this;
}
/**
* Add a haptic effect to the end of the current composition.
*
* @param effect The effect to add to this composition as a primitive
* @param delay The amount of time in milliseconds to wait before playing this primitive
* @return The {@link Composition} object to enable adding multiple primitives in one chain.
* @hide
* <p>If this effect is repeating (e.g. created by {@link VibrationEffect#createWaveform}
* with a non-negative repeat index, or created by another composition that has effects
* repeating indefinitely), then no more effects or primitives will be accepted by this
* composition after this method. Such effects should be cancelled via
* {@link Vibrator#cancel()}.
*
* @param effect The effect to add to the end of this composition.
* @return This {@link Composition} object to enable adding multiple elements in one chain.
*
* @throws UnreachableAfterRepeatingIndefinitelyException if the composition is currently
* ending with a repeating effect.
*/
@TestApi
@NonNull
public Composition addEffect(@NonNull VibrationEffect effect,
@IntRange(from = 0) int delay) {
Preconditions.checkArgumentNonnegative(delay);
if (delay > 0) {
// Created a segment sustaining the zero amplitude to represent the delay.
addSegment(new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0,
/* duration= */ delay));
}
public Composition addEffect(@NonNull VibrationEffect effect) {
return addSegments(effect);
}
/**
* Add a haptic effect to the end of the current composition and play it on repeat,
* indefinitely.
*
* <p>The entire effect will be played on repeat, indefinitely, after all other elements
* already added to this composition are played. No more effects or primitives will be
* accepted by this composition after this method. Such effects should be cancelled via
* {@link Vibrator#cancel()}.
*
* @param effect The effect to add to the end of this composition, must be finite.
* @return This {@link Composition} object to enable adding multiple elements in one chain,
* although only {@link #compose()} can follow this call.
*
* @throws IllegalArgumentException if the given effect is already repeating indefinitely.
* @throws UnreachableAfterRepeatingIndefinitelyException if the composition is currently
* ending with a repeating effect.
*/
@NonNull
public Composition repeatEffectIndefinitely(@NonNull VibrationEffect effect) {
Preconditions.checkArgument(effect.getDuration() < Long.MAX_VALUE,
"Can't repeat an indefinitely repeating effect. Consider addEffect instead.");
int previousSegmentCount = mSegments.size();
addSegments(effect);
// Set repeat after segments were added, since addSegments checks this index.
mRepeatIndex = previousSegmentCount;
return this;
}
/**
* Add a haptic primitive to the end of the current composition.
*
@@ -879,8 +966,10 @@ public abstract class VibrationEffect implements Parcelable {
* default scale applied.
*
* @param primitiveId The primitive to add
* @return This {@link Composition} object to enable adding multiple elements in one chain.
*
* @return The {@link Composition} object to enable adding multiple primitives in one chain.
* @throws UnreachableAfterRepeatingIndefinitelyException if the composition is currently
* ending with a repeating effect.
*/
@NonNull
public Composition addPrimitive(@PrimitiveType int primitiveId) {
@@ -894,8 +983,10 @@ public abstract class VibrationEffect implements Parcelable {
*
* @param primitiveId The primitive to add
* @param scale The scale to apply to the intensity of the primitive.
* @return This {@link Composition} object to enable adding multiple elements in one chain.
*
* @return The {@link Composition} object to enable adding multiple primitives in one chain.
* @throws UnreachableAfterRepeatingIndefinitelyException if the composition is currently
* ending with a repeating effect.
*/
@NonNull
public Composition addPrimitive(@PrimitiveType int primitiveId,
@@ -910,7 +1001,10 @@ public abstract class VibrationEffect implements Parcelable {
* @param scale The scale to apply to the intensity of the primitive.
* @param delay The amount of time in milliseconds to wait before playing this primitive,
* starting at the time the previous element in this composition is finished.
* @return The {@link Composition} object to enable adding multiple primitives in one chain.
* @return This {@link Composition} object to enable adding multiple elements in one chain.
*
* @throws UnreachableAfterRepeatingIndefinitelyException if the composition is currently
* ending with a repeating effect.
*/
@NonNull
public Composition addPrimitive(@PrimitiveType int primitiveId,
@@ -923,9 +1017,7 @@ public abstract class VibrationEffect implements Parcelable {
private Composition addSegment(VibrationEffectSegment segment) {
if (mRepeatIndex >= 0) {
throw new IllegalStateException(
"Composition already have a repeating effect so any new primitive would be"
+ " unreachable.");
throw new UnreachableAfterRepeatingIndefinitelyException();
}
mSegments.add(segment);
return this;
@@ -933,9 +1025,7 @@ public abstract class VibrationEffect implements Parcelable {
private Composition addSegments(VibrationEffect effect) {
if (mRepeatIndex >= 0) {
throw new IllegalStateException(
"Composition already have a repeating effect so any new primitive would be"
+ " unreachable.");
throw new UnreachableAfterRepeatingIndefinitelyException();
}
Composed composed = (Composed) effect;
if (composed.getRepeatIndex() >= 0) {
@@ -1001,162 +1091,251 @@ public abstract class VibrationEffect implements Parcelable {
/**
* A builder for waveform haptic effects.
*
* <p>Waveform vibrations constitute of one or more timed segments where the vibration
* amplitude, frequency or both can linearly ramp to new values.
* <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.
*
* <p>Waveform segments may have zero duration, which represent a jump to new vibration
* amplitude and/or frequency values.
* <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>Waveform segments may have the same start and end vibration amplitude and frequency,
* which represent a step where the amplitude and frequency are maintained for that duration.
* <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)}.
*
* @hide
* @see VibrationEffect#startWaveform()
* @see VibrationEffect#startWaveform
*/
@TestApi
public static final class WaveformBuilder {
// Epsilon used for float comparison of amplitude and frequency values on transitions.
private static final float EPSILON = 1e-5f;
private ArrayList<VibrationEffectSegment> mSegments = new ArrayList<>();
private float mLastAmplitude = 0f;
private float mLastFrequencyHz = 0f;
WaveformBuilder() {}
/**
* Vibrate with given amplitude for the given duration, in millis, keeping the previous
* frequency the same.
* Add a transition to new vibration parameter value to the end of this waveform.
*
* <p>If the duration is zero the vibrator will jump to new amplitude.
* <p>The duration represents how long the vibrator should take to smoothly transition to
* the new vibration parameter. If the duration is zero then the vibrator will jump to the
* new value as fast as possible.
*
* @param amplitude The amplitude for this step
* @param duration The duration of this step in milliseconds
* @return The {@link WaveformBuilder} object to enable adding multiple steps in chain.
* <p>Vibration parameter values will be truncated to conform to the device capabilities
* according to the {@link android.os.vibrator.VibratorFrequencyProfile}.
*
* @param duration The length of time this transition should take. Value must be
* non-negative and will be truncated to milliseconds.
* @param targetParameter The new target {@link VibrationParameter} value to be reached
* after the given duration.
* @return This {@link WaveformBuilder} object to enable adding multiple transitions in
* chain.
*/
@SuppressLint("MissingGetterMatchingBuilder")
@SuppressWarnings("MissingGetterMatchingBuilder") // No getters to segments once created.
@NonNull
public WaveformBuilder addStep(@FloatRange(from = 0f, to = 1f) float amplitude,
@IntRange(from = 0) int duration) {
mSegments.add(new StepSegment(amplitude, getPreviousFrequencyHz(), duration));
public WaveformBuilder addTransition(@NonNull Duration duration,
@NonNull VibrationParameter targetParameter) {
Preconditions.checkNotNull(duration, "Duration is null");
checkVibrationParameter(targetParameter, "targetParameter");
float amplitude = extractTargetAmplitude(targetParameter, /* target2= */ null);
float frequencyHz = extractTargetFrequency(targetParameter, /* target2= */ null);
addTransitionSegment(duration, amplitude, frequencyHz);
return this;
}
/**
* Vibrate with given amplitude and frequency for the given duration, in millis.
* Add a transition to new vibration parameters to the end of this waveform.
*
* <p>If the duration is zero the vibrator will jump to new amplitude.
* <p>The duration represents how long the vibrator should take to smoothly transition to
* the new vibration parameters. If the duration is zero then the vibrator will jump to the
* new values as fast as possible.
*
* @param amplitude The amplitude for this step
* @param frequencyHz The frequency for this step, in hertz
* @param duration The duration of this step in milliseconds
* @return The {@link WaveformBuilder} object to enable adding multiple steps in chain.
* <p>Vibration parameters values will be truncated to conform to the device capabilities
* according to the {@link android.os.vibrator.VibratorFrequencyProfile}.
*
* @param duration The length of time this transition should take. Value must be
* non-negative and will be truncated to milliseconds.
* @param targetParameter1 The first target {@link VibrationParameter} value to be reached
* after the given duration.
* @param targetParameter2 The second target {@link VibrationParameter} value to be reached
* after the given duration, must be a different type of parameter
* than the one specified by the first argument.
* @return This {@link WaveformBuilder} object to enable adding multiple transitions in
* chain.
*/
@SuppressLint("MissingGetterMatchingBuilder")
@SuppressWarnings("MissingGetterMatchingBuilder") // No getters to segments once created.
@NonNull
public WaveformBuilder addStep(@FloatRange(from = 0f, to = 1f) float amplitude,
@FloatRange(from = 1f) float frequencyHz,
@IntRange(from = 0) int duration) {
Preconditions.checkArgument(frequencyHz >= 1, "Frequency must be >= 1");
mSegments.add(new StepSegment(amplitude, frequencyHz, duration));
public WaveformBuilder addTransition(@NonNull Duration duration,
@NonNull VibrationParameter targetParameter1,
@NonNull VibrationParameter targetParameter2) {
Preconditions.checkNotNull(duration, "Duration is null");
checkVibrationParameter(targetParameter1, "targetParameter1");
checkVibrationParameter(targetParameter2, "targetParameter2");
Preconditions.checkArgument(
!Objects.equals(targetParameter1.getClass(), targetParameter2.getClass()),
"Parameter arguments must specify different parameter types");
float amplitude = extractTargetAmplitude(targetParameter1, targetParameter2);
float frequencyHz = extractTargetFrequency(targetParameter1, targetParameter2);
addTransitionSegment(duration, amplitude, frequencyHz);
return this;
}
/**
* Ramp vibration linearly for the given duration, in millis, from previous amplitude value
* to the given one, keeping previous frequency.
* Add a duration to sustain the last vibration parameters of this waveform.
*
* <p>If the duration is zero the vibrator will jump to new amplitude.
* <p>The duration represents how long the vibrator should sustain the last set of
* parameters provided to this builder.
*
* @param amplitude The final amplitude this ramp should reach
* @param duration The duration of this ramp in milliseconds
* @return The {@link WaveformBuilder} object to enable adding multiple steps in chain.
* @param duration The length of time the last values should be sustained by the vibrator.
* Value must be >= 1ms.
* @return This {@link WaveformBuilder} object to enable adding multiple transitions in
* chain.
*/
@SuppressLint("MissingGetterMatchingBuilder")
@SuppressWarnings("MissingGetterMatchingBuilder") // No getters to segments once created.
@NonNull
public WaveformBuilder addRamp(@FloatRange(from = 0f, to = 1f) float amplitude,
@IntRange(from = 0) int duration) {
mSegments.add(new RampSegment(getPreviousAmplitude(), amplitude,
getPreviousFrequencyHz(), getPreviousFrequencyHz(), duration));
public WaveformBuilder addSustain(@NonNull Duration duration) {
int durationMs = (int) duration.toMillis();
Preconditions.checkArgument(durationMs >= 1, "Sustain duration must be >= 1ms");
mSegments.add(new StepSegment(mLastAmplitude, mLastFrequencyHz, durationMs));
return this;
}
/**
* Ramp vibration linearly for the given duration, in millis, from previous amplitude and
* frequency values to the given ones.
*
* <p>If the duration is zero the vibrator will jump to new amplitude and frequency.
*
* @param amplitude The final amplitude this ramp should reach
* @param frequencyHz The final frequency this ramp should reach, in hertz
* @param duration The duration of this ramp in milliseconds
* @return The {@link WaveformBuilder} object to enable adding multiple steps in chain.
*/
@SuppressLint("MissingGetterMatchingBuilder")
@NonNull
public WaveformBuilder addRamp(@FloatRange(from = 0f, to = 1f) float amplitude,
@FloatRange(from = 1f) float frequencyHz,
@IntRange(from = 0) int duration) {
Preconditions.checkArgument(frequencyHz >= 1, "Frequency must be >= 1");
mSegments.add(new RampSegment(getPreviousAmplitude(), amplitude,
getPreviousFrequencyHz(), frequencyHz, duration));
return this;
}
/**
* Compose all the steps together into a single {@link VibrationEffect}.
* Build the waveform as a single {@link VibrationEffect}.
*
* The {@link WaveformBuilder} object is still valid after this call, so you can
* continue adding more primitives to it and generating more {@link VibrationEffect}s by
* calling this method again.
*
* @return The {@link VibrationEffect} resulting from the composition of the steps.
* @return The {@link VibrationEffect} resulting from the list of transitions.
*/
@NonNull
public VibrationEffect build() {
return build(/* repeat= */ -1);
}
/**
* Compose all the steps together into a single {@link VibrationEffect}.
*
* <p>To cause the pattern to repeat, pass the index at which to start the repetition
* (starting at 0), or -1 to disable repeating.
*
* <p>The {@link WaveformBuilder} object is still valid after this call, so you can
* continue adding more primitives to it and generating more {@link VibrationEffect}s by
* calling this method again.
*
* @return The {@link VibrationEffect} resulting from the composition of the steps.
*/
@NonNull
public VibrationEffect build(int repeat) {
if (mSegments.isEmpty()) {
throw new IllegalStateException(
"WaveformBuilder must have at least one element to build.");
"WaveformBuilder must have at least one transition to build.");
}
VibrationEffect effect = new Composed(mSegments, repeat);
VibrationEffect effect = new Composed(mSegments, /* repeatIndex= */ -1);
effect.validate();
return effect;
}
private float getPreviousFrequencyHz() {
if (!mSegments.isEmpty()) {
VibrationEffectSegment segment = mSegments.get(mSegments.size() - 1);
if (segment instanceof StepSegment) {
return ((StepSegment) segment).getFrequencyHz();
} else if (segment instanceof RampSegment) {
return ((RampSegment) segment).getEndFrequencyHz();
}
}
return 0;
private void checkVibrationParameter(@NonNull VibrationParameter vibrationParameter,
String paramName) {
Preconditions.checkNotNull(vibrationParameter, "%s is null", paramName);
Preconditions.checkArgument(
(vibrationParameter instanceof AmplitudeVibrationParameter)
|| (vibrationParameter instanceof FrequencyVibrationParameter),
"%s is a unknown parameter", paramName);
}
private float getPreviousAmplitude() {
if (!mSegments.isEmpty()) {
VibrationEffectSegment segment = mSegments.get(mSegments.size() - 1);
if (segment instanceof StepSegment) {
return ((StepSegment) segment).getAmplitude();
} else if (segment instanceof RampSegment) {
return ((RampSegment) segment).getEndAmplitude();
private float extractTargetAmplitude(@Nullable VibrationParameter target1,
@Nullable VibrationParameter target2) {
if (target2 instanceof AmplitudeVibrationParameter) {
return ((AmplitudeVibrationParameter) target2).amplitude;
}
if (target1 instanceof AmplitudeVibrationParameter) {
return ((AmplitudeVibrationParameter) target1).amplitude;
}
return mLastAmplitude;
}
private float extractTargetFrequency(@Nullable VibrationParameter target1,
@Nullable VibrationParameter target2) {
if (target2 instanceof FrequencyVibrationParameter) {
return ((FrequencyVibrationParameter) target2).frequencyHz;
}
if (target1 instanceof FrequencyVibrationParameter) {
return ((FrequencyVibrationParameter) target1).frequencyHz;
}
return mLastFrequencyHz;
}
private void addTransitionSegment(Duration duration, float targetAmplitude,
float targetFrequency) {
Preconditions.checkNotNull(duration, "Duration is null");
Preconditions.checkArgument(!duration.isNegative(),
"Transition duration must be non-negative");
int durationMs = (int) duration.toMillis();
// Ignore transitions with zero duration, but keep values for next additions.
if (durationMs > 0) {
if ((Math.abs(mLastAmplitude - targetAmplitude) < EPSILON)
&& (Math.abs(mLastFrequencyHz - targetFrequency) < EPSILON)) {
// No value is changing, this can be best represented by a step segment.
mSegments.add(new StepSegment(targetAmplitude, targetFrequency, durationMs));
} else {
mSegments.add(new RampSegment(mLastAmplitude, targetAmplitude,
mLastFrequencyHz, targetFrequency, durationMs));
}
}
return 0;
mLastAmplitude = targetAmplitude;
mLastFrequencyHz = targetFrequency;
}
}
/**
* A representation of a single vibration parameter.
*
* <p>This is to describe a waveform haptic effect, which consists of one or more timed
* transitions to a new set of {@link VibrationParameter}s.
*
* <p>Examples of concrete parameters are the vibration amplitude or frequency.
*
* @see VibrationEffect.WaveformBuilder
*/
@SuppressWarnings("UserHandleName") // This is not a regular set of parameters, no *Params.
public static class VibrationParameter {
VibrationParameter() {
}
/**
* The target vibration amplitude.
*
* @param amplitude The amplitude value, between 0 and 1, inclusive, where 0 represents the
* vibrator turned off and 1 represents the maximum amplitude the vibrator
* can reach across all supported frequencies.
* @return The {@link VibrationParameter} instance that represents given amplitude.
*/
@NonNull
public static VibrationParameter targetAmplitude(
@FloatRange(from = 0, to = 1) float amplitude) {
return new AmplitudeVibrationParameter(amplitude);
}
/**
* The target vibration frequency.
*
* @param frequencyHz The frequency value, in hertz.
* @return The {@link VibrationParameter} instance that represents given frequency.
*/
@NonNull
public static VibrationParameter targetFrequency(@FloatRange(from = 1) float frequencyHz) {
return new FrequencyVibrationParameter(frequencyHz);
}
}
/** The vibration amplitude, represented by a value in [0,1]. */
private static final class AmplitudeVibrationParameter extends VibrationParameter {
public final float amplitude;
AmplitudeVibrationParameter(float amplitude) {
Preconditions.checkArgument((amplitude >= 0) && (amplitude <= 1),
"Amplitude must be within [0,1]");
this.amplitude = amplitude;
}
}
/** The vibration frequency, in hertz, or zero to represent undefined frequency. */
private static final class FrequencyVibrationParameter extends VibrationParameter {
public final float frequencyHz;
FrequencyVibrationParameter(float frequencyHz) {
Preconditions.checkArgument(frequencyHz >= 1, "Frequency must be >= 1");
Preconditions.checkArgument(Float.isFinite(frequencyHz), "Frequency must be finite");
this.frequencyHz = frequencyHz;
}
}

View File

@@ -16,6 +16,9 @@
package android.os;
import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
@@ -32,6 +35,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.net.Uri;
import android.os.VibrationEffect.Composition.UnreachableAfterRepeatingIndefinitelyException;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.StepSegment;
@@ -43,6 +47,8 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import java.time.Duration;
@Presubmit
@RunWith(MockitoJUnitRunner.class)
public class VibrationEffectTest {
@@ -122,16 +128,7 @@ public class VibrationEffectTest {
VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1).validate();
VibrationEffect.createWaveform(new long[]{10, 10}, new int[] {0, 0}, -1).validate();
VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, 0).validate();
VibrationEffect.startWaveform()
.addStep(/* amplitude= */ 1, /* duration= */ 10)
.addRamp(/* amplitude= */ 0, /* duration= */ 20)
.addStep(/* amplitude= */ 1, /* frequencyHz= */ 1, /* duration= */ 100)
.addRamp(/* amplitude= */ 0.5f, /* frequencyHz= */ 100, /* duration= */ 50)
.build()
.validate();
assertThrows(IllegalStateException.class,
() -> VibrationEffect.startWaveform().build().validate());
assertThrows(IllegalArgumentException.class,
() -> VibrationEffect.createWaveform(new long[0], new int[0], -1).validate());
assertThrows(IllegalArgumentException.class,
@@ -145,27 +142,31 @@ public class VibrationEffectTest {
assertThrows(IllegalArgumentException.class,
() -> VibrationEffect.createWaveform(
TEST_TIMINGS, TEST_AMPLITUDES, TEST_TIMINGS.length).validate());
}
@Test
public void testValidateWaveformBuilder() {
VibrationEffect.startWaveform(targetAmplitude(1))
.addTransition(Duration.ofSeconds(1), targetAmplitude(0.5f), targetFrequency(100))
.addTransition(Duration.ZERO, targetAmplitude(0f), targetFrequency(200))
.addSustain(Duration.ofMinutes(2))
.addTransition(Duration.ofMillis(10), targetAmplitude(1f), targetFrequency(50))
.addSustain(Duration.ofMillis(1))
.addTransition(Duration.ZERO, targetFrequency(150))
.addSustain(Duration.ofMillis(2))
.addTransition(Duration.ofSeconds(15), targetAmplitude(1))
.build()
.validate();
assertThrows(IllegalStateException.class,
() -> VibrationEffect.startWaveform().build().validate());
assertThrows(IllegalArgumentException.class, () -> targetAmplitude(-2));
assertThrows(IllegalArgumentException.class, () -> targetFrequency(0));
assertThrows(IllegalArgumentException.class,
() -> VibrationEffect.startWaveform()
.addStep(/* amplitude= */ -2, 10).build().validate());
() -> VibrationEffect.startWaveform().addTransition(
Duration.ofMillis(-10), targetAmplitude(1)).build().validate());
assertThrows(IllegalArgumentException.class,
() -> VibrationEffect.startWaveform()
.addStep(1, /* frequencyHz= */ -1f, 10).build().validate());
assertThrows(IllegalArgumentException.class,
() -> VibrationEffect.startWaveform()
.addStep(1, /* duration= */ -1).build().validate());
assertThrows(IllegalArgumentException.class,
() -> VibrationEffect.startWaveform()
.addStep(1, 100f, /* duration= */ -1).build().validate());
assertThrows(IllegalArgumentException.class,
() -> VibrationEffect.startWaveform()
.addRamp(/* amplitude= */ -3, 10).build().validate());
assertThrows(IllegalArgumentException.class,
() -> VibrationEffect.startWaveform()
.addRamp(1, /* frequencyHz= */ 0, 10).build().validate());
assertThrows(IllegalArgumentException.class,
() -> VibrationEffect.startWaveform()
.addRamp(1, 10f, /* duration= */ -3).build().validate());
() -> VibrationEffect.startWaveform().addSustain(Duration.ZERO).build().validate());
}
@Test
@@ -174,14 +175,24 @@ public class VibrationEffectTest {
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
.addEffect(TEST_ONE_SHOT)
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
.addEffect(TEST_WAVEFORM, 100)
.addOffDuration(Duration.ofMillis(100))
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
.addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
.compose()
.validate();
VibrationEffect.startComposition()
.repeatEffectIndefinitely(TEST_ONE_SHOT)
.compose()
.validate();
assertThrows(IllegalStateException.class,
() -> VibrationEffect.startComposition().compose().validate());
assertThrows(IllegalStateException.class,
() -> VibrationEffect.startComposition()
.addOffDuration(Duration.ofSeconds(0))
.compose()
.validate());
assertThrows(IllegalArgumentException.class,
() -> VibrationEffect.startComposition().addPrimitive(-1).compose().validate());
assertThrows(IllegalArgumentException.class,
@@ -196,12 +207,27 @@ public class VibrationEffectTest {
.validate());
assertThrows(IllegalArgumentException.class,
() -> VibrationEffect.startComposition()
.addEffect(TEST_ONE_SHOT, /* delay= */ -10)
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, -1)
.compose()
.validate());
assertThrows(IllegalArgumentException.class,
() -> VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, -1)
.repeatEffectIndefinitely(
// Repeating waveform.
VibrationEffect.createWaveform(
new long[] { 10 }, new int[] { 100}, 0))
.compose()
.validate());
assertThrows(UnreachableAfterRepeatingIndefinitelyException.class,
() -> VibrationEffect.startComposition()
.repeatEffectIndefinitely(TEST_WAVEFORM)
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
.compose()
.validate());
assertThrows(UnreachableAfterRepeatingIndefinitelyException.class,
() -> VibrationEffect.startComposition()
.repeatEffectIndefinitely(TEST_WAVEFORM)
.addEffect(TEST_ONE_SHOT)
.compose()
.validate());
}
@@ -354,9 +380,9 @@ public class VibrationEffectTest {
assertFalse(VibrationEffect.createWaveform(
new long[]{200, 200, 700}, new int[]{1, 2, 3}, -1).isHapticFeedbackCandidate());
assertFalse(VibrationEffect.startWaveform()
.addRamp(1, 500)
.addStep(1, 200)
.addRamp(0, 500)
.addTransition(Duration.ofMillis(500), targetAmplitude(1))
.addTransition(Duration.ofMillis(200), targetAmplitude(0.5f))
.addTransition(Duration.ofMillis(500), targetAmplitude(0))
.build()
.isHapticFeedbackCandidate());
}
@@ -367,9 +393,9 @@ public class VibrationEffectTest {
assertTrue(VibrationEffect.createWaveform(
new long[]{100, 200, 300}, new int[]{1, 2, 3}, -1).isHapticFeedbackCandidate());
assertTrue(VibrationEffect.startWaveform()
.addRamp(1, 300)
.addStep(1, 200)
.addRamp(0, 300)
.addTransition(Duration.ofMillis(300), targetAmplitude(1))
.addTransition(Duration.ofMillis(200), targetAmplitude(0.5f))
.addTransition(Duration.ofMillis(300), targetAmplitude(0))
.build()
.isHapticFeedbackCandidate());
}

View File

@@ -16,6 +16,9 @@
package com.android.server.notification;
import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
@@ -30,6 +33,7 @@ import android.util.Slog;
import com.android.internal.R;
import com.android.server.pm.PackageManagerService;
import java.time.Duration;
import java.util.Arrays;
/**
@@ -89,8 +93,7 @@ public final class VibratorHelper {
* Safely create a {@link VibrationEffect} from given waveform description.
*
* <p>The waveform is described by a sequence of values for target amplitude, frequency and
* duration, that are forwarded to
* {@link VibrationEffect.WaveformBuilder#addRamp(float, float, int)}.
* duration, that are forwarded to {@link VibrationEffect.WaveformBuilder#addTransition}.
*
* <p>This method returns {@code null} if the pattern is also {@code null} or invalid.
*
@@ -114,16 +117,17 @@ public final class VibratorHelper {
VibrationEffect.WaveformBuilder waveformBuilder = VibrationEffect.startWaveform();
for (int i = 0; i < length; i += 3) {
waveformBuilder.addRamp(
/* amplitude= */ values[i],
/* frequencyHz= */ values[i + 1],
/* duration= */ (int) values[i + 2]);
waveformBuilder.addTransition(Duration.ofMillis((int) values[i + 2]),
targetAmplitude(values[i]), targetFrequency(values[i + 1]));
}
VibrationEffect effect = waveformBuilder.build();
if (insistent) {
return waveformBuilder.build(/* repeat= */ 0);
return VibrationEffect.startComposition()
.repeatEffectIndefinitely(effect)
.compose();
}
return waveformBuilder.build();
return effect;
} catch (IllegalArgumentException e) {
Slog.e(TAG, "Error creating vibration PWLE waveform with pattern: "
+ Arrays.toString(values));

View File

@@ -16,6 +16,9 @@
package com.android.server.vibrator;
import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -65,6 +68,7 @@ import libcore.util.NativeAllocationRegistry;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
@@ -1711,7 +1715,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
long duration = Long.parseLong(getNextArgRequired());
int amplitude = hasAmplitude ? Integer.parseInt(getNextArgRequired())
: VibrationEffect.DEFAULT_AMPLITUDE;
composition.addEffect(VibrationEffect.createOneShot(duration, amplitude), delay);
composition.addOffDuration(Duration.ofMillis(delay));
composition.addEffect(VibrationEffect.createOneShot(duration, amplitude));
}
private void addWaveformToComposition(VibrationEffect.Composition composition) {
@@ -1762,23 +1767,44 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
}
// Add delay before the waveform.
composition.addOffDuration(Duration.ofMillis(delay));
VibrationEffect.WaveformBuilder waveform = VibrationEffect.startWaveform();
for (int i = 0; i < durations.size(); i++) {
if (isContinuous) {
if (hasFrequencies) {
waveform.addRamp(amplitudes.get(i), frequencies.get(i), durations.get(i));
} else {
waveform.addRamp(amplitudes.get(i), durations.get(i));
}
Duration transitionDuration = isContinuous
? Duration.ofMillis(durations.get(i))
: Duration.ZERO;
if (hasFrequencies) {
waveform.addTransition(transitionDuration, targetAmplitude(amplitudes.get(i)),
targetFrequency(frequencies.get(i)));
} else {
waveform.addTransition(transitionDuration, targetAmplitude(amplitudes.get(i)));
}
if (!isContinuous) {
waveform.addSustain(Duration.ofMillis(durations.get(i)));
}
if ((i > 0) && (i == repeat)) {
// Add segment that is not repeated to the composition and reset builder.
composition.addEffect(waveform.build());
if (hasFrequencies) {
waveform.addStep(amplitudes.get(i), frequencies.get(i), durations.get(i));
waveform = VibrationEffect.startWaveform(targetAmplitude(amplitudes.get(i)),
targetFrequency(frequencies.get(i)));
} else {
waveform.addStep(amplitudes.get(i), durations.get(i));
waveform = VibrationEffect.startWaveform(
targetAmplitude(amplitudes.get(i)));
}
}
}
composition.addEffect(waveform.build(repeat), delay);
if (repeat < 0) {
composition.addEffect(waveform.build());
} else {
// The waveform was already split at the repeat index, just repeat what remains.
composition.repeatEffectIndefinitely(waveform.build());
}
}
private void addPrebakedToComposition(VibrationEffect.Composition composition) {
@@ -1796,7 +1822,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
int effectId = Integer.parseInt(getNextArgRequired());
composition.addEffect(VibrationEffect.get(effectId, shouldFallback), delay);
composition.addOffDuration(Duration.ofMillis(delay));
composition.addEffect(VibrationEffect.get(effectId, shouldFallback));
}
private void addPrimitivesToComposition(VibrationEffect.Composition composition) {

View File

@@ -16,6 +16,9 @@
package com.android.server.vibrator;
import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -69,6 +72,7 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.time.Duration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@@ -525,7 +529,8 @@ public class VibrationThreadTest {
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
.addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
.addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK), /* delay= */ 100)
.addOffDuration(Duration.ofMillis(100))
.addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
.compose();
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
waitForCompletion(thread);
@@ -558,11 +563,12 @@ public class VibrationThreadTest {
0.5f /* 100Hz*/, 1 /* 150Hz */, 0.6f /* 200Hz */);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.startWaveform()
.addStep(1, 10)
.addRamp(0, 20)
.addStep(0.8f, 100, 30)
.addRamp(0.6f, 200, 40)
VibrationEffect effect = VibrationEffect.startWaveform(targetAmplitude(1))
.addSustain(Duration.ofMillis(10))
.addTransition(Duration.ofMillis(20), targetAmplitude(0))
.addTransition(Duration.ZERO, targetAmplitude(0.8f), targetFrequency(100))
.addSustain(Duration.ofMillis(30))
.addTransition(Duration.ofMillis(40), targetAmplitude(0.6f), targetFrequency(200))
.build();
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
waitForCompletion(thread);
@@ -595,11 +601,12 @@ public class VibrationThreadTest {
fakeVibrator.setPwleSizeMax(2);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.startWaveform()
.addStep(1, 10)
.addRamp(0, 20)
.addStep(0.8f, 10, 30)
.addRamp(0.6f, 100, 40)
VibrationEffect effect = VibrationEffect.startWaveform(targetAmplitude(1))
.addSustain(Duration.ofMillis(10))
.addTransition(Duration.ofMillis(20), targetAmplitude(0))
.addTransition(Duration.ZERO, targetAmplitude(0.8f), targetFrequency(100))
.addSustain(Duration.ofMillis(30))
.addTransition(Duration.ofMillis(40), targetAmplitude(0.6f), targetFrequency(200))
.build();
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
waitForCompletion(thread);
@@ -1261,7 +1268,9 @@ public class VibrationThreadTest {
fakeVibrator.setPwleSizeMax(2);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.startWaveform().addRamp(1, 1).build();
VibrationEffect effect = VibrationEffect.startWaveform()
.addTransition(Duration.ofMillis(1), targetAmplitude(1))
.build();
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
waitForCompletion(thread);