Merge "Use composition size limit on repeating effects" into tm-dev am: 48a15efcc7 am: 1a9e68638d
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17474926 Change-Id: I07311446e1ec39e3a37f4c25150816a2dc956c1e Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -141,8 +141,16 @@ abstract class AbstractVibratorStep extends Step {
|
|||||||
*/
|
*/
|
||||||
protected List<Step> nextSteps(long nextStartTime, long vibratorOffTimeout,
|
protected List<Step> nextSteps(long nextStartTime, long vibratorOffTimeout,
|
||||||
int segmentsPlayed) {
|
int segmentsPlayed) {
|
||||||
|
int nextSegmentIndex = segmentIndex + segmentsPlayed;
|
||||||
|
int effectSize = effect.getSegments().size();
|
||||||
|
int repeatIndex = effect.getRepeatIndex();
|
||||||
|
if (nextSegmentIndex >= effectSize && repeatIndex >= 0) {
|
||||||
|
// Count the loops that were played.
|
||||||
|
int loopSize = effectSize - repeatIndex;
|
||||||
|
nextSegmentIndex = repeatIndex + ((nextSegmentIndex - effectSize) % loopSize);
|
||||||
|
}
|
||||||
Step nextStep = conductor.nextVibrateStep(nextStartTime, controller, effect,
|
Step nextStep = conductor.nextVibrateStep(nextStartTime, controller, effect,
|
||||||
segmentIndex + segmentsPlayed, vibratorOffTimeout);
|
nextSegmentIndex, vibratorOffTimeout);
|
||||||
return nextStep == null ? VibrationStepConductor.EMPTY_STEP_LIST : Arrays.asList(nextStep);
|
return nextStep == null ? VibrationStepConductor.EMPTY_STEP_LIST : Arrays.asList(nextStep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,11 @@ import java.util.List;
|
|||||||
* {@link PrimitiveSegment} starting at the current index.
|
* {@link PrimitiveSegment} starting at the current index.
|
||||||
*/
|
*/
|
||||||
final class ComposePrimitivesVibratorStep extends AbstractVibratorStep {
|
final class ComposePrimitivesVibratorStep extends AbstractVibratorStep {
|
||||||
|
/**
|
||||||
|
* Default limit to the number of primitives in a composition, if none is defined by the HAL,
|
||||||
|
* to prevent repeating effects from generating an infinite list.
|
||||||
|
*/
|
||||||
|
private static final int DEFAULT_COMPOSITION_SIZE_LIMIT = 100;
|
||||||
|
|
||||||
ComposePrimitivesVibratorStep(VibrationStepConductor conductor, long startTime,
|
ComposePrimitivesVibratorStep(VibrationStepConductor conductor, long startTime,
|
||||||
VibratorController controller, VibrationEffect.Composed effect, int index,
|
VibratorController controller, VibrationEffect.Composed effect, int index,
|
||||||
@@ -49,18 +54,8 @@ final class ComposePrimitivesVibratorStep extends AbstractVibratorStep {
|
|||||||
// Load the next PrimitiveSegments to create a single compose call to the vibrator,
|
// Load the next PrimitiveSegments to create a single compose call to the vibrator,
|
||||||
// limited to the vibrator composition maximum size.
|
// limited to the vibrator composition maximum size.
|
||||||
int limit = controller.getVibratorInfo().getCompositionSizeMax();
|
int limit = controller.getVibratorInfo().getCompositionSizeMax();
|
||||||
int segmentCount = limit > 0
|
List<PrimitiveSegment> primitives = unrollPrimitiveSegments(effect, segmentIndex,
|
||||||
? Math.min(effect.getSegments().size(), segmentIndex + limit)
|
limit > 0 ? limit : DEFAULT_COMPOSITION_SIZE_LIMIT);
|
||||||
: effect.getSegments().size();
|
|
||||||
List<PrimitiveSegment> primitives = new ArrayList<>();
|
|
||||||
for (int i = segmentIndex; i < segmentCount; i++) {
|
|
||||||
VibrationEffectSegment segment = effect.getSegments().get(i);
|
|
||||||
if (segment instanceof PrimitiveSegment) {
|
|
||||||
primitives.add((PrimitiveSegment) segment);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (primitives.isEmpty()) {
|
if (primitives.isEmpty()) {
|
||||||
Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePrimitivesStep: "
|
Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePrimitivesStep: "
|
||||||
@@ -81,4 +76,44 @@ final class ComposePrimitivesVibratorStep extends AbstractVibratorStep {
|
|||||||
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
|
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the primitive segments to be played by this step as a single composition, starting at
|
||||||
|
* {@code startIndex} until:
|
||||||
|
*
|
||||||
|
* <ol>
|
||||||
|
* <li>There are no more segments in the effect;
|
||||||
|
* <li>The first non-primitive segment is found;
|
||||||
|
* <li>The given limit to the composition size is reached.
|
||||||
|
* </ol>
|
||||||
|
*
|
||||||
|
* <p>If the effect is repeating then this method will generate the largest composition within
|
||||||
|
* given limit.
|
||||||
|
*/
|
||||||
|
private List<PrimitiveSegment> unrollPrimitiveSegments(VibrationEffect.Composed effect,
|
||||||
|
int startIndex, int limit) {
|
||||||
|
List<PrimitiveSegment> segments = new ArrayList<>(limit);
|
||||||
|
int segmentCount = effect.getSegments().size();
|
||||||
|
int repeatIndex = effect.getRepeatIndex();
|
||||||
|
|
||||||
|
for (int i = startIndex; segments.size() < limit; i++) {
|
||||||
|
if (i == segmentCount) {
|
||||||
|
if (repeatIndex >= 0) {
|
||||||
|
i = repeatIndex;
|
||||||
|
} else {
|
||||||
|
// Non-repeating effect, stop collecting primitives.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VibrationEffectSegment segment = effect.getSegments().get(i);
|
||||||
|
if (segment instanceof PrimitiveSegment) {
|
||||||
|
segments.add((PrimitiveSegment) segment);
|
||||||
|
} else {
|
||||||
|
// First non-primitive segment, stop collecting primitives.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return segments;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ import java.util.List;
|
|||||||
* {@link StepSegment} or {@link RampSegment} starting at the current index.
|
* {@link StepSegment} or {@link RampSegment} starting at the current index.
|
||||||
*/
|
*/
|
||||||
final class ComposePwleVibratorStep extends AbstractVibratorStep {
|
final class ComposePwleVibratorStep extends AbstractVibratorStep {
|
||||||
|
/**
|
||||||
|
* Default limit to the number of PWLE segments, if none is defined by the HAL, to prevent
|
||||||
|
* repeating effects from generating an infinite list.
|
||||||
|
*/
|
||||||
|
private static final int DEFAULT_PWLE_SIZE_LIMIT = 100;
|
||||||
|
|
||||||
ComposePwleVibratorStep(VibrationStepConductor conductor, long startTime,
|
ComposePwleVibratorStep(VibrationStepConductor conductor, long startTime,
|
||||||
VibratorController controller, VibrationEffect.Composed effect, int index,
|
VibratorController controller, VibrationEffect.Composed effect, int index,
|
||||||
@@ -50,18 +55,8 @@ final class ComposePwleVibratorStep extends AbstractVibratorStep {
|
|||||||
// Load the next RampSegments to create a single composePwle call to the vibrator,
|
// Load the next RampSegments to create a single composePwle call to the vibrator,
|
||||||
// limited to the vibrator PWLE maximum size.
|
// limited to the vibrator PWLE maximum size.
|
||||||
int limit = controller.getVibratorInfo().getPwleSizeMax();
|
int limit = controller.getVibratorInfo().getPwleSizeMax();
|
||||||
int segmentCount = limit > 0
|
List<RampSegment> pwles = unrollRampSegments(effect, segmentIndex,
|
||||||
? Math.min(effect.getSegments().size(), segmentIndex + limit)
|
limit > 0 ? limit : DEFAULT_PWLE_SIZE_LIMIT);
|
||||||
: effect.getSegments().size();
|
|
||||||
List<RampSegment> pwles = new ArrayList<>();
|
|
||||||
for (int i = segmentIndex; i < segmentCount; i++) {
|
|
||||||
VibrationEffectSegment segment = effect.getSegments().get(i);
|
|
||||||
if (segment instanceof RampSegment) {
|
|
||||||
pwles.add((RampSegment) segment);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pwles.isEmpty()) {
|
if (pwles.isEmpty()) {
|
||||||
Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePwleStep: "
|
Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePwleStep: "
|
||||||
@@ -81,4 +76,88 @@ final class ComposePwleVibratorStep extends AbstractVibratorStep {
|
|||||||
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
|
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the ramp segments to be played by this step for a waveform, starting at
|
||||||
|
* {@code startIndex} until:
|
||||||
|
*
|
||||||
|
* <ol>
|
||||||
|
* <li>There are no more segments in the effect;
|
||||||
|
* <li>The first non-ramp segment is found;
|
||||||
|
* <li>The given limit to the PWLE size is reached.
|
||||||
|
* </ol>
|
||||||
|
*
|
||||||
|
* <p>If the effect is repeating then this method will generate the largest PWLE within given
|
||||||
|
* limit. This will also optimize to end the list at a ramp to zero-amplitude, if possible, and
|
||||||
|
* avoid braking down the effect in non-zero amplitude.
|
||||||
|
*/
|
||||||
|
private List<RampSegment> unrollRampSegments(VibrationEffect.Composed effect, int startIndex,
|
||||||
|
int limit) {
|
||||||
|
List<RampSegment> segments = new ArrayList<>(limit);
|
||||||
|
float bestBreakAmplitude = 1;
|
||||||
|
int bestBreakPosition = limit; // Exclusive index.
|
||||||
|
|
||||||
|
int segmentCount = effect.getSegments().size();
|
||||||
|
int repeatIndex = effect.getRepeatIndex();
|
||||||
|
|
||||||
|
// Loop once after reaching the limit to see if breaking it will really be necessary, then
|
||||||
|
// apply the best break position found, otherwise return the full list as it fits the limit.
|
||||||
|
for (int i = startIndex; segments.size() <= limit; i++) {
|
||||||
|
if (i == segmentCount) {
|
||||||
|
if (repeatIndex >= 0) {
|
||||||
|
i = repeatIndex;
|
||||||
|
} else {
|
||||||
|
// Non-repeating effect, stop collecting ramps.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VibrationEffectSegment segment = effect.getSegments().get(i);
|
||||||
|
if (segment instanceof RampSegment) {
|
||||||
|
RampSegment rampSegment = (RampSegment) segment;
|
||||||
|
segments.add(rampSegment);
|
||||||
|
|
||||||
|
if (isBetterBreakPosition(segments, bestBreakAmplitude, limit)) {
|
||||||
|
// Mark this position as the best one so far to break a long waveform.
|
||||||
|
bestBreakAmplitude = rampSegment.getEndAmplitude();
|
||||||
|
bestBreakPosition = segments.size(); // Break after this ramp ends.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// First non-ramp segment, stop collecting ramps.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return segments.size() > limit
|
||||||
|
// Remove excessive segments, using the best breaking position recorded.
|
||||||
|
? segments.subList(0, bestBreakPosition)
|
||||||
|
// Return all collected ramp segments.
|
||||||
|
: segments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the current segment list represents a better break position for a PWLE,
|
||||||
|
* given the current amplitude being used for breaking it at a smaller size and the size limit.
|
||||||
|
*/
|
||||||
|
private boolean isBetterBreakPosition(List<RampSegment> segments,
|
||||||
|
float currentBestBreakAmplitude, int limit) {
|
||||||
|
RampSegment lastSegment = segments.get(segments.size() - 1);
|
||||||
|
float breakAmplitudeCandidate = lastSegment.getEndAmplitude();
|
||||||
|
int breakPositionCandidate = segments.size();
|
||||||
|
|
||||||
|
if (breakPositionCandidate > limit) {
|
||||||
|
// We're beyond limit, last break position found should be used.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (breakAmplitudeCandidate == 0) {
|
||||||
|
// Breaking at amplitude zero at any position is always preferable.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (breakPositionCandidate < limit / 2) {
|
||||||
|
// Avoid breaking at the first half of the allowed maximum size, even if amplitudes are
|
||||||
|
// lower, to avoid creating PWLEs that are too small unless it's to break at zero.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Prefer lower amplitudes at a later position for breaking the PWLE in a more subtle way.
|
||||||
|
return breakAmplitudeCandidate <= currentBestBreakAmplitude;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,12 @@ import java.util.List;
|
|||||||
* and amplitude to simulate waveforms represented by a sequence of {@link StepSegment}.
|
* and amplitude to simulate waveforms represented by a sequence of {@link StepSegment}.
|
||||||
*/
|
*/
|
||||||
final class SetAmplitudeVibratorStep extends AbstractVibratorStep {
|
final class SetAmplitudeVibratorStep extends AbstractVibratorStep {
|
||||||
|
/**
|
||||||
|
* The repeating waveform keeps the vibrator ON all the time. Use a minimum duration to
|
||||||
|
* prevent short patterns from turning the vibrator ON too frequently.
|
||||||
|
*/
|
||||||
|
private static final int REPEATING_EFFECT_ON_DURATION = 5000; // 5s
|
||||||
|
|
||||||
private long mNextOffTime;
|
private long mNextOffTime;
|
||||||
|
|
||||||
SetAmplitudeVibratorStep(VibrationStepConductor conductor, long startTime,
|
SetAmplitudeVibratorStep(VibrationStepConductor conductor, long startTime,
|
||||||
@@ -170,10 +176,7 @@ final class SetAmplitudeVibratorStep extends AbstractVibratorStep {
|
|||||||
repeatIndex = -1;
|
repeatIndex = -1;
|
||||||
}
|
}
|
||||||
if (i == startIndex) {
|
if (i == startIndex) {
|
||||||
// The repeating waveform keeps the vibrator ON all the time. Use a minimum
|
return Math.max(timing, REPEATING_EFFECT_ON_DURATION);
|
||||||
// of 1s duration to prevent short patterns from turning the vibrator ON too
|
|
||||||
// frequently.
|
|
||||||
return Math.max(timing, 1000);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (i == segmentCount && effect.getRepeatIndex() < 0) {
|
if (i == segmentCount && effect.getRepeatIndex() < 0) {
|
||||||
|
|||||||
@@ -276,7 +276,7 @@ public class VibrationThreadTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void vibrate_singleVibratorRepeatingShortAlwaysOnWaveform_turnsVibratorOnForASecond()
|
public void vibrate_singleVibratorRepeatingShortAlwaysOnWaveform_turnsVibratorOnForLonger()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
|
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
|
||||||
fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
|
fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
|
||||||
@@ -293,10 +293,70 @@ public class VibrationThreadTest {
|
|||||||
|
|
||||||
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
|
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
|
||||||
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
|
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
|
||||||
assertEquals(Arrays.asList(expectedOneShot(1000)),
|
assertEquals(Arrays.asList(expectedOneShot(5000)),
|
||||||
fakeVibrator.getEffectSegments(vibrationId));
|
fakeVibrator.getEffectSegments(vibrationId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void vibrate_singleVibratorRepeatingPwle_generatesLargestPwles() throws Exception {
|
||||||
|
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
|
||||||
|
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
|
||||||
|
fakeVibrator.setMinFrequency(100);
|
||||||
|
fakeVibrator.setResonantFrequency(150);
|
||||||
|
fakeVibrator.setFrequencyResolution(50);
|
||||||
|
fakeVibrator.setMaxAmplitudes(1, 1, 1);
|
||||||
|
fakeVibrator.setPwleSizeMax(10);
|
||||||
|
|
||||||
|
long vibrationId = 1;
|
||||||
|
VibrationEffect effect = VibrationEffect.startWaveform(targetAmplitude(1))
|
||||||
|
// Very long segment so thread will be cancelled after first PWLE is triggered.
|
||||||
|
.addTransition(Duration.ofMillis(100), targetFrequency(100))
|
||||||
|
.build();
|
||||||
|
VibrationEffect repeatingEffect = VibrationEffect.startComposition()
|
||||||
|
.repeatEffectIndefinitely(effect)
|
||||||
|
.compose();
|
||||||
|
VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, repeatingEffect);
|
||||||
|
|
||||||
|
assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
|
||||||
|
TEST_TIMEOUT_MILLIS));
|
||||||
|
conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
|
||||||
|
waitForCompletion();
|
||||||
|
|
||||||
|
// PWLE size max was used to generate a single vibrate call with 10 segments.
|
||||||
|
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
|
||||||
|
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
|
||||||
|
assertEquals(10, fakeVibrator.getEffectSegments(vibrationId).size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void vibrate_singleVibratorRepeatingPrimitives_generatesLargestComposition()
|
||||||
|
throws Exception {
|
||||||
|
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
|
||||||
|
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
|
||||||
|
fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK);
|
||||||
|
fakeVibrator.setCompositionSizeMax(10);
|
||||||
|
|
||||||
|
long vibrationId = 1;
|
||||||
|
VibrationEffect effect = VibrationEffect.startComposition()
|
||||||
|
// Very long delay so thread will be cancelled after first PWLE is triggered.
|
||||||
|
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
|
||||||
|
.compose();
|
||||||
|
VibrationEffect repeatingEffect = VibrationEffect.startComposition()
|
||||||
|
.repeatEffectIndefinitely(effect)
|
||||||
|
.compose();
|
||||||
|
VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, repeatingEffect);
|
||||||
|
|
||||||
|
assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
|
||||||
|
TEST_TIMEOUT_MILLIS));
|
||||||
|
conductor.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ false);
|
||||||
|
waitForCompletion();
|
||||||
|
|
||||||
|
// Composition size max was used to generate a single vibrate call with 10 primitives.
|
||||||
|
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_SUPERSEDED);
|
||||||
|
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
|
||||||
|
assertEquals(10, fakeVibrator.getEffectSegments(vibrationId).size());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void vibrate_singleVibratorRepeatingLongAlwaysOnWaveform_turnsVibratorOnForACycle()
|
public void vibrate_singleVibratorRepeatingLongAlwaysOnWaveform_turnsVibratorOnForACycle()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
@@ -319,7 +379,7 @@ public class VibrationThreadTest {
|
|||||||
fakeVibrator.getEffectSegments(vibrationId));
|
fakeVibrator.getEffectSegments(vibrationId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@LargeTest
|
||||||
@Test
|
@Test
|
||||||
public void vibrate_singleVibratorRepeatingAlwaysOnWaveform_turnsVibratorBackOn()
|
public void vibrate_singleVibratorRepeatingAlwaysOnWaveform_turnsVibratorBackOn()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
@@ -329,22 +389,21 @@ public class VibrationThreadTest {
|
|||||||
long vibrationId = 1;
|
long vibrationId = 1;
|
||||||
int[] amplitudes = new int[]{1, 2};
|
int[] amplitudes = new int[]{1, 2};
|
||||||
VibrationEffect effect = VibrationEffect.createWaveform(
|
VibrationEffect effect = VibrationEffect.createWaveform(
|
||||||
new long[]{900, 50}, amplitudes, 0);
|
new long[]{4900, 50}, amplitudes, 0);
|
||||||
VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
|
VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
|
||||||
|
|
||||||
assertTrue(waitUntil(() -> fakeVibrator.getAmplitudes().size() > 2 * amplitudes.length,
|
assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibrationId).size() > 1,
|
||||||
1000 + TEST_TIMEOUT_MILLIS));
|
5000 + TEST_TIMEOUT_MILLIS));
|
||||||
conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
|
conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
|
||||||
waitForCompletion();
|
waitForCompletion();
|
||||||
|
|
||||||
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
|
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
|
||||||
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
|
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
|
||||||
assertEquals(2, fakeVibrator.getEffectSegments(vibrationId).size());
|
// First time turn vibrator ON for minimum of 5s.
|
||||||
// First time turn vibrator ON for minimum of 1s.
|
assertEquals(5000L, fakeVibrator.getEffectSegments(vibrationId).get(0).getDuration());
|
||||||
assertEquals(1000L, fakeVibrator.getEffectSegments(vibrationId).get(0).getDuration());
|
|
||||||
// Vibrator turns off in the middle of the second execution of first step, turn it back ON
|
// Vibrator turns off in the middle of the second execution of first step, turn it back ON
|
||||||
// for another 1s + remaining of 850ms.
|
// for another 5s + remaining of 850ms.
|
||||||
assertEquals(1850,
|
assertEquals(4900 + 50 + 4900,
|
||||||
fakeVibrator.getEffectSegments(vibrationId).get(1).getDuration(), /* delta= */ 20);
|
fakeVibrator.getEffectSegments(vibrationId).get(1).getDuration(), /* delta= */ 20);
|
||||||
// Set amplitudes for a cycle {1, 2}, start second loop then turn it back on to same value.
|
// Set amplitudes for a cycle {1, 2}, start second loop then turn it back on to same value.
|
||||||
assertEquals(expectedAmplitudes(1, 2, 1, 1),
|
assertEquals(expectedAmplitudes(1, 2, 1, 1),
|
||||||
@@ -530,12 +589,18 @@ public class VibrationThreadTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void vibrate_singleVibratorComposedEffects_runsDifferentVibrations() throws Exception {
|
public void vibrate_singleVibratorComposedEffects_runsDifferentVibrations() throws Exception {
|
||||||
mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
|
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
|
||||||
mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(
|
fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
|
||||||
|
fakeVibrator.setSupportedPrimitives(
|
||||||
VibrationEffect.Composition.PRIMITIVE_CLICK,
|
VibrationEffect.Composition.PRIMITIVE_CLICK,
|
||||||
VibrationEffect.Composition.PRIMITIVE_TICK);
|
VibrationEffect.Composition.PRIMITIVE_TICK);
|
||||||
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS,
|
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS,
|
||||||
IVibrator.CAP_AMPLITUDE_CONTROL);
|
IVibrator.CAP_COMPOSE_PWLE_EFFECTS, IVibrator.CAP_AMPLITUDE_CONTROL);
|
||||||
|
fakeVibrator.setMinFrequency(100);
|
||||||
|
fakeVibrator.setResonantFrequency(150);
|
||||||
|
fakeVibrator.setFrequencyResolution(50);
|
||||||
|
fakeVibrator.setMaxAmplitudes(
|
||||||
|
0.5f /* 100Hz*/, 1 /* 150Hz */, 0.6f /* 200Hz */);
|
||||||
|
|
||||||
long vibrationId = 1;
|
long vibrationId = 1;
|
||||||
VibrationEffect effect = VibrationEffect.startComposition()
|
VibrationEffect effect = VibrationEffect.startComposition()
|
||||||
@@ -543,7 +608,11 @@ public class VibrationThreadTest {
|
|||||||
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
|
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
|
||||||
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
|
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
|
||||||
.addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
|
.addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
|
||||||
.addOffDuration(Duration.ofMillis(100))
|
.addEffect(VibrationEffect.startWaveform()
|
||||||
|
.addTransition(Duration.ofMillis(10),
|
||||||
|
targetAmplitude(1), targetFrequency(100))
|
||||||
|
.addTransition(Duration.ofMillis(20), targetFrequency(120))
|
||||||
|
.build())
|
||||||
.addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
|
.addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
|
||||||
.compose();
|
.compose();
|
||||||
startThreadAndDispatcher(vibrationId, effect);
|
startThreadAndDispatcher(vibrationId, effect);
|
||||||
@@ -552,7 +621,7 @@ public class VibrationThreadTest {
|
|||||||
// Use first duration the vibrator is turned on since we cannot estimate the clicks.
|
// Use first duration the vibrator is turned on since we cannot estimate the clicks.
|
||||||
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
|
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
|
||||||
verify(mManagerHooks).noteVibratorOff(eq(UID));
|
verify(mManagerHooks).noteVibratorOff(eq(UID));
|
||||||
verify(mControllerCallbacks, times(4)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
|
verify(mControllerCallbacks, times(5)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
|
||||||
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
|
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
|
||||||
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
|
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
|
||||||
assertEquals(Arrays.asList(
|
assertEquals(Arrays.asList(
|
||||||
@@ -560,6 +629,10 @@ public class VibrationThreadTest {
|
|||||||
expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0),
|
expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0),
|
||||||
expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0),
|
expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0),
|
||||||
expectedPrebaked(VibrationEffect.EFFECT_CLICK),
|
expectedPrebaked(VibrationEffect.EFFECT_CLICK),
|
||||||
|
expectedRamp(/* startAmplitude= */ 0, /* endAmplitude= */ 0.5f,
|
||||||
|
/* startFrequencyHz= */ 150, /* endFrequencyHz= */ 100, /* duration= */ 10),
|
||||||
|
expectedRamp(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.7f,
|
||||||
|
/* startFrequencyHz= */ 100, /* endFrequencyHz= */ 120, /* duration= */ 20),
|
||||||
expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
|
expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
|
||||||
mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
|
mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
|
||||||
assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
|
assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
|
||||||
@@ -605,30 +678,36 @@ public class VibrationThreadTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void vibrate_singleVibratorLargePwle_splitsVibratorComposeCalls() {
|
public void vibrate_singleVibratorLargePwle_splitsComposeCallWhenAmplitudeIsLowest() {
|
||||||
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
|
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
|
||||||
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
|
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
|
||||||
fakeVibrator.setMinFrequency(100);
|
fakeVibrator.setMinFrequency(100);
|
||||||
fakeVibrator.setResonantFrequency(150);
|
fakeVibrator.setResonantFrequency(150);
|
||||||
fakeVibrator.setFrequencyResolution(50);
|
fakeVibrator.setFrequencyResolution(50);
|
||||||
fakeVibrator.setMaxAmplitudes(1, 1, 1);
|
fakeVibrator.setMaxAmplitudes(1, 1, 1);
|
||||||
fakeVibrator.setPwleSizeMax(2);
|
fakeVibrator.setPwleSizeMax(3);
|
||||||
|
|
||||||
long vibrationId = 1;
|
long vibrationId = 1;
|
||||||
VibrationEffect effect = VibrationEffect.startWaveform(targetAmplitude(1))
|
VibrationEffect effect = VibrationEffect.startWaveform(targetAmplitude(1))
|
||||||
.addSustain(Duration.ofMillis(10))
|
.addSustain(Duration.ofMillis(10))
|
||||||
.addTransition(Duration.ofMillis(20), targetAmplitude(0))
|
.addTransition(Duration.ofMillis(20), targetAmplitude(0))
|
||||||
|
// Waveform will be split here, after vibration goes to zero amplitude
|
||||||
.addTransition(Duration.ZERO, targetAmplitude(0.8f), targetFrequency(100))
|
.addTransition(Duration.ZERO, targetAmplitude(0.8f), targetFrequency(100))
|
||||||
.addSustain(Duration.ofMillis(30))
|
.addSustain(Duration.ofMillis(30))
|
||||||
.addTransition(Duration.ofMillis(40), targetAmplitude(0.6f), targetFrequency(200))
|
.addTransition(Duration.ofMillis(40), targetAmplitude(0.6f), targetFrequency(200))
|
||||||
|
// Waveform will be split here at lowest amplitude.
|
||||||
|
.addTransition(Duration.ofMillis(40), targetAmplitude(0.7f), targetFrequency(200))
|
||||||
|
.addTransition(Duration.ofMillis(40), targetAmplitude(0.6f), targetFrequency(200))
|
||||||
.build();
|
.build();
|
||||||
startThreadAndDispatcher(vibrationId, effect);
|
startThreadAndDispatcher(vibrationId, effect);
|
||||||
waitForCompletion();
|
waitForCompletion();
|
||||||
|
|
||||||
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
|
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
|
||||||
// Vibrator compose called twice.
|
|
||||||
verify(mControllerCallbacks, times(2)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
|
// Vibrator compose called 3 times with 2 segments instead of 2 times with 3 segments.
|
||||||
assertEquals(4, fakeVibrator.getEffectSegments(vibrationId).size());
|
// Using best split points instead of max-packing PWLEs.
|
||||||
|
verify(mControllerCallbacks, times(3)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
|
||||||
|
assertEquals(6, fakeVibrator.getEffectSegments(vibrationId).size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user