diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java index d162c74aeea2a..00ce14fdfd283 100644 --- a/core/java/android/os/VibratorInfo.java +++ b/core/java/android/os/VibratorInfo.java @@ -573,21 +573,29 @@ public class VibratorInfo implements Parcelable { * supported frequency range is empty. */ public float getMaxAmplitude(float frequencyHz) { - if (isEmpty() || Float.isNaN(frequencyHz)) { + if (isEmpty() || Float.isNaN(frequencyHz) || !mFrequencyRangeHz.contains(frequencyHz)) { // Unsupported frequency requested, vibrator cannot play at this frequency. return 0; } - float position = (frequencyHz - mMinFrequencyHz) / mFrequencyResolutionHz; - int floorIndex = (int) Math.floor(position); - int ceilIndex = (int) Math.ceil(position); - if (floorIndex < 0 || floorIndex >= mMaxAmplitudes.length) { - return 0; - } - if (floorIndex != ceilIndex && ceilIndex < mMaxAmplitudes.length) { - // Value in between two mapped frequency values, use the lowest supported one. - return MathUtils.min(mMaxAmplitudes[floorIndex], mMaxAmplitudes[ceilIndex]); - } - return mMaxAmplitudes[floorIndex]; + + // Subtract minFrequencyHz to simplify offset calculations. + float mappingFreq = frequencyHz - mMinFrequencyHz; + + // Find the bucket to interpolate within. + // Any calculated index should be safe, except exactly equal to max amplitude can be + // one step too high, so constrain it to guarantee safety. + int startIdx = MathUtils.constrain( + /* amount= */ (int) Math.floor(mappingFreq / mFrequencyResolutionHz), + /* low= */ 0, /* high= */ mMaxAmplitudes.length - 1); + int nextIdx = MathUtils.constrain( + /* amount= */ startIdx + 1, + /* low= */ 0, /* high= */ mMaxAmplitudes.length - 1); + + // Linearly interpolate the amplitudes based on the frequency range of the bucket. + return MathUtils.constrainedMap( + mMaxAmplitudes[startIdx], mMaxAmplitudes[nextIdx], + startIdx * mFrequencyResolutionHz, nextIdx * mFrequencyResolutionHz, + mappingFreq); } /** Returns the raw list of maximum relative output accelerations from the vibrator. */ diff --git a/core/tests/coretests/src/android/os/VibratorInfoTest.java b/core/tests/coretests/src/android/os/VibratorInfoTest.java index 84253e6a33c4b..88766e2d97d4f 100644 --- a/core/tests/coretests/src/android/os/VibratorInfoTest.java +++ b/core/tests/coretests/src/android/os/VibratorInfoTest.java @@ -19,6 +19,7 @@ package android.os; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import android.hardware.vibrator.Braking; @@ -144,6 +145,7 @@ public class VibratorInfoTest { assertTrue( new VibratorInfo.Builder(TEST_VIBRATOR_ID).build().getFrequencyProfile().isEmpty()); } + @Test public void testFrequencyProfile_invalidValuesCreatesEmptyProfile() { // Invalid, contains NaN values or empty array. @@ -172,6 +174,31 @@ public class VibratorInfoTest { 150, 50, /* frequencyResolutionHz= */ 10, TEST_AMPLITUDE_MAP).isEmpty()); } + @Test + public void testGetFrequencyRangeHz_emptyProfileReturnsNull() { + assertNull(new VibratorInfo.FrequencyProfile( + Float.NaN, 50, 25, TEST_AMPLITUDE_MAP).getFrequencyRangeHz()); + assertNull(new VibratorInfo.FrequencyProfile( + 150, Float.NaN, 25, TEST_AMPLITUDE_MAP).getFrequencyRangeHz()); + assertNull(new VibratorInfo.FrequencyProfile( + 150, 50, Float.NaN, TEST_AMPLITUDE_MAP).getFrequencyRangeHz()); + assertNull(new VibratorInfo.FrequencyProfile(150, 50, 25, null).getFrequencyRangeHz()); + } + + @Test + public void testGetFrequencyRangeHz_validProfileReturnsMappedValues() { + VibratorInfo.FrequencyProfile profile = new VibratorInfo.FrequencyProfile( + /* resonantFrequencyHz= */ 150, + /* minFrequencyHz= */ 50, + /* frequencyResolutionHz= */ 25, + /* maxAmplitudes= */ new float[]{ + /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, + /* 200Hz= */ 0.8f}); + + assertEquals(50f, profile.getFrequencyRangeHz().getLower(), TEST_TOLERANCE); + assertEquals(200f, profile.getFrequencyRangeHz().getUpper(), TEST_TOLERANCE); + } + @Test public void testGetMaxAmplitude_emptyProfileReturnsAlwaysZero() { VibratorInfo.FrequencyProfile profile = EMPTY_FREQUENCY_PROFILE; @@ -191,7 +218,7 @@ public class VibratorInfoTest { } @Test - public void testGetMaxAmplitude_validprofileReturnsMappedValues() { + public void testGetMaxAmplitude_validProfileReturnsMappedValues() { VibratorInfo.FrequencyProfile profile = new VibratorInfo.FrequencyProfile( /* resonantFrequencyHz= */ 150, /* minFrequencyHz= */ 50, @@ -200,16 +227,23 @@ public class VibratorInfoTest { /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f}); + // Values in the max amplitudes array should return exact measurement. assertEquals(1f, profile.getMaxAmplitude(150f), TEST_TOLERANCE); assertEquals(0.9f, profile.getMaxAmplitude(175f), TEST_TOLERANCE); assertEquals(0.8f, profile.getMaxAmplitude(125f), TEST_TOLERANCE); + + // Min and max frequencies should return exact measurement from array. assertEquals(0.8f, profile.getMaxAmplitude(200f), TEST_TOLERANCE); assertEquals(0.1f, profile.getMaxAmplitude(50f), TEST_TOLERANCE); - // 145Hz maps to the max amplitude for 125Hz, which is lower. - assertEquals(0.8f, profile.getMaxAmplitude(145f), TEST_TOLERANCE); // 145Hz - // 185Hz maps to the max amplitude for 200Hz, which is lower. - assertEquals(0.8f, profile.getMaxAmplitude(185f), TEST_TOLERANCE); // 185Hz + // Values outside [50Hz, 200Hz] just return 0. + assertEquals(0f, profile.getMaxAmplitude(49f), TEST_TOLERANCE); + assertEquals(0f, profile.getMaxAmplitude(201f), TEST_TOLERANCE); + + // 145Hz maps to linear value between 125Hz and 150Hz max amplitudes 0.8 and 1. + assertEquals(0.96f, profile.getMaxAmplitude(145f), TEST_TOLERANCE); + // 185Hz maps to linear value between 175Hz and 200Hz max amplitudes 0.9 and 0.8. + assertEquals(0.86f, profile.getMaxAmplitude(185f), TEST_TOLERANCE); } @Test