Loading core/java/android/os/VibratorInfo.java +58 −6 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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); Loading @@ -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); Loading @@ -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 Loading @@ -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 Loading @@ -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. * Loading @@ -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; } Loading @@ -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); } /** Loading Loading @@ -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"); } Loading @@ -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]; Loading Loading @@ -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)) Loading core/tests/coretests/src/android/os/VibratorInfoTest.java +19 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -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; Loading @@ -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; Loading @@ -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); } } } services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java +78 −24 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading @@ -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()); } } /** Loading @@ -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) { Loading services/core/java/com/android/server/vibrator/VibrationThread.java +3 −9 Original line number Diff line number Diff line Loading @@ -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); } Loading Loading @@ -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); } Loading Loading @@ -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; } Loading @@ -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()]), Loading services/core/java/com/android/server/vibrator/VibratorController.java +30 −7 Original line number Diff line number Diff line Loading @@ -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); } Loading Loading @@ -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() { Loading Loading @@ -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; Loading Loading @@ -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); Loading @@ -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
core/java/android/os/VibratorInfo.java +58 −6 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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); Loading @@ -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); Loading @@ -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 Loading @@ -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 Loading @@ -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. * Loading @@ -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; } Loading @@ -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); } /** Loading Loading @@ -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"); } Loading @@ -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]; Loading Loading @@ -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)) Loading
core/tests/coretests/src/android/os/VibratorInfoTest.java +19 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -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; Loading @@ -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; Loading @@ -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); } } }
services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java +78 −24 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading @@ -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()); } } /** Loading @@ -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) { Loading
services/core/java/com/android/server/vibrator/VibrationThread.java +3 −9 Original line number Diff line number Diff line Loading @@ -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); } Loading Loading @@ -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); } Loading Loading @@ -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; } Loading @@ -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()]), Loading
services/core/java/com/android/server/vibrator/VibratorController.java +30 −7 Original line number Diff line number Diff line Loading @@ -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); } Loading Loading @@ -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() { Loading Loading @@ -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; Loading Loading @@ -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); Loading @@ -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); } } }