Merge "Fix composed VibrationEffect timing out"
diff --git a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
index eebd046..be90530 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 @@
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 @@
* @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 @@
@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 @@
}
controller.off();
getVibration().stats().reportVibratorOff();
+ mPendingVibratorOffDeadline = 0;
}
protected void changeAmplitude(float amplitude) {
@@ -109,40 +140,29 @@
}
/**
- * 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 @@
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 8585e34..fb5140d 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 @@
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 @@
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 @@
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 @@
+ " 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 f8b9926..545ec5b 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 @@
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 @@
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 81f52c9..8bfa2c3 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 @@
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 @@
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 @@
+ 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 419021478..d91bafa 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 @@
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 @@
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 @@
}
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 @@
}
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 8cf5fb3..84da9f2 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 @@
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 @@
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 6fb9111..1672470 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 @@
*/
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 @@
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 @@
// 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 @@
+ 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 0799b95..0af1718 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 @@
@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 @@
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 2f12a82..d1cde60 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 @@
* 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 @@
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 ca162ef..efc240d3 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 @@
}
@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 @@
}
@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 36bec75..b8e1612 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.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 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
.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 @@
.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 @@
.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 @@
.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 @@
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 @@
.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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,