diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 275c775b88bc..313e75aff5ce 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2679,6 +2679,16 @@
The waveform is described by a sequence of values for target amplitude, frequency and + * duration, that are forwarded to + * {@link VibrationEffect.WaveformBuilder#addRamp(float, float, int)}. + * + *
This method returns {@code null} if the pattern is also {@code null} or invalid. + * + * @param values The list of values describing the waveform as a sequence of target amplitude, + * frequency and duration. + * @param insistent {@code true} if the vibration should loop until it is cancelled. + */ + @Nullable + public static VibrationEffect createPwleWaveformVibration(@Nullable float[] values, + boolean insistent) { + try { + if (values == null) { + return null; + } + + int length = values.length; + // The waveform is described by triples (amplitude, frequency, duration) + if ((length == 0) || (length % 3 != 0)) { + return null; + } + + VibrationEffect.WaveformBuilder waveformBuilder = VibrationEffect.startWaveform(); + for (int i = 0; i < length; i += 3) { + waveformBuilder.addRamp(/* amplitude= */ values[i], /* frequency= */ values[i + 1], + /* duration= */ (int) values[i + 2]); + } + + if (insistent) { + return waveformBuilder.build(/* repeat= */ 0); + } + return waveformBuilder.build(); + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Error creating vibration PWLE waveform with pattern: " + + Arrays.toString(values)); + } + return null; + } + /** * Vibrate the device with given {@code effect}. * @@ -106,7 +153,10 @@ public final class VibratorHelper { */ public VibrationEffect createFallbackVibration(boolean insistent) { if (mVibrator.hasFrequencyControl()) { - return createChirpVibration(FALLBACK_CHIRP_RAMP_DURATION_MILLIS, insistent); + VibrationEffect effect = createPwleWaveformVibration(mFallbackPwlePattern, insistent); + if (effect != null) { + return effect; + } } return createWaveformVibration(mFallbackPattern, insistent); } @@ -118,29 +168,29 @@ public final class VibratorHelper { */ public VibrationEffect createDefaultVibration(boolean insistent) { if (mVibrator.hasFrequencyControl()) { - return createChirpVibration(DEFAULT_CHIRP_RAMP_DURATION_MILLIS, insistent); + VibrationEffect effect = createPwleWaveformVibration(mDefaultPwlePattern, insistent); + if (effect != null) { + return effect; + } } return createWaveformVibration(mDefaultPattern, insistent); } - private static VibrationEffect createChirpVibration(int rampDuration, boolean insistent) { - VibrationEffect.WaveformBuilder waveformBuilder = VibrationEffect.startWaveform() - .addStep(/* amplitude= */ 0, /* frequency= */ -0.85f, /* duration= */ 0) - .addRamp(/* amplitude= */ 1, /* frequency= */ -0.25f, rampDuration) - .addStep(/* amplitude= */ 1, /* frequency= */ -0.25f, CHIRP_LEVEL_DURATION_MILLIS) - .addRamp(/* amplitude= */ 0, /* frequency= */ -0.85f, rampDuration); - - if (insistent) { - return waveformBuilder - .addStep(/* amplitude= */ 0, CHIRP_LEVEL_DURATION_MILLIS) - .build(/* repeat= */ 0); + @Nullable + private static float[] getFloatArray(Resources resources, int resId) { + TypedArray array = resources.obtainTypedArray(resId); + try { + float[] values = new float[array.length()]; + for (int i = 0; i < values.length; i++) { + values[i] = array.getFloat(i, Float.NaN); + if (Float.isNaN(values[i])) { + return null; + } + } + return values; + } finally { + array.recycle(); } - - VibrationEffect singleBeat = waveformBuilder.build(); - return VibrationEffect.startComposition() - .addEffect(singleBeat) - .addEffect(singleBeat, /* delay= */ CHIRP_LEVEL_DURATION_MILLIS) - .compose(); } private static long[] getLongArray(Resources resources, int resId, int maxLength, long[] def) { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java index c77a474e032c..8bc0c6c65fa3 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java @@ -40,7 +40,10 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public class VibratorHelperTest extends UiServiceTestCase { + // OFF/ON vibration pattern private static final long[] CUSTOM_PATTERN = new long[] { 100, 200, 300, 400 }; + // (amplitude, frequency, duration) triples list + private static final float[] PWLE_PATTERN = new float[] { 1, 0, 100 }; @Mock private Vibrator mVibrator; @@ -58,12 +61,16 @@ public class VibratorHelperTest extends UiServiceTestCase { public void createWaveformVibration_insistent_createsRepeatingVibration() { assertRepeatingVibration( VibratorHelper.createWaveformVibration(CUSTOM_PATTERN, /* insistent= */ true)); + assertRepeatingVibration( + VibratorHelper.createPwleWaveformVibration(PWLE_PATTERN, /* insistent= */ true)); } @Test public void createWaveformVibration_nonInsistent_createsSingleShotVibration() { assertSingleVibration( VibratorHelper.createWaveformVibration(CUSTOM_PATTERN, /* insistent= */ false)); + assertSingleVibration( + VibratorHelper.createPwleWaveformVibration(PWLE_PATTERN, /* insistent= */ false)); } @Test @@ -71,6 +78,11 @@ public class VibratorHelperTest extends UiServiceTestCase { assertNull(VibratorHelper.createWaveformVibration(null, false)); assertNull(VibratorHelper.createWaveformVibration(new long[0], false)); assertNull(VibratorHelper.createWaveformVibration(new long[] { 0, 0 }, false)); + + assertNull(VibratorHelper.createPwleWaveformVibration(null, false)); + assertNull(VibratorHelper.createPwleWaveformVibration(new float[0], false)); + assertNull(VibratorHelper.createPwleWaveformVibration(new float[] { 0 }, false)); + assertNull(VibratorHelper.createPwleWaveformVibration(new float[] { 0, 0, 0 }, false)); } @Test