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
This commit is contained in:
Lais Andrade
2022-07-08 18:19:34 +01:00
parent bb3501c4e7
commit 0cc2d21e9b
11 changed files with 314 additions and 199 deletions

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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. */

View File

@@ -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) {
}

View File

@@ -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)
@@ -665,6 +665,47 @@ public class VibrationThreadTest {
assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
}
@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);

View File

@@ -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,13 +1132,49 @@ 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);
@@ -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,