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

Commit 736995c4 authored by Lais Andrade's avatar Lais Andrade
Browse files

Replace vibration ramps with steps on devices without PWLE

Replace each RampSegment with a sequence of fixed amplitude steps that
can be played on devices without composePwle support.

Fix: 183519636
Test: DeviceVibrationEffectAdapterTest
Change-Id: Ib28d34b9954440060c21ade2b3ddc4b3300a2a86
parent 33f631ad
Loading
Loading
Loading
Loading
+78 −6
Original line number Diff line number Diff line
@@ -26,11 +26,14 @@ import android.util.MathUtils;
import android.util.Range;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/** Adapts a {@link VibrationEffect} to a specific device, taking into account its capabilities. */
final class DeviceVibrationEffectAdapter implements VibrationEffectModifier<VibratorInfo> {

    private static final int RAMP_STEP_DURATION_MILLIS = 5;

    /** Adapts a sequence of {@link VibrationEffectSegment} to device's capabilities. */
    interface SegmentsAdapter {

@@ -38,16 +41,17 @@ final class DeviceVibrationEffectAdapter implements VibrationEffectModifier<Vibr
         * Modifies the given segments list by adding/removing segments to it based on the
         * device capabilities specified by given {@link VibratorInfo}.
         *
         * @param segments    List of {@link VibrationEffectSegment} to be adapter to the device.
         * @param repeatIndex Repeat index on the current segment list.
         * @param segments    List of {@link VibrationEffectSegment} to be modified.
         * @param repeatIndex Repeat index of the vibration with given segment list.
         * @param info        The device vibrator info that the segments must be adapted to.
         * @return The new repeat index on the modifies list.
         * @return The new repeat index to be used for the modified list.
         */
        int apply(List<VibrationEffectSegment> segments, int repeatIndex, VibratorInfo info);
    }

    private final SegmentsAdapter mAmplitudeFrequencyAdapter;
    private final SegmentsAdapter mStepToRampAdapter;
    private final SegmentsAdapter mRampToStepsAdapter;

    DeviceVibrationEffectAdapter() {
        this(new ClippingAmplitudeFrequencyAdapter());
@@ -56,6 +60,7 @@ final class DeviceVibrationEffectAdapter implements VibrationEffectModifier<Vibr
    DeviceVibrationEffectAdapter(SegmentsAdapter amplitudeFrequencyAdapter) {
        mAmplitudeFrequencyAdapter = amplitudeFrequencyAdapter;
        mStepToRampAdapter = new StepToRampAdapter();
        mRampToStepsAdapter = new RampToStepsAdapter(RAMP_STEP_DURATION_MILLIS);
    }

    @Override
@@ -68,7 +73,10 @@ final class DeviceVibrationEffectAdapter implements VibrationEffectModifier<Vibr
        List<VibrationEffectSegment> newSegments = new ArrayList<>(composed.getSegments());
        int newRepeatIndex = composed.getRepeatIndex();

        // Maps steps that should be handled by PWLE to ramps.
        // Replace ramps with a sequence of fixed steps, or no-op if PWLE capability present.
        newRepeatIndex = mRampToStepsAdapter.apply(newSegments, newRepeatIndex, info);

        // Replace steps that should be handled by PWLE to ramps, or no-op if capability missing.
        // This should be done before frequency is converted from relative to absolute values.
        newRepeatIndex = mStepToRampAdapter.apply(newSegments, newRepeatIndex, info);

@@ -76,7 +84,6 @@ final class DeviceVibrationEffectAdapter implements VibrationEffectModifier<Vibr
        // to absolute values in Hertz.
        newRepeatIndex = mAmplitudeFrequencyAdapter.apply(newSegments, newRepeatIndex, info);

        // TODO(b/167947076): add ramp to step adapter
        // TODO(b/167947076): add filter that removes unsupported primitives
        // TODO(b/167947076): add filter that replaces unsupported prebaked with fallback

@@ -86,7 +93,7 @@ final class DeviceVibrationEffectAdapter implements VibrationEffectModifier<Vibr
    /**
     * Adapter that converts step segments that should be handled as PWLEs to ramp segments.
     *
     * <p>This leves the list unchanged if the device do not have compose PWLE capability.
     * <p>This leaves the list unchanged if the device do not have compose PWLE capability.
     */
    private static final class StepToRampAdapter implements SegmentsAdapter {
        @Override
@@ -123,6 +130,71 @@ final class DeviceVibrationEffectAdapter implements VibrationEffectModifier<Vibr
        }
    }

