Loading core/api/test-current.txt +32 −0 Original line number Diff line number Diff line Loading @@ -1703,6 +1703,7 @@ 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 Loading @@ -1724,6 +1725,20 @@ 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, to=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, to=1.0f) float, @IntRange(from=0) int); method @NonNull public android.os.VibrationEffect build(); method @NonNull public android.os.VibrationEffect build(int); } public class VintfObject { method public static String[] getHalNamesAndVersions(); method public static String getSepolicyVersion(); Loading Loading @@ -1870,11 +1885,28 @@ package android.os.vibrator { field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.PrimitiveSegment> CREATOR; } public final class RampSegment extends android.os.vibrator.VibrationEffectSegment { method @NonNull public android.os.vibrator.RampSegment applyEffectStrength(int); method public int describeContents(); method public long getDuration(); method public float getEndAmplitude(); method public float getEndFrequency(); method public float getStartAmplitude(); method public float getStartFrequency(); method public boolean hasNonZeroAmplitude(); method @NonNull public android.os.vibrator.RampSegment resolve(int); method @NonNull public android.os.vibrator.RampSegment scale(float); method public void validate(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.RampSegment> CREATOR; } public final class StepSegment extends android.os.vibrator.VibrationEffectSegment { method @NonNull public android.os.vibrator.StepSegment applyEffectStrength(int); method public int describeContents(); method public float getAmplitude(); method public long getDuration(); method public float getFrequency(); method public boolean hasNonZeroAmplitude(); method @NonNull public android.os.vibrator.StepSegment resolve(int); method @NonNull public android.os.vibrator.StepSegment scale(float); Loading core/java/android/os/VibrationEffect.java +232 −3 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ 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; Loading @@ -30,6 +31,7 @@ import android.hardware.vibrator.V1_3.Effect; import android.net.Uri; import android.os.vibrator.PrebakedSegment; import android.os.vibrator.PrimitiveSegment; import android.os.vibrator.RampSegment; import android.os.vibrator.StepSegment; import android.os.vibrator.VibrationEffectSegment; import android.util.MathUtils; Loading Loading @@ -250,7 +252,7 @@ public abstract class VibrationEffect implements Parcelable { for (int i = 0; i < timings.length; i++) { float parsedAmplitude = amplitudes[i] == DEFAULT_AMPLITUDE ? DEFAULT_AMPLITUDE : (float) amplitudes[i] / MAX_AMPLITUDE; segments.add(new StepSegment(parsedAmplitude, (int) timings[i])); segments.add(new StepSegment(parsedAmplitude, /* frequency= */ 0, (int) timings[i])); } VibrationEffect effect = new Composed(segments, repeat); effect.validate(); Loading Loading @@ -389,8 +391,26 @@ public abstract class VibrationEffect implements Parcelable { * @see VibrationEffect.Composition */ @NonNull public static VibrationEffect.Composition startComposition() { return new VibrationEffect.Composition(); public static Composition startComposition() { return new Composition(); } /** * 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. * * <p>For simpler waveform patterns see {@link #createWaveform} methods. * * @hide * @see VibrationEffect.WaveformBuilder */ @TestApi @NonNull public static WaveformBuilder startWaveform() { return new WaveformBuilder(); } @Override Loading Loading @@ -770,6 +790,42 @@ public abstract class VibrationEffect implements Parcelable { Composition() {} /** * Add a haptic effect to the end of the current composition. * * <p>Similar to {@link #addEffect(VibrationEffect, int)} , but with no delay applied. * * @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 */ @TestApi @NonNull public Composition addEffect(@NonNull VibrationEffect effect) { return addEffect(effect, /* delay= */ 0); } /** * 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 */ @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, /* frequency= */ 0, /* duration= */ delay)); } return addSegments(effect); } /** * Add a haptic primitive to the end of the current composition. * Loading Loading @@ -829,6 +885,21 @@ public abstract class VibrationEffect implements Parcelable { return this; } private Composition addSegments(VibrationEffect effect) { if (mRepeatIndex >= 0) { throw new IllegalStateException( "Composition already have a repeating effect so any new primitive would be" + " unreachable."); } Composed composed = (Composed) effect; if (composed.getRepeatIndex() >= 0) { // Start repeating from the index relative to the composed waveform. mRepeatIndex = mSegments.size() + composed.getRepeatIndex(); } mSegments.addAll(composed.getSegments()); return this; } /** * Compose all of the added primitives together into a single {@link VibrationEffect}. * Loading Loading @@ -881,6 +952,164 @@ 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 segments may have zero duration, which represent a jump to new vibration * amplitude and/or frequency values. * * <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. * * @hide * @see VibrationEffect#startWaveform() */ @TestApi public static final class WaveformBuilder { private ArrayList<VibrationEffectSegment> mSegments = new ArrayList<>(); WaveformBuilder() {} /** * Vibrate with given amplitude for the given duration, in millis, keeping the previous * frequency the same. * * <p>If the duration is zero the vibrator will jump to new amplitude. * * @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. */ @SuppressLint("MissingGetterMatchingBuilder") @NonNull public WaveformBuilder addStep(@FloatRange(from = 0f, to = 1f) float amplitude, @IntRange(from = 0) int duration) { return addStep(amplitude, getPreviousFrequency(), duration); } /** * Vibrate with given amplitude for the given duration, in millis, keeping the previous * vibration frequency the same. * * <p>If the duration is zero the vibrator will jump to new amplitude. * * @param amplitude The amplitude for this step * @param frequency The frequency for this step * @param duration The duration of this step in milliseconds * @return The {@link WaveformBuilder} object to enable adding multiple steps in chain. */ @SuppressLint("MissingGetterMatchingBuilder") @NonNull public WaveformBuilder addStep(@FloatRange(from = 0f, to = 1f) float amplitude, @FloatRange(from = -1f, to = 1f) float frequency, @IntRange(from = 0) int duration) { mSegments.add(new StepSegment(amplitude, frequency, duration)); return this; } /** * Ramp vibration linearly for the given duration, in millis, from previous amplitude value * to the given one, keeping previous frequency. * * <p>If the duration is zero the vibrator will jump to new amplitude. * * @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. */ @SuppressLint("MissingGetterMatchingBuilder") @NonNull public WaveformBuilder addRamp(@FloatRange(from = 0f, to = 1f) float amplitude, @IntRange(from = 0) int duration) { return addRamp(amplitude, getPreviousFrequency(), duration); } /** * 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 frequency The final frequency 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. */ @SuppressLint("MissingGetterMatchingBuilder") @NonNull public WaveformBuilder addRamp(@FloatRange(from = 0f, to = 1f) float amplitude, @FloatRange(from = -1f, to = 1f) float frequency, @IntRange(from = 0) int duration) { mSegments.add(new RampSegment(getPreviousAmplitude(), amplitude, getPreviousFrequency(), frequency, duration)); return this; } /** * Compose all of the steps together into 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. */ @NonNull public VibrationEffect build() { return build(/* repeat= */ -1); } /** * Compose all of 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."); } VibrationEffect effect = new Composed(mSegments, repeat); effect.validate(); return effect; } private float getPreviousFrequency() { if (!mSegments.isEmpty()) { VibrationEffectSegment segment = mSegments.get(mSegments.size() - 1); if (segment instanceof StepSegment) { return ((StepSegment) segment).getFrequency(); } else if (segment instanceof RampSegment) { return ((RampSegment) segment).getEndFrequency(); } } return 0; } 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(); } } return 0; } } @NonNull public static final Parcelable.Creator<VibrationEffect> CREATOR = new Parcelable.Creator<VibrationEffect>() { Loading core/java/android/os/vibrator/RampSegment.java 0 → 100644 +176 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.os.vibrator; import android.annotation.NonNull; import android.annotation.TestApi; import android.os.Parcel; import android.os.VibrationEffect; import com.android.internal.util.Preconditions; import java.util.Objects; /** * Representation of {@link VibrationEffectSegment} that ramps vibration amplitude and/or frequency * for a specified duration. * * @hide */ @TestApi public final class RampSegment extends VibrationEffectSegment { private final float mStartAmplitude; private final float mStartFrequency; private final float mEndAmplitude; private final float mEndFrequency; private final int mDuration; RampSegment(@NonNull Parcel in) { this(in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat(), in.readInt()); } /** @hide */ public RampSegment(float startAmplitude, float endAmplitude, float startFrequency, float endFrequency, int duration) { mStartAmplitude = startAmplitude; mEndAmplitude = endAmplitude; mStartFrequency = startFrequency; mEndFrequency = endFrequency; mDuration = duration; } @Override public boolean equals(Object o) { if (!(o instanceof RampSegment)) { return false; } RampSegment other = (RampSegment) o; return Float.compare(mStartAmplitude, other.mStartAmplitude) == 0 && Float.compare(mEndAmplitude, other.mEndAmplitude) == 0 && Float.compare(mStartFrequency, other.mStartFrequency) == 0 && Float.compare(mEndFrequency, other.mEndFrequency) == 0 && mDuration == other.mDuration; } public float getStartAmplitude() { return mStartAmplitude; } public float getEndAmplitude() { return mEndAmplitude; } public float getStartFrequency() { return mStartFrequency; } public float getEndFrequency() { return mEndFrequency; } @Override public long getDuration() { return mDuration; } @Override public boolean hasNonZeroAmplitude() { return mStartAmplitude > 0 || mEndAmplitude > 0; } @Override public void validate() { Preconditions.checkArgumentNonnegative(mDuration, "Durations must all be >= 0, got " + mDuration); Preconditions.checkArgumentInRange(mStartAmplitude, 0f, 1f, "startAmplitude"); Preconditions.checkArgumentInRange(mEndAmplitude, 0f, 1f, "endAmplitude"); } @NonNull @Override public RampSegment resolve(int defaultAmplitude) { // Default amplitude is not supported for ramping. return this; } @NonNull @Override public RampSegment scale(float scaleFactor) { float newStartAmplitude = VibrationEffect.scale(mStartAmplitude, scaleFactor); float newEndAmplitude = VibrationEffect.scale(mEndAmplitude, scaleFactor); if (Float.compare(mStartAmplitude, newStartAmplitude) == 0 && Float.compare(mEndAmplitude, newEndAmplitude) == 0) { return this; } return new RampSegment(newStartAmplitude, newEndAmplitude, mStartFrequency, mEndFrequency, mDuration); } @NonNull @Override public RampSegment applyEffectStrength(int effectStrength) { return this; } @Override public int hashCode() { return Objects.hash(mStartAmplitude, mEndAmplitude, mStartFrequency, mEndFrequency, mDuration); } @Override public String toString() { return "Ramp{startAmplitude=" + mStartAmplitude + ", endAmplitude=" + mEndAmplitude + ", startFrequency=" + mStartFrequency + ", endFrequency=" + mEndFrequency + ", duration=" + mDuration + "}"; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(@NonNull Parcel out, int flags) { out.writeInt(PARCEL_TOKEN_RAMP); out.writeFloat(mStartAmplitude); out.writeFloat(mEndAmplitude); out.writeFloat(mStartFrequency); out.writeFloat(mEndFrequency); out.writeInt(mDuration); } @NonNull public static final Creator<RampSegment> CREATOR = new Creator<RampSegment>() { @Override public RampSegment createFromParcel(Parcel in) { // Skip the type token in.readInt(); return new RampSegment(in); } @Override public RampSegment[] newArray(int size) { return new RampSegment[size]; } }; } core/java/android/os/vibrator/StepSegment.java +18 −7 Original line number Diff line number Diff line Loading @@ -27,23 +27,25 @@ import com.android.internal.util.Preconditions; import java.util.Objects; /** * Representation of {@link VibrationEffectSegment} that holds a fixed vibration amplitude for a * specified duration. * Representation of {@link VibrationEffectSegment} that holds a fixed vibration amplitude and * frequency for a specified duration. * * @hide */ @TestApi public final class StepSegment extends VibrationEffectSegment { private final float mAmplitude; private final float mFrequency; private final int mDuration; StepSegment(@NonNull Parcel in) { this(in.readFloat(), in.readInt()); this(in.readFloat(), in.readFloat(), in.readInt()); } /** @hide */ public StepSegment(float amplitude, int duration) { public StepSegment(float amplitude, float frequency, int duration) { mAmplitude = amplitude; mFrequency = frequency; mDuration = duration; } Loading @@ -54,6 +56,7 @@ public final class StepSegment extends VibrationEffectSegment { } StepSegment other = (StepSegment) o; return Float.compare(mAmplitude, other.mAmplitude) == 0 && Float.compare(mFrequency, other.mFrequency) == 0 && mDuration == other.mDuration; } Loading @@ -61,6 +64,10 @@ public final class StepSegment extends VibrationEffectSegment { return mAmplitude; } public float getFrequency() { return mFrequency; } @Override public long getDuration() { return mDuration; Loading Loading @@ -92,7 +99,8 @@ public final class StepSegment extends VibrationEffectSegment { if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) != 0) { return this; } return new StepSegment((float) defaultAmplitude / VibrationEffect.MAX_AMPLITUDE, mDuration); return new StepSegment((float) defaultAmplitude / VibrationEffect.MAX_AMPLITUDE, mFrequency, mDuration); } @NonNull Loading @@ -101,7 +109,8 @@ public final class StepSegment extends VibrationEffectSegment { if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) == 0) { return this; } return new StepSegment(VibrationEffect.scale(mAmplitude, scaleFactor), mDuration); return new StepSegment(VibrationEffect.scale(mAmplitude, scaleFactor), mFrequency, mDuration); } @NonNull Loading @@ -112,12 +121,13 @@ public final class StepSegment extends VibrationEffectSegment { @Override public int hashCode() { return Objects.hash(mAmplitude, mDuration); return Objects.hash(mAmplitude, mFrequency, mDuration); } @Override public String toString() { return "Step{amplitude=" + mAmplitude + ", frequency=" + mFrequency + ", duration=" + mDuration + "}"; } Loading @@ -131,6 +141,7 @@ public final class StepSegment extends VibrationEffectSegment { public void writeToParcel(@NonNull Parcel out, int flags) { out.writeInt(PARCEL_TOKEN_STEP); out.writeFloat(mAmplitude); out.writeFloat(mFrequency); out.writeInt(mDuration); } Loading core/java/android/os/vibrator/VibrationEffectSegment.java +5 −1 Original line number Diff line number Diff line Loading @@ -31,7 +31,8 @@ import android.os.VibrationEffect; * <ol> * <li>A predefined vibration effect; * <li>A composable effect primitive; * <li>Fixed amplitude value to be held for a specified duration; * <li>Fixed amplitude and frequency values to be held for a specified duration; * <li>Pairs of amplitude and frequency values to be ramped to for a specified duration; * </ol> * * @hide Loading @@ -42,6 +43,7 @@ public abstract class VibrationEffectSegment implements Parcelable { static final int PARCEL_TOKEN_PREBAKED = 1; static final int PARCEL_TOKEN_PRIMITIVE = 2; static final int PARCEL_TOKEN_STEP = 3; static final int PARCEL_TOKEN_RAMP = 4; /** Prevent subclassing from outside of this package */ VibrationEffectSegment() { Loading Loading @@ -96,6 +98,8 @@ public abstract class VibrationEffectSegment implements Parcelable { switch (in.readInt()) { case PARCEL_TOKEN_STEP: return new StepSegment(in); case PARCEL_TOKEN_RAMP: return new RampSegment(in); case PARCEL_TOKEN_PREBAKED: return new PrebakedSegment(in); case PARCEL_TOKEN_PRIMITIVE: Loading Loading
core/api/test-current.txt +32 −0 Original line number Diff line number Diff line Loading @@ -1703,6 +1703,7 @@ 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 Loading @@ -1724,6 +1725,20 @@ 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, to=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, to=1.0f) float, @IntRange(from=0) int); method @NonNull public android.os.VibrationEffect build(); method @NonNull public android.os.VibrationEffect build(int); } public class VintfObject { method public static String[] getHalNamesAndVersions(); method public static String getSepolicyVersion(); Loading Loading @@ -1870,11 +1885,28 @@ package android.os.vibrator { field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.PrimitiveSegment> CREATOR; } public final class RampSegment extends android.os.vibrator.VibrationEffectSegment { method @NonNull public android.os.vibrator.RampSegment applyEffectStrength(int); method public int describeContents(); method public long getDuration(); method public float getEndAmplitude(); method public float getEndFrequency(); method public float getStartAmplitude(); method public float getStartFrequency(); method public boolean hasNonZeroAmplitude(); method @NonNull public android.os.vibrator.RampSegment resolve(int); method @NonNull public android.os.vibrator.RampSegment scale(float); method public void validate(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.RampSegment> CREATOR; } public final class StepSegment extends android.os.vibrator.VibrationEffectSegment { method @NonNull public android.os.vibrator.StepSegment applyEffectStrength(int); method public int describeContents(); method public float getAmplitude(); method public long getDuration(); method public float getFrequency(); method public boolean hasNonZeroAmplitude(); method @NonNull public android.os.vibrator.StepSegment resolve(int); method @NonNull public android.os.vibrator.StepSegment scale(float); Loading
core/java/android/os/VibrationEffect.java +232 −3 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ 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; Loading @@ -30,6 +31,7 @@ import android.hardware.vibrator.V1_3.Effect; import android.net.Uri; import android.os.vibrator.PrebakedSegment; import android.os.vibrator.PrimitiveSegment; import android.os.vibrator.RampSegment; import android.os.vibrator.StepSegment; import android.os.vibrator.VibrationEffectSegment; import android.util.MathUtils; Loading Loading @@ -250,7 +252,7 @@ public abstract class VibrationEffect implements Parcelable { for (int i = 0; i < timings.length; i++) { float parsedAmplitude = amplitudes[i] == DEFAULT_AMPLITUDE ? DEFAULT_AMPLITUDE : (float) amplitudes[i] / MAX_AMPLITUDE; segments.add(new StepSegment(parsedAmplitude, (int) timings[i])); segments.add(new StepSegment(parsedAmplitude, /* frequency= */ 0, (int) timings[i])); } VibrationEffect effect = new Composed(segments, repeat); effect.validate(); Loading Loading @@ -389,8 +391,26 @@ public abstract class VibrationEffect implements Parcelable { * @see VibrationEffect.Composition */ @NonNull public static VibrationEffect.Composition startComposition() { return new VibrationEffect.Composition(); public static Composition startComposition() { return new Composition(); } /** * 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. * * <p>For simpler waveform patterns see {@link #createWaveform} methods. * * @hide * @see VibrationEffect.WaveformBuilder */ @TestApi @NonNull public static WaveformBuilder startWaveform() { return new WaveformBuilder(); } @Override Loading Loading @@ -770,6 +790,42 @@ public abstract class VibrationEffect implements Parcelable { Composition() {} /** * Add a haptic effect to the end of the current composition. * * <p>Similar to {@link #addEffect(VibrationEffect, int)} , but with no delay applied. * * @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 */ @TestApi @NonNull public Composition addEffect(@NonNull VibrationEffect effect) { return addEffect(effect, /* delay= */ 0); } /** * 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 */ @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, /* frequency= */ 0, /* duration= */ delay)); } return addSegments(effect); } /** * Add a haptic primitive to the end of the current composition. * Loading Loading @@ -829,6 +885,21 @@ public abstract class VibrationEffect implements Parcelable { return this; } private Composition addSegments(VibrationEffect effect) { if (mRepeatIndex >= 0) { throw new IllegalStateException( "Composition already have a repeating effect so any new primitive would be" + " unreachable."); } Composed composed = (Composed) effect; if (composed.getRepeatIndex() >= 0) { // Start repeating from the index relative to the composed waveform. mRepeatIndex = mSegments.size() + composed.getRepeatIndex(); } mSegments.addAll(composed.getSegments()); return this; } /** * Compose all of the added primitives together into a single {@link VibrationEffect}. * Loading Loading @@ -881,6 +952,164 @@ 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 segments may have zero duration, which represent a jump to new vibration * amplitude and/or frequency values. * * <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. * * @hide * @see VibrationEffect#startWaveform() */ @TestApi public static final class WaveformBuilder { private ArrayList<VibrationEffectSegment> mSegments = new ArrayList<>(); WaveformBuilder() {} /** * Vibrate with given amplitude for the given duration, in millis, keeping the previous * frequency the same. * * <p>If the duration is zero the vibrator will jump to new amplitude. * * @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. */ @SuppressLint("MissingGetterMatchingBuilder") @NonNull public WaveformBuilder addStep(@FloatRange(from = 0f, to = 1f) float amplitude, @IntRange(from = 0) int duration) { return addStep(amplitude, getPreviousFrequency(), duration); } /** * Vibrate with given amplitude for the given duration, in millis, keeping the previous * vibration frequency the same. * * <p>If the duration is zero the vibrator will jump to new amplitude. * * @param amplitude The amplitude for this step * @param frequency The frequency for this step * @param duration The duration of this step in milliseconds * @return The {@link WaveformBuilder} object to enable adding multiple steps in chain. */ @SuppressLint("MissingGetterMatchingBuilder") @NonNull public WaveformBuilder addStep(@FloatRange(from = 0f, to = 1f) float amplitude, @FloatRange(from = -1f, to = 1f) float frequency, @IntRange(from = 0) int duration) { mSegments.add(new StepSegment(amplitude, frequency, duration)); return this; } /** * Ramp vibration linearly for the given duration, in millis, from previous amplitude value * to the given one, keeping previous frequency. * * <p>If the duration is zero the vibrator will jump to new amplitude. * * @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. */ @SuppressLint("MissingGetterMatchingBuilder") @NonNull public WaveformBuilder addRamp(@FloatRange(from = 0f, to = 1f) float amplitude, @IntRange(from = 0) int duration) { return addRamp(amplitude, getPreviousFrequency(), duration); } /** * 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 frequency The final frequency 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. */ @SuppressLint("MissingGetterMatchingBuilder") @NonNull public WaveformBuilder addRamp(@FloatRange(from = 0f, to = 1f) float amplitude, @FloatRange(from = -1f, to = 1f) float frequency, @IntRange(from = 0) int duration) { mSegments.add(new RampSegment(getPreviousAmplitude(), amplitude, getPreviousFrequency(), frequency, duration)); return this; } /** * Compose all of the steps together into 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. */ @NonNull public VibrationEffect build() { return build(/* repeat= */ -1); } /** * Compose all of 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."); } VibrationEffect effect = new Composed(mSegments, repeat); effect.validate(); return effect; } private float getPreviousFrequency() { if (!mSegments.isEmpty()) { VibrationEffectSegment segment = mSegments.get(mSegments.size() - 1); if (segment instanceof StepSegment) { return ((StepSegment) segment).getFrequency(); } else if (segment instanceof RampSegment) { return ((RampSegment) segment).getEndFrequency(); } } return 0; } 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(); } } return 0; } } @NonNull public static final Parcelable.Creator<VibrationEffect> CREATOR = new Parcelable.Creator<VibrationEffect>() { Loading
core/java/android/os/vibrator/RampSegment.java 0 → 100644 +176 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.os.vibrator; import android.annotation.NonNull; import android.annotation.TestApi; import android.os.Parcel; import android.os.VibrationEffect; import com.android.internal.util.Preconditions; import java.util.Objects; /** * Representation of {@link VibrationEffectSegment} that ramps vibration amplitude and/or frequency * for a specified duration. * * @hide */ @TestApi public final class RampSegment extends VibrationEffectSegment { private final float mStartAmplitude; private final float mStartFrequency; private final float mEndAmplitude; private final float mEndFrequency; private final int mDuration; RampSegment(@NonNull Parcel in) { this(in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat(), in.readInt()); } /** @hide */ public RampSegment(float startAmplitude, float endAmplitude, float startFrequency, float endFrequency, int duration) { mStartAmplitude = startAmplitude; mEndAmplitude = endAmplitude; mStartFrequency = startFrequency; mEndFrequency = endFrequency; mDuration = duration; } @Override public boolean equals(Object o) { if (!(o instanceof RampSegment)) { return false; } RampSegment other = (RampSegment) o; return Float.compare(mStartAmplitude, other.mStartAmplitude) == 0 && Float.compare(mEndAmplitude, other.mEndAmplitude) == 0 && Float.compare(mStartFrequency, other.mStartFrequency) == 0 && Float.compare(mEndFrequency, other.mEndFrequency) == 0 && mDuration == other.mDuration; } public float getStartAmplitude() { return mStartAmplitude; } public float getEndAmplitude() { return mEndAmplitude; } public float getStartFrequency() { return mStartFrequency; } public float getEndFrequency() { return mEndFrequency; } @Override public long getDuration() { return mDuration; } @Override public boolean hasNonZeroAmplitude() { return mStartAmplitude > 0 || mEndAmplitude > 0; } @Override public void validate() { Preconditions.checkArgumentNonnegative(mDuration, "Durations must all be >= 0, got " + mDuration); Preconditions.checkArgumentInRange(mStartAmplitude, 0f, 1f, "startAmplitude"); Preconditions.checkArgumentInRange(mEndAmplitude, 0f, 1f, "endAmplitude"); } @NonNull @Override public RampSegment resolve(int defaultAmplitude) { // Default amplitude is not supported for ramping. return this; } @NonNull @Override public RampSegment scale(float scaleFactor) { float newStartAmplitude = VibrationEffect.scale(mStartAmplitude, scaleFactor); float newEndAmplitude = VibrationEffect.scale(mEndAmplitude, scaleFactor); if (Float.compare(mStartAmplitude, newStartAmplitude) == 0 && Float.compare(mEndAmplitude, newEndAmplitude) == 0) { return this; } return new RampSegment(newStartAmplitude, newEndAmplitude, mStartFrequency, mEndFrequency, mDuration); } @NonNull @Override public RampSegment applyEffectStrength(int effectStrength) { return this; } @Override public int hashCode() { return Objects.hash(mStartAmplitude, mEndAmplitude, mStartFrequency, mEndFrequency, mDuration); } @Override public String toString() { return "Ramp{startAmplitude=" + mStartAmplitude + ", endAmplitude=" + mEndAmplitude + ", startFrequency=" + mStartFrequency + ", endFrequency=" + mEndFrequency + ", duration=" + mDuration + "}"; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(@NonNull Parcel out, int flags) { out.writeInt(PARCEL_TOKEN_RAMP); out.writeFloat(mStartAmplitude); out.writeFloat(mEndAmplitude); out.writeFloat(mStartFrequency); out.writeFloat(mEndFrequency); out.writeInt(mDuration); } @NonNull public static final Creator<RampSegment> CREATOR = new Creator<RampSegment>() { @Override public RampSegment createFromParcel(Parcel in) { // Skip the type token in.readInt(); return new RampSegment(in); } @Override public RampSegment[] newArray(int size) { return new RampSegment[size]; } }; }
core/java/android/os/vibrator/StepSegment.java +18 −7 Original line number Diff line number Diff line Loading @@ -27,23 +27,25 @@ import com.android.internal.util.Preconditions; import java.util.Objects; /** * Representation of {@link VibrationEffectSegment} that holds a fixed vibration amplitude for a * specified duration. * Representation of {@link VibrationEffectSegment} that holds a fixed vibration amplitude and * frequency for a specified duration. * * @hide */ @TestApi public final class StepSegment extends VibrationEffectSegment { private final float mAmplitude; private final float mFrequency; private final int mDuration; StepSegment(@NonNull Parcel in) { this(in.readFloat(), in.readInt()); this(in.readFloat(), in.readFloat(), in.readInt()); } /** @hide */ public StepSegment(float amplitude, int duration) { public StepSegment(float amplitude, float frequency, int duration) { mAmplitude = amplitude; mFrequency = frequency; mDuration = duration; } Loading @@ -54,6 +56,7 @@ public final class StepSegment extends VibrationEffectSegment { } StepSegment other = (StepSegment) o; return Float.compare(mAmplitude, other.mAmplitude) == 0 && Float.compare(mFrequency, other.mFrequency) == 0 && mDuration == other.mDuration; } Loading @@ -61,6 +64,10 @@ public final class StepSegment extends VibrationEffectSegment { return mAmplitude; } public float getFrequency() { return mFrequency; } @Override public long getDuration() { return mDuration; Loading Loading @@ -92,7 +99,8 @@ public final class StepSegment extends VibrationEffectSegment { if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) != 0) { return this; } return new StepSegment((float) defaultAmplitude / VibrationEffect.MAX_AMPLITUDE, mDuration); return new StepSegment((float) defaultAmplitude / VibrationEffect.MAX_AMPLITUDE, mFrequency, mDuration); } @NonNull Loading @@ -101,7 +109,8 @@ public final class StepSegment extends VibrationEffectSegment { if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) == 0) { return this; } return new StepSegment(VibrationEffect.scale(mAmplitude, scaleFactor), mDuration); return new StepSegment(VibrationEffect.scale(mAmplitude, scaleFactor), mFrequency, mDuration); } @NonNull Loading @@ -112,12 +121,13 @@ public final class StepSegment extends VibrationEffectSegment { @Override public int hashCode() { return Objects.hash(mAmplitude, mDuration); return Objects.hash(mAmplitude, mFrequency, mDuration); } @Override public String toString() { return "Step{amplitude=" + mAmplitude + ", frequency=" + mFrequency + ", duration=" + mDuration + "}"; } Loading @@ -131,6 +141,7 @@ public final class StepSegment extends VibrationEffectSegment { public void writeToParcel(@NonNull Parcel out, int flags) { out.writeInt(PARCEL_TOKEN_STEP); out.writeFloat(mAmplitude); out.writeFloat(mFrequency); out.writeInt(mDuration); } Loading
core/java/android/os/vibrator/VibrationEffectSegment.java +5 −1 Original line number Diff line number Diff line Loading @@ -31,7 +31,8 @@ import android.os.VibrationEffect; * <ol> * <li>A predefined vibration effect; * <li>A composable effect primitive; * <li>Fixed amplitude value to be held for a specified duration; * <li>Fixed amplitude and frequency values to be held for a specified duration; * <li>Pairs of amplitude and frequency values to be ramped to for a specified duration; * </ol> * * @hide Loading @@ -42,6 +43,7 @@ public abstract class VibrationEffectSegment implements Parcelable { static final int PARCEL_TOKEN_PREBAKED = 1; static final int PARCEL_TOKEN_PRIMITIVE = 2; static final int PARCEL_TOKEN_STEP = 3; static final int PARCEL_TOKEN_RAMP = 4; /** Prevent subclassing from outside of this package */ VibrationEffectSegment() { Loading Loading @@ -96,6 +98,8 @@ public abstract class VibrationEffectSegment implements Parcelable { switch (in.readInt()) { case PARCEL_TOKEN_STEP: return new StepSegment(in); case PARCEL_TOKEN_RAMP: return new RampSegment(in); case PARCEL_TOKEN_PREBAKED: return new PrebakedSegment(in); case PARCEL_TOKEN_PRIMITIVE: Loading