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

Commit 2a3dac7f authored by Michael Wright's avatar Michael Wright
Browse files

Add resonant frequency and Q factor to VibratorInfo

Bug: 181276733
Test: atest VibratorInfoTest VibratorManagerService
Change-Id: I206da897fba7c1b065979aa3fb165cc536fbaf6c
parent 50ccbcd5
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
@@ -207,6 +207,28 @@ public abstract class Vibrator {
     */
    public abstract boolean hasAmplitudeControl();

    /**
     * Gets the resonant frequency of the vibrator.
     *
     * @return the resonant frequency of the vibrator, or {@link Float#NaN NaN} if it's unknown or
     *         this vibrator is a composite of multiple physical devices.
     * @hide
     */
    public float getResonantFrequency() {
        return Float.NaN;
    }

    /**
     * Gets the <a href="https://en.wikipedia.org/wiki/Q_factor">Q factor</a> of the vibrator.
     *
     * @return the Q factor of the vibrator, or {@link Float#NaN NaN} if it's unknown or
     *         this vibrator is a composite of multiple physical devices.
     * @hide
     */
    public float getQFactor() {
        return Float.NaN;
    }

    /**
     * Configure an always-on haptics effect.
     *
+36 −3
Original line number Diff line number Diff line
@@ -42,21 +42,27 @@ public final class VibratorInfo implements Parcelable {
    private final SparseBooleanArray mSupportedEffects;
    @Nullable
    private final SparseBooleanArray mSupportedPrimitives;
    private final float mResonantFrequency;
    private final float mQFactor;

    VibratorInfo(Parcel in) {
        mId = in.readInt();
        mCapabilities = in.readLong();
        mSupportedEffects = in.readSparseBooleanArray();
        mSupportedPrimitives = in.readSparseBooleanArray();
        mResonantFrequency = in.readFloat();
        mQFactor = in.readFloat();
    }

    /** @hide */
    public VibratorInfo(int id, long capabilities, int[] supportedEffects,
            int[] supportedPrimitives) {
            int[] supportedPrimitives, float resonantFrequency, float qFactor) {
        mId = id;
        mCapabilities = capabilities;
        mSupportedEffects = toSparseBooleanArray(supportedEffects);
        mSupportedPrimitives = toSparseBooleanArray(supportedPrimitives);
        mResonantFrequency = resonantFrequency;
        mQFactor = qFactor;
    }

    @Override
@@ -65,6 +71,8 @@ public final class VibratorInfo implements Parcelable {
        dest.writeLong(mCapabilities);
        dest.writeSparseBooleanArray(mSupportedEffects);
        dest.writeSparseBooleanArray(mSupportedPrimitives);
        dest.writeFloat(mResonantFrequency);
        dest.writeFloat(mQFactor);
    }

    @Override
@@ -83,12 +91,15 @@ public final class VibratorInfo implements Parcelable {
        VibratorInfo that = (VibratorInfo) o;
        return mId == that.mId && mCapabilities == that.mCapabilities
                && Objects.equals(mSupportedEffects, that.mSupportedEffects)
                && Objects.equals(mSupportedPrimitives, that.mSupportedPrimitives);
                && Objects.equals(mSupportedPrimitives, that.mSupportedPrimitives)
                && Objects.equals(mResonantFrequency, that.mResonantFrequency)
                && Objects.equals(mQFactor, that.mQFactor);
    }

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

    @Override
@@ -99,6 +110,8 @@ public final class VibratorInfo implements Parcelable {
                + ", mCapabilities flags=" + Long.toBinaryString(mCapabilities)
                + ", mSupportedEffects=" + Arrays.toString(getSupportedEffectsNames())
                + ", mSupportedPrimitives=" + Arrays.toString(getSupportedPrimitivesNames())
                + ", mResonantFrequency=" + mResonantFrequency
                + ", mQFactor=" + mQFactor
                + '}';
    }

@@ -156,6 +169,26 @@ public final class VibratorInfo implements Parcelable {
        return (mCapabilities & capability) == capability;
    }

    /**
     * Gets the resonant frequency of the vibrator.
     *
     * @return the resonant frequency of the vibrator, or {@link Float#NaN NaN} if it's unknown or
     *         this vibrator is a composite of multiple physical devices.
     */
    public float getResonantFrequency() {
        return mResonantFrequency;
    }

    /**
     * Gets the <a href="https://en.wikipedia.org/wiki/Q_factor">Q factor</a> of the vibrator.
     *
     * @return the Q factor of the vibrator, or {@link Float#NaN NaN} if it's unknown or
     *         this vibrator is a composite of multiple physical devices.
     */
    public float getQFactor() {
        return mQFactor;
    }

    private String[] getCapabilitiesNames() {
        List<String> names = new ArrayList<>();
        if (hasCapability(IVibrator.CAP_ON_CALLBACK)) {
+139 −42
Original line number Diff line number Diff line
@@ -16,9 +16,10 @@

package android.os;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;

import android.hardware.vibrator.IVibrator;
import android.platform.test.annotations.Presubmit;
@@ -33,72 +34,128 @@ public class VibratorInfoTest {

    @Test
    public void testHasAmplitudeControl() {
        assertFalse(createInfo(/* capabilities= */ 0).hasAmplitudeControl());
        assertTrue(createInfo(IVibrator.CAP_COMPOSE_EFFECTS
                | IVibrator.CAP_AMPLITUDE_CONTROL).hasAmplitudeControl());
        VibratorInfo noCapabilities = new InfoBuilder().build();
        assertFalse(noCapabilities.hasAmplitudeControl());
        VibratorInfo composeAndAmplitudeControl = new InfoBuilder()
                .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS
                        | IVibrator.CAP_AMPLITUDE_CONTROL)
                .build();
        assertTrue(composeAndAmplitudeControl.hasAmplitudeControl());
    }

    @Test
    public void testHasCapabilities() {
        assertTrue(createInfo(IVibrator.CAP_COMPOSE_EFFECTS)
                .hasCapability(IVibrator.CAP_COMPOSE_EFFECTS));
        assertFalse(createInfo(IVibrator.CAP_COMPOSE_EFFECTS)
                .hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL));
        VibratorInfo info = new InfoBuilder()
                .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
                .build();
        assertTrue(info.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS));
        assertFalse(info.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL));
    }

    @Test
    public void testIsEffectSupported() {
        VibratorInfo info = new VibratorInfo(/* id= */ 0, /* capabilities= */0,
                new int[]{VibrationEffect.EFFECT_CLICK}, null);
        VibratorInfo noEffects = new InfoBuilder().build();
        VibratorInfo canClick = new InfoBuilder()
                .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
                .build();
        assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN,
                createInfo(/* capabilities= */ 0).isEffectSupported(VibrationEffect.EFFECT_CLICK));
                noEffects.isEffectSupported(VibrationEffect.EFFECT_CLICK));
        assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_YES,
                info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
                canClick.isEffectSupported(VibrationEffect.EFFECT_CLICK));
        assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO,
                info.isEffectSupported(VibrationEffect.EFFECT_TICK));
                canClick.isEffectSupported(VibrationEffect.EFFECT_TICK));
    }

    @Test
    public void testIsPrimitiveSupported() {
        VibratorInfo info = new VibratorInfo(/* id= */ 0, IVibrator.CAP_COMPOSE_EFFECTS,
                null, new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK});
        VibratorInfo info = new InfoBuilder()
                .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
                .setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK)
                .build();
        assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
        assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK));

        // Returns false when there is no compose capability.
        info = new VibratorInfo(/* id= */ 0, /* capabilities= */ 0,
                null, new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK});
        info = new InfoBuilder()
                .setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK)
                .build();
        assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
    }

    @Test
    public void testEquals() {
        VibratorInfo empty = new VibratorInfo(1, 0, null, null);
        VibratorInfo complete = new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL,
                new int[]{VibrationEffect.EFFECT_CLICK},
                new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK});
        InfoBuilder completeBuilder = new InfoBuilder()
                .setId(1)
                .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
                .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
                .setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK)
                .setQFactor(2f)
                .setResonantFrequency(150f);
        VibratorInfo complete = completeBuilder.build();

        assertEquals(complete, complete);
        assertEquals(complete, new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL,
                new int[]{VibrationEffect.EFFECT_CLICK},
                new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK}));

        assertFalse(empty.equals(new VibratorInfo(1, 0, new int[]{}, new int[]{})));
        assertFalse(complete.equals(new VibratorInfo(1, IVibrator.CAP_COMPOSE_EFFECTS,
                new int[]{VibrationEffect.EFFECT_CLICK},
                new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK})));
        assertFalse(complete.equals(new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL,
                new int[]{}, new int[]{})));
        assertFalse(complete.equals(new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL,
                null, new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK})));
        assertFalse(complete.equals(new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL,
                new int[]{VibrationEffect.EFFECT_CLICK}, null)));
        assertEquals(complete, completeBuilder.build());

        VibratorInfo completeWithComposeControl = completeBuilder
                .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
                .build();
        assertNotEquals(complete, completeWithComposeControl);

        VibratorInfo completeWithNoEffects = completeBuilder
                .setSupportedEffects()
                .setSupportedPrimitives()
                .build();
        assertNotEquals(complete, completeWithNoEffects);

        VibratorInfo completeWithUnknownEffects = completeBuilder
                .setSupportedEffects(null)
                .build();
        assertNotEquals(complete, completeWithNoEffects);

        VibratorInfo completeWithUnknownPrimitives = completeBuilder
                .setSupportedPrimitives(null)
                .build();
        assertNotEquals(complete, completeWithUnknownPrimitives);

        VibratorInfo completeWithDifferentF0 = completeBuilder
                .setResonantFrequency(complete.getResonantFrequency() + 3f)
                .build();
        assertNotEquals(complete, completeWithDifferentF0);

        VibratorInfo completeWithUnknownF0 = completeBuilder
                .setResonantFrequency(Float.NaN)
                .build();
        assertNotEquals(complete, completeWithUnknownF0);

        VibratorInfo completeWithUnknownQFactor = completeBuilder
                .setQFactor(Float.NaN)
                .build();
        assertNotEquals(complete, completeWithUnknownQFactor);

        VibratorInfo completeWithDifferentQFactor = completeBuilder
                .setQFactor(complete.getQFactor() + 3f)
                .build();
        assertNotEquals(complete, completeWithDifferentQFactor);

        VibratorInfo empty = new InfoBuilder().setId(1).build();
        VibratorInfo emptyWithKnownSupport = new InfoBuilder()
                .setId(1)
                .setSupportedEffects()
                .setSupportedPrimitives()
                .build();
        assertNotEquals(empty, emptyWithKnownSupport);
    }

    @Test
    public void testSerialization() {
        VibratorInfo original = new VibratorInfo(1, IVibrator.CAP_COMPOSE_EFFECTS,
                new int[]{VibrationEffect.EFFECT_CLICK}, null);
    public void testParceling() {
        VibratorInfo original = new InfoBuilder()
                .setId(1)
                .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
                .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
                .setSupportedPrimitives(null)
                .setResonantFrequency(1.3f)
                .setQFactor(Float.NaN)
                .build();

        Parcel parcel = Parcel.obtain();
        original.writeToParcel(parcel, 0);
@@ -107,7 +164,47 @@ public class VibratorInfoTest {
        assertEquals(original, restored);
    }

    private static VibratorInfo createInfo(long capabilities) {
        return new VibratorInfo(/* id= */ 0, capabilities, null, null);
    private static class InfoBuilder {
        private int mId = 0;
        private int mCapabilities = 0;
        private int[] mSupportedEffects = null;
        private int[] mSupportedPrimitives = null;
        private float mResonantFrequency = Float.NaN;
        private float mQFactor = Float.NaN;

        public InfoBuilder setId(int id) {
            mId = id;
            return this;
        }

        public InfoBuilder setCapabilities(int capabilities) {
            mCapabilities = capabilities;
            return this;
        }

        public InfoBuilder setSupportedEffects(int... supportedEffects) {
            mSupportedEffects = supportedEffects;
            return this;
        }

        public InfoBuilder setSupportedPrimitives(int... supportedPrimitives) {
            mSupportedPrimitives = supportedPrimitives;
            return this;
        }

        public InfoBuilder setResonantFrequency(float resonantFrequency) {
            mResonantFrequency = resonantFrequency;
            return this;
        }

        public InfoBuilder setQFactor(float qFactor) {
            mQFactor = qFactor;
            return this;
        }

        public VibratorInfo build() {
            return new VibratorInfo(mId, mCapabilities, mSupportedEffects, mSupportedPrimitives,
                    mResonantFrequency, mQFactor);
        }
    }
}
+60 −61
Original line number Diff line number Diff line
@@ -54,50 +54,6 @@ final class VibratorController {
        void onComplete(int vibratorId, long vibrationId);
    }

    /**
     * Initializes the native part of this controller, creating a global reference to given
     * {@link OnVibrationCompleteListener} and returns a newly allocated native pointer. This
     * wrapper is responsible for deleting this pointer by calling the method pointed
     * by {@link #vibratorGetFinalizer()}.
     *
     * <p><b>Note:</b> Make sure the given implementation of {@link OnVibrationCompleteListener}
     * do not hold any strong reference to the instance responsible for deleting the returned
     * pointer, to avoid creating a cyclic GC root reference.
     */
    static native long vibratorInit(int vibratorId, OnVibrationCompleteListener listener);

    /**
     * Returns pointer to native function responsible for cleaning up the native pointer allocated
     * and returned by {@link #vibratorInit(int, OnVibrationCompleteListener)}.
     */
    static native long vibratorGetFinalizer();

    static native boolean vibratorIsAvailable(long nativePtr);

    static native void vibratorOn(long nativePtr, long milliseconds, long vibrationId);

    static native void vibratorOff(long nativePtr);

    static native void vibratorSetAmplitude(long nativePtr, int amplitude);

    static native int[] vibratorGetSupportedEffects(long nativePtr);

    static native int[] vibratorGetSupportedPrimitives(long nativePtr);

    static native long vibratorPerformEffect(
            long nativePtr, long effect, long strength, long vibrationId);

    static native long vibratorPerformComposedEffect(
            long nativePtr, VibrationEffect.Composition.PrimitiveEffect[] effect, long vibrationId);

    static native void vibratorSetExternalControl(long nativePtr, boolean enabled);

    static native long vibratorGetCapabilities(long nativePtr);

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

    static native void vibratorAlwaysOnDisable(long nativePtr, long id);

    VibratorController(int vibratorId, OnVibrationCompleteListener listener) {
        this(vibratorId, listener, new NativeWrapper());
    }
@@ -109,7 +65,8 @@ final class VibratorController {
        mNativeWrapper.init(vibratorId, listener);

        mVibratorInfo = new VibratorInfo(vibratorId, nativeWrapper.getCapabilities(),
                nativeWrapper.getSupportedEffects(), nativeWrapper.getSupportedPrimitives());
                nativeWrapper.getSupportedEffects(), nativeWrapper.getSupportedPrimitives(),
                nativeWrapper.getResonantFrequency(), nativeWrapper.getQFactor());
    }

    /** Register state listener for this vibrator. */
@@ -336,13 +293,47 @@ final class VibratorController {
    /** Wrapper around the static-native methods of {@link VibratorController} for tests. */
    @VisibleForTesting
    public static class NativeWrapper {
        /**
         * Initializes the native part of this controller, creating a global reference to given
         * {@link OnVibrationCompleteListener} and returns a newly allocated native pointer. This
         * wrapper is responsible for deleting this pointer by calling the method pointed
         * by {@link #getNativeFinalizer()}.
         *
         * <p><b>Note:</b> Make sure the given implementation of {@link OnVibrationCompleteListener}
         * do not hold any strong reference to the instance responsible for deleting the returned
         * pointer, to avoid creating a cyclic GC root reference.
         */
        private static native long nativeInit(int vibratorId, OnVibrationCompleteListener listener);

        /**
         * Returns pointer to native function responsible for cleaning up the native pointer
         * allocated and returned by {@link #nativeInit(int, OnVibrationCompleteListener)}.
         */
        private static native long getNativeFinalizer();
        private static native boolean isAvailable(long nativePtr);
        private static native void on(long nativePtr, long milliseconds, long vibrationId);
        private static native void off(long nativePtr);
        private static native void setAmplitude(long nativePtr, int amplitude);
        private static native int[] getSupportedEffects(long nativePtr);
        private static native int[] getSupportedPrimitives(long nativePtr);
        private static native long performEffect(
                long nativePtr, long effect, long strength, long vibrationId);
        private static native long performComposedEffect(long nativePtr,
                VibrationEffect.Composition.PrimitiveEffect[] effect, long vibrationId);
        private static native void setExternalControl(long nativePtr, boolean enabled);
        private static native long getCapabilities(long nativePtr);
        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 float getResonantFrequency(long nativePtr);
        private static native float getQFactor(long nativePtr);

        private long mNativePtr = 0;

        /** Initializes native controller and allocation registry to destroy native instances. */
        public void init(int vibratorId, OnVibrationCompleteListener listener) {
            mNativePtr = VibratorController.vibratorInit(vibratorId, listener);
            long finalizerPtr = VibratorController.vibratorGetFinalizer();
            mNativePtr = nativeInit(vibratorId, listener);
            long finalizerPtr = getNativeFinalizer();

            if (finalizerPtr != 0) {
                NativeAllocationRegistry registry =
@@ -354,65 +345,73 @@ final class VibratorController {

        /** Check if the vibrator is currently available. */
        public boolean isAvailable() {
            return VibratorController.vibratorIsAvailable(mNativePtr);
            return isAvailable(mNativePtr);
        }

        /** Turns vibrator on for given time. */
        public void on(long milliseconds, long vibrationId) {
            VibratorController.vibratorOn(mNativePtr, milliseconds, vibrationId);
            on(mNativePtr, milliseconds, vibrationId);
        }

        /** Turns vibrator off. */
        public void off() {
            VibratorController.vibratorOff(mNativePtr);
            off(mNativePtr);
        }

        /** Sets the amplitude for the vibrator to run. */
        public void setAmplitude(int amplitude) {
            VibratorController.vibratorSetAmplitude(mNativePtr, amplitude);
            setAmplitude(mNativePtr, amplitude);
        }

        /** Returns all predefined effects supported by the device vibrator. */
        public int[] getSupportedEffects() {
            return VibratorController.vibratorGetSupportedEffects(mNativePtr);
            return getSupportedEffects(mNativePtr);
        }

        /** Returns all compose primitives supported by the device vibrator. */
        public int[] getSupportedPrimitives() {
            return VibratorController.vibratorGetSupportedPrimitives(mNativePtr);
            return getSupportedPrimitives(mNativePtr);
        }

        /** Turns vibrator on to perform one of the supported effects. */
        public long perform(long effect, long strength, long vibrationId) {
            return VibratorController.vibratorPerformEffect(
                    mNativePtr, effect, strength, vibrationId);
            return performEffect(mNativePtr, effect, strength, vibrationId);
        }

        /** Turns vibrator on to perform one of the supported composed effects. */
        public long compose(
                VibrationEffect.Composition.PrimitiveEffect[] effect, long vibrationId) {
            return VibratorController.vibratorPerformComposedEffect(mNativePtr, effect,
                    vibrationId);
            return performComposedEffect(mNativePtr, effect, vibrationId);
        }

        /** Enabled the device vibrator to be controlled by another service. */
        public void setExternalControl(boolean enabled) {
            VibratorController.vibratorSetExternalControl(mNativePtr, enabled);
            setExternalControl(mNativePtr, enabled);
        }

        /** Returns all capabilities of the device vibrator. */
        public long getCapabilities() {
            return VibratorController.vibratorGetCapabilities(mNativePtr);
            return getCapabilities(mNativePtr);
        }

        /** Enable always-on vibration with given id and effect. */
        public void alwaysOnEnable(long id, long effect, long strength) {
            VibratorController.vibratorAlwaysOnEnable(mNativePtr, id, effect, strength);
            alwaysOnEnable(mNativePtr, id, effect, strength);
        }

        /** Disable always-on vibration for given id. */
        public void alwaysOnDisable(long id) {
            VibratorController.vibratorAlwaysOnDisable(mNativePtr, id);
            alwaysOnDisable(mNativePtr, id);
        }

        /** Gets the vibrator's resonant frequency (F0) */
        public float getResonantFrequency() {
            return getResonantFrequency(mNativePtr);
        }

        /** Gets the vibrator's Q factor */
        public float getQFactor() {
            return getQFactor(mNativePtr);
        }
    }
}
+45 −24

File changed.

Preview size limit exceeded, changes collapsed.

Loading