    /**
     * Adapter that converts ramp segments that to a sequence of fixed step segments.
     *
     * <p>This leaves the list unchanged if the device have compose PWLE capability.
     */
    private static final class RampToStepsAdapter implements SegmentsAdapter {
        private final int mStepDuration;

        RampToStepsAdapter(int stepDuration) {
            mStepDuration = stepDuration;
        }

        @Override
        public int apply(List<VibrationEffectSegment> segments, int repeatIndex,
                VibratorInfo info) {
            if (info.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
                // The vibrator have PWLE capability, so keep the segments unchanged.
                return repeatIndex;
            }
            int segmentCount = segments.size();
            for (int i = 0; i < segmentCount; i++) {
                VibrationEffectSegment segment = segments.get(i);
                if (!(segment instanceof RampSegment)) {
                    continue;
                }
                List<StepSegment> steps = apply((RampSegment) segment);
                segments.remove(i);
                segments.addAll(i, steps);
                int addedSegments = steps.size() - 1;
                if (repeatIndex > i) {
                    repeatIndex += addedSegments;
                }
                i += addedSegments;
                segmentCount += addedSegments;
            }
            return repeatIndex;
        }

        private List<StepSegment> apply(RampSegment ramp) {
            if (Float.compare(ramp.getStartAmplitude(), ramp.getEndAmplitude()) == 0) {
                // Amplitude is the same, so return a single step to simulate this ramp.
                return Arrays.asList(
                        new StepSegment(ramp.getStartAmplitude(), ramp.getStartFrequency(),
                                (int) ramp.getDuration()));
            }

            List<StepSegment> steps = new ArrayList<>();
            int stepCount = (int) (ramp.getDuration() + mStepDuration - 1) / mStepDuration;
            for (int i = 0; i < stepCount - 1; i++) {
                float pos = (float) i / stepCount;
                steps.add(new StepSegment(
                        interpolate(ramp.getStartAmplitude(), ramp.getEndAmplitude(), pos),
                        interpolate(ramp.getStartFrequency(), ramp.getEndFrequency(), pos),
                        mStepDuration));
            }
            int duration = (int) ramp.getDuration() - mStepDuration * (stepCount - 1);
            steps.add(new StepSegment(ramp.getEndAmplitude(), ramp.getEndFrequency(), duration));
            return steps;
        }

        private static float interpolate(float start, float end, float position) {
            return start + position * (end - start);
        }
    }

