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

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

Merge "Add haptic composition API."

parents 4e09642d 274640e7
Loading
Loading
Loading
Loading
+20 −0
Original line number Diff line number Diff line
@@ -36927,6 +36927,7 @@ package android.os {
    method public static android.os.VibrationEffect createWaveform(long[], int);
    method public static android.os.VibrationEffect createWaveform(long[], int[], int);
    method public int describeContents();
    method @NonNull public static android.os.VibrationEffect.Composition startComposition();
    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
@@ -36935,7 +36936,26 @@ package android.os {
    field public static final int EFFECT_TICK = 2; // 0x2
  }
  public static class VibrationEffect.Composition {
    ctor public VibrationEffect.Composition();
    method @Nullable public android.os.VibrationEffect.Composition addPrimitive(int);
    method @Nullable public android.os.VibrationEffect.Composition addPrimitive(int, @FloatRange(from=0.0f, to=1.0f) float);
    method @Nullable 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();
    field public static final int PRIMITIVE_CLICK = 1; // 0x1
    field public static final int PRIMITIVE_LIGHT_TICK = 7; // 0x7
    field public static final int PRIMITIVE_QUICK_FALL = 6; // 0x6
    field public static final int PRIMITIVE_QUICK_RISE = 4; // 0x4
    field public static final int PRIMITIVE_SLOW_RISE = 5; // 0x5
    field public static final int PRIMITIVE_SPIN = 3; // 0x3
    field public static final int PRIMITIVE_THUD = 2; // 0x2
  }
  public abstract class Vibrator {
    method @Nullable public Boolean areAllEffectsSupported(@NonNull int...);
    method public boolean areAllPrimitivesSupported(@NonNull int...);
    method @Nullable public boolean[] areEffectsSupported(@NonNull int...);
    method @NonNull public boolean[] arePrimitivesSupported(@NonNull int...);
    method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel();
    method public abstract boolean hasAmplitudeControl();
    method public abstract boolean hasVibrator();
+2 −0
Original line number Diff line number Diff line
@@ -24,6 +24,8 @@ interface IVibratorService
{
    boolean hasVibrator();
    boolean hasAmplitudeControl();
    boolean[] areEffectsSupported(in int[] effectIds);
    boolean[] arePrimitivesSupported(in int[] primitiveIds);
    boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, in VibrationEffect effect,
            in VibrationAttributes attributes);
    void vibrate(int uid, String opPkg, in VibrationEffect effect,
+23 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.os;

import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.media.AudioAttributes;
@@ -103,6 +104,28 @@ public class SystemVibrator extends Vibrator {
        }
    }

    @Override
    public boolean[] areEffectsSupported(@VibrationEffect.EffectType int... effectIds) {
        try {
            return mService.areEffectsSupported(effectIds);
        } catch (RemoteException e) {
            Log.w(TAG, "Failed to query effect support");
        }
        return new boolean[effectIds.length];
    }

    @Override
    public boolean[] arePrimitivesSupported(
            @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
        try {
            return mService.arePrimitivesSupported(primitiveIds);
        } catch (RemoteException e) {
            Log.w(TAG, "Failed to query effect support");
        }
        return new boolean[primitiveIds.length];
    }


    @Override
    public void cancel() {
        if (mService == null) {
+1 −3
Original line number Diff line number Diff line
@@ -17,6 +17,4 @@
package android.os;

parcelable VibrationEffect;
parcelable VibrationEffect.OneShotVibration;
parcelable VibrationEffect.WaveformVibration;
parcelable VibrationEffect.EffectVibration;
parcelable VibrationEffect.Composition.PrimitiveEffect;
 No newline at end of file
+346 −0
Original line number Diff line number Diff line
@@ -16,7 +16,9 @@

package android.os;

import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
@@ -28,9 +30,14 @@ import android.hardware.vibrator.V1_3.Effect;
import android.net.Uri;
import android.util.MathUtils;

import com.android.internal.util.Preconditions;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
@@ -41,6 +48,8 @@ public abstract class VibrationEffect implements Parcelable {
    private static final int PARCEL_TOKEN_ONE_SHOT = 1;
    private static final int PARCEL_TOKEN_WAVEFORM = 2;
    private static final int PARCEL_TOKEN_EFFECT = 3;
    private static final int PARCEL_TOKEN_COMPOSITION = 4;


    /**
     * The default vibration strength of the device.
@@ -359,6 +368,16 @@ public abstract class VibrationEffect implements Parcelable {
        return null;
    }

    /**
     * Start composing a haptic effect.
     *
     * @see VibrationEffect.Composition
     */
    @NonNull
    public static VibrationEffect.Composition startComposition() {
        return new VibrationEffect.Composition();
    }

    @Override
    public int describeContents() {
        return 0;
@@ -839,6 +858,331 @@ public abstract class VibrationEffect implements Parcelable {
            };
    }

    /** @hide */
    public static final class Composed extends VibrationEffect implements Parcelable {
        private final ArrayList<Composition.PrimitiveEffect> mPrimitiveEffects;

        /**
         * @hide
         */
        @SuppressWarnings("unchecked")
        public Composed(@NonNull Parcel in) {
            this(in.readArrayList(Composed.class.getClassLoader()));
        }

        /**
         * @hide
         */
        public Composed(List<Composition.PrimitiveEffect> effects) {
            mPrimitiveEffects = new ArrayList<>(Objects.requireNonNull(effects));
        }

        /**
         * @hide
         */
        @NonNull
        public List<Composition.PrimitiveEffect> getPrimitiveEffects() {
            return mPrimitiveEffects;
        }

        @Override
        public long getDuration() {
            return -1;
        }


        /**
         * @hide
         */
        @Override
        public void validate() {
            for (Composition.PrimitiveEffect effect : mPrimitiveEffects) {
                Composition.checkPrimitive(effect.id);
                Preconditions.checkArgumentInRange(
                        effect.scale, 0.0f, 1.0f, "scale");
            }
        }

        @Override
        public void writeToParcel(@NonNull Parcel out, int flags) {
            out.writeInt(PARCEL_TOKEN_COMPOSITION);
            out.writeList(mPrimitiveEffects);
        }

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Composed composed = (Composed) o;
            return mPrimitiveEffects.equals(composed.mPrimitiveEffects);
        }

        @Override
        public int hashCode() {
            return Objects.hash(mPrimitiveEffects);
        }

        @Override
        public String toString() {
            return "Composed{mPrimitiveEffects=" + mPrimitiveEffects + '}';
        }

        public static final @NonNull Parcelable.Creator<Composed> CREATOR =
                new Parcelable.Creator<Composed>() {
                    @Override
                    public Composed createFromParcel(@NonNull Parcel in) {
                        // Skip the type token
                        in.readInt();
                        return new Composed(in);
                    }

                    @Override
                    @NonNull
                    public Composed[] newArray(int size) {
                        return new Composed[size];
                    }
                };
    }

    /**
     * A composition of haptic primitives that, when combined, create a single haptic effect.
     *
     * @see VibrationEffect#startComposition()
     */
    public static class Composition {
        /** @hide */
        @IntDef(prefix = { "PRIMITIVE_" }, value = {
                PRIMITIVE_CLICK,
                PRIMITIVE_THUD,
                PRIMITIVE_SPIN,
                PRIMITIVE_QUICK_RISE,
                PRIMITIVE_SLOW_RISE,
                PRIMITIVE_QUICK_FALL,
                PRIMITIVE_LIGHT_TICK,
        })
        @Retention(RetentionPolicy.SOURCE)
        public @interface Primitive {}

        /**
         * No haptic effect. Used to generate extended delays between primitives.
         * @hide
         */
        public static final int PRIMITIVE_NOOP = 0;
        /**
         * This effect should produce a sharp, crisp click sensation.
         */
        public static final int PRIMITIVE_CLICK = 1;
        /**
         * A haptic effect that simulates downwards movement with gravity. Often
         * followed by extra energy of hitting and reverberation to augment
         * physicality.
         */
        public static final int PRIMITIVE_THUD = 2;
        /**
         * A haptic effect that simulates spinning momentum.
         */
        public static final int PRIMITIVE_SPIN = 3;
        /**
         * A haptic effect that simulates quick upward movement against gravity.
         */
        public static final int PRIMITIVE_QUICK_RISE = 4;
        /**
         * A haptic effect that simulates slow upward movement against gravity.
         */
        public static final int PRIMITIVE_SLOW_RISE = 5;
        /**
         * A haptic effect that simulates quick downwards movement with gravity.
         */
        public static final int PRIMITIVE_QUICK_FALL = 6;
        /**
         * This very short effect should produce a light crisp sensation intended
         * to be used repetitively for dynamic feedback.
         */
        public static final int PRIMITIVE_LIGHT_TICK = 7;


        private ArrayList<PrimitiveEffect> mEffects = new ArrayList<>();

        /**
         * Add a haptic primitive to the end of the current composition.
         *
         * Similar to {@link #addPrimitive(int, float, int)}, but with no delay and a
         * default scale applied.
         *
         * @param primitiveId The primitive to add
         *
         * @return The {@link Composition} object to enable adding multiple primitives in one chain.
         */
        @Nullable
        public Composition addPrimitive(@Primitive int primitiveId) {
            addPrimitive(primitiveId, /*scale*/ 1.0f, /*delay*/ 0);
            return this;
        }

        /**
         * Add a haptic primitive to the end of the current composition.
         *
         * Similar to {@link #addPrimitive(int, float, int)}, but with no delay.
         *
         * @param primitiveId The primitive to add
         * @param scale The scale to apply to the intensity of the primitive.
         *
         * @return The {@link Composition} object to enable adding multiple primitives in one chain.
         */
        @Nullable
        public Composition addPrimitive(@Primitive int primitiveId,
                @FloatRange(from = 0f, to = 1f) float scale) {
            addPrimitive(primitiveId, scale, /*delay*/ 0);
            return this;
        }

        /**
         * Add a haptic primitive to the end of the current composition.
         *
         * @param primitiveId The primitive to add
         * @param scale The scale to apply to the intensity of the primitive.
         * @param delay The amount of time, in milliseconds, to wait before playing the prior
         *              primitive and this one
         * @return The {@link Composition} object to enable adding multiple primitives in one chain.
         */
        @Nullable
        public Composition addPrimitive(@Primitive int primitiveId,
                @FloatRange(from = 0f, to = 1f) float scale, @IntRange(from = 0) int delay) {
            mEffects.add(new PrimitiveEffect(checkPrimitive(primitiveId), scale, delay));
            return this;
        }

        /**
         * Compose all of the added primitives together into a single {@link VibrationEffect}.
         *
         * The {@link Composition} 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 primitives.
         */
        @NonNull
        public VibrationEffect compose() {
            if (mEffects.isEmpty()) {
                throw new IllegalStateException(
                        "Composition must have at least one element to compose.");
            }
            return new VibrationEffect.Composed(mEffects);
        }

        /**
         * @throws IllegalArgumentException throws if the primitive ID is not within the valid range
         * @hide
         *
         */
        static int checkPrimitive(int primitiveId) {
            Preconditions.checkArgumentInRange(primitiveId, PRIMITIVE_NOOP, PRIMITIVE_LIGHT_TICK,
                    "primitiveId");
            return primitiveId;
        }

        /**
         * Convert the primitive ID to a human readable string for debugging
         * @param id The ID to convert
         * @return The ID in a human readable format.
         * @hide
         */
        public static String primitiveToString(@Primitive int id) {
            switch (id) {
                case PRIMITIVE_NOOP:
                    return "PRIMITIVE_NOOP";
                case PRIMITIVE_CLICK:
                    return "PRIMITIVE_CLICK";
                case PRIMITIVE_THUD:
                    return "PRIMITIVE_THUD";
                case PRIMITIVE_SPIN:
                    return "PRIMITIVE_SPIN";
                case PRIMITIVE_QUICK_RISE:
                    return "PRIMITIVE_QUICK_RISE";
                case PRIMITIVE_SLOW_RISE:
                    return "PRIMITIVE_SLOW_RISE";
                case PRIMITIVE_QUICK_FALL:
                    return "PRIMITIVE_QUICK_FALL";
                case PRIMITIVE_LIGHT_TICK:
                    return "PRIMITIVE_LIGHT_TICK";

                default:
                    return Integer.toString(id);

            }
        }


        /**
         * @hide
         */
        public static class PrimitiveEffect implements Parcelable {
            public int id;
            public float scale;
            public int delay;

            PrimitiveEffect(int id, float scale, int delay) {
                this.id = id;
                this.scale = scale;
                this.delay = delay;
            }

            @Override
            public void writeToParcel(Parcel dest, int flags) {
                dest.writeInt(id);
                dest.writeFloat(scale);
                dest.writeInt(delay);
            }

            @Override
            public int describeContents() {
                return 0;
            }

            @Override
            public String toString() {
                return "PrimitiveEffect{"
                        + "id=" + primitiveToString(id)
                        + ", scale=" + scale
                        + ", delay=" + delay
                        + '}';
            }

            @Override
            public boolean equals(Object o) {
                if (this == o) return true;
                if (o == null || getClass() != o.getClass()) return false;
                PrimitiveEffect that = (PrimitiveEffect) o;
                return id == that.id
                        && Float.compare(that.scale, scale) == 0
                        && delay == that.delay;
            }

            @Override
            public int hashCode() {
                return Objects.hash(id, scale, delay);
            }


            public static final @NonNull Parcelable.Creator<PrimitiveEffect> CREATOR =
                    new Parcelable.Creator<PrimitiveEffect>() {
                        @Override
                        public PrimitiveEffect createFromParcel(Parcel in) {
                            return new PrimitiveEffect(in.readInt(), in.readFloat(), in.readInt());
                        }
                        @Override
                        public PrimitiveEffect[] newArray(int size) {
                            return new PrimitiveEffect[size];
                        }
                    };
        }
    }

    public static final @NonNull Parcelable.Creator<VibrationEffect> CREATOR =
            new Parcelable.Creator<VibrationEffect>() {
                @Override
@@ -850,6 +1194,8 @@ public abstract class VibrationEffect implements Parcelable {
                        return new Waveform(in);
                    } else if (token == PARCEL_TOKEN_EFFECT) {
                        return new Prebaked(in);
                    } else if (token == PARCEL_TOKEN_COMPOSITION) {
                        return new Composed(in);
                    } else {
                        throw new IllegalStateException(
                                "Unexpected vibration event type token in parcel.");
Loading