Loading core/api/test-current.txt +2 −0 Original line number Diff line number Diff line Loading @@ -1908,6 +1908,7 @@ package android.os { } public abstract class VibrationEffect implements android.os.Parcelable { method @Nullable public abstract long[] computeCreateWaveformOffOnTimingsOrNull(); method public static android.os.VibrationEffect get(int); method public static android.os.VibrationEffect get(int, boolean); method @Nullable public static android.os.VibrationEffect get(android.net.Uri, android.content.Context); Loading @@ -1922,6 +1923,7 @@ package android.os { } public static final class VibrationEffect.Composed extends android.os.VibrationEffect { method @Nullable public long[] computeCreateWaveformOffOnTimingsOrNull(); method public long getDuration(); method public int getRepeatIndex(); method @NonNull public java.util.List<android.os.vibrator.VibrationEffectSegment> getSegments(); Loading core/java/android/os/VibrationEffect.java +95 −0 Original line number Diff line number Diff line Loading @@ -226,6 +226,31 @@ public abstract class VibrationEffect implements Parcelable { return createWaveform(timings, amplitudes, repeat); } /** * Computes a legacy vibration pattern (i.e. a pattern with duration values for "off/on" * vibration components) that is equivalent to this VibrationEffect. * * <p>All non-repeating effects created with {@link #createWaveform(int[], int)} are convertible * into an equivalent vibration pattern with this method. It is not guaranteed that an effect * created with other means becomes converted into an equivalent legacy vibration pattern, even * if it has an equivalent vibration pattern. If this method is unable to create an equivalent * vibration pattern for such effects, it will return {@code null}. * * <p>Note that a valid equivalent long[] pattern cannot be created for an effect that has any * form of repeating behavior, regardless of how the effect was created. For repeating effects, * the method will always return {@code null}. * * @return a long array representing a vibration pattern equivalent to the VibrationEffect, if * the method successfully derived a vibration pattern equivalent to the effect * (this will always be the case if the effect was created via * {@link #createWaveform(int[], int)} and is non-repeating). Otherwise, returns * {@code null}. * @hide */ @TestApi @Nullable public abstract long[] computeCreateWaveformOffOnTimingsOrNull(); /** * Create a waveform vibration. * Loading Loading @@ -643,6 +668,51 @@ public abstract class VibrationEffect implements Parcelable { /** @hide */ @Override @Nullable public long[] computeCreateWaveformOffOnTimingsOrNull() { if (getRepeatIndex() >= 0) { // Repeating effects cannot be fully represented as a long[] legacy pattern. return null; } List<VibrationEffectSegment> segments = getSegments(); // The maximum possible size of the final pattern is 1 plus the number of segments in // the original effect. This is because we will add an empty "off" segment at the // start of the pattern if the first segment of the original effect is an "on" segment. // (because the legacy patterns start with an "off" pattern). Other than this one case, // we will add the durations of back-to-back segments of similar amplitudes (amplitudes // that are all "on" or "off") and create a pattern entry for the total duration, which // will not take more number pattern entries than the number of segments processed. long[] patternBuffer = new long[segments.size() + 1]; int patternIndex = 0; for (int i = 0; i < segments.size(); i++) { StepSegment stepSegment = castToValidStepSegmentForOffOnTimingsOrNull(segments.get(i)); if (stepSegment == null) { // This means that there is 1 or more segments of this effect that is/are not a // possible component of a legacy vibration pattern. Thus, the VibrationEffect // does not have any equivalent legacy vibration pattern. return null; } boolean isSegmentOff = stepSegment.getAmplitude() == 0; // Even pattern indices are "off", and odd pattern indices are "on" boolean isCurrentPatternIndexOff = (patternIndex % 2) == 0; if (isSegmentOff != isCurrentPatternIndexOff) { // Move the pattern index one step ahead, so that the current segment's // "off"/"on" property matches that of the index's ++patternIndex; } patternBuffer[patternIndex] += stepSegment.getDuration(); } return Arrays.copyOf(patternBuffer, patternIndex + 1); } /** @hide */ @Override public void validate() { int segmentCount = mSegments.size(); boolean hasNonZeroDuration = false; Loading Loading @@ -806,6 +876,31 @@ public abstract class VibrationEffect implements Parcelable { return new Composed[size]; } }; /** * Casts a provided {@link VibrationEffectSegment} to a {@link StepSegment} and returns it, * only if it can possibly be a segment for an effect created via * {@link #createWaveform(int[], int)}. Otherwise, returns {@code null}. */ @Nullable private static StepSegment castToValidStepSegmentForOffOnTimingsOrNull( VibrationEffectSegment segment) { if (!(segment instanceof StepSegment)) { return null; } StepSegment stepSegment = (StepSegment) segment; if (stepSegment.getFrequencyHz() != 0) { return null; } float amplitude = stepSegment.getAmplitude(); if (amplitude != 0 && amplitude != DEFAULT_AMPLITUDE) { return null; } return stepSegment; } } /** Loading core/tests/coretests/src/android/os/VibrationEffectTest.java +359 −3 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.os; import static android.os.VibrationEffect.DEFAULT_AMPLITUDE; import static android.os.VibrationEffect.VibrationParameter.targetAmplitude; import static android.os.VibrationEffect.VibrationParameter.targetFrequency; Loading Loading @@ -48,6 +49,7 @@ import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import java.time.Duration; import java.util.Arrays; @Presubmit @RunWith(MockitoJUnitRunner.class) Loading @@ -62,15 +64,362 @@ public class VibrationEffectTest { private static final int TEST_AMPLITUDE = 100; private static final long[] TEST_TIMINGS = new long[] { 100, 100, 200 }; private static final int[] TEST_AMPLITUDES = new int[] { 255, 0, VibrationEffect.DEFAULT_AMPLITUDE }; new int[] { 255, 0, DEFAULT_AMPLITUDE }; private static final VibrationEffect TEST_ONE_SHOT = VibrationEffect.createOneShot(TEST_TIMING, TEST_AMPLITUDE); private static final VibrationEffect DEFAULT_ONE_SHOT = VibrationEffect.createOneShot(TEST_TIMING, VibrationEffect.DEFAULT_AMPLITUDE); VibrationEffect.createOneShot(TEST_TIMING, DEFAULT_AMPLITUDE); private static final VibrationEffect TEST_WAVEFORM = VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1); @Test public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesOnEvenIndices() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1, 2, 3, 4, 5}, /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {1, 2, 3, 4, 5}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesOnOddIndices() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1, 2, 3, 4, 5}, /* amplitudes= */ new int[] { DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {0, 1, 2, 3, 4, 5}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesAtTheStart() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1, 2, 3}, /* amplitudes= */ new int[] {0, 0, DEFAULT_AMPLITUDE}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {3, 3}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesAtTheEnd() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1, 2, 3}, /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, 0, 0}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {0, 1, 5}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsAndAmplitudes_allDefaultAmplitudes() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1, 2, 3}, /* amplitudes= */ new int[] { DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {0, 6}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsAndAmplitudes_allZeroAmplitudes() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1, 2, 3}, /* amplitudes= */ new int[] {0, 0, 0}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {6}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsAndAmplitudes_sparsedZeroAmplitudes() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1, 2, 3, 4, 5, 6, 7}, /* amplitudes= */ new int[] { 0, 0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE, 0}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {3, 3, 4, 11, 7}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsAndAmplitudes_oneTimingWithDefaultAmplitude() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1}, /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {0, 1}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsAndAmplitudes_oneTimingWithZeroAmplitude() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1}, /* amplitudes= */ new int[] {0}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {1}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsAndAmplitudes_repeating() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1, 2, 3, 4, 5}, /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0}, /* repeatIndex= */ 0); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1, 2, 3, 4, 5}, /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0}, /* repeatIndex= */ 3); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1, 2}, /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE}, /* repeatIndex= */ 1); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsAndAmplitudes_badAmplitude() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1}, /* amplitudes= */ new int[] {200}, /* repeatIndex= */ -1); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsOnly_nonZeroTimings() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1, 2, 3}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {1, 2, 3}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsOnly_oneValue() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {5}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {5}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsOnly_zeroesAtTheEnd() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1, 2, 3, 0, 0}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {1, 2, 3, 0, 0}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsOnly_zeroesAtTheStart() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {0, 0, 1, 2, 3}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {0, 0, 1, 2, 3}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsOnly_zeroesAtTheMiddle() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1, 2, 0, 0, 3, 4, 5}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {1, 2, 0, 0, 3, 4, 5}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsOnly_sparsedZeroes() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {0, 1, 2, 0, 0, 3, 4, 5, 0}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {0, 1, 2, 0, 0, 3, 4, 5, 0}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsOnly_repeating() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {0, 1, 2, 0, 0, 3, 4, 5, 0}, /* repeatIndex= */ 0); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1, 2, 3, 4}, /* repeatIndex= */ 2); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_notPatternPased() { VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_oneShot_defaultAmplitude() { VibrationEffect effect = VibrationEffect.createOneShot( /* milliseconds= */ 5, /* ampliutde= */ DEFAULT_AMPLITUDE); long[] expectedPattern = new long[] {0, 5}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_oneShot_badAmplitude() { VibrationEffect effect = VibrationEffect.createOneShot( /* milliseconds= */ 5, /* ampliutde= */ 50); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_composition_noOffDuration() { VibrationEffect effect = VibrationEffect.startComposition() .addEffect( VibrationEffect.createWaveform( /* timings= */ new long[] {5}, /* repeatIndex= */ -1)) .addEffect( VibrationEffect.createWaveform( /* timings= */ new long[] {2, 3}, /* repeatIndex= */ -1)) .addEffect( VibrationEffect.createWaveform( /* timings= */ new long[] {10, 20}, /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE}, /* repeatIndex= */ -1)) .addEffect( VibrationEffect.createWaveform( /* timings= */ new long[] {4, 5}, /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE}, /* repeatIndex= */ -1)) .compose(); long[] expectedPattern = new long[] {7, 33, 4, 5}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_composition_withOffDuration() { VibrationEffect effect = VibrationEffect.startComposition() .addOffDuration(Duration.ofMillis(20)) .addEffect( VibrationEffect.createWaveform( /* timings= */ new long[] {10, 20}, /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE}, /* repeatIndex= */ -1)) .addEffect( VibrationEffect.createWaveform( /* timings= */ new long[] {30, 40}, /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE}, /* repeatIndex= */ -1)) .addOffDuration(Duration.ofMillis(10)) .addEffect( VibrationEffect.createWaveform( /* timings= */ new long[] {4, 5}, /* repeatIndex= */ -1)) .addOffDuration(Duration.ofMillis(5)) .compose(); long[] expectedPattern = new long[] {30, 90, 14, 5, 5}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_composition_withPrimitives() { VibrationEffect effect = VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK) .addOffDuration(Duration.ofMillis(20)) .addEffect( VibrationEffect.createWaveform( /* timings= */ new long[] {5}, /* repeatIndex= */ -1)) .compose(); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_composition_repeating() { VibrationEffect effect = VibrationEffect.startComposition() .addEffect( VibrationEffect.createWaveform( /* timings= */ new long[] {5}, /* repeatIndex= */ -1)) .repeatEffectIndefinitely( VibrationEffect.createWaveform( /* timings= */ new long[] {2, 3}, /* repeatIndex= */ -1)) .compose(); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_effectsViaStartWaveform() { // Effects created via startWaveform are not expected to be converted to long[] patterns, as // they are not configured to always play with the default amplitude. VibrationEffect effect = VibrationEffect.startWaveform(targetFrequency(60)) .addTransition(Duration.ofMillis(100), targetAmplitude(1), targetFrequency(120)) .addSustain(Duration.ofMillis(200)) .addTransition(Duration.ofMillis(100), targetAmplitude(0), targetFrequency(60)) .build(); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); effect = VibrationEffect.startWaveform(targetFrequency(60)) .addTransition(Duration.ofMillis(80), targetAmplitude(1)) .addSustain(Duration.ofMillis(200)) .addTransition(Duration.ofMillis(100), targetAmplitude(0)) .build(); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); effect = VibrationEffect.startWaveform(targetFrequency(60)) .addTransition(Duration.ofMillis(100), targetFrequency(50)) .addSustain(Duration.ofMillis(50)) .addTransition(Duration.ofMillis(20), targetFrequency(75)) .build(); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void getRingtones_noPrebakedRingtones() { Resources r = mockRingtoneResources(new String[0]); Loading Loading @@ -100,7 +449,7 @@ public class VibrationEffectTest { @Test public void testValidateOneShot() { VibrationEffect.createOneShot(1, 255).validate(); VibrationEffect.createOneShot(1, VibrationEffect.DEFAULT_AMPLITUDE).validate(); VibrationEffect.createOneShot(1, DEFAULT_AMPLITUDE).validate(); assertThrows(IllegalArgumentException.class, () -> VibrationEffect.createOneShot(-1, 255).validate()); Loading Loading @@ -501,6 +850,13 @@ public class VibrationEffectTest { assertTrue(VibrationEffect.get(VibrationEffect.EFFECT_TICK).isHapticFeedbackCandidate()); } private void assertArrayEq(long[] expected, long[] actual) { assertTrue( String.format("Expected pattern %s, but was %s", Arrays.toString(expected), Arrays.toString(actual)), Arrays.equals(expected, actual)); } private Resources mockRingtoneResources() { return mockRingtoneResources(new String[]{ RINGTONE_URI_1, Loading Loading
core/api/test-current.txt +2 −0 Original line number Diff line number Diff line Loading @@ -1908,6 +1908,7 @@ package android.os { } public abstract class VibrationEffect implements android.os.Parcelable { method @Nullable public abstract long[] computeCreateWaveformOffOnTimingsOrNull(); method public static android.os.VibrationEffect get(int); method public static android.os.VibrationEffect get(int, boolean); method @Nullable public static android.os.VibrationEffect get(android.net.Uri, android.content.Context); Loading @@ -1922,6 +1923,7 @@ package android.os { } public static final class VibrationEffect.Composed extends android.os.VibrationEffect { method @Nullable public long[] computeCreateWaveformOffOnTimingsOrNull(); method public long getDuration(); method public int getRepeatIndex(); method @NonNull public java.util.List<android.os.vibrator.VibrationEffectSegment> getSegments(); Loading
core/java/android/os/VibrationEffect.java +95 −0 Original line number Diff line number Diff line Loading @@ -226,6 +226,31 @@ public abstract class VibrationEffect implements Parcelable { return createWaveform(timings, amplitudes, repeat); } /** * Computes a legacy vibration pattern (i.e. a pattern with duration values for "off/on" * vibration components) that is equivalent to this VibrationEffect. * * <p>All non-repeating effects created with {@link #createWaveform(int[], int)} are convertible * into an equivalent vibration pattern with this method. It is not guaranteed that an effect * created with other means becomes converted into an equivalent legacy vibration pattern, even * if it has an equivalent vibration pattern. If this method is unable to create an equivalent * vibration pattern for such effects, it will return {@code null}. * * <p>Note that a valid equivalent long[] pattern cannot be created for an effect that has any * form of repeating behavior, regardless of how the effect was created. For repeating effects, * the method will always return {@code null}. * * @return a long array representing a vibration pattern equivalent to the VibrationEffect, if * the method successfully derived a vibration pattern equivalent to the effect * (this will always be the case if the effect was created via * {@link #createWaveform(int[], int)} and is non-repeating). Otherwise, returns * {@code null}. * @hide */ @TestApi @Nullable public abstract long[] computeCreateWaveformOffOnTimingsOrNull(); /** * Create a waveform vibration. * Loading Loading @@ -643,6 +668,51 @@ public abstract class VibrationEffect implements Parcelable { /** @hide */ @Override @Nullable public long[] computeCreateWaveformOffOnTimingsOrNull() { if (getRepeatIndex() >= 0) { // Repeating effects cannot be fully represented as a long[] legacy pattern. return null; } List<VibrationEffectSegment> segments = getSegments(); // The maximum possible size of the final pattern is 1 plus the number of segments in // the original effect. This is because we will add an empty "off" segment at the // start of the pattern if the first segment of the original effect is an "on" segment. // (because the legacy patterns start with an "off" pattern). Other than this one case, // we will add the durations of back-to-back segments of similar amplitudes (amplitudes // that are all "on" or "off") and create a pattern entry for the total duration, which // will not take more number pattern entries than the number of segments processed. long[] patternBuffer = new long[segments.size() + 1]; int patternIndex = 0; for (int i = 0; i < segments.size(); i++) { StepSegment stepSegment = castToValidStepSegmentForOffOnTimingsOrNull(segments.get(i)); if (stepSegment == null) { // This means that there is 1 or more segments of this effect that is/are not a // possible component of a legacy vibration pattern. Thus, the VibrationEffect // does not have any equivalent legacy vibration pattern. return null; } boolean isSegmentOff = stepSegment.getAmplitude() == 0; // Even pattern indices are "off", and odd pattern indices are "on" boolean isCurrentPatternIndexOff = (patternIndex % 2) == 0; if (isSegmentOff != isCurrentPatternIndexOff) { // Move the pattern index one step ahead, so that the current segment's // "off"/"on" property matches that of the index's ++patternIndex; } patternBuffer[patternIndex] += stepSegment.getDuration(); } return Arrays.copyOf(patternBuffer, patternIndex + 1); } /** @hide */ @Override public void validate() { int segmentCount = mSegments.size(); boolean hasNonZeroDuration = false; Loading Loading @@ -806,6 +876,31 @@ public abstract class VibrationEffect implements Parcelable { return new Composed[size]; } }; /** * Casts a provided {@link VibrationEffectSegment} to a {@link StepSegment} and returns it, * only if it can possibly be a segment for an effect created via * {@link #createWaveform(int[], int)}. Otherwise, returns {@code null}. */ @Nullable private static StepSegment castToValidStepSegmentForOffOnTimingsOrNull( VibrationEffectSegment segment) { if (!(segment instanceof StepSegment)) { return null; } StepSegment stepSegment = (StepSegment) segment; if (stepSegment.getFrequencyHz() != 0) { return null; } float amplitude = stepSegment.getAmplitude(); if (amplitude != 0 && amplitude != DEFAULT_AMPLITUDE) { return null; } return stepSegment; } } /** Loading
core/tests/coretests/src/android/os/VibrationEffectTest.java +359 −3 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.os; import static android.os.VibrationEffect.DEFAULT_AMPLITUDE; import static android.os.VibrationEffect.VibrationParameter.targetAmplitude; import static android.os.VibrationEffect.VibrationParameter.targetFrequency; Loading Loading @@ -48,6 +49,7 @@ import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import java.time.Duration; import java.util.Arrays; @Presubmit @RunWith(MockitoJUnitRunner.class) Loading @@ -62,15 +64,362 @@ public class VibrationEffectTest { private static final int TEST_AMPLITUDE = 100; private static final long[] TEST_TIMINGS = new long[] { 100, 100, 200 }; private static final int[] TEST_AMPLITUDES = new int[] { 255, 0, VibrationEffect.DEFAULT_AMPLITUDE }; new int[] { 255, 0, DEFAULT_AMPLITUDE }; private static final VibrationEffect TEST_ONE_SHOT = VibrationEffect.createOneShot(TEST_TIMING, TEST_AMPLITUDE); private static final VibrationEffect DEFAULT_ONE_SHOT = VibrationEffect.createOneShot(TEST_TIMING, VibrationEffect.DEFAULT_AMPLITUDE); VibrationEffect.createOneShot(TEST_TIMING, DEFAULT_AMPLITUDE); private static final VibrationEffect TEST_WAVEFORM = VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1); @Test public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesOnEvenIndices() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1, 2, 3, 4, 5}, /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {1, 2, 3, 4, 5}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesOnOddIndices() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1, 2, 3, 4, 5}, /* amplitudes= */ new int[] { DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {0, 1, 2, 3, 4, 5}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesAtTheStart() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1, 2, 3}, /* amplitudes= */ new int[] {0, 0, DEFAULT_AMPLITUDE}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {3, 3}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesAtTheEnd() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1, 2, 3}, /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, 0, 0}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {0, 1, 5}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsAndAmplitudes_allDefaultAmplitudes() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1, 2, 3}, /* amplitudes= */ new int[] { DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {0, 6}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsAndAmplitudes_allZeroAmplitudes() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1, 2, 3}, /* amplitudes= */ new int[] {0, 0, 0}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {6}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsAndAmplitudes_sparsedZeroAmplitudes() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1, 2, 3, 4, 5, 6, 7}, /* amplitudes= */ new int[] { 0, 0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE, 0}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {3, 3, 4, 11, 7}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsAndAmplitudes_oneTimingWithDefaultAmplitude() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1}, /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {0, 1}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsAndAmplitudes_oneTimingWithZeroAmplitude() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1}, /* amplitudes= */ new int[] {0}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {1}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsAndAmplitudes_repeating() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1, 2, 3, 4, 5}, /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0}, /* repeatIndex= */ 0); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1, 2, 3, 4, 5}, /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0}, /* repeatIndex= */ 3); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1, 2}, /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE}, /* repeatIndex= */ 1); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsAndAmplitudes_badAmplitude() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1}, /* amplitudes= */ new int[] {200}, /* repeatIndex= */ -1); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsOnly_nonZeroTimings() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1, 2, 3}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {1, 2, 3}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsOnly_oneValue() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {5}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {5}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsOnly_zeroesAtTheEnd() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1, 2, 3, 0, 0}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {1, 2, 3, 0, 0}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsOnly_zeroesAtTheStart() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {0, 0, 1, 2, 3}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {0, 0, 1, 2, 3}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsOnly_zeroesAtTheMiddle() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1, 2, 0, 0, 3, 4, 5}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {1, 2, 0, 0, 3, 4, 5}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsOnly_sparsedZeroes() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {0, 1, 2, 0, 0, 3, 4, 5, 0}, /* repeatIndex= */ -1); long[] expectedPattern = new long[] {0, 1, 2, 0, 0, 3, 4, 5, 0}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_timingsOnly_repeating() { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[] {0, 1, 2, 0, 0, 3, 4, 5, 0}, /* repeatIndex= */ 0); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); effect = VibrationEffect.createWaveform( /* timings= */ new long[] {1, 2, 3, 4}, /* repeatIndex= */ 2); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_notPatternPased() { VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_oneShot_defaultAmplitude() { VibrationEffect effect = VibrationEffect.createOneShot( /* milliseconds= */ 5, /* ampliutde= */ DEFAULT_AMPLITUDE); long[] expectedPattern = new long[] {0, 5}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_oneShot_badAmplitude() { VibrationEffect effect = VibrationEffect.createOneShot( /* milliseconds= */ 5, /* ampliutde= */ 50); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_composition_noOffDuration() { VibrationEffect effect = VibrationEffect.startComposition() .addEffect( VibrationEffect.createWaveform( /* timings= */ new long[] {5}, /* repeatIndex= */ -1)) .addEffect( VibrationEffect.createWaveform( /* timings= */ new long[] {2, 3}, /* repeatIndex= */ -1)) .addEffect( VibrationEffect.createWaveform( /* timings= */ new long[] {10, 20}, /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE}, /* repeatIndex= */ -1)) .addEffect( VibrationEffect.createWaveform( /* timings= */ new long[] {4, 5}, /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE}, /* repeatIndex= */ -1)) .compose(); long[] expectedPattern = new long[] {7, 33, 4, 5}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_composition_withOffDuration() { VibrationEffect effect = VibrationEffect.startComposition() .addOffDuration(Duration.ofMillis(20)) .addEffect( VibrationEffect.createWaveform( /* timings= */ new long[] {10, 20}, /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE}, /* repeatIndex= */ -1)) .addEffect( VibrationEffect.createWaveform( /* timings= */ new long[] {30, 40}, /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE}, /* repeatIndex= */ -1)) .addOffDuration(Duration.ofMillis(10)) .addEffect( VibrationEffect.createWaveform( /* timings= */ new long[] {4, 5}, /* repeatIndex= */ -1)) .addOffDuration(Duration.ofMillis(5)) .compose(); long[] expectedPattern = new long[] {30, 90, 14, 5, 5}; assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_composition_withPrimitives() { VibrationEffect effect = VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK) .addOffDuration(Duration.ofMillis(20)) .addEffect( VibrationEffect.createWaveform( /* timings= */ new long[] {5}, /* repeatIndex= */ -1)) .compose(); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_composition_repeating() { VibrationEffect effect = VibrationEffect.startComposition() .addEffect( VibrationEffect.createWaveform( /* timings= */ new long[] {5}, /* repeatIndex= */ -1)) .repeatEffectIndefinitely( VibrationEffect.createWaveform( /* timings= */ new long[] {2, 3}, /* repeatIndex= */ -1)) .compose(); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void computeLegacyPattern_effectsViaStartWaveform() { // Effects created via startWaveform are not expected to be converted to long[] patterns, as // they are not configured to always play with the default amplitude. VibrationEffect effect = VibrationEffect.startWaveform(targetFrequency(60)) .addTransition(Duration.ofMillis(100), targetAmplitude(1), targetFrequency(120)) .addSustain(Duration.ofMillis(200)) .addTransition(Duration.ofMillis(100), targetAmplitude(0), targetFrequency(60)) .build(); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); effect = VibrationEffect.startWaveform(targetFrequency(60)) .addTransition(Duration.ofMillis(80), targetAmplitude(1)) .addSustain(Duration.ofMillis(200)) .addTransition(Duration.ofMillis(100), targetAmplitude(0)) .build(); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); effect = VibrationEffect.startWaveform(targetFrequency(60)) .addTransition(Duration.ofMillis(100), targetFrequency(50)) .addSustain(Duration.ofMillis(50)) .addTransition(Duration.ofMillis(20), targetFrequency(75)) .build(); assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); } @Test public void getRingtones_noPrebakedRingtones() { Resources r = mockRingtoneResources(new String[0]); Loading Loading @@ -100,7 +449,7 @@ public class VibrationEffectTest { @Test public void testValidateOneShot() { VibrationEffect.createOneShot(1, 255).validate(); VibrationEffect.createOneShot(1, VibrationEffect.DEFAULT_AMPLITUDE).validate(); VibrationEffect.createOneShot(1, DEFAULT_AMPLITUDE).validate(); assertThrows(IllegalArgumentException.class, () -> VibrationEffect.createOneShot(-1, 255).validate()); Loading Loading @@ -501,6 +850,13 @@ public class VibrationEffectTest { assertTrue(VibrationEffect.get(VibrationEffect.EFFECT_TICK).isHapticFeedbackCandidate()); } private void assertArrayEq(long[] expected, long[] actual) { assertTrue( String.format("Expected pattern %s, but was %s", Arrays.toString(expected), Arrays.toString(actual)), Arrays.equals(expected, actual)); } private Resources mockRingtoneResources() { return mockRingtoneResources(new String[]{ RINGTONE_URI_1, Loading