    /**
     * Adapter that clips frequency values to {@link VibratorInfo#getFrequencyRange()} and
     * amplitude values to respective {@link VibratorInfo#getMaxAmplitude}.
+51 −8
Original line number Diff line number Diff line
@@ -76,6 +76,38 @@ public class DeviceVibrationEffectAdapterTest {
        assertEquals(effect, mAdapter.apply(effect, createVibratorInfo(TEST_FREQUENCY_MAPPING)));
    }

    @Test
    public void testStepAndRampSegments_withoutPwleCapability_convertsRampsToSteps() {
        VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
                new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 10),
                new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 100),
                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0.2f,
                        /* startFrequency= */ -4, /* endFrequency= */ 2, /* duration= */ 10),
                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
                        /* startFrequency= */ 0, /* endFrequency= */ 0, /* duration= */ 11),
                new RampSegment(/* startAmplitude= */ 0.65f, /* endAmplitude= */ 0.65f,
                        /* startFrequency= */ 0, /* endFrequency= */ 1, /* duration= */ 200)),
                /* repeatIndex= */ 3);

        VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList(
                new StepSegment(/* amplitude= */ 0, Float.NaN, /* duration= */ 10),
                new StepSegment(/* amplitude= */ 0.5f, Float.NaN, /* duration= */ 100),
                // 10ms ramp becomes 2 steps
                new StepSegment(/* amplitude= */ 1, Float.NaN, /* duration= */ 5),
                new StepSegment(/* amplitude= */ 0.2f, Float.NaN, /* duration= */ 5),
                // 11ms ramp becomes 3 steps
                new StepSegment(/* amplitude= */ 0.8f, Float.NaN, /* duration= */ 5),
                new StepSegment(/* amplitude= */ 0.6f, Float.NaN, /* duration= */ 5),
                new StepSegment(/* amplitude= */ 0.2f, Float.NaN, /* duration= */ 1),
                // 200ms ramp with same amplitude becomes a single step
                new StepSegment(/* amplitude= */ 0.65f, Float.NaN, /* duration= */ 200)),
                // Repeat index fixed after intermediate steps added
                /* repeatIndex= */ 4);

        VibratorInfo info = createVibratorInfo(EMPTY_FREQUENCY_MAPPING);
        assertEquals(expected, mAdapter.apply(effect, info));
    }

    @Test
    public void testStepAndRampSegments_withPwleCapability_convertsStepsToRamps() {
        VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
@@ -131,7 +163,7 @@ public class DeviceVibrationEffectAdapterTest {
    }

    @Test
    public void testStepAndRampSegments_emptyMapping_returnsSameAmplitudesAndFrequencyZero() {
    public void testStepAndRampSegments_withEmptyFreqMapping_returnsSameAmplitudesAndZeroFreq() {
        VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
                new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 10),
                new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 100),
@@ -142,8 +174,11 @@ public class DeviceVibrationEffectAdapterTest {
                /* repeatIndex= */ 2);

        VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList(
                new StepSegment(/* amplitude= */ 0, /* frequency= */ Float.NaN, /* duration= */ 10),
                new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ Float.NaN,
                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 0,
                        /* startFrequency= */ Float.NaN, /* endFrequency= */ Float.NaN,
                        /* duration= */ 10),
                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.5f,
                        /* startFrequency= */ Float.NaN, /* endFrequency= */ Float.NaN,
                        /* duration= */ 100),
                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 1,
                        /* startFrequency= */ Float.NaN, /* endFrequency= */ Float.NaN,
@@ -153,11 +188,13 @@ public class DeviceVibrationEffectAdapterTest {
                        /* duration= */ 20)),
                /* repeatIndex= */ 2);

        assertEquals(expected, mAdapter.apply(effect, createVibratorInfo(EMPTY_FREQUENCY_MAPPING)));
        VibratorInfo info = createVibratorInfo(EMPTY_FREQUENCY_MAPPING,
                IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
        assertEquals(expected, mAdapter.apply(effect, info));
    }

    @Test
    public void testStepAndRampSegments_nonEmptyMapping_returnsClippedValues() {
    public void testStepAndRampSegments_withValidFreqMapping_returnsClippedValues() {
        VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
                new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 10),
                new StepSegment(/* amplitude= */ 1, /* frequency= */ -1, /* duration= */ 100),
@@ -168,15 +205,21 @@ public class DeviceVibrationEffectAdapterTest {
                /* repeatIndex= */ 2);

        VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList(
                new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 150, /* duration= */ 10),
                new StepSegment(/* amplitude= */ 0.8f, /* frequency= */ 125, /* duration= */ 100),
                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.5f,
                        /* startFrequency= */ 150, /* endFrequency= */ 150,
                        /* duration= */ 10),
                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.8f,
                        /* startFrequency= */ 125, /* endFrequency= */ 125,
                        /* duration= */ 100),
                new RampSegment(/* startAmplitude= */ 0.1f, /* endAmplitude= */ 0.8f,
                        /* startFrequency= */ 50, /* endFrequency= */ 200, /* duration= */ 50),
                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.1f,
                        /* startFrequency= */ 200, /* endFrequency= */ 50, /* duration= */ 20)),
                /* repeatIndex= */ 2);

        assertEquals(expected, mAdapter.apply(effect, createVibratorInfo(TEST_FREQUENCY_MAPPING)));
        VibratorInfo info = createVibratorInfo(TEST_FREQUENCY_MAPPING,
                IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
        assertEquals(expected, mAdapter.apply(effect, info));
    }

    private static VibratorInfo createVibratorInfo(VibratorInfo.FrequencyMapping frequencyMapping,