Make VibrationEffect.WaveformBuilder public
Update WaveformBuilder to have a single method that adds transitions to new sets of vibration attributes for a fixed duration. The parameters are represented by a new type VibrationEffect.VibrationParameter, and have two concrete types now: amplitude and frequency. Update VibrationEffect.Composition API to add effects and repeating effects, removing the repeat index parameter from WaveformBuilder. Remove delay parameter from Composition in favor of a new method addOffPeriod to make it less ambiguous when the delay is played in the composition. Bug: 203785430 Test: VibrationEffectTest Change-Id: I40cbfaff2de3231378da7db6a2bc5c9252e6d8f8
This commit is contained in:
@@ -32926,6 +32926,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
|
||||
@@ -32935,10 +32938,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
|
||||
@@ -32949,6 +32955,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...);
|
||||
|
||||
@@ -1786,7 +1786,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
|
||||
@@ -1804,20 +1803,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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user