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

Commit 1c879e13 authored by Lais Andrade's avatar Lais Andrade Committed by Automerger Merge Worker
Browse files

Merge "Implement PWLE support" into sc-dev am: 1eba10e2

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/13969748

Change-Id: I4be9ca83d3a38aee1e519a66363cbe60f6f7f60c
parents a226cf34 1eba10e2
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