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