Loading core/java/android/os/VibratorInfo.java +14 −44 Original line number Diff line number Diff line Loading @@ -23,8 +23,8 @@ import android.hardware.vibrator.IVibrator; import android.os.vibrator.Flags; import android.util.IndentingPrintWriter; import android.util.MathUtils; import android.util.Pair; import android.util.Range; import android.util.Slog; import android.util.SparseBooleanArray; import android.util.SparseIntArray; Loading @@ -32,7 +32,6 @@ import com.android.internal.util.Preconditions; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Objects; Loading Loading @@ -691,13 +690,23 @@ public class VibratorInfo implements Parcelable { mResonantFrequencyHz = resonantFrequencyHz; boolean isValid = !Float.isNaN(resonantFrequencyHz) && (resonantFrequencyHz > 0) && (frequenciesHz != null && outputAccelerationsGs != null) boolean isValid = (frequenciesHz != null && outputAccelerationsGs != null) && (frequenciesHz.length == outputAccelerationsGs.length) && (frequenciesHz.length > 0); if (Flags.decoupleFrequencyProfileFromResonance()) { isValid = isValid && (Float.isNaN(resonantFrequencyHz) || (resonantFrequencyHz > 0)); } else { isValid = isValid && !Float.isNaN(resonantFrequencyHz) && (resonantFrequencyHz > 0); } if (!isValid) { Slog.e(TAG, "Invalid frequency profile received from HAL." + " resonantFrequencyHz=" + resonantFrequencyHz + ", frequenciesHz=" + Arrays.toString(frequenciesHz) + ", outputAccelerationsGs=" + Arrays.toString(outputAccelerationsGs)); mFrequenciesHz = null; mOutputAccelerationsGs = null; mMinFrequencyHz = Float.NaN; Loading Loading @@ -916,45 +925,6 @@ public class VibratorInfo implements Parcelable { return new FrequencyProfile[size]; } }; private static void deduplicateAndSortList(List<Pair<Float, Float>> list) { if (list == null || list.size() < 2) { return; // Nothing to dedupe } list.sort(Comparator.comparing(pair -> pair.first)); // Remove duplicates from the list int writeIndex = 1; for (int i = 1; i < list.size(); i++) { Pair<Float, Float> currentPair = list.get(i); Pair<Float, Float> previousPair = list.get(writeIndex - 1); if (currentPair.first.compareTo(previousPair.first) != 0) { list.set(writeIndex++, currentPair); } } list.subList(writeIndex, list.size()).clear(); } private static ArrayList<Pair<Float, Float>> extractFrequencyToOutputAccelerationData( float[] frequencies, float[] outputAccelerations) { if (frequencies == null || outputAccelerations == null || frequencies.length == 0 || frequencies.length != outputAccelerations.length) { return new ArrayList<>(); // Return empty list for invalid or mismatched data } ArrayList<Pair<Float, Float>> frequencyToOutputAccelerationList = new ArrayList<>( frequencies.length); for (int i = 0; i < frequencies.length; i++) { frequencyToOutputAccelerationList.add( new Pair<>(frequencies[i], outputAccelerations[i])); } return frequencyToOutputAccelerationList; } } /** Loading core/java/android/os/vibrator/flags.aconfig +10 −0 Original line number Diff line number Diff line Loading @@ -176,3 +176,13 @@ flag { purpose: PURPOSE_FEATURE } } flag { namespace: "haptics" name: "decouple_frequency_profile_from_resonance" description: "Removes frequency profiles dependency on resonant frequency when not present" bug: "430503750" metadata { purpose: PURPOSE_BUGFIX } } core/tests/vibrator/src/android/os/VibratorInfoTest.java +34 −1 Original line number Diff line number Diff line Loading @@ -26,14 +26,23 @@ import static org.junit.Assert.assertTrue; import android.hardware.vibrator.Braking; import android.hardware.vibrator.IVibrator; import android.os.vibrator.Flags; import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.util.Range; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class VibratorInfoTest { @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); private static final float TEST_TOLERANCE = 1e-5f; private static final int TEST_VIBRATOR_ID = 1; Loading Loading @@ -195,7 +204,8 @@ public class VibratorInfoTest { } @Test public void testFrequencyProfile_invalidValuesCreatesEmptyProfile() { @RequiresFlagsDisabled(Flags.FLAG_DECOUPLE_FREQUENCY_PROFILE_FROM_RESONANCE) public void testFrequencyProfile_flagDisabled_invalidValuesCreatesEmptyProfile() { // Invalid resonant frequency. assertThat(new VibratorInfo.FrequencyProfile(Float.NaN, TEST_FREQUENCIES, TEST_OUTPUT_ACCELERATIONS).isEmpty()).isTrue(); Loading @@ -210,6 +220,29 @@ public class VibratorInfoTest { /*outputAccelerationsGs=*/ new float[]{0.8f, 1.0f, 2.0f}).isEmpty()).isTrue(); } @Test @RequiresFlagsEnabled(Flags.FLAG_DECOUPLE_FREQUENCY_PROFILE_FROM_RESONANCE) public void testFrequencyProfile_invalidValuesCreatesEmptyProfile() { // Negative resonant frequency. assertThat(new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/-1f, TEST_FREQUENCIES, TEST_OUTPUT_ACCELERATIONS).isEmpty()).isTrue(); // No frequency-acceleration data assertThat(new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/150f, /*frequenciesHz=*/ null, /*outputAccelerationsGs=*/ null).isEmpty()).isTrue(); // Mismatching frequency and output acceleration lists assertThat(new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/150f, /*frequenciesHz=*/ new float[]{30f, 40f, 50f, 100f}, /*outputAccelerationsGs=*/ new float[]{0.8f, 1.0f, 2.0f}).isEmpty()).isTrue(); } @Test @RequiresFlagsEnabled(Flags.FLAG_DECOUPLE_FREQUENCY_PROFILE_FROM_RESONANCE) public void testFrequencyProfile_creationWithoutResonantFrequency_isValid() { // Frequency profile is not dependent on resonant frequency. assertThat(new VibratorInfo.FrequencyProfile(Float.NaN, TEST_FREQUENCIES, TEST_OUTPUT_ACCELERATIONS).isEmpty()).isFalse(); } @Test public void testGetFrequenciesAndOutputAccelerations_noFrequencyAccelerationData_returnNull() { VibratorInfo.FrequencyProfile emptyFrequencyProfile = Loading services/core/java/com/android/server/vibrator/PwleSegmentsValidator.java +46 −16 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.server.vibrator; import android.hardware.vibrator.IVibrator; import android.os.VibratorInfo; import android.os.vibrator.BasicPwleSegment; import android.os.vibrator.Flags; import android.os.vibrator.PwleSegment; import android.os.vibrator.VibrationEffectSegment; Loading @@ -28,17 +29,15 @@ import java.util.List; * Validates {@link PwleSegment} and {@link BasicPwleSegment} instances to ensure they are * compatible with the device's capabilities. * * <p>This validator performs the following checks: * <p>This validator enforced the following rules: * <ul> * <li>For {@link PwleSegment}: * <li>A {@link BasicPwleSegment} is always considered <b>invalid</b>. Its presence indicates * a failure in the steps that should have converted it to a {@link PwleSegment}.</li> * <li>For a {@link PwleSegment} to be valid, all of the following must be true: * <ul> * <li>Verifies that the device supports {@link IVibrator#CAP_COMPOSE_PWLE_EFFECTS_V2}. * <li>Verifies that each segment's start and end frequencies fall within the supported range. * </ul> * </li> * <li>For {@link BasicPwleSegment}: * <ul> * <li>Verifies that the device supports {@link IVibrator#CAP_COMPOSE_PWLE_EFFECTS_V2}. * <li>The device must support {@link IVibrator#CAP_COMPOSE_PWLE_EFFECTS_V2}.</li> * <li>The device has a valid {@link VibratorInfo.FrequencyProfile}.</li> * <li>The segment's start and end frequencies must fall within the supported range.</li> * </ul> * </li> * </ul> Loading @@ -49,8 +48,11 @@ final class PwleSegmentsValidator implements VibrationSegmentsValidator { public boolean hasValidSegments(VibratorInfo info, List<VibrationEffectSegment> segments) { boolean hasPwleCapability = info.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2); float minFrequency = info.getFrequencyProfile().getMinFrequencyHz(); float maxFrequency = info.getFrequencyProfile().getMaxFrequencyHz(); VibratorInfo.FrequencyProfile frequencyProfile = info.getFrequencyProfile(); if (!Flags.decoupleFrequencyProfileFromResonance()) { float minFrequency = frequencyProfile.getMinFrequencyHz(); float maxFrequency = frequencyProfile.getMaxFrequencyHz(); for (VibrationEffectSegment segment : segments) { if (segment instanceof BasicPwleSegment && !hasPwleCapability) { Loading @@ -68,4 +70,32 @@ final class PwleSegmentsValidator implements VibrationSegmentsValidator { return true; } for (VibrationEffectSegment segment : segments) { if (segment instanceof BasicPwleSegment) { return false; } if (segment instanceof PwleSegment pwleSegment) { if (!hasPwleCapability || isFrequencyOutOfRange(pwleSegment.getStartFrequencyHz(), frequencyProfile) || isFrequencyOutOfRange(pwleSegment.getEndFrequencyHz(), frequencyProfile)) { return false; } } } return true; } /** * Checks if the given frequency is unsupported. * * <p>A frequency is considered unsupported if it's outside the supported range or if the * frequency profile is empty. */ private boolean isFrequencyOutOfRange(float frequency, VibratorInfo.FrequencyProfile profile) { return profile.isEmpty() || frequency < profile.getMinFrequencyHz() || frequency > profile.getMaxFrequencyHz(); } } services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java +65 −1 Original line number Diff line number Diff line Loading @@ -77,7 +77,8 @@ public class DeviceAdapterTest { private static final int PWLE_WITHOUT_FREQUENCIES_VIBRATOR_ID = 3; private static final int PWLE_V2_VIBRATOR_ID = 4; private static final int PWLE_V2_BASIC_VIBRATOR_ID = 5; private static final int BASIC_VIBRATOR_ID = 6; private static final int PWLE_V2_EMPTY_PROFILE_VIBRATOR_ID = 6; private static final int BASIC_VIBRATOR_ID = 7; private static final float TEST_MIN_FREQUENCY = 50; private static final float TEST_RESONANT_FREQUENCY = 150; private static final float TEST_FREQUENCY_RESOLUTION = 25; Loading Loading @@ -133,6 +134,8 @@ public class DeviceAdapterTest { vibrators.put(PWLE_V2_BASIC_VIBRATOR_ID, createPwleV2Vibrator(PWLE_V2_VIBRATOR_ID, TEST_BASIC_FREQUENCIES_HZ, TEST_BASIC_OUTPUT_ACCELERATIONS_GS)); vibrators.put(PWLE_V2_EMPTY_PROFILE_VIBRATOR_ID, createPwleV2VibratorWithEmptyProfile(PWLE_V2_EMPTY_PROFILE_VIBRATOR_ID)); vibrators.put(BASIC_VIBRATOR_ID, createBasicVibrator(BASIC_VIBRATOR_ID)); for (int i = 0; i < vibrators.size(); i++) { vibrators.valueAt(i).init((vibratorId, vibrationId, stepId) -> {}); Loading Loading @@ -322,6 +325,12 @@ public class DeviceAdapterTest { new StepSegment(1, 175, 10), new StepSegment(1, 0, 50)), /* repeatIndex= */ 1)) .addVibrator(PWLE_V2_EMPTY_PROFILE_VIBRATOR_ID, new VibrationEffect.Composed(Arrays.asList( // Step(amplitude, frequencyHz, duration) new StepSegment(1, 175, 10), new StepSegment(1, 0, 50)), /* repeatIndex= */ 1)) .combine(); assertThat(CombinedVibration.createParallel(effect).adapt(mAdapter)).isEqualTo(expected); Loading Loading @@ -415,6 +424,39 @@ public class DeviceAdapterTest { assertThat(mAdapter.adaptToVibrator(PWLE_V2_VIBRATOR_ID, effect)).isNull(); } @Test @EnableFlags(value = {Flags.FLAG_NORMALIZED_PWLE_EFFECTS, Flags.FLAG_DECOUPLE_FREQUENCY_PROFILE_FROM_RESONANCE}) public void testPwleSegment_withEmptyProfile_returnsNull() { VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList( new PwleSegment(1, 0.2f, 30, 60, 20), new PwleSegment(0.8f, 0.2f, 60, 100, 100), new PwleSegment(0.65f, 0.65f, 100, 50, 50)), /* repeatIndex= */ 1); assertThat(mAdapter.adaptToVibrator(PWLE_V2_EMPTY_PROFILE_VIBRATOR_ID, effect)).isNull(); } @Test @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) @DisableFlags(Flags.FLAG_DECOUPLE_FREQUENCY_PROFILE_FROM_RESONANCE) public void testPwleSegment_withEmptyProfile_flagDisabled_returnsAdaptedSegments() { VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList( new PwleSegment(1, 0.2f, 30, 60, 20), new PwleSegment(0.8f, 0.2f, 60, 100, 100), new PwleSegment(0.65f, 0.65f, 100, 50, 50)), /* repeatIndex= */ 1); VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList( new PwleSegment(1, 0.2f, 30, 60, 20), new PwleSegment(0.8f, 0.2f, 60, 100, 100), new PwleSegment(0.65f, 0.65f, 100, 50, 50)), /* repeatIndex= */ 1); assertThat(mAdapter.adaptToVibrator(PWLE_V2_EMPTY_PROFILE_VIBRATOR_ID, effect)).isEqualTo( expected); } @Test @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) public void testBasicPwleSegment_withoutPwleV2Capability_returnsNull() { Loading Loading @@ -448,6 +490,19 @@ public class DeviceAdapterTest { assertThat(mAdapter.adaptToVibrator(PWLE_V2_BASIC_VIBRATOR_ID, effect)).isEqualTo(expected); } @Test @EnableFlags({Flags.FLAG_NORMALIZED_PWLE_EFFECTS, Flags.FLAG_DECOUPLE_FREQUENCY_PROFILE_FROM_RESONANCE}) public void testBasicPwleSegment_withEmptyProfile_returnsNull() { VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList( new BasicPwleSegment(0.0f, 0.5f, 0.0f, 0.5f, 20), new BasicPwleSegment(0.5f, 1.0f, 0.5f, 1.0f, 100), new BasicPwleSegment(1.0f, 0.0f, 1.0f, 0.5f, 100)), /* repeatIndex= */ 1); assertThat(mAdapter.adaptToVibrator(PWLE_V2_EMPTY_PROFILE_VIBRATOR_ID, effect)).isNull(); } @Test @DisableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY) public void testPrimitiveWithRelativeDelay_withoutFlag_returnsNull() { Loading Loading @@ -615,6 +670,15 @@ public class DeviceAdapterTest { return helper.newInitializedHalVibrator(vibratorId, mHalCallbacks); } private HalVibrator createPwleV2VibratorWithEmptyProfile(int vibratorId) { HalVibratorHelper helper = createVibratorHelperWithEffects( IVibrator.CAP_GET_RESONANT_FREQUENCY, IVibrator.CAP_FREQUENCY_CONTROL, IVibrator.CAP_COMPOSE_EFFECTS, IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2); helper.setFrequenciesHz(null); helper.setOutputAccelerationsGs(null); return helper.newInitializedHalVibrator(vibratorId, mHalCallbacks); } private HalVibratorHelper createVibratorHelperWithEffects(int... capabilities) { HalVibratorHelper helper = new HalVibratorHelper(mTestLooper.getLooper()); helper.setCapabilities(capabilities); Loading Loading
core/java/android/os/VibratorInfo.java +14 −44 Original line number Diff line number Diff line Loading @@ -23,8 +23,8 @@ import android.hardware.vibrator.IVibrator; import android.os.vibrator.Flags; import android.util.IndentingPrintWriter; import android.util.MathUtils; import android.util.Pair; import android.util.Range; import android.util.Slog; import android.util.SparseBooleanArray; import android.util.SparseIntArray; Loading @@ -32,7 +32,6 @@ import com.android.internal.util.Preconditions; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Objects; Loading Loading @@ -691,13 +690,23 @@ public class VibratorInfo implements Parcelable { mResonantFrequencyHz = resonantFrequencyHz; boolean isValid = !Float.isNaN(resonantFrequencyHz) && (resonantFrequencyHz > 0) && (frequenciesHz != null && outputAccelerationsGs != null) boolean isValid = (frequenciesHz != null && outputAccelerationsGs != null) && (frequenciesHz.length == outputAccelerationsGs.length) && (frequenciesHz.length > 0); if (Flags.decoupleFrequencyProfileFromResonance()) { isValid = isValid && (Float.isNaN(resonantFrequencyHz) || (resonantFrequencyHz > 0)); } else { isValid = isValid && !Float.isNaN(resonantFrequencyHz) && (resonantFrequencyHz > 0); } if (!isValid) { Slog.e(TAG, "Invalid frequency profile received from HAL." + " resonantFrequencyHz=" + resonantFrequencyHz + ", frequenciesHz=" + Arrays.toString(frequenciesHz) + ", outputAccelerationsGs=" + Arrays.toString(outputAccelerationsGs)); mFrequenciesHz = null; mOutputAccelerationsGs = null; mMinFrequencyHz = Float.NaN; Loading Loading @@ -916,45 +925,6 @@ public class VibratorInfo implements Parcelable { return new FrequencyProfile[size]; } }; private static void deduplicateAndSortList(List<Pair<Float, Float>> list) { if (list == null || list.size() < 2) { return; // Nothing to dedupe } list.sort(Comparator.comparing(pair -> pair.first)); // Remove duplicates from the list int writeIndex = 1; for (int i = 1; i < list.size(); i++) { Pair<Float, Float> currentPair = list.get(i); Pair<Float, Float> previousPair = list.get(writeIndex - 1); if (currentPair.first.compareTo(previousPair.first) != 0) { list.set(writeIndex++, currentPair); } } list.subList(writeIndex, list.size()).clear(); } private static ArrayList<Pair<Float, Float>> extractFrequencyToOutputAccelerationData( float[] frequencies, float[] outputAccelerations) { if (frequencies == null || outputAccelerations == null || frequencies.length == 0 || frequencies.length != outputAccelerations.length) { return new ArrayList<>(); // Return empty list for invalid or mismatched data } ArrayList<Pair<Float, Float>> frequencyToOutputAccelerationList = new ArrayList<>( frequencies.length); for (int i = 0; i < frequencies.length; i++) { frequencyToOutputAccelerationList.add( new Pair<>(frequencies[i], outputAccelerations[i])); } return frequencyToOutputAccelerationList; } } /** Loading
core/java/android/os/vibrator/flags.aconfig +10 −0 Original line number Diff line number Diff line Loading @@ -176,3 +176,13 @@ flag { purpose: PURPOSE_FEATURE } } flag { namespace: "haptics" name: "decouple_frequency_profile_from_resonance" description: "Removes frequency profiles dependency on resonant frequency when not present" bug: "430503750" metadata { purpose: PURPOSE_BUGFIX } }
core/tests/vibrator/src/android/os/VibratorInfoTest.java +34 −1 Original line number Diff line number Diff line Loading @@ -26,14 +26,23 @@ import static org.junit.Assert.assertTrue; import android.hardware.vibrator.Braking; import android.hardware.vibrator.IVibrator; import android.os.vibrator.Flags; import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.util.Range; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class VibratorInfoTest { @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); private static final float TEST_TOLERANCE = 1e-5f; private static final int TEST_VIBRATOR_ID = 1; Loading Loading @@ -195,7 +204,8 @@ public class VibratorInfoTest { } @Test public void testFrequencyProfile_invalidValuesCreatesEmptyProfile() { @RequiresFlagsDisabled(Flags.FLAG_DECOUPLE_FREQUENCY_PROFILE_FROM_RESONANCE) public void testFrequencyProfile_flagDisabled_invalidValuesCreatesEmptyProfile() { // Invalid resonant frequency. assertThat(new VibratorInfo.FrequencyProfile(Float.NaN, TEST_FREQUENCIES, TEST_OUTPUT_ACCELERATIONS).isEmpty()).isTrue(); Loading @@ -210,6 +220,29 @@ public class VibratorInfoTest { /*outputAccelerationsGs=*/ new float[]{0.8f, 1.0f, 2.0f}).isEmpty()).isTrue(); } @Test @RequiresFlagsEnabled(Flags.FLAG_DECOUPLE_FREQUENCY_PROFILE_FROM_RESONANCE) public void testFrequencyProfile_invalidValuesCreatesEmptyProfile() { // Negative resonant frequency. assertThat(new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/-1f, TEST_FREQUENCIES, TEST_OUTPUT_ACCELERATIONS).isEmpty()).isTrue(); // No frequency-acceleration data assertThat(new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/150f, /*frequenciesHz=*/ null, /*outputAccelerationsGs=*/ null).isEmpty()).isTrue(); // Mismatching frequency and output acceleration lists assertThat(new VibratorInfo.FrequencyProfile(/*resonantFrequencyHz=*/150f, /*frequenciesHz=*/ new float[]{30f, 40f, 50f, 100f}, /*outputAccelerationsGs=*/ new float[]{0.8f, 1.0f, 2.0f}).isEmpty()).isTrue(); } @Test @RequiresFlagsEnabled(Flags.FLAG_DECOUPLE_FREQUENCY_PROFILE_FROM_RESONANCE) public void testFrequencyProfile_creationWithoutResonantFrequency_isValid() { // Frequency profile is not dependent on resonant frequency. assertThat(new VibratorInfo.FrequencyProfile(Float.NaN, TEST_FREQUENCIES, TEST_OUTPUT_ACCELERATIONS).isEmpty()).isFalse(); } @Test public void testGetFrequenciesAndOutputAccelerations_noFrequencyAccelerationData_returnNull() { VibratorInfo.FrequencyProfile emptyFrequencyProfile = Loading
services/core/java/com/android/server/vibrator/PwleSegmentsValidator.java +46 −16 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.server.vibrator; import android.hardware.vibrator.IVibrator; import android.os.VibratorInfo; import android.os.vibrator.BasicPwleSegment; import android.os.vibrator.Flags; import android.os.vibrator.PwleSegment; import android.os.vibrator.VibrationEffectSegment; Loading @@ -28,17 +29,15 @@ import java.util.List; * Validates {@link PwleSegment} and {@link BasicPwleSegment} instances to ensure they are * compatible with the device's capabilities. * * <p>This validator performs the following checks: * <p>This validator enforced the following rules: * <ul> * <li>For {@link PwleSegment}: * <li>A {@link BasicPwleSegment} is always considered <b>invalid</b>. Its presence indicates * a failure in the steps that should have converted it to a {@link PwleSegment}.</li> * <li>For a {@link PwleSegment} to be valid, all of the following must be true: * <ul> * <li>Verifies that the device supports {@link IVibrator#CAP_COMPOSE_PWLE_EFFECTS_V2}. * <li>Verifies that each segment's start and end frequencies fall within the supported range. * </ul> * </li> * <li>For {@link BasicPwleSegment}: * <ul> * <li>Verifies that the device supports {@link IVibrator#CAP_COMPOSE_PWLE_EFFECTS_V2}. * <li>The device must support {@link IVibrator#CAP_COMPOSE_PWLE_EFFECTS_V2}.</li> * <li>The device has a valid {@link VibratorInfo.FrequencyProfile}.</li> * <li>The segment's start and end frequencies must fall within the supported range.</li> * </ul> * </li> * </ul> Loading @@ -49,8 +48,11 @@ final class PwleSegmentsValidator implements VibrationSegmentsValidator { public boolean hasValidSegments(VibratorInfo info, List<VibrationEffectSegment> segments) { boolean hasPwleCapability = info.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2); float minFrequency = info.getFrequencyProfile().getMinFrequencyHz(); float maxFrequency = info.getFrequencyProfile().getMaxFrequencyHz(); VibratorInfo.FrequencyProfile frequencyProfile = info.getFrequencyProfile(); if (!Flags.decoupleFrequencyProfileFromResonance()) { float minFrequency = frequencyProfile.getMinFrequencyHz(); float maxFrequency = frequencyProfile.getMaxFrequencyHz(); for (VibrationEffectSegment segment : segments) { if (segment instanceof BasicPwleSegment && !hasPwleCapability) { Loading @@ -68,4 +70,32 @@ final class PwleSegmentsValidator implements VibrationSegmentsValidator { return true; } for (VibrationEffectSegment segment : segments) { if (segment instanceof BasicPwleSegment) { return false; } if (segment instanceof PwleSegment pwleSegment) { if (!hasPwleCapability || isFrequencyOutOfRange(pwleSegment.getStartFrequencyHz(), frequencyProfile) || isFrequencyOutOfRange(pwleSegment.getEndFrequencyHz(), frequencyProfile)) { return false; } } } return true; } /** * Checks if the given frequency is unsupported. * * <p>A frequency is considered unsupported if it's outside the supported range or if the * frequency profile is empty. */ private boolean isFrequencyOutOfRange(float frequency, VibratorInfo.FrequencyProfile profile) { return profile.isEmpty() || frequency < profile.getMinFrequencyHz() || frequency > profile.getMaxFrequencyHz(); } }
services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java +65 −1 Original line number Diff line number Diff line Loading @@ -77,7 +77,8 @@ public class DeviceAdapterTest { private static final int PWLE_WITHOUT_FREQUENCIES_VIBRATOR_ID = 3; private static final int PWLE_V2_VIBRATOR_ID = 4; private static final int PWLE_V2_BASIC_VIBRATOR_ID = 5; private static final int BASIC_VIBRATOR_ID = 6; private static final int PWLE_V2_EMPTY_PROFILE_VIBRATOR_ID = 6; private static final int BASIC_VIBRATOR_ID = 7; private static final float TEST_MIN_FREQUENCY = 50; private static final float TEST_RESONANT_FREQUENCY = 150; private static final float TEST_FREQUENCY_RESOLUTION = 25; Loading Loading @@ -133,6 +134,8 @@ public class DeviceAdapterTest { vibrators.put(PWLE_V2_BASIC_VIBRATOR_ID, createPwleV2Vibrator(PWLE_V2_VIBRATOR_ID, TEST_BASIC_FREQUENCIES_HZ, TEST_BASIC_OUTPUT_ACCELERATIONS_GS)); vibrators.put(PWLE_V2_EMPTY_PROFILE_VIBRATOR_ID, createPwleV2VibratorWithEmptyProfile(PWLE_V2_EMPTY_PROFILE_VIBRATOR_ID)); vibrators.put(BASIC_VIBRATOR_ID, createBasicVibrator(BASIC_VIBRATOR_ID)); for (int i = 0; i < vibrators.size(); i++) { vibrators.valueAt(i).init((vibratorId, vibrationId, stepId) -> {}); Loading Loading @@ -322,6 +325,12 @@ public class DeviceAdapterTest { new StepSegment(1, 175, 10), new StepSegment(1, 0, 50)), /* repeatIndex= */ 1)) .addVibrator(PWLE_V2_EMPTY_PROFILE_VIBRATOR_ID, new VibrationEffect.Composed(Arrays.asList( // Step(amplitude, frequencyHz, duration) new StepSegment(1, 175, 10), new StepSegment(1, 0, 50)), /* repeatIndex= */ 1)) .combine(); assertThat(CombinedVibration.createParallel(effect).adapt(mAdapter)).isEqualTo(expected); Loading Loading @@ -415,6 +424,39 @@ public class DeviceAdapterTest { assertThat(mAdapter.adaptToVibrator(PWLE_V2_VIBRATOR_ID, effect)).isNull(); } @Test @EnableFlags(value = {Flags.FLAG_NORMALIZED_PWLE_EFFECTS, Flags.FLAG_DECOUPLE_FREQUENCY_PROFILE_FROM_RESONANCE}) public void testPwleSegment_withEmptyProfile_returnsNull() { VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList( new PwleSegment(1, 0.2f, 30, 60, 20), new PwleSegment(0.8f, 0.2f, 60, 100, 100), new PwleSegment(0.65f, 0.65f, 100, 50, 50)), /* repeatIndex= */ 1); assertThat(mAdapter.adaptToVibrator(PWLE_V2_EMPTY_PROFILE_VIBRATOR_ID, effect)).isNull(); } @Test @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) @DisableFlags(Flags.FLAG_DECOUPLE_FREQUENCY_PROFILE_FROM_RESONANCE) public void testPwleSegment_withEmptyProfile_flagDisabled_returnsAdaptedSegments() { VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList( new PwleSegment(1, 0.2f, 30, 60, 20), new PwleSegment(0.8f, 0.2f, 60, 100, 100), new PwleSegment(0.65f, 0.65f, 100, 50, 50)), /* repeatIndex= */ 1); VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList( new PwleSegment(1, 0.2f, 30, 60, 20), new PwleSegment(0.8f, 0.2f, 60, 100, 100), new PwleSegment(0.65f, 0.65f, 100, 50, 50)), /* repeatIndex= */ 1); assertThat(mAdapter.adaptToVibrator(PWLE_V2_EMPTY_PROFILE_VIBRATOR_ID, effect)).isEqualTo( expected); } @Test @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) public void testBasicPwleSegment_withoutPwleV2Capability_returnsNull() { Loading Loading @@ -448,6 +490,19 @@ public class DeviceAdapterTest { assertThat(mAdapter.adaptToVibrator(PWLE_V2_BASIC_VIBRATOR_ID, effect)).isEqualTo(expected); } @Test @EnableFlags({Flags.FLAG_NORMALIZED_PWLE_EFFECTS, Flags.FLAG_DECOUPLE_FREQUENCY_PROFILE_FROM_RESONANCE}) public void testBasicPwleSegment_withEmptyProfile_returnsNull() { VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList( new BasicPwleSegment(0.0f, 0.5f, 0.0f, 0.5f, 20), new BasicPwleSegment(0.5f, 1.0f, 0.5f, 1.0f, 100), new BasicPwleSegment(1.0f, 0.0f, 1.0f, 0.5f, 100)), /* repeatIndex= */ 1); assertThat(mAdapter.adaptToVibrator(PWLE_V2_EMPTY_PROFILE_VIBRATOR_ID, effect)).isNull(); } @Test @DisableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY) public void testPrimitiveWithRelativeDelay_withoutFlag_returnsNull() { Loading Loading @@ -615,6 +670,15 @@ public class DeviceAdapterTest { return helper.newInitializedHalVibrator(vibratorId, mHalCallbacks); } private HalVibrator createPwleV2VibratorWithEmptyProfile(int vibratorId) { HalVibratorHelper helper = createVibratorHelperWithEffects( IVibrator.CAP_GET_RESONANT_FREQUENCY, IVibrator.CAP_FREQUENCY_CONTROL, IVibrator.CAP_COMPOSE_EFFECTS, IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2); helper.setFrequenciesHz(null); helper.setOutputAccelerationsGs(null); return helper.newInitializedHalVibrator(vibratorId, mHalCallbacks); } private HalVibratorHelper createVibratorHelperWithEffects(int... capabilities) { HalVibratorHelper helper = new HalVibratorHelper(mTestLooper.getLooper()); helper.setCapabilities(capabilities); Loading