Loading services/core/java/com/android/server/vibrator/AbstractVibratorStep.java +9 −1 Original line number Diff line number Diff line Loading @@ -141,8 +141,16 @@ abstract class AbstractVibratorStep extends Step { */ protected List<Step> nextSteps(long nextStartTime, long vibratorOffTimeout, 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, segmentIndex + segmentsPlayed, vibratorOffTimeout); nextSegmentIndex, vibratorOffTimeout); return nextStep == null ? VibrationStepConductor.EMPTY_STEP_LIST : Arrays.asList(nextStep); } } services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java +47 −12 Original line number Diff line number Diff line Loading @@ -32,6 +32,11 @@ import java.util.List; * {@link PrimitiveSegment} starting at the current index. */ 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, VibratorController controller, VibrationEffect.Composed effect, int index, Loading @@ -49,18 +54,8 @@ final class ComposePrimitivesVibratorStep extends AbstractVibratorStep { // Load the next PrimitiveSegments to create a single compose call to the vibrator, // limited to the vibrator composition maximum size. int limit = controller.getVibratorInfo().getCompositionSizeMax(); int segmentCount = limit > 0 ? Math.min(effect.getSegments().size(), segmentIndex + 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; } } List<PrimitiveSegment> primitives = unrollPrimitiveSegments(effect, segmentIndex, limit > 0 ? limit : DEFAULT_COMPOSITION_SIZE_LIMIT); if (primitives.isEmpty()) { Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePrimitivesStep: " Loading @@ -81,4 +76,44 @@ final class ComposePrimitivesVibratorStep extends AbstractVibratorStep { 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; } } services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java +91 −12 Original line number Diff line number Diff line Loading @@ -33,6 +33,11 @@ import java.util.List; * {@link StepSegment} or {@link RampSegment} starting at the current index. */ 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, VibratorController controller, VibrationEffect.Composed effect, int index, Loading @@ -50,18 +55,8 @@ final class ComposePwleVibratorStep extends AbstractVibratorStep { // Load the next RampSegments to create a single composePwle call to the vibrator, // limited to the vibrator PWLE maximum size. int limit = controller.getVibratorInfo().getPwleSizeMax(); int segmentCount = limit > 0 ? Math.min(effect.getSegments().size(), segmentIndex + 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; } } List<RampSegment> pwles = unrollRampSegments(effect, segmentIndex, limit > 0 ? limit : DEFAULT_PWLE_SIZE_LIMIT); if (pwles.isEmpty()) { Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePwleStep: " Loading @@ -81,4 +76,88 @@ final class ComposePwleVibratorStep extends AbstractVibratorStep { 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; } } services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java +7 −4 Original line number Diff line number Diff line Loading @@ -33,6 +33,12 @@ import java.util.List; * and amplitude to simulate waveforms represented by a sequence of {@link StepSegment}. */ 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; SetAmplitudeVibratorStep(VibrationStepConductor conductor, long startTime, Loading Loading @@ -170,10 +176,7 @@ final class SetAmplitudeVibratorStep extends AbstractVibratorStep { repeatIndex = -1; } if (i == startIndex) { // The repeating waveform keeps the vibrator ON all the time. Use a minimum // of 1s duration to prevent short patterns from turning the vibrator ON too // frequently. return Math.max(timing, 1000); return Math.max(timing, REPEATING_EFFECT_ON_DURATION); } } if (i == segmentCount && effect.getRepeatIndex() < 0) { Loading services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java +101 −22 Original line number Diff line number Diff line Loading @@ -276,7 +276,7 @@ public class VibrationThreadTest { } @Test public void vibrate_singleVibratorRepeatingShortAlwaysOnWaveform_turnsVibratorOnForASecond() public void vibrate_singleVibratorRepeatingShortAlwaysOnWaveform_turnsVibratorOnForLonger() throws Exception { FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); Loading @@ -293,10 +293,70 @@ public class VibrationThreadTest { verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList(expectedOneShot(1000)), assertEquals(Arrays.asList(expectedOneShot(5000)), 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 public void vibrate_singleVibratorRepeatingLongAlwaysOnWaveform_turnsVibratorOnForACycle() throws Exception { Loading @@ -319,7 +379,7 @@ public class VibrationThreadTest { fakeVibrator.getEffectSegments(vibrationId)); } @LargeTest @Test public void vibrate_singleVibratorRepeatingAlwaysOnWaveform_turnsVibratorBackOn() throws Exception { Loading @@ -329,22 +389,21 @@ public class VibrationThreadTest { long vibrationId = 1; int[] amplitudes = new int[]{1, 2}; VibrationEffect effect = VibrationEffect.createWaveform( new long[]{900, 50}, amplitudes, 0); new long[]{4900, 50}, amplitudes, 0); VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect); assertTrue(waitUntil(() -> fakeVibrator.getAmplitudes().size() > 2 * amplitudes.length, 1000 + TEST_TIMEOUT_MILLIS)); assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibrationId).size() > 1, 5000 + TEST_TIMEOUT_MILLIS)); conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false); waitForCompletion(); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(2, fakeVibrator.getEffectSegments(vibrationId).size()); // First time turn vibrator ON for minimum of 1s. assertEquals(1000L, fakeVibrator.getEffectSegments(vibrationId).get(0).getDuration()); // First time turn vibrator ON for minimum of 5s. assertEquals(5000L, fakeVibrator.getEffectSegments(vibrationId).get(0).getDuration()); // Vibrator turns off in the middle of the second execution of first step, turn it back ON // for another 1s + remaining of 850ms. assertEquals(1850, // for another 5s + remaining of 850ms. assertEquals(4900 + 50 + 4900, 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. assertEquals(expectedAmplitudes(1, 2, 1, 1), Loading Loading @@ -530,12 +589,18 @@ public class VibrationThreadTest { @Test public void vibrate_singleVibratorComposedEffects_runsDifferentVibrations() throws Exception { mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects(VibrationEffect.EFFECT_CLICK); mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives( FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK); fakeVibrator.setSupportedPrimitives( VibrationEffect.Composition.PRIMITIVE_CLICK, VibrationEffect.Composition.PRIMITIVE_TICK); mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS, IVibrator.CAP_AMPLITUDE_CONTROL); fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS, 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; VibrationEffect effect = VibrationEffect.startComposition() Loading @@ -543,7 +608,11 @@ public class VibrationThreadTest { .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f) .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)) .compose(); startThreadAndDispatcher(vibrationId, effect); Loading @@ -552,7 +621,7 @@ public class VibrationThreadTest { // Use first duration the vibrator is turned on since we cannot estimate the clicks. verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L)); 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); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList( Loading @@ -560,6 +629,10 @@ public class VibrationThreadTest { expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0), expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0), 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)), mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId)); assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()); Loading Loading @@ -605,30 +678,36 @@ public class VibrationThreadTest { } @Test public void vibrate_singleVibratorLargePwle_splitsVibratorComposeCalls() { public void vibrate_singleVibratorLargePwle_splitsComposeCallWhenAmplitudeIsLowest() { 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(2); fakeVibrator.setPwleSizeMax(3); long vibrationId = 1; VibrationEffect effect = VibrationEffect.startWaveform(targetAmplitude(1)) .addSustain(Duration.ofMillis(10)) .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)) .addSustain(Duration.ofMillis(30)) .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(); startThreadAndDispatcher(vibrationId, effect); waitForCompletion(); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED); // Vibrator compose called twice. verify(mControllerCallbacks, times(2)).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); assertEquals(4, fakeVibrator.getEffectSegments(vibrationId).size()); // Vibrator compose called 3 times with 2 segments instead of 2 times with 3 segments. // 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 Loading Loading
services/core/java/com/android/server/vibrator/AbstractVibratorStep.java +9 −1 Original line number Diff line number Diff line Loading @@ -141,8 +141,16 @@ abstract class AbstractVibratorStep extends Step { */ protected List<Step> nextSteps(long nextStartTime, long vibratorOffTimeout, 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, segmentIndex + segmentsPlayed, vibratorOffTimeout); nextSegmentIndex, vibratorOffTimeout); return nextStep == null ? VibrationStepConductor.EMPTY_STEP_LIST : Arrays.asList(nextStep); } }
services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java +47 −12 Original line number Diff line number Diff line Loading @@ -32,6 +32,11 @@ import java.util.List; * {@link PrimitiveSegment} starting at the current index. */ 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, VibratorController controller, VibrationEffect.Composed effect, int index, Loading @@ -49,18 +54,8 @@ final class ComposePrimitivesVibratorStep extends AbstractVibratorStep { // Load the next PrimitiveSegments to create a single compose call to the vibrator, // limited to the vibrator composition maximum size. int limit = controller.getVibratorInfo().getCompositionSizeMax(); int segmentCount = limit > 0 ? Math.min(effect.getSegments().size(), segmentIndex + 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; } } List<PrimitiveSegment> primitives = unrollPrimitiveSegments(effect, segmentIndex, limit > 0 ? limit : DEFAULT_COMPOSITION_SIZE_LIMIT); if (primitives.isEmpty()) { Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePrimitivesStep: " Loading @@ -81,4 +76,44 @@ final class ComposePrimitivesVibratorStep extends AbstractVibratorStep { 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; } }
services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java +91 −12 Original line number Diff line number Diff line Loading @@ -33,6 +33,11 @@ import java.util.List; * {@link StepSegment} or {@link RampSegment} starting at the current index. */ 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, VibratorController controller, VibrationEffect.Composed effect, int index, Loading @@ -50,18 +55,8 @@ final class ComposePwleVibratorStep extends AbstractVibratorStep { // Load the next RampSegments to create a single composePwle call to the vibrator, // limited to the vibrator PWLE maximum size. int limit = controller.getVibratorInfo().getPwleSizeMax(); int segmentCount = limit > 0 ? Math.min(effect.getSegments().size(), segmentIndex + 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; } } List<RampSegment> pwles = unrollRampSegments(effect, segmentIndex, limit > 0 ? limit : DEFAULT_PWLE_SIZE_LIMIT); if (pwles.isEmpty()) { Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePwleStep: " Loading @@ -81,4 +76,88 @@ final class ComposePwleVibratorStep extends AbstractVibratorStep { 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; } }
services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java +7 −4 Original line number Diff line number Diff line Loading @@ -33,6 +33,12 @@ import java.util.List; * and amplitude to simulate waveforms represented by a sequence of {@link StepSegment}. */ 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; SetAmplitudeVibratorStep(VibrationStepConductor conductor, long startTime, Loading Loading @@ -170,10 +176,7 @@ final class SetAmplitudeVibratorStep extends AbstractVibratorStep { repeatIndex = -1; } if (i == startIndex) { // The repeating waveform keeps the vibrator ON all the time. Use a minimum // of 1s duration to prevent short patterns from turning the vibrator ON too // frequently. return Math.max(timing, 1000); return Math.max(timing, REPEATING_EFFECT_ON_DURATION); } } if (i == segmentCount && effect.getRepeatIndex() < 0) { Loading
services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java +101 −22 Original line number Diff line number Diff line Loading @@ -276,7 +276,7 @@ public class VibrationThreadTest { } @Test public void vibrate_singleVibratorRepeatingShortAlwaysOnWaveform_turnsVibratorOnForASecond() public void vibrate_singleVibratorRepeatingShortAlwaysOnWaveform_turnsVibratorOnForLonger() throws Exception { FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); Loading @@ -293,10 +293,70 @@ public class VibrationThreadTest { verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList(expectedOneShot(1000)), assertEquals(Arrays.asList(expectedOneShot(5000)), 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 public void vibrate_singleVibratorRepeatingLongAlwaysOnWaveform_turnsVibratorOnForACycle() throws Exception { Loading @@ -319,7 +379,7 @@ public class VibrationThreadTest { fakeVibrator.getEffectSegments(vibrationId)); } @LargeTest @Test public void vibrate_singleVibratorRepeatingAlwaysOnWaveform_turnsVibratorBackOn() throws Exception { Loading @@ -329,22 +389,21 @@ public class VibrationThreadTest { long vibrationId = 1; int[] amplitudes = new int[]{1, 2}; VibrationEffect effect = VibrationEffect.createWaveform( new long[]{900, 50}, amplitudes, 0); new long[]{4900, 50}, amplitudes, 0); VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect); assertTrue(waitUntil(() -> fakeVibrator.getAmplitudes().size() > 2 * amplitudes.length, 1000 + TEST_TIMEOUT_MILLIS)); assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibrationId).size() > 1, 5000 + TEST_TIMEOUT_MILLIS)); conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false); waitForCompletion(); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(2, fakeVibrator.getEffectSegments(vibrationId).size()); // First time turn vibrator ON for minimum of 1s. assertEquals(1000L, fakeVibrator.getEffectSegments(vibrationId).get(0).getDuration()); // First time turn vibrator ON for minimum of 5s. assertEquals(5000L, fakeVibrator.getEffectSegments(vibrationId).get(0).getDuration()); // Vibrator turns off in the middle of the second execution of first step, turn it back ON // for another 1s + remaining of 850ms. assertEquals(1850, // for another 5s + remaining of 850ms. assertEquals(4900 + 50 + 4900, 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. assertEquals(expectedAmplitudes(1, 2, 1, 1), Loading Loading @@ -530,12 +589,18 @@ public class VibrationThreadTest { @Test public void vibrate_singleVibratorComposedEffects_runsDifferentVibrations() throws Exception { mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects(VibrationEffect.EFFECT_CLICK); mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives( FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK); fakeVibrator.setSupportedPrimitives( VibrationEffect.Composition.PRIMITIVE_CLICK, VibrationEffect.Composition.PRIMITIVE_TICK); mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS, IVibrator.CAP_AMPLITUDE_CONTROL); fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS, 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; VibrationEffect effect = VibrationEffect.startComposition() Loading @@ -543,7 +608,11 @@ public class VibrationThreadTest { .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f) .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)) .compose(); startThreadAndDispatcher(vibrationId, effect); Loading @@ -552,7 +621,7 @@ public class VibrationThreadTest { // Use first duration the vibrator is turned on since we cannot estimate the clicks. verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L)); 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); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList( Loading @@ -560,6 +629,10 @@ public class VibrationThreadTest { expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0), expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0), 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)), mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId)); assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()); Loading Loading @@ -605,30 +678,36 @@ public class VibrationThreadTest { } @Test public void vibrate_singleVibratorLargePwle_splitsVibratorComposeCalls() { public void vibrate_singleVibratorLargePwle_splitsComposeCallWhenAmplitudeIsLowest() { 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(2); fakeVibrator.setPwleSizeMax(3); long vibrationId = 1; VibrationEffect effect = VibrationEffect.startWaveform(targetAmplitude(1)) .addSustain(Duration.ofMillis(10)) .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)) .addSustain(Duration.ofMillis(30)) .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(); startThreadAndDispatcher(vibrationId, effect); waitForCompletion(); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED); // Vibrator compose called twice. verify(mControllerCallbacks, times(2)).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); assertEquals(4, fakeVibrator.getEffectSegments(vibrationId).size()); // Vibrator compose called 3 times with 2 segments instead of 2 times with 3 segments. // 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 Loading