diff --git a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java index cce1ef4db3810..6d01123f6ead4 100644 --- a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java +++ b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java @@ -179,7 +179,9 @@ final class SetAmplitudeVibratorStep extends AbstractVibratorStep { while (i < segmentCount) { VibrationEffectSegment segment = segments.get(i); if (!(segment instanceof StepSegment) - || ((StepSegment) segment).getAmplitude() == 0) { + // play() will ignore segments with zero duration, so it's important that + // zero-duration segments don't affect this method. + || (segment.getDuration() > 0 && ((StepSegment) segment).getAmplitude() == 0)) { break; } timing += segment.getDuration(); diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java index d9a51a01ee5f2..01b2d0fa59386 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java @@ -19,6 +19,8 @@ package com.android.server.vibrator; import static android.os.VibrationEffect.VibrationParameter.targetAmplitude; import static android.os.VibrationEffect.VibrationParameter.targetFrequency; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -304,6 +306,80 @@ public class VibrationThreadTest { fakeVibrator.getEffectSegments(vibrationId)); } + @Test + public void vibrate_singleVibratorPatternWithZeroDurationSteps_skipsZeroDurationSteps() { + mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); + + VibrationEffect effect = VibrationEffect.createWaveform( + /* timings= */ new long[]{0, 100, 50, 100, 0, 0, 0, 50}, /* repeat= */ -1); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; + waitForCompletion(); + + verify(mManagerHooks).noteVibratorOn(eq(UID), eq(300L)); + verify(mManagerHooks).noteVibratorOff(eq(UID)); + + verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED); + assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse(); + + assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId)) + .isEqualTo(expectedOneShots(100L, 150L)); + } + + @Test + public void vibrate_singleVibratorPatternWithZeroDurationAndAmplitude_skipsZeroDurationSteps() { + mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); + + int[] amplitudes = new int[]{1, 2, 0, 3, 4, 5, 0, 6}; + VibrationEffect effect = VibrationEffect.createWaveform( + /* timings= */ new long[]{0, 100, 0, 50, 50, 0, 100, 50}, amplitudes, + /* repeat= */ -1); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; + waitForCompletion(); + + verify(mManagerHooks).noteVibratorOn(eq(UID), eq(350L)); + verify(mManagerHooks).noteVibratorOff(eq(UID)); + + verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED); + assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse(); + + assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId)) + .isEqualTo(expectedOneShots(200L, 50L)); + } + + @LargeTest + @Test + public void vibrate_singleVibratorRepeatingPatternWithZeroDurationSteps_repeatsEffectCorrectly() + throws Exception { + FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); + fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); + + VibrationEffect effect = VibrationEffect.createWaveform( + /* timings= */ new long[]{0, 200, 50, 100, 0, 50, 50, 100}, /* repeat= */ 0); + VibrationStepConductor conductor = startThreadAndDispatcher(effect); + long vibrationId = conductor.getVibration().id; + // We are expect this test to repeat the vibration effect twice, which would result in 5 + // segments being played: + // 200ms ON + // 150ms ON (100ms + 50ms, skips 0ms) + // 300ms ON (100ms + 200ms looping to the start and skipping first 0ms) + // 150ms ON (100ms + 50ms, skips 0ms) + // 300ms ON (100ms + 200ms looping to the start and skipping first 0ms) + assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibrationId).size() >= 5, + 5000L + TEST_TIMEOUT_MILLIS)); + conductor.notifyCancelled( + new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER), + /* immediate= */ false); + waitForCompletion(); + + verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER); + assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse(); + + assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).subList(0, 5)) + .isEqualTo(expectedOneShots(200L, 150L, 300L, 150L, 300L)); + } + @Test public void vibrate_singleVibratorRepeatingPwle_generatesLargestPwles() throws Exception { FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); @@ -1640,6 +1716,12 @@ public class VibrationThreadTest { /* frequencyHz= */ 0, (int) millis); } + private List expectedOneShots(long... millis) { + return Arrays.stream(millis) + .mapToObj(this::expectedOneShot) + .collect(Collectors.toList()); + } + private VibrationEffectSegment expectedPrebaked(int effectId) { return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM); }