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

Commit 68a199f6 authored by Lais Andrade's avatar Lais Andrade
Browse files

Implement PWLE support

Implement composePwle method and related frequency control getter
introduced to the IVibrator interface.

Bug: 167947076
Test: VibratorControllerTest and VibraionThreadTest
Change-Id: If15f47fae33bce0bcb916e84af0c6712068f3a00
parent 834b65d8
Loading
Loading
Loading
Loading
+58 −6
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package android.os;
import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.vibrator.Braking;
import android.hardware.vibrator.IVibrator;
import android.util.Log;
import android.util.MathUtils;
@@ -45,6 +46,8 @@ public final class VibratorInfo implements Parcelable {
    @Nullable
    private final SparseBooleanArray mSupportedEffects;
    @Nullable
    private final SparseBooleanArray mSupportedBraking;
    @Nullable
    private final SparseBooleanArray mSupportedPrimitives;
    private final float mQFactor;
    private final FrequencyMapping mFrequencyMapping;
@@ -53,17 +56,19 @@ public final class VibratorInfo implements Parcelable {
        mId = in.readInt();
        mCapabilities = in.readLong();
        mSupportedEffects = in.readSparseBooleanArray();
        mSupportedBraking = in.readSparseBooleanArray();
        mSupportedPrimitives = in.readSparseBooleanArray();
        mQFactor = in.readFloat();
        mFrequencyMapping = in.readParcelable(VibratorInfo.class.getClassLoader());
    }

    /** @hide */
    public VibratorInfo(int id, long capabilities, int[] supportedEffects,
    public VibratorInfo(int id, long capabilities, int[] supportedEffects, int[] supportedBraking,
            int[] supportedPrimitives, float qFactor, @NonNull FrequencyMapping frequencyMapping) {
        mId = id;
        mCapabilities = capabilities;
        mSupportedEffects = toSparseBooleanArray(supportedEffects);
        mSupportedBraking = toSparseBooleanArray(supportedBraking);
        mSupportedPrimitives = toSparseBooleanArray(supportedPrimitives);
        mQFactor = qFactor;
        mFrequencyMapping = frequencyMapping;
@@ -74,6 +79,7 @@ public final class VibratorInfo implements Parcelable {
        dest.writeInt(mId);
        dest.writeLong(mCapabilities);
        dest.writeSparseBooleanArray(mSupportedEffects);
        dest.writeSparseBooleanArray(mSupportedBraking);
        dest.writeSparseBooleanArray(mSupportedPrimitives);
        dest.writeFloat(mQFactor);
        dest.writeParcelable(mFrequencyMapping, flags);
@@ -95,6 +101,7 @@ public final class VibratorInfo implements Parcelable {
        VibratorInfo that = (VibratorInfo) o;
        return mId == that.mId && mCapabilities == that.mCapabilities
                && Objects.equals(mSupportedEffects, that.mSupportedEffects)
                && Objects.equals(mSupportedBraking, that.mSupportedBraking)
                && Objects.equals(mSupportedPrimitives, that.mSupportedPrimitives)
                && Objects.equals(mQFactor, that.mQFactor)
                && Objects.equals(mFrequencyMapping, that.mFrequencyMapping);
@@ -102,8 +109,8 @@ public final class VibratorInfo implements Parcelable {

    @Override
    public int hashCode() {
        return Objects.hash(mId, mCapabilities, mSupportedEffects, mSupportedPrimitives,
                mQFactor, mFrequencyMapping);
        return Objects.hash(mId, mCapabilities, mSupportedEffects, mSupportedBraking,
                mSupportedPrimitives, mQFactor, mFrequencyMapping);
    }

    @Override
@@ -113,6 +120,7 @@ public final class VibratorInfo implements Parcelable {
                + ", mCapabilities=" + Arrays.toString(getCapabilitiesNames())
                + ", mCapabilities flags=" + Long.toBinaryString(mCapabilities)
                + ", mSupportedEffects=" + Arrays.toString(getSupportedEffectsNames())
                + ", mSupportedBraking=" + Arrays.toString(getSupportedBrakingNames())
                + ", mSupportedPrimitives=" + Arrays.toString(getSupportedPrimitivesNames())
                + ", mQFactor=" + mQFactor
                + ", mFrequencyMapping=" + mFrequencyMapping
@@ -133,6 +141,23 @@ public final class VibratorInfo implements Parcelable {
        return hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL);
    }

    /**
     * Returns a default value to be applied to composed PWLE effects for braking.
     *
     * @return a supported braking value, one of android.hardware.vibrator.Braking.*
     */
    public int getDefaultBraking() {
        if (mSupportedBraking != null) {
            int size = mSupportedBraking.size();
            for (int i = 0; i < size; i++) {
                if (mSupportedBraking.keyAt(i) != Braking.NONE) {
                    return mSupportedBraking.keyAt(i);
                }
            }
        }
        return Braking.NONE;
    }

    /**
     * Query whether the vibrator supports the given effect.
     *
@@ -147,7 +172,7 @@ public final class VibratorInfo implements Parcelable {
        if (mSupportedEffects == null) {
            return Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN;
        }
        return mSupportedEffects.get(effectId, false) ? Vibrator.VIBRATION_EFFECT_SUPPORT_YES
        return mSupportedEffects.get(effectId) ? Vibrator.VIBRATION_EFFECT_SUPPORT_YES
                : Vibrator.VIBRATION_EFFECT_SUPPORT_NO;
    }

@@ -160,7 +185,7 @@ public final class VibratorInfo implements Parcelable {
    public boolean isPrimitiveSupported(
            @VibrationEffect.Composition.PrimitiveType int primitiveId) {
        return hasCapability(IVibrator.CAP_COMPOSE_EFFECTS) && mSupportedPrimitives != null
                && mSupportedPrimitives.get(primitiveId, false);
                && mSupportedPrimitives.get(primitiveId);
    }

    /**
@@ -251,12 +276,18 @@ public final class VibratorInfo implements Parcelable {
        if (hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
            names.add("COMPOSE_EFFECTS");
        }
        if (hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
            names.add("COMPOSE_PWLE_EFFECTS");
        }
        if (hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
            names.add("ALWAYS_ON_CONTROL");
        }
        if (hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) {
            names.add("AMPLITUDE_CONTROL");
        }
        if (hasCapability(IVibrator.CAP_FREQUENCY_CONTROL)) {
            names.add("FREQUENCY_CONTROL");
        }
        if (hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
            names.add("EXTERNAL_CONTROL");
        }
@@ -277,6 +308,26 @@ public final class VibratorInfo implements Parcelable {
        return names;
    }

    private String[] getSupportedBrakingNames() {
        if (mSupportedBraking == null) {
            return new String[0];
        }
        String[] names = new String[mSupportedBraking.size()];
        for (int i = 0; i < mSupportedBraking.size(); i++) {
            switch (mSupportedBraking.keyAt(i)) {
                case Braking.NONE:
                    names[i] = "NONE";
                    break;
                case Braking.CLAB:
                    names[i] = "CLAB";
                    break;
                default:
                    names[i] = Integer.toString(mSupportedBraking.keyAt(i));
            }
        }
        return names;
    }

    private String[] getSupportedPrimitivesNames() {
        if (mSupportedPrimitives == null) {
            return new String[0];
@@ -478,7 +529,8 @@ public final class VibratorInfo implements Parcelable {
        @Override
        public String toString() {
            return "FrequencyMapping{"
                    + "mMinFrequency=" + mMinFrequencyHz
                    + "mRelativeFrequencyRange=" + mRelativeFrequencyRange
                    + ", mMinFrequency=" + mMinFrequencyHz
                    + ", mResonantFrequency=" + mResonantFrequencyHz
                    + ", mMaxFrequency="
                    + (mMinFrequencyHz + mFrequencyResolutionHz * (mMaxAmplitudes.length - 1))
+19 −2
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;

import android.hardware.vibrator.Braking;
import android.hardware.vibrator.IVibrator;
import android.platform.test.annotations.Presubmit;
import android.util.Range;
@@ -97,6 +98,16 @@ public class VibratorInfoTest {
        assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
    }

    @Test
    public void testGetDefaultBraking_returnsFirstSupportedBraking() {
        assertEquals(Braking.NONE, new InfoBuilder().build().getDefaultBraking());
        assertEquals(Braking.CLAB,
                new InfoBuilder()
                        .setSupportedBraking(Braking.NONE, Braking.CLAB)
                        .build()
                        .getDefaultBraking());
    }

    @Test
    public void testGetFrequencyRange_invalidFrequencyMappingReturnsEmptyRange() {
        // Invalid, contains NaN values or empty array.
@@ -318,6 +329,7 @@ public class VibratorInfoTest {
        private int mId = 0;
        private int mCapabilities = 0;
        private int[] mSupportedEffects = null;
        private int[] mSupportedBraking = null;
        private int[] mSupportedPrimitives = null;
        private float mQFactor = Float.NaN;
        private VibratorInfo.FrequencyMapping mFrequencyMapping = EMPTY_FREQUENCY_MAPPING;
@@ -337,6 +349,11 @@ public class VibratorInfoTest {
            return this;
        }

        public InfoBuilder setSupportedBraking(int... supportedBraking) {
            mSupportedBraking = supportedBraking;
            return this;
        }

        public InfoBuilder setSupportedPrimitives(int... supportedPrimitives) {
            mSupportedPrimitives = supportedPrimitives;
            return this;
@@ -353,8 +370,8 @@ public class VibratorInfoTest {
        }

        public VibratorInfo build() {
            return new VibratorInfo(mId, mCapabilities, mSupportedEffects, mSupportedPrimitives,
                    mQFactor, mFrequencyMapping);
            return new VibratorInfo(mId, mCapabilities, mSupportedEffects, mSupportedBraking,
                    mSupportedPrimitives, mQFactor, mFrequencyMapping);
        }
    }
}
+78 −24
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.vibrator;

import android.hardware.vibrator.IVibrator;
import android.os.VibrationEffect;
import android.os.VibratorInfo;
import android.os.vibrator.RampSegment;
@@ -30,25 +31,31 @@ import java.util.List;
/** Adapts a {@link VibrationEffect} to a specific device, taking into account its capabilities. */
final class DeviceVibrationEffectAdapter implements VibrationEffectModifier<VibratorInfo> {

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

        /**
     * Adapts a sequence of {@link VibrationEffectSegment} to device's absolute frequency values
     * and respective supported amplitudes.
         * Modifies the given segments list by adding/removing segments to it based on the
         * device capabilities specified by given {@link VibratorInfo}.
         *
     * <p>This adapter preserves the segment count.
         * @param segments    List of {@link VibrationEffectSegment} to be adapter to the device.
         * @param repeatIndex Repeat index on the current segment list.
         * @param info        The device vibrator info that the segments must be adapted to.
         * @return The new repeat index on the modifies list.
         */
    interface AmplitudeFrequencyAdapter {
        List<VibrationEffectSegment> apply(List<VibrationEffectSegment> segments,
                VibratorInfo info);
        int apply(List<VibrationEffectSegment> segments, int repeatIndex, VibratorInfo info);
    }

    private final AmplitudeFrequencyAdapter mAmplitudeFrequencyAdapter;
    private final SegmentsAdapter mAmplitudeFrequencyAdapter;
    private final SegmentsAdapter mStepToRampAdapter;

    DeviceVibrationEffectAdapter() {
        this(new ClippingAmplitudeFrequencyAdapter());
    }

    DeviceVibrationEffectAdapter(AmplitudeFrequencyAdapter amplitudeFrequencyAdapter) {
    DeviceVibrationEffectAdapter(SegmentsAdapter amplitudeFrequencyAdapter) {
        mAmplitudeFrequencyAdapter = amplitudeFrequencyAdapter;
        mStepToRampAdapter = new StepToRampAdapter();
    }

    @Override
@@ -58,14 +65,62 @@ final class DeviceVibrationEffectAdapter implements VibrationEffectModifier<Vibr
        }

        VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
        List<VibrationEffectSegment> mappedSegments = mAmplitudeFrequencyAdapter.apply(
                composed.getSegments(), info);
        List<VibrationEffectSegment> newSegments = new ArrayList<>(composed.getSegments());
        int newRepeatIndex = composed.getRepeatIndex();

        // Maps steps that should be handled by PWLE to ramps.
        // This should be done before frequency is converted from relative to absolute values.
        newRepeatIndex = mStepToRampAdapter.apply(newSegments, newRepeatIndex, info);

        // Adapt amplitude and frequency values to device supported ones, converting frequency
        // to absolute values in Hertz.
        newRepeatIndex = mAmplitudeFrequencyAdapter.apply(newSegments, newRepeatIndex, info);

        // TODO(b/167947076): add ramp to step adapter once PWLE capability is introduced
        // 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

        return new VibrationEffect.Composed(mappedSegments, composed.getRepeatIndex());
        return new VibrationEffect.Composed(newSegments, newRepeatIndex);
    }

    /**
     * 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.
     */
    private static final class StepToRampAdapter implements SegmentsAdapter {
        @Override
        public int apply(List<VibrationEffectSegment> segments, int repeatIndex,
                VibratorInfo info) {
            if (!info.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
                // The vibrator do not have PWLE capability, so keep the segments unchanged.
                return repeatIndex;
            }
            int segmentCount = segments.size();
            // Convert steps that require frequency control to ramps.
            for (int i = 0; i < segmentCount; i++) {
                VibrationEffectSegment segment = segments.get(i);
                if ((segment instanceof StepSegment)
                        && ((StepSegment) segment).getFrequency() != 0) {
                    segments.set(i, apply((StepSegment) segment));
                }
            }
            // Convert steps that are next to ramps to also become ramps, so they can be composed
            // together in the same PWLE waveform.
            for (int i = 1; i < segmentCount; i++) {
                if (segments.get(i) instanceof RampSegment) {
                    for (int j = i - 1; j >= 0 && (segments.get(j) instanceof StepSegment); j--) {
                        segments.set(j, apply((StepSegment) segments.get(j)));
                    }
                }
            }
            return repeatIndex;
        }

        private RampSegment apply(StepSegment segment) {
            return new RampSegment(segment.getAmplitude(), segment.getAmplitude(),
                    segment.getFrequency(), segment.getFrequency(), (int) segment.getDuration());
        }
    }

    /**
@@ -74,25 +129,24 @@ final class DeviceVibrationEffectAdapter implements VibrationEffectModifier<Vibr
     *
     * <p>Devices with no frequency control will collapse all frequencies to zero and leave
     * amplitudes unchanged.
     *
     * <p>The frequency value returned in segments will be absolute, conveted with
     * {@link VibratorInfo#getAbsoluteFrequency(float)}.
     */
    private static final class ClippingAmplitudeFrequencyAdapter
            implements AmplitudeFrequencyAdapter {
    private static final class ClippingAmplitudeFrequencyAdapter implements SegmentsAdapter {
        @Override
        public List<VibrationEffectSegment> apply(List<VibrationEffectSegment> segments,
        public int apply(List<VibrationEffectSegment> segments, int repeatIndex,
                VibratorInfo info) {
            List<VibrationEffectSegment> result = new ArrayList<>();
            int segmentCount = segments.size();
            for (int i = 0; i < segmentCount; i++) {
                VibrationEffectSegment segment = segments.get(i);
                if (segment instanceof StepSegment) {
                    result.add(apply((StepSegment) segment, info));
                    segments.set(i, apply((StepSegment) segment, info));
                } else if (segment instanceof RampSegment) {
                    result.add(apply((RampSegment) segment, info));
                } else {
                    result.add(segment);
                    segments.set(i, apply((RampSegment) segment, info));
                }
            }
            return result;
            return repeatIndex;
        }

        private StepSegment apply(StepSegment segment, VibratorInfo info) {
+3 −9
Original line number Diff line number Diff line
@@ -280,7 +280,6 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
                    vibratorOffTimeout);
        }
        if (segment instanceof RampSegment) {
            // TODO(b/167947076): check capabilities to play steps with PWLE once APIs introduced
            return new ComposePwleStep(latestStartTime, controller, effect, segmentIndex,
                    vibratorOffTimeout);
        }
@@ -828,14 +827,14 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
                }

                if (DEBUG) {
                    Slog.d(TAG, "Compose " + primitives.size() + " primitives on vibrator "
                    Slog.d(TAG, "Compose " + primitives + " primitives on vibrator "
                            + controller.getVibratorInfo().getId());
                }
                mVibratorOnResult = controller.on(
                        primitives.toArray(new PrimitiveSegment[primitives.size()]),
                        mVibration.id);

                return nextSteps(/* segmntsPlayed= */ primitives.size());
                return nextSteps(/* segmentsPlayed= */ primitives.size());
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
            }
@@ -865,11 +864,6 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
                    VibrationEffectSegment segment = effect.getSegments().get(i);
                    if (segment instanceof RampSegment) {
                        pwles.add((RampSegment) segment);
                    } else if (segment instanceof StepSegment) {
                        StepSegment stepSegment = (StepSegment) segment;
                        pwles.add(new RampSegment(stepSegment.getAmplitude(),
                                stepSegment.getAmplitude(), stepSegment.getFrequency(),
                                stepSegment.getFrequency(), (int) stepSegment.getDuration()));
                    } else {
                        break;
                    }
@@ -882,7 +876,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
                }

                if (DEBUG) {
                    Slog.d(TAG, "Compose " + pwles.size() + " PWLEs on vibrator "
                    Slog.d(TAG, "Compose " + pwles + " PWLEs on vibrator "
                            + controller.getVibratorInfo().getId());
                }
                mVibratorOnResult = controller.on(pwles.toArray(new RampSegment[pwles.size()]),
+30 −7
Original line number Diff line number Diff line
@@ -66,7 +66,8 @@ final class VibratorController {
            NativeWrapper nativeWrapper) {
        mNativeWrapper = nativeWrapper;
        mNativeWrapper.init(vibratorId, listener);
        mVibratorInfo = mNativeWrapper.getInfo();
        // TODO(b/167947076): load suggested range from config
        mVibratorInfo = mNativeWrapper.getInfo(/* suggestedFrequencyRange= */ 100);
        Preconditions.checkNotNull(mVibratorInfo, "Failed to retrieve data for vibrator %d",
                vibratorId);
    }
@@ -251,9 +252,18 @@ final class VibratorController {
     * @return The duration of the effect playing, or 0 if unsupported.
     */
    public long on(RampSegment[] primitives, long vibrationId) {
        // TODO(b/167947076): forward to the HAL once APIs are introduced
        if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
            return 0;
        }
        synchronized (mLock) {
            int braking = mVibratorInfo.getDefaultBraking();
            long duration = mNativeWrapper.composePwle(primitives, braking, vibrationId);
            if (duration > 0) {
                notifyVibratorOnLocked();
            }
            return duration;
        }
    }

    /** Turns off the vibrator.This will affect the state of {@link #isVibrating()}. */
    public void off() {
@@ -337,13 +347,21 @@ final class VibratorController {
        private static native void setAmplitude(long nativePtr, float amplitude);
        private static native long performEffect(long nativePtr, long effect, long strength,
                long vibrationId);

        private static native long performComposedEffect(long nativePtr, PrimitiveSegment[] effect,
                long vibrationId);

        private static native long performPwleEffect(long nativePtr, RampSegment[] effect,
                int braking, long vibrationId);

        private static native void setExternalControl(long nativePtr, boolean enabled);

        private static native void alwaysOnEnable(long nativePtr, long id, long effect,
                long strength);

        private static native void alwaysOnDisable(long nativePtr, long id);
        private static native VibratorInfo getInfo(long nativePtr);

        private static native VibratorInfo getInfo(long nativePtr, float suggestedFrequencyRange);

        private long mNativePtr = 0;

@@ -385,11 +403,16 @@ final class VibratorController {
            return performEffect(mNativePtr, effect, strength, vibrationId);
        }

        /** Turns vibrator on to perform one of the supported composed effects. */
        /** Turns vibrator on to perform effect composed of give primitives effect. */
        public long compose(PrimitiveSegment[] primitives, long vibrationId) {
            return performComposedEffect(mNativePtr, primitives, vibrationId);
        }

        /** Turns vibrator on to perform PWLE effect composed of given primitives. */
        public long composePwle(RampSegment[] primitives, int braking, long vibrationId) {
            return performPwleEffect(mNativePtr, primitives, braking, vibrationId);
        }

        /** Enabled the device vibrator to be controlled by another service. */
        public void setExternalControl(boolean enabled) {
            setExternalControl(mNativePtr, enabled);
@@ -406,8 +429,8 @@ final class VibratorController {
        }

        /** Return device vibrator metadata. */
        public VibratorInfo getInfo() {
            return getInfo(mNativePtr);
        public VibratorInfo getInfo(float suggestedFrequencyRange) {
            return getInfo(mNativePtr, suggestedFrequencyRange);
        }
    }
}
Loading