summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Lais Andrade <lsandrade@google.com> 2022-07-08 18:19:34 +0100
committer Lais Andrade <lsandrade@google.com> 2022-08-22 16:55:33 +0100
commit0cc2d21e9bdefc80b926965ab51ad0e02e8c97c0 (patch)
tree62651c19db1b6241ebe5d4106cf6c923f8899c13
parentbb3501c4e7c0f41c7303943f9f6cbe498dbb0e30 (diff)
Fix composed VibrationEffect timing out
Fix timeout of playback of composed vibration effects (not only primitives). This happens because the timings used for triggering the next step after a HAL callback is received is propagaing the extra timeout given to the current step to wait before playing it, causing the consecutive steps to wait for a timeout no longer necessary. This is being tested now in VibrationThreadTest, with a composition of effects and primitives that has a realtime replacement with fallback, and in VibrationManagerServiceTest, that is using blocking vibrate calls to avoid some flakiness also found in those tests. Fix: 238311929 Test: VibrationManagerServiceTest & VibrationThreadTest Change-Id: I15ec5b977076903660b676ef671a472c10d9c30b
-rw-r--r--services/core/java/com/android/server/vibrator/AbstractVibratorStep.java92
-rw-r--r--services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java24
-rw-r--r--services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java18
-rw-r--r--services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java15
-rw-r--r--services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java21
-rw-r--r--services/core/java/com/android/server/vibrator/RampOffVibratorStep.java12
-rw-r--r--services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java68
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationStepConductor.java13
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java43
-rw-r--r--services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java203
11 files changed, 314 insertions, 199 deletions
diff --git a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
index eebd046b2601..be9053055fe3 100644
--- a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
@@ -31,9 +31,9 @@ abstract class AbstractVibratorStep extends Step {
public final VibratorController controller;
public final VibrationEffect.Composed effect;
public final int segmentIndex;
- public final long previousStepVibratorOffTimeout;
long mVibratorOnResult;
+ long mPendingVibratorOffDeadline;
boolean mVibratorCompleteCallbackReceived;
/**
@@ -43,19 +43,19 @@ abstract class AbstractVibratorStep extends Step {
* @param controller The vibrator that is playing the effect.
* @param effect The effect being played in this step.
* @param index The index of the next segment to be played by this step
- * @param previousStepVibratorOffTimeout The time the vibrator is expected to complete any
+ * @param pendingVibratorOffDeadline The time the vibrator is expected to complete any
* previous vibration and turn off. This is used to allow this step to
* be triggered when the completion callback is received, and can
* be used to play effects back-to-back.
*/
AbstractVibratorStep(VibrationStepConductor conductor, long startTime,
VibratorController controller, VibrationEffect.Composed effect, int index,
- long previousStepVibratorOffTimeout) {
+ long pendingVibratorOffDeadline) {
super(conductor, startTime);
this.controller = controller;
this.effect = effect;
this.segmentIndex = index;
- this.previousStepVibratorOffTimeout = previousStepVibratorOffTimeout;
+ mPendingVibratorOffDeadline = pendingVibratorOffDeadline;
}
public int getVibratorId() {
@@ -69,27 +69,57 @@ abstract class AbstractVibratorStep extends Step {
@Override
public boolean acceptVibratorCompleteCallback(int vibratorId) {
- boolean isSameVibrator = controller.getVibratorInfo().getId() == vibratorId;
- mVibratorCompleteCallbackReceived |= isSameVibrator;
+ if (getVibratorId() != vibratorId) {
+ return false;
+ }
+
// Only activate this step if a timeout was set to wait for the vibration to complete,
// otherwise we are waiting for the correct time to play the next step.
- return isSameVibrator && (previousStepVibratorOffTimeout > SystemClock.uptimeMillis());
+ boolean shouldAcceptCallback = mPendingVibratorOffDeadline > SystemClock.uptimeMillis();
+ if (VibrationThread.DEBUG) {
+ Slog.d(VibrationThread.TAG,
+ "Received completion callback from " + vibratorId
+ + ", accepted = " + shouldAcceptCallback);
+ }
+
+ // The callback indicates this vibrator has stopped, reset the timeout.
+ mPendingVibratorOffDeadline = 0;
+ mVibratorCompleteCallbackReceived = true;
+ return shouldAcceptCallback;
}
@Override
public List<Step> cancel() {
return Arrays.asList(new CompleteEffectVibratorStep(conductor, SystemClock.uptimeMillis(),
- /* cancelled= */ true, controller, previousStepVibratorOffTimeout));
+ /* cancelled= */ true, controller, mPendingVibratorOffDeadline));
}
@Override
public void cancelImmediately() {
- if (previousStepVibratorOffTimeout > SystemClock.uptimeMillis()) {
+ if (mPendingVibratorOffDeadline > SystemClock.uptimeMillis()) {
// Vibrator might be running from previous steps, so turn it off while canceling.
stopVibrating();
}
}
+ protected long handleVibratorOnResult(long vibratorOnResult) {
+ mVibratorOnResult = vibratorOnResult;
+ if (VibrationThread.DEBUG) {
+ Slog.d(VibrationThread.TAG,
+ "Turned on vibrator " + getVibratorId() + ", result = " + mVibratorOnResult);
+ }
+ if (mVibratorOnResult > 0) {
+ // Vibrator was turned on by this step, with vibratorOnResult as the duration.
+ // Set an extra timeout to wait for the vibrator completion callback.
+ mPendingVibratorOffDeadline = SystemClock.uptimeMillis() + mVibratorOnResult
+ + VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT;
+ } else {
+ // Vibrator does not support the request or failed to turn on, reset callback deadline.
+ mPendingVibratorOffDeadline = 0;
+ }
+ return mVibratorOnResult;
+ }
+
protected void stopVibrating() {
if (VibrationThread.DEBUG) {
Slog.d(VibrationThread.TAG,
@@ -97,6 +127,7 @@ abstract class AbstractVibratorStep extends Step {
}
controller.off();
getVibration().stats().reportVibratorOff();
+ mPendingVibratorOffDeadline = 0;
}
protected void changeAmplitude(float amplitude) {
@@ -109,40 +140,29 @@ abstract class AbstractVibratorStep extends Step {
}
/**
- * Return the {@link VibrationStepConductor#nextVibrateStep} with same timings, only jumping
- * the segments.
- */
- protected List<Step> skipToNextSteps(int segmentsSkipped) {
- return nextSteps(startTime, previousStepVibratorOffTimeout, segmentsSkipped);
- }
-
- /**
- * Return the {@link VibrationStepConductor#nextVibrateStep} with same start and off timings
- * calculated from {@link #getVibratorOnDuration()}, jumping all played segments.
- *
- * <p>This method has same behavior as {@link #skipToNextSteps(int)} when the vibrator
- * result is non-positive, meaning the vibrator has either ignored or failed to turn on.
+ * Return the {@link VibrationStepConductor#nextVibrateStep} with start and off timings
+ * calculated from {@link #getVibratorOnDuration()} based on the current
+ * {@link SystemClock#uptimeMillis()} and jumping all played segments from the effect.
*/
protected List<Step> nextSteps(int segmentsPlayed) {
- if (mVibratorOnResult <= 0) {
- // Vibration was not started, so just skip the played segments and keep timings.
- return skipToNextSteps(segmentsPlayed);
+ // Schedule next steps to run right away.
+ long nextStartTime = SystemClock.uptimeMillis();
+ if (mVibratorOnResult > 0) {
+ // Vibrator was turned on by this step, with mVibratorOnResult as the duration.
+ // Schedule next steps for right after the vibration finishes.
+ nextStartTime += mVibratorOnResult;
}
- long nextStartTime = SystemClock.uptimeMillis() + mVibratorOnResult;
- long nextVibratorOffTimeout =
- nextStartTime + VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT;
- return nextSteps(nextStartTime, nextVibratorOffTimeout, segmentsPlayed);
+ return nextSteps(nextStartTime, segmentsPlayed);
}
/**
- * Return the {@link VibrationStepConductor#nextVibrateStep} with given start and off timings,
- * which might be calculated independently, jumping all played segments.
+ * Return the {@link VibrationStepConductor#nextVibrateStep} with given start time,
+ * which might be calculated independently, and jumping all played segments from the effect.
*
- * <p>This should be used when the vibrator on/off state is not responsible for the steps
- * execution timings, e.g. while playing the vibrator amplitudes.
+ * <p>This should be used when the vibrator on/off state is not responsible for the step
+ * execution timing, e.g. while playing the vibrator amplitudes.
*/
- protected List<Step> nextSteps(long nextStartTime, long vibratorOffTimeout,
- int segmentsPlayed) {
+ protected List<Step> nextSteps(long nextStartTime, int segmentsPlayed) {
int nextSegmentIndex = segmentIndex + segmentsPlayed;
int effectSize = effect.getSegments().size();
int repeatIndex = effect.getRepeatIndex();
@@ -154,7 +174,7 @@ abstract class AbstractVibratorStep extends Step {
nextSegmentIndex = repeatIndex + ((nextSegmentIndex - effectSize) % loopSize);
}
Step nextStep = conductor.nextVibrateStep(nextStartTime, controller, effect,
- nextSegmentIndex, vibratorOffTimeout);
+ nextSegmentIndex, mPendingVibratorOffDeadline);
return nextStep == null ? VibrationStepConductor.EMPTY_STEP_LIST : Arrays.asList(nextStep);
}
}
diff --git a/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java b/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java
index 8585e3473ef3..fb5140d862b7 100644
--- a/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java
@@ -34,9 +34,9 @@ final class CompleteEffectVibratorStep extends AbstractVibratorStep {
private final boolean mCancelled;
CompleteEffectVibratorStep(VibrationStepConductor conductor, long startTime, boolean cancelled,
- VibratorController controller, long previousStepVibratorOffTimeout) {
+ VibratorController controller, long pendingVibratorOffDeadline) {
super(conductor, startTime, controller, /* effect= */ null, /* index= */ -1,
- previousStepVibratorOffTimeout);
+ pendingVibratorOffDeadline);
mCancelled = cancelled;
}
@@ -73,10 +73,11 @@ final class CompleteEffectVibratorStep extends AbstractVibratorStep {
return VibrationStepConductor.EMPTY_STEP_LIST;
}
+ long now = SystemClock.uptimeMillis();
float currentAmplitude = controller.getCurrentAmplitude();
long remainingOnDuration =
- previousStepVibratorOffTimeout - VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT
- - SystemClock.uptimeMillis();
+ mPendingVibratorOffDeadline - now
+ - VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT;
long rampDownDuration =
Math.min(remainingOnDuration,
conductor.vibrationSettings.getRampDownDuration());
@@ -89,8 +90,10 @@ final class CompleteEffectVibratorStep extends AbstractVibratorStep {
stopVibrating();
return VibrationStepConductor.EMPTY_STEP_LIST;
} else {
+ // Vibration is completing normally, turn off after the deadline in case we
+ // don't receive the callback in time (callback also triggers it right away).
return Arrays.asList(new TurnOffVibratorStep(
- conductor, previousStepVibratorOffTimeout, controller));
+ conductor, mPendingVibratorOffDeadline, controller));
}
}
@@ -100,13 +103,18 @@ final class CompleteEffectVibratorStep extends AbstractVibratorStep {
+ " from amplitude " + currentAmplitude
+ " for " + rampDownDuration + "ms");
}
+
+ // If we are cancelling this vibration then make sure the vibrator will be turned off
+ // immediately after the ramp off duration. Otherwise, this is a planned ramp off for
+ // the remaining ON duration, then just propagate the mPendingVibratorOffDeadline so the
+ // turn off step will wait for the vibration completion callback and end gracefully.
+ long rampOffVibratorOffDeadline =
+ mCancelled ? (now + rampDownDuration) : mPendingVibratorOffDeadline;
float amplitudeDelta = currentAmplitude / (rampDownDuration / stepDownDuration);
float amplitudeTarget = currentAmplitude - amplitudeDelta;
- long newVibratorOffTimeout =
- mCancelled ? rampDownDuration : previousStepVibratorOffTimeout;
return Arrays.asList(
new RampOffVibratorStep(conductor, startTime, amplitudeTarget, amplitudeDelta,
- controller, newVibratorOffTimeout));
+ controller, rampOffVibratorOffDeadline));
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
diff --git a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
index f8b99265246a..545ec5bcff03 100644
--- a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
@@ -40,11 +40,11 @@ final class ComposePrimitivesVibratorStep extends AbstractVibratorStep {
ComposePrimitivesVibratorStep(VibrationStepConductor conductor, long startTime,
VibratorController controller, VibrationEffect.Composed effect, int index,
- long previousStepVibratorOffTimeout) {
+ long pendingVibratorOffDeadline) {
// This step should wait for the last vibration to finish (with the timeout) and for the
// intended step start time (to respect the effect delays).
- super(conductor, Math.max(startTime, previousStepVibratorOffTimeout), controller, effect,
- index, previousStepVibratorOffTimeout);
+ super(conductor, Math.max(startTime, pendingVibratorOffDeadline), controller, effect,
+ index, pendingVibratorOffDeadline);
}
@Override
@@ -60,18 +60,22 @@ final class ComposePrimitivesVibratorStep extends AbstractVibratorStep {
if (primitives.isEmpty()) {
Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePrimitivesStep: "
+ effect.getSegments().get(segmentIndex));
- return skipToNextSteps(/* segmentsSkipped= */ 1);
+ // Skip this step and play the next one right away.
+ return nextSteps(/* segmentsPlayed= */ 1);
}
if (VibrationThread.DEBUG) {
Slog.d(VibrationThread.TAG, "Compose " + primitives + " primitives on vibrator "
- + controller.getVibratorInfo().getId());
+ + getVibratorId());
}
+
PrimitiveSegment[] primitivesArray =
primitives.toArray(new PrimitiveSegment[primitives.size()]);
- mVibratorOnResult = controller.on(primitivesArray, getVibration().id);
- getVibration().stats().reportComposePrimitives(mVibratorOnResult, primitivesArray);
+ long vibratorOnResult = controller.on(primitivesArray, getVibration().id);
+ handleVibratorOnResult(vibratorOnResult);
+ getVibration().stats().reportComposePrimitives(vibratorOnResult, primitivesArray);
+ // The next start and off times will be calculated from mVibratorOnResult.
return nextSteps(/* segmentsPlayed= */ primitives.size());
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
diff --git a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
index 81f52c912f28..8bfa2c3cd082 100644
--- a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
@@ -41,11 +41,11 @@ final class ComposePwleVibratorStep extends AbstractVibratorStep {
ComposePwleVibratorStep(VibrationStepConductor conductor, long startTime,
VibratorController controller, VibrationEffect.Composed effect, int index,
- long previousStepVibratorOffTimeout) {
+ long pendingVibratorOffDeadline) {
// This step should wait for the last vibration to finish (with the timeout) and for the
// intended step start time (to respect the effect delays).
- super(conductor, Math.max(startTime, previousStepVibratorOffTimeout), controller, effect,
- index, previousStepVibratorOffTimeout);
+ super(conductor, Math.max(startTime, pendingVibratorOffDeadline), controller, effect,
+ index, pendingVibratorOffDeadline);
}
@Override
@@ -61,7 +61,8 @@ final class ComposePwleVibratorStep extends AbstractVibratorStep {
if (pwles.isEmpty()) {
Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePwleStep: "
+ effect.getSegments().get(segmentIndex));
- return skipToNextSteps(/* segmentsSkipped= */ 1);
+ // Skip this step and play the next one right away.
+ return nextSteps(/* segmentsPlayed= */ 1);
}
if (VibrationThread.DEBUG) {
@@ -69,9 +70,11 @@ final class ComposePwleVibratorStep extends AbstractVibratorStep {
+ controller.getVibratorInfo().getId());
}
RampSegment[] pwlesArray = pwles.toArray(new RampSegment[pwles.size()]);
- mVibratorOnResult = controller.on(pwlesArray, getVibration().id);
- getVibration().stats().reportComposePwle(mVibratorOnResult, pwlesArray);
+ long vibratorOnResult = controller.on(pwlesArray, getVibration().id);
+ handleVibratorOnResult(vibratorOnResult);
+ getVibration().stats().reportComposePwle(vibratorOnResult, pwlesArray);
+ // The next start and off times will be calculated from mVibratorOnResult.
return nextSteps(/* segmentsPlayed= */ pwles.size());
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
diff --git a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
index 41902147838d..d91bafa7c8c2 100644
--- a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
@@ -35,11 +35,11 @@ final class PerformPrebakedVibratorStep extends AbstractVibratorStep {
PerformPrebakedVibratorStep(VibrationStepConductor conductor, long startTime,
VibratorController controller, VibrationEffect.Composed effect, int index,
- long previousStepVibratorOffTimeout) {
+ long pendingVibratorOffDeadline) {
// This step should wait for the last vibration to finish (with the timeout) and for the
// intended step start time (to respect the effect delays).
- super(conductor, Math.max(startTime, previousStepVibratorOffTimeout), controller, effect,
- index, previousStepVibratorOffTimeout);
+ super(conductor, Math.max(startTime, pendingVibratorOffDeadline), controller, effect,
+ index, pendingVibratorOffDeadline);
}
@Override
@@ -50,7 +50,8 @@ final class PerformPrebakedVibratorStep extends AbstractVibratorStep {
if (!(segment instanceof PrebakedSegment)) {
Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a "
+ "PerformPrebakedVibratorStep: " + segment);
- return skipToNextSteps(/* segmentsSkipped= */ 1);
+ // Skip this step and play the next one right away.
+ return nextSteps(/* segmentsPlayed= */ 1);
}
PrebakedSegment prebaked = (PrebakedSegment) segment;
@@ -61,10 +62,11 @@ final class PerformPrebakedVibratorStep extends AbstractVibratorStep {
}
VibrationEffect fallback = getVibration().getFallback(prebaked.getEffectId());
- mVibratorOnResult = controller.on(prebaked, getVibration().id);
- getVibration().stats().reportPerformEffect(mVibratorOnResult, prebaked);
+ long vibratorOnResult = controller.on(prebaked, getVibration().id);
+ handleVibratorOnResult(vibratorOnResult);
+ getVibration().stats().reportPerformEffect(vibratorOnResult, prebaked);
- if (mVibratorOnResult == 0 && prebaked.shouldFallback()
+ if (vibratorOnResult == 0 && prebaked.shouldFallback()
&& (fallback instanceof VibrationEffect.Composed)) {
if (VibrationThread.DEBUG) {
Slog.d(VibrationThread.TAG, "Playing fallback for effect "
@@ -72,14 +74,15 @@ final class PerformPrebakedVibratorStep extends AbstractVibratorStep {
}
AbstractVibratorStep fallbackStep = conductor.nextVibrateStep(startTime, controller,
replaceCurrentSegment((VibrationEffect.Composed) fallback),
- segmentIndex, previousStepVibratorOffTimeout);
+ segmentIndex, mPendingVibratorOffDeadline);
List<Step> fallbackResult = fallbackStep.play();
// Update the result with the fallback result so this step is seamlessly
// replaced by the fallback to any outer application of this.
- mVibratorOnResult = fallbackStep.getVibratorOnDuration();
+ handleVibratorOnResult(fallbackStep.getVibratorOnDuration());
return fallbackResult;
}
+ // The next start and off times will be calculated from mVibratorOnResult.
return nextSteps(/* segmentsPlayed= */ 1);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
diff --git a/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java b/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java
index 8cf5fb394d9d..84da9f2c58ec 100644
--- a/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java
@@ -30,9 +30,9 @@ final class RampOffVibratorStep extends AbstractVibratorStep {
RampOffVibratorStep(VibrationStepConductor conductor, long startTime, float amplitudeTarget,
float amplitudeDelta, VibratorController controller,
- long previousStepVibratorOffTimeout) {
+ long pendingVibratorOffDeadline) {
super(conductor, startTime, controller, /* effect= */ null, /* index= */ -1,
- previousStepVibratorOffTimeout);
+ pendingVibratorOffDeadline);
mAmplitudeTarget = amplitudeTarget;
mAmplitudeDelta = amplitudeDelta;
}
@@ -68,15 +68,17 @@ final class RampOffVibratorStep extends AbstractVibratorStep {
float newAmplitudeTarget = mAmplitudeTarget - mAmplitudeDelta;
if (newAmplitudeTarget < VibrationStepConductor.RAMP_OFF_AMPLITUDE_MIN) {
- // Vibrator amplitude cannot go further down, just turn it off.
+ // Vibrator amplitude cannot go further down, just turn it off with the configured
+ // deadline that has been adjusted for the scenario when this was triggered by a
+ // cancelled vibration.
return Arrays.asList(new TurnOffVibratorStep(
- conductor, previousStepVibratorOffTimeout, controller));
+ conductor, mPendingVibratorOffDeadline, controller));
}
return Arrays.asList(new RampOffVibratorStep(
conductor,
startTime + conductor.vibrationSettings.getRampStepDuration(),
newAmplitudeTarget, mAmplitudeDelta, controller,
- previousStepVibratorOffTimeout));
+ mPendingVibratorOffDeadline));
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
diff --git a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
index 6fb9111793ea..1672470f1f1a 100644
--- a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
@@ -39,26 +39,34 @@ final class SetAmplitudeVibratorStep extends AbstractVibratorStep {
*/
private static final int REPEATING_EFFECT_ON_DURATION = 5000; // 5s
- private long mNextOffTime;
-
SetAmplitudeVibratorStep(VibrationStepConductor conductor, long startTime,
VibratorController controller, VibrationEffect.Composed effect, int index,
- long previousStepVibratorOffTimeout) {
+ long pendingVibratorOffDeadline) {
// This step has a fixed startTime coming from the timings of the waveform it's playing.
- super(conductor, startTime, controller, effect, index, previousStepVibratorOffTimeout);
- mNextOffTime = previousStepVibratorOffTimeout;
+ super(conductor, startTime, controller, effect, index, pendingVibratorOffDeadline);
}
@Override
public boolean acceptVibratorCompleteCallback(int vibratorId) {
- if (controller.getVibratorInfo().getId() == vibratorId) {
- mVibratorCompleteCallbackReceived = true;
- mNextOffTime = SystemClock.uptimeMillis();
+ // Ensure the super method is called and will reset the off timeout and boolean flag.
+ // This is true if the vibrator was ON and this callback has the same vibratorId.
+ if (!super.acceptVibratorCompleteCallback(vibratorId)) {
+ return false;
}
+
// Timings are tightly controlled here, so only trigger this step if the vibrator was
// supposed to be ON but has completed prematurely, to turn it back on as soon as
- // possible.
- return mNextOffTime < startTime && controller.getCurrentAmplitude() > 0;
+ // possible. If the vibrator turned off during a zero-amplitude step, just wait for
+ // the correct start time of this step before playing it.
+ boolean shouldAcceptCallback =
+ (SystemClock.uptimeMillis() < startTime) && (controller.getCurrentAmplitude() > 0);
+
+ if (VibrationThread.DEBUG) {
+ Slog.d(VibrationThread.TAG,
+ "Amplitude step received completion callback from " + vibratorId
+ + ", accepted = " + shouldAcceptCallback);
+ }
+ return shouldAcceptCallback;
}
@Override
@@ -78,40 +86,38 @@ final class SetAmplitudeVibratorStep extends AbstractVibratorStep {
if (mVibratorCompleteCallbackReceived && latency < 0) {
// This step was run early because the vibrator turned off prematurely.
// Turn it back on and return this same step to run at the exact right time.
- mNextOffTime = turnVibratorBackOn(/* remainingDuration= */ -latency);
+ turnVibratorBackOn(/* remainingDuration= */ -latency);
return Arrays.asList(new SetAmplitudeVibratorStep(conductor, startTime, controller,
- effect, segmentIndex, mNextOffTime));
+ effect, segmentIndex, mPendingVibratorOffDeadline));
}
VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
if (!(segment instanceof StepSegment)) {
Slog.w(VibrationThread.TAG,
"Ignoring wrong segment for a SetAmplitudeVibratorStep: " + segment);
- return skipToNextSteps(/* segmentsSkipped= */ 1);
+ // Use original startTime to avoid propagating latencies to the waveform.
+ return nextSteps(startTime, /* segmentsPlayed= */ 1);
}
StepSegment stepSegment = (StepSegment) segment;
if (stepSegment.getDuration() == 0) {
- // Skip waveform entries with zero timing.
- return skipToNextSteps(/* segmentsSkipped= */ 1);
+ // Use original startTime to avoid propagating latencies to the waveform.
+ return nextSteps(startTime, /* segmentsPlayed= */ 1);
}
float amplitude = stepSegment.getAmplitude();
if (amplitude == 0) {
- if (previousStepVibratorOffTimeout > now) {
+ if (mPendingVibratorOffDeadline > now) {
// Amplitude cannot be set to zero, so stop the vibrator.
stopVibrating();
- mNextOffTime = now;
}
} else {
- if (startTime >= mNextOffTime) {
+ if (startTime >= mPendingVibratorOffDeadline) {
// Vibrator is OFF. Turn vibrator back on for the duration of another
// cycle before setting the amplitude.
long onDuration = getVibratorOnDuration(effect, segmentIndex);
if (onDuration > 0) {
- mVibratorOnResult = startVibrating(onDuration);
- mNextOffTime = now + onDuration
- + VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT;
+ startVibrating(onDuration);
}
}
changeAmplitude(amplitude);
@@ -119,27 +125,32 @@ final class SetAmplitudeVibratorStep extends AbstractVibratorStep {
// Use original startTime to avoid propagating latencies to the waveform.
long nextStartTime = startTime + segment.getDuration();
- return nextSteps(nextStartTime, mNextOffTime, /* segmentsPlayed= */ 1);
+ return nextSteps(nextStartTime, /* segmentsPlayed= */ 1);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
}
- private long turnVibratorBackOn(long remainingDuration) {
+ private void turnVibratorBackOn(long remainingDuration) {
long onDuration = getVibratorOnDuration(effect, segmentIndex);
if (onDuration <= 0) {
// Vibrator is supposed to go back off when this step starts, so just leave it off.
- return previousStepVibratorOffTimeout;
+ return;
}
onDuration += remainingDuration;
+
+ if (VibrationThread.DEBUG) {
+ Slog.d(VibrationThread.TAG,
+ "Turning the vibrator back ON using the remaining duration of "
+ + remainingDuration + "ms, for a total of " + onDuration + "ms");
+ }
+
float expectedAmplitude = controller.getCurrentAmplitude();
- mVibratorOnResult = startVibrating(onDuration);
- if (mVibratorOnResult > 0) {
+ long vibratorOnResult = startVibrating(onDuration);
+ if (vibratorOnResult > 0) {
// Set the amplitude back to the value it was supposed to be playing at.
changeAmplitude(expectedAmplitude);
}
- return SystemClock.uptimeMillis() + onDuration
- + VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT;
}
private long startVibrating(long duration) {
@@ -149,6 +160,7 @@ final class SetAmplitudeVibratorStep extends AbstractVibratorStep {
+ duration + "ms");
}
long vibratorOnResult = controller.on(duration, getVibration().id);
+ handleVibratorOnResult(vibratorOnResult);
getVibration().stats().reportVibratorOn(vibratorOnResult);
return vibratorOnResult;
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index 0799b955b6f1..0af171871792 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -112,8 +112,7 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
@Nullable
AbstractVibratorStep nextVibrateStep(long startTime, VibratorController controller,
- VibrationEffect.Composed effect, int segmentIndex,
- long previousStepVibratorOffTimeout) {
+ VibrationEffect.Composed effect, int segmentIndex, long pendingVibratorOffDeadline) {
if (Build.IS_DEBUGGABLE) {
expectIsVibrationThread(true);
}
@@ -123,24 +122,24 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
if (segmentIndex < 0) {
// No more segments to play, last step is to complete the vibration on this vibrator.
return new CompleteEffectVibratorStep(this, startTime, /* cancelled= */ false,
- controller, previousStepVibratorOffTimeout);
+ controller, pendingVibratorOffDeadline);
}
VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
if (segment instanceof PrebakedSegment) {
return new PerformPrebakedVibratorStep(this, startTime, controller, effect,
- segmentIndex, previousStepVibratorOffTimeout);
+ segmentIndex, pendingVibratorOffDeadline);
}
if (segment instanceof PrimitiveSegment) {
return new ComposePrimitivesVibratorStep(this, startTime, controller, effect,
- segmentIndex, previousStepVibratorOffTimeout);
+ segmentIndex, pendingVibratorOffDeadline);
}
if (segment instanceof RampSegment) {
return new ComposePwleVibratorStep(this, startTime, controller, effect, segmentIndex,
- previousStepVibratorOffTimeout);
+ pendingVibratorOffDeadline);
}
return new SetAmplitudeVibratorStep(this, startTime, controller, effect, segmentIndex,
- previousStepVibratorOffTimeout);
+ pendingVibratorOffDeadline);
}
/** Called when this conductor is going to be started running by the VibrationThread. */
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 2f12a820eb81..d1cde602b391 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -387,8 +387,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
* An internal-only version of vibrate that allows the caller access to the {@link Vibration}.
* The Vibration is only returned if it is ongoing after this method returns.
*/
- @Nullable
@VisibleForTesting
+ @Nullable
Vibration vibrateInternal(int uid, String opPkg, @NonNull CombinedVibration effect,
@Nullable VibrationAttributes attrs, String reason, IBinder token) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
@@ -1844,6 +1844,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
attrs, commonOptions.description, deathBinder);
if (vib != null && !commonOptions.background) {
try {
+ // Waits for the client vibration to finish, but the VibrationThread may still
+ // do cleanup after this.
vib.waitForEnd();
} catch (InterruptedException e) {
}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index ca162efe0f6e..efc240d3f172 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -574,7 +574,7 @@ public class VibrationThreadTest {
}
@Test
- public void vibrate_singleVibratorComposedAndNoCapability_ignoresVibration() throws Exception {
+ public void vibrate_singleVibratorComposedAndNoCapability_ignoresVibration() {
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
@@ -666,6 +666,47 @@ public class VibrationThreadTest {
}
@Test
+ public void vibrate_singleVibratorComposedWithFallback_replacedInTheMiddleOfComposition() {
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+ fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ fakeVibrator.setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_CLICK,
+ VibrationEffect.Composition.PRIMITIVE_TICK);
+ fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+
+ long vibrationId = 1;
+ VibrationEffect fallback = VibrationEffect.createOneShot(10, 100);
+ VibrationEffect effect = VibrationEffect.startComposition()
+ .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
+ .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_TICK))
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
+ .compose();
+ Vibration vib = createVibration(vibrationId, CombinedVibration.createParallel(effect));
+ vib.addFallback(VibrationEffect.EFFECT_TICK, fallback);
+ startThreadAndDispatcher(vib);
+ waitForCompletion();
+
+ // Use first duration the vibrator is turned on since we cannot estimate the clicks.
+ verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong());
+ verify(mManagerHooks).noteVibratorOff(eq(UID));
+ verify(mControllerCallbacks, times(4)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
+ verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+ assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+
+ List<VibrationEffectSegment> segments =
+ mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId);
+ assertTrue("Wrong segments: " + segments, segments.size() >= 4);
+ assertTrue(segments.get(0) instanceof PrebakedSegment);
+ assertTrue(segments.get(1) instanceof PrimitiveSegment);
+ for (int i = 2; i < segments.size() - 1; i++) {
+ // One or more step segments as fallback for the EFFECT_TICK.
+ assertTrue(segments.get(i) instanceof StepSegment);
+ }
+ assertTrue(segments.get(segments.size() - 1) instanceof PrimitiveSegment);
+ }
+
+ @Test
public void vibrate_singleVibratorPwle_runsComposePwle() throws Exception {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 36bec750e3bc..b8e1612049f0 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -72,6 +72,7 @@ import android.os.VibratorInfo;
import android.os.test.TestLooper;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
import android.platform.test.annotations.Presubmit;
@@ -99,6 +100,7 @@ import org.mockito.junit.MockitoRule;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -391,22 +393,22 @@ public class VibratorManagerServiceTest {
IVibratorStateListener listenerMock = mockVibratorStateListener();
service.registerVibratorStateListener(1, listenerMock);
- vibrate(service, VibrationEffect.createOneShot(40, 100), ALARM_ATTRS);
- // Wait until service knows vibrator is on.
- assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
- // Wait until effect ends.
- assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+ long oneShotDuration = 20;
+ vibrateAndWaitUntilFinished(service,
+ VibrationEffect.createOneShot(oneShotDuration, VibrationEffect.DEFAULT_AMPLITUDE),
+ ALARM_ATTRS);
InOrder inOrderVerifier = inOrder(listenerMock);
// First notification done when listener is registered.
inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
inOrderVerifier.verify(listenerMock).onVibrating(eq(true));
- inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
+ // The last notification is after the vibration has completed.
+ inOrderVerifier.verify(listenerMock, timeout(TEST_TIMEOUT_MILLIS)).onVibrating(eq(false));
inOrderVerifier.verifyNoMoreInteractions();
InOrder batteryVerifier = inOrder(mBatteryStatsMock);
batteryVerifier.verify(mBatteryStatsMock)
- .noteVibratorOn(UID, 40 + mVibrationConfig.getRampDownDurationMs());
+ .noteVibratorOn(UID, oneShotDuration + mVibrationConfig.getRampDownDurationMs());
batteryVerifier.verify(mBatteryStatsMock).noteVibratorOff(UID);
}
@@ -577,22 +579,18 @@ public class VibratorManagerServiceTest {
setRingerMode(AudioManager.RINGER_MODE_SILENT);
VibratorManagerService service = createSystemReadyService();
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
- // Wait before checking it never played.
- assertFalse(waitUntil(s -> !fakeVibrator.getAllEffectSegments().isEmpty(),
- service, /* timeout= */ 50));
+ vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+ RINGTONE_ATTRS);
setRingerMode(AudioManager.RINGER_MODE_NORMAL);
service = createSystemReadyService();
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK), RINGTONE_ATTRS);
- assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 1,
- service, TEST_TIMEOUT_MILLIS));
+ vibrateAndWaitUntilFinished(
+ service, VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK), RINGTONE_ATTRS);
setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
service = createSystemReadyService();
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK), RINGTONE_ATTRS);
- assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 2,
- service, TEST_TIMEOUT_MILLIS));
+ vibrateAndWaitUntilFinished(
+ service, VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK), RINGTONE_ATTRS);
assertEquals(
Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_HEAVY_CLICK),
@@ -607,27 +605,18 @@ public class VibratorManagerServiceTest {
fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_TICK, VibrationEffect.EFFECT_CLICK,
VibrationEffect.EFFECT_HEAVY_CLICK, VibrationEffect.EFFECT_DOUBLE_CLICK);
VibratorManagerService service = createSystemReadyService();
- mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
- // The haptic feedback should be ignored in low power, but not the ringtone. The end
- // of the test asserts which actual effects ended up playing.
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), HAPTIC_FEEDBACK_ATTRS);
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
- assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 1,
- service, TEST_TIMEOUT_MILLIS));
- // Allow the ringtone to complete, as the other vibrations won't cancel it.
- assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+ mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+ vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK),
+ HAPTIC_FEEDBACK_ATTRS);
+ vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+ RINGTONE_ATTRS);
mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK),
- /* attrs= */ null);
- assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 2,
- service, TEST_TIMEOUT_MILLIS));
-
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK),
- NOTIFICATION_ATTRS);
- assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 3,
- service, TEST_TIMEOUT_MILLIS));
+ vibrateAndWaitUntilFinished(service,
+ VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK), /* attrs= */ null);
+ vibrateAndWaitUntilFinished(service,
+ VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK), NOTIFICATION_ATTRS);
assertEquals(
Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK),
@@ -693,22 +682,17 @@ public class VibratorManagerServiceTest {
Vibrator.VIBRATION_INTENSITY_HIGH);
VibratorManagerService service = createSystemReadyService();
- VibrationAttributes enforceFreshAttrs = new VibrationAttributes.Builder()
+ VibrationAttributes notificationWithFreshAttrs = new VibrationAttributes.Builder()
.setUsage(VibrationAttributes.USAGE_NOTIFICATION)
.setFlags(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)
.build();
setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
Vibrator.VIBRATION_INTENSITY_LOW);
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), NOTIFICATION_ATTRS);
- // VibrationThread will start this vibration async, so wait before vibrating a second time.
- assertTrue(waitUntil(s -> mVibratorProviders.get(0).getAllEffectSegments().size() > 0,
- service, TEST_TIMEOUT_MILLIS));
-
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), enforceFreshAttrs);
- // VibrationThread will start this vibration async, so wait before checking.
- assertTrue(waitUntil(s -> mVibratorProviders.get(0).getAllEffectSegments().size() > 1,
- service, TEST_TIMEOUT_MILLIS));
+ vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+ NOTIFICATION_ATTRS);
+ vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK),
+ notificationWithFreshAttrs);
assertEquals(
Arrays.asList(
@@ -784,21 +768,22 @@ public class VibratorManagerServiceTest {
vibrate(service, repeatingEffect, new VibrationAttributes.Builder().setUsage(
VibrationAttributes.USAGE_UNKNOWN).build());
- // VibrationThread will start this vibration async, so wait before checking it started.
+ // VibrationThread will start this vibration async, wait until it has started.
assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
service, TEST_TIMEOUT_MILLIS));
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), HAPTIC_FEEDBACK_ATTRS);
-
- // Wait before checking it never played a second effect.
- assertFalse(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1,
- service, /* timeout= */ 50));
+ vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+ HAPTIC_FEEDBACK_ATTRS);
// The time estimate is recorded when the vibration starts, repeating vibrations
// are capped at BATTERY_STATS_REPEATING_VIBRATION_DURATION (=5000).
verify(mBatteryStatsMock).noteVibratorOn(UID, 5000);
// The second vibration shouldn't have recorded that the vibrators were turned on.
verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
+ // No segment played is the prebaked CLICK from the second vibration.
+ assertFalse(
+ mVibratorProviders.get(1).getAllEffectSegments().stream()
+ .anyMatch(segment -> segment instanceof PrebakedSegment));
}
@Test
@@ -811,7 +796,7 @@ public class VibratorManagerServiceTest {
new long[]{10_000, 10_000}, new int[]{128, 255}, -1);
vibrate(service, alarmEffect, ALARM_ATTRS);
- // VibrationThread will start this vibration async, so wait before checking it started.
+ // VibrationThread will start this vibration async, wait until it has started.
assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
service, TEST_TIMEOUT_MILLIS));
@@ -841,14 +826,15 @@ public class VibratorManagerServiceTest {
assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
service, TEST_TIMEOUT_MILLIS));
- vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS);
-
- // Wait before checking it never played a second effect.
- assertFalse(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1,
- service, /* timeout= */ 50));
+ vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+ HAPTIC_FEEDBACK_ATTRS);
// The second vibration shouldn't have recorded that the vibrators were turned on.
verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
+ // The second vibration shouldn't have played any prebaked segment.
+ assertFalse(
+ mVibratorProviders.get(1).getAllEffectSegments().stream()
+ .anyMatch(segment -> segment instanceof PrebakedSegment));
}
@Test
@@ -856,6 +842,7 @@ public class VibratorManagerServiceTest {
throws Exception {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
VibratorManagerService service = createSystemReadyService();
VibrationEffect effect = VibrationEffect.createWaveform(
@@ -866,14 +853,16 @@ public class VibratorManagerServiceTest {
assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
service, TEST_TIMEOUT_MILLIS));
- vibrate(service, effect, RINGTONE_ATTRS);
-
- // VibrationThread will start this vibration async, so wait before checking it started.
- assertTrue(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1,
- service, TEST_TIMEOUT_MILLIS));
+ vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+ RINGTONE_ATTRS);
// The second vibration should have recorded that the vibrators were turned on.
verify(mBatteryStatsMock, times(2)).noteVibratorOn(anyInt(), anyLong());
+ // One segment played is the prebaked CLICK from the second vibration.
+ assertEquals(1,
+ mVibratorProviders.get(1).getAllEffectSegments().stream()
+ .filter(PrebakedSegment.class::isInstance)
+ .count());
}
@Test
@@ -892,12 +881,10 @@ public class VibratorManagerServiceTest {
CombinedVibration effect = CombinedVibration.createParallel(
VibrationEffect.createOneShot(10, 10));
- vibrate(service, effect, ALARM_ATTRS);
- verify(mIInputManagerMock).vibrateCombined(eq(1), eq(effect), any());
+ vibrateAndWaitUntilFinished(service, effect, ALARM_ATTRS);
- // VibrationThread will start this vibration async, so wait before checking it never played.
- assertFalse(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
- service, /* timeout= */ 50));
+ verify(mIInputManagerMock).vibrateCombined(eq(1), eq(effect), any());
+ assertTrue(mVibratorProviders.get(1).getAllEffectSegments().isEmpty());
}
@Test
@@ -992,9 +979,7 @@ public class VibratorManagerServiceTest {
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
.compose())
.combine();
- vibrate(service, effect, ALARM_ATTRS);
- assertTrue(waitUntil(s -> !fakeVibrator1.getAllEffectSegments().isEmpty(), service,
- TEST_TIMEOUT_MILLIS));
+ vibrateAndWaitUntilFinished(service, effect, ALARM_ATTRS);
verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
verify(mNativeWrapperMock).triggerSynced(anyLong());
@@ -1016,9 +1001,7 @@ public class VibratorManagerServiceTest {
.addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
.addVibrator(2, VibrationEffect.createOneShot(10, 100))
.combine();
- vibrate(service, effect, ALARM_ATTRS);
- assertTrue(waitUntil(s -> !fakeVibrator1.getAllEffectSegments().isEmpty(), service,
- TEST_TIMEOUT_MILLIS));
+ vibrateAndWaitUntilFinished(service, effect, ALARM_ATTRS);
verify(mNativeWrapperMock, never()).prepareSynced(any());
verify(mNativeWrapperMock, never()).triggerSynced(anyLong());
@@ -1036,9 +1019,7 @@ public class VibratorManagerServiceTest {
.addVibrator(1, VibrationEffect.createOneShot(10, 50))
.addVibrator(2, VibrationEffect.createOneShot(10, 100))
.combine();
- vibrate(service, effect, ALARM_ATTRS);
- assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
- service, TEST_TIMEOUT_MILLIS));
+ vibrateAndWaitUntilFinished(service, effect, ALARM_ATTRS);
verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
verify(mNativeWrapperMock, never()).triggerSynced(anyLong());
@@ -1057,9 +1038,7 @@ public class VibratorManagerServiceTest {
.addVibrator(1, VibrationEffect.createOneShot(10, 50))
.addVibrator(2, VibrationEffect.createOneShot(10, 100))
.combine();
- vibrate(service, effect, ALARM_ATTRS);
- assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
- service, TEST_TIMEOUT_MILLIS));
+ vibrateAndWaitUntilFinished(service, effect, ALARM_ATTRS);
verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
verify(mNativeWrapperMock).triggerSynced(anyLong());
@@ -1096,28 +1075,21 @@ public class VibratorManagerServiceTest {
VibrationEffect.Composition.PRIMITIVE_TICK);
VibratorManagerService service = createSystemReadyService();
- vibrate(service, VibrationEffect.startComposition()
+ vibrateAndWaitUntilFinished(service, VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
.compose(), HAPTIC_FEEDBACK_ATTRS);
- assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 1,
- service, TEST_TIMEOUT_MILLIS));
- vibrate(service, CombinedVibration.startSequential()
+ vibrateAndWaitUntilFinished(service, CombinedVibration.startSequential()
.addNext(1, VibrationEffect.createOneShot(100, 125))
.combine(), NOTIFICATION_ATTRS);
- assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 2,
- service, TEST_TIMEOUT_MILLIS));
- vibrate(service, VibrationEffect.startComposition()
+ vibrateAndWaitUntilFinished(service, VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
.compose(), ALARM_ATTRS);
- assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 3,
- service, TEST_TIMEOUT_MILLIS));
// Ring vibrations have intensity OFF and are not played.
- vibrate(service, VibrationEffect.createOneShot(100, 125), RINGTONE_ATTRS);
- assertFalse(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() > 3,
- service, /* timeout= */ 50));
+ vibrateAndWaitUntilFinished(service, VibrationEffect.createOneShot(100, 125),
+ RINGTONE_ATTRS);
// Only 3 effects played successfully.
assertEquals(3, fakeVibrator.getAllEffectSegments().size());
@@ -1145,6 +1117,7 @@ public class VibratorManagerServiceTest {
.combine(),
HAPTIC_FEEDBACK_ATTRS);
+ // VibrationThread will start this vibration async, so wait until vibration is triggered.
assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
@@ -1159,14 +1132,50 @@ public class VibratorManagerServiceTest {
VibratorManagerService service = createSystemReadyService();
vibrate(service, VibrationEffect.createOneShot(1000, 100), HAPTIC_FEEDBACK_ATTRS);
+
+ // VibrationThread will start this vibration async, so wait until vibration is triggered.
assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
service.updateServiceState();
+
// Vibration is not stopped nearly after updating service.
assertFalse(waitUntil(s -> !s.isVibrating(1), service, 50));
}
@Test
+ public void vibrate_prebakedAndComposedVibrationsWithFallbacks_playsFallbackOnlyForPredefined()
+ throws Exception {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ mVibratorProviders.get(1).setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_CLICK);
+
+ VibratorManagerService service = createSystemReadyService();
+ vibrateAndWaitUntilFinished(service,
+ VibrationEffect.startComposition()
+ .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+ .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK))
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .compose(),
+ ALARM_ATTRS);
+
+ List<VibrationEffectSegment> segments = mVibratorProviders.get(1).getAllEffectSegments();
+ // At least one step segment played as fallback for unusupported vibration effect
+ assertTrue(segments.size() > 2);
+ // 0: Supported effect played
+ assertTrue(segments.get(0) instanceof PrebakedSegment);
+ // 1: No segment for unsupported primitive
+ // 2: One or more intermediate step segments as fallback for unsupported effect
+ for (int i = 1; i < segments.size() - 1; i++) {
+ assertTrue(segments.get(i) instanceof StepSegment);
+ }
+ // 3: Supported primitive played
+ assertTrue(segments.get(segments.size() - 1) instanceof PrimitiveSegment);
+ }
+
+ @Test
public void cancelVibrate_withoutUsageFilter_stopsVibrating() throws Exception {
mockVibrators(1);
VibratorManagerService service = createSystemReadyService();
@@ -1175,9 +1184,13 @@ public class VibratorManagerServiceTest {
assertFalse(service.isVibrating(1));
vibrate(service, VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100), ALARM_ATTRS);
+
+ // VibrationThread will start this vibration async, so wait until vibration is triggered.
assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
service.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, service);
+
+ // Alarm cancelled on filter match all.
assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
}
@@ -1187,6 +1200,8 @@ public class VibratorManagerServiceTest {
VibratorManagerService service = createSystemReadyService();
vibrate(service, VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100), ALARM_ATTRS);
+
+ // VibrationThread will start this vibration async, so wait until vibration is triggered.
assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
// Vibration is not cancelled with a different usage.
@@ -1216,6 +1231,8 @@ public class VibratorManagerServiceTest {
VibratorManagerService service = createSystemReadyService();
vibrate(service, VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100), attrs);
+
+ // VibrationThread will start this vibration async, so wait until vibration is triggered.
assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
// Do not cancel UNKNOWN vibration when filter is being applied for other usages.
@@ -1232,6 +1249,8 @@ public class VibratorManagerServiceTest {
assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
vibrate(service, VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100), attrs);
+
+ // VibrationThread will start this vibration async, so wait until vibration is triggered.
assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
// Cancel UNKNOWN vibration when all vibrations are being cancelled.
@@ -1312,6 +1331,8 @@ public class VibratorManagerServiceTest {
VibrationEffect effect = VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100);
vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS);
+
+ // VibrationThread will start this vibration async, so wait until vibration is triggered.
assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME, AUDIO_ATTRS,