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

Commit ececfffd authored by Ahmad Khalil's avatar Ahmad Khalil
Browse files

Add ability to loop vibration effects

Introducing new APIs `createRepeatingEffect` to allow the creation of looping vibrations, by either having a preamble with a repeating effect or just the repeating effect.

Bug: 347037126
Flag: android.os.vibrator.normalized_pwle_effects
Test: atest FrameworksVibratorCoreTests
Change-Id: I4c7689a8a5e820eabf637dc92c249bc1342c493f
parent c19a2266
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -34373,6 +34373,8 @@ package android.os {
  public abstract class VibrationEffect implements android.os.Parcelable {
    method public static android.os.VibrationEffect createOneShot(long, int);
    method @NonNull public static android.os.VibrationEffect createPredefined(int);
    method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @NonNull public static android.os.VibrationEffect createRepeatingEffect(@NonNull android.os.VibrationEffect);
    method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @NonNull public static android.os.VibrationEffect createRepeatingEffect(@NonNull android.os.VibrationEffect, @NonNull android.os.VibrationEffect);
    method public static android.os.VibrationEffect createWaveform(long[], int);
    method public static android.os.VibrationEffect createWaveform(long[], int[], int);
    method public int describeContents();
+43 −0
Original line number Diff line number Diff line
@@ -1403,6 +1403,49 @@ public abstract class VibrationEffect implements Parcelable {
                };
    }

    /**
     * Creates a new {@link VibrationEffect} that repeats the given effect indefinitely.
     *
     * <p>The input vibration must not be a repeating vibration. If it is, an
     * {@link IllegalArgumentException} will be thrown.
     *
     * @param effect The {@link VibrationEffect} that will be repeated.
     * @return A {@link VibrationEffect} that repeats the effect indefinitely.
     * @throws IllegalArgumentException if the effect is already a repeating vibration.
     */
    @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
    @NonNull
    public static VibrationEffect createRepeatingEffect(@NonNull VibrationEffect effect) {
        return VibrationEffect.startComposition()
                .repeatEffectIndefinitely(effect)
                .compose();
    }

    /**
     * Creates a new {@link VibrationEffect} by merging the preamble and repeating vibration effect.
     *
     * <p>Neither input vibration may already be repeating. An {@link IllegalArgumentException} will
     * be thrown if either input vibration is set to repeat indefinitely.
     *
     * @param preamble        The starting vibration effect, which must be finite.
     * @param repeatingEffect The vibration effect to be repeated indefinitely after the preamble.
     * @return A {@link VibrationEffect} that plays the preamble once followed by the
     * `repeatingEffect` indefinitely.
     * @throws IllegalArgumentException if either preamble or repeatingEffect is already a repeating
     *                                  vibration.
     */
    @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
    @NonNull
    public static VibrationEffect createRepeatingEffect(@NonNull VibrationEffect preamble,
            @NonNull VibrationEffect repeatingEffect) {
        Preconditions.checkArgument(preamble.getDuration() < Long.MAX_VALUE,
                "Can't repeat an indefinitely repeating effect.");
        return VibrationEffect.startComposition()
                .addEffect(preamble)
                .repeatEffectIndefinitely(repeatingEffect)
                .compose();
    }

    /**
     * A composition of haptic elements that are combined to be playable as a single
     * {@link VibrationEffect}.
+86 −0
Original line number Diff line number Diff line
@@ -401,6 +401,17 @@ public class VibrationEffectTest {
        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
    }

    @Test
    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
    public void computeLegacyPattern_repeatingEffect() {
        VibrationEffect repeatingEffect = VibrationEffect.createRepeatingEffect(TEST_ONE_SHOT,
                TEST_WAVEFORM);
        assertNull(repeatingEffect.computeCreateWaveformOffOnTimingsOrNull());

        repeatingEffect = VibrationEffect.createRepeatingEffect(TEST_WAVEFORM);
        assertNull(repeatingEffect.computeCreateWaveformOffOnTimingsOrNull());
    }

    @Test
    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
    public void computeLegacyPattern_effectsViaStartWaveformEnvelope() {
@@ -525,6 +536,17 @@ public class VibrationEffectTest {
        assertThat(effect.cropToLengthOrNull(2)).isNull();
    }

    @Test
    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
    public void cropToLength_repeatingEffect() {
        VibrationEffect repeatingEffect = VibrationEffect.createRepeatingEffect(TEST_ONE_SHOT,
                TEST_WAVEFORM);
        assertThat(repeatingEffect.cropToLengthOrNull(1)).isNull();

        repeatingEffect = VibrationEffect.createRepeatingEffect(TEST_WAVEFORM);
        assertThat(repeatingEffect.cropToLengthOrNull(1)).isNull();
    }

    @Test
    public void getRingtones_noPrebakedRingtones() {
        Resources r = mockRingtoneResources(new String[0]);
@@ -621,6 +643,30 @@ public class VibrationEffectTest {
                .build()
                .validate();

        VibrationEffect.createRepeatingEffect(
                /*preamble=*/ VibrationEffect.startWaveformEnvelope()
                        .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f,
                                /*timeMillis=*/ 20)
                        .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f,
                                /*timeMillis=*/ 50)
                        .build(),
                /*repeatingEffect=*/ VibrationEffect.startWaveformEnvelope()
                        .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f,
                                /*timeMillis=*/ 20)
                        .addControlPoint(/*amplitude=*/ 0.5f, /*frequencyHz=*/ 150f,
                                /*timeMillis=*/ 100)
                        .build()
        ).validate();

        VibrationEffect.createRepeatingEffect(
                /*effect=*/ VibrationEffect.startWaveformEnvelope()
                        .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f,
                                /*timeMillis=*/ 20)
                        .addControlPoint(/*amplitude=*/ 0.5f, /*frequencyHz=*/ 150f,
                                /*timeMillis=*/ 100)
                        .build()
        ).validate();

        assertThrows(IllegalStateException.class,
                () -> VibrationEffect.startWaveformEnvelope().build().validate());
        assertThrows(IllegalArgumentException.class,
@@ -725,6 +771,21 @@ public class VibrationEffectTest {
                .repeatEffectIndefinitely(TEST_ONE_SHOT)
                .compose()
                .validate();
        VibrationEffect.startComposition()
                .addEffect(TEST_ONE_SHOT)
                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK)
                .addEffect(VibrationEffect.createRepeatingEffect(
                        /*preamble=*/ VibrationEffect.createPredefined(
                                VibrationEffect.EFFECT_DOUBLE_CLICK),
                        /*repeatingEffect=*/ TEST_WAVEFORM))
                .compose()
                .validate();
        VibrationEffect.startComposition()
                .addEffect(TEST_ONE_SHOT)
                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK)
                .addEffect(VibrationEffect.createRepeatingEffect(TEST_WAVEFORM))
                .compose()
                .validate();

        // Make sure class summary javadoc examples compile and are valid.
        // NOTE: IF THIS IS UPDATED, PLEASE ALSO UPDATE Composition javadocs.
@@ -787,6 +848,31 @@ public class VibrationEffectTest {
                        .addEffect(TEST_ONE_SHOT)
                        .compose()
                        .validate());

        assertThrows(UnreachableAfterRepeatingIndefinitelyException.class,
                () -> VibrationEffect.startComposition()
                        .addEffect(VibrationEffect.createRepeatingEffect(
                                /*preamble=*/ VibrationEffect.createPredefined(
                                        VibrationEffect.EFFECT_DOUBLE_CLICK),
                                /*repeatingEffect=*/ TEST_WAVEFORM))
                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
                        .compose()
                        .validate());
        assertThrows(UnreachableAfterRepeatingIndefinitelyException.class,
                () -> VibrationEffect.startComposition()
                        .addEffect(VibrationEffect.createRepeatingEffect(
                                        /*preamble=*/ VibrationEffect.createPredefined(
                                                VibrationEffect.EFFECT_DOUBLE_CLICK),
                                        /*repeatingEffect=*/ TEST_WAVEFORM))
                        .addEffect(TEST_ONE_SHOT)
                        .compose()
                        .validate());
        assertThrows(UnreachableAfterRepeatingIndefinitelyException.class,
                () -> VibrationEffect.startComposition()
                        .addEffect(VibrationEffect.createRepeatingEffect(TEST_WAVEFORM))
                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
                        .compose()
                        .validate());
    }

    @Test