Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9db9a56c authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Introduce WaveformBuilder to VibrationEffect" into sc-dev

parents 699d25c0 601a944e
Loading
Loading
Loading
Loading
+32 −0
Original line number Diff line number Diff line
@@ -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
@@ -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();
@@ -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);
+232 −3
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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();
@@ -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
@@ -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.
         *
@@ -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}.
         *
@@ -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>() {
+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];
                }
            };
}
+18 −7
Original line number Diff line number Diff line
@@ -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;
    }

@@ -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;
    }

@@ -61,6 +64,10 @@ public final class StepSegment extends VibrationEffectSegment {
        return mAmplitude;
    }

    public float getFrequency() {
        return mFrequency;
    }

    @Override
    public long getDuration() {
        return mDuration;
@@ -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
@@ -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
@@ -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
                + "}";
    }
@@ -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);
    }

+5 −1
Original line number Diff line number Diff line
@@ -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
@@ -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() {
@@ -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