From 88abcd0d56e5d8f6f7b1606313460cf6daab0e9a Mon Sep 17 00:00:00 2001 From: Lais Andrade Date: Wed, 10 Feb 2021 19:12:09 +0000 Subject: [PATCH] Add support for chained steps to VibrationThread Change the design of VibrationThread to allow all steps to be played in a chain. Replace the generation of a predefined list of steps to represent a combined effect by a design where each step played adds the next ones to the queue. This new approach will be necessary to allow mixing and chaining steps to play composed and waveform effects, which will be required by the PWLE waveform support. Bug: 167947076 Test: VibratonThreadTest Change-Id: I664f7477f7720af14fed963fa479ba38298a027c --- .../server/vibrator/VibrationThread.java | 1211 +++++++++-------- .../server/vibrator/VibrationThreadTest.java | 10 +- 2 files changed, 621 insertions(+), 600 deletions(-) diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java index 3893267690963..b90408fe53717 100644 --- a/services/core/java/com/android/server/vibrator/VibrationThread.java +++ b/services/core/java/com/android/server/vibrator/VibrationThread.java @@ -16,6 +16,7 @@ package com.android.server.vibrator; +import android.annotation.NonNull; import android.annotation.Nullable; import android.hardware.vibrator.IVibratorManager; import android.os.CombinedVibrationEffect; @@ -35,9 +36,9 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.util.FrameworkStatsLog; -import com.google.android.collect.Lists; - import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; import java.util.List; import java.util.PriorityQueue; @@ -47,11 +48,16 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { private static final boolean DEBUG = false; /** - * Extra timeout added to the end of each synced vibration step as a timeout for the callback - * wait, to ensure it finishes even when callbacks from individual vibrators are lost. + * Extra timeout added to the end of each vibration step to ensure it finishes even when + * vibrator callbacks are lost. */ private static final long CALLBACKS_EXTRA_TIMEOUT = 100; + /** Fixed large duration used to note repeating vibrations to {@link IBatteryStats}. */ + private static final long BATTERY_STATS_REPEATING_VIBRATION_DURATION = 5_000; + + private static final List EMPTY_STEP_LIST = new ArrayList<>(); + /** Callbacks for playing a {@link Vibration}. */ interface VibrationCallbacks { @@ -83,13 +89,10 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { private final IBatteryStats mBatteryStatsService; private final Vibration mVibration; private final VibrationCallbacks mCallbacks; - private final SparseArray mVibrators; + private final SparseArray mVibrators = new SparseArray<>(); + private final StepQueue mStepQueue = new StepQueue(); - @GuardedBy("mLock") - @Nullable - private VibrateStep mCurrentVibrateStep; - @GuardedBy("mLock") - private boolean mForceStop; + private volatile boolean mForceStop; VibrationThread(Vibration vib, SparseArray availableVibrators, PowerManager.WakeLock wakeLock, IBatteryStats batteryStatsService, @@ -102,7 +105,6 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { mBatteryStatsService = batteryStatsService; CombinedVibrationEffect effect = vib.getEffect(); - mVibrators = new SparseArray<>(); for (int i = 0; i < availableVibrators.size(); i++) { if (effect.hasVibrator(availableVibrators.keyAt(i))) { mVibrators.put(availableVibrators.keyAt(i), availableVibrators.valueAt(i)); @@ -110,6 +112,15 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { } } + Vibration getVibration() { + return mVibration; + } + + @VisibleForTesting + SparseArray getVibrators() { + return mVibrators; + } + @Override public void binderDied() { if (DEBUG) { @@ -136,8 +147,11 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { /** Cancel current vibration and shuts down the thread gracefully. */ public void cancel() { + mForceStop = true; synchronized (mLock) { - mForceStop = true; + if (DEBUG) { + Slog.d(TAG, "Vibration cancelled"); + } mLock.notify(); } } @@ -148,11 +162,10 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { if (DEBUG) { Slog.d(TAG, "Synced vibration complete reported by vibrator manager"); } - if (mCurrentVibrateStep != null) { - for (int i = 0; i < mVibrators.size(); i++) { - mCurrentVibrateStep.vibratorComplete(mVibrators.keyAt(i)); - } + for (int i = 0; i < mVibrators.size(); i++) { + mStepQueue.consumeOnVibratorComplete(mVibrators.keyAt(i)); } + mLock.notify(); } } @@ -162,120 +175,73 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { if (DEBUG) { Slog.d(TAG, "Vibration complete reported by vibrator " + vibratorId); } - if (mCurrentVibrateStep != null) { - mCurrentVibrateStep.vibratorComplete(vibratorId); - } + mStepQueue.consumeOnVibratorComplete(vibratorId); + mLock.notify(); } } - Vibration getVibration() { - return mVibration; - } - - @VisibleForTesting - SparseArray getVibrators() { - return mVibrators; - } - private Vibration.Status playVibration() { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "playVibration"); try { - List steps = generateSteps(mVibration.getEffect()); - if (steps.isEmpty()) { - // No vibrator matching any incoming vibration effect. - return Vibration.Status.IGNORED; - } - Vibration.Status status = Vibration.Status.FINISHED; - final int stepCount = steps.size(); - for (int i = 0; i < stepCount; i++) { - Step step = steps.get(i); - synchronized (mLock) { - if (step instanceof VibrateStep) { - mCurrentVibrateStep = (VibrateStep) step; + CombinedVibrationEffect.Sequential effect = toSequential(mVibration.getEffect()); + int stepsPlayed = 0; + + synchronized (mLock) { + mStepQueue.offer(new StartVibrateStep(effect)); + Step topOfQueue; + + while ((topOfQueue = mStepQueue.peek()) != null) { + long waitTime = topOfQueue.calculateWaitTime(); + if (waitTime <= 0) { + stepsPlayed += mStepQueue.consume(); } else { - mCurrentVibrateStep = null; + try { + mLock.wait(waitTime); + } catch (InterruptedException e) { } + } + if (mForceStop) { + mStepQueue.cancel(); + return Vibration.Status.CANCELLED; } } - status = step.play(); - if (status != Vibration.Status.FINISHED) { - // This step was ignored by the vibrators, probably effects were unsupported. - break; - } - if (mForceStop) { - break; - } } - if (mForceStop) { - return Vibration.Status.CANCELLED; - } - return status; + + // Some effects might be ignored because the specified vibrator don't exist or doesn't + // support the effect. We only report ignored here if nothing was played besides the + // StartVibrateStep (which means every attempt to turn on the vibrator was ignored). + return stepsPlayed > effect.getEffects().size() + ? Vibration.Status.FINISHED : Vibration.Status.IGNORED_UNSUPPORTED; } finally { Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); } } - private List generateSteps(CombinedVibrationEffect effect) { - if (effect instanceof CombinedVibrationEffect.Sequential) { - CombinedVibrationEffect.Sequential sequential = - (CombinedVibrationEffect.Sequential) effect; - List steps = new ArrayList<>(); - final int sequentialEffectCount = sequential.getEffects().size(); - for (int i = 0; i < sequentialEffectCount; i++) { - int delay = sequential.getDelays().get(i); - if (delay > 0) { - steps.add(new DelayStep(delay)); - } - steps.addAll(generateSteps(sequential.getEffects().get(i))); + private void noteVibratorOn(long duration) { + try { + if (duration <= 0) { + return; } - final int stepCount = steps.size(); - for (int i = 0; i < stepCount; i++) { - if (steps.get(i) instanceof VibrateStep) { - return steps; - } + if (duration == Long.MAX_VALUE) { + // Repeating duration has started. Report a fixed duration here, noteVibratorOff + // should be called when this is cancelled. + duration = BATTERY_STATS_REPEATING_VIBRATION_DURATION; } - // No valid vibrate step was generated, ignore effect completely. - return Lists.newArrayList(); + mBatteryStatsService.noteVibratorOn(mVibration.uid, duration); + FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED, + mVibration.uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON, + duration); + } catch (RemoteException e) { } - VibrateStep vibrateStep = null; - if (effect instanceof CombinedVibrationEffect.Mono) { - vibrateStep = createVibrateStep(mapToAvailableVibrators( - ((CombinedVibrationEffect.Mono) effect).getEffect())); - } else if (effect instanceof CombinedVibrationEffect.Stereo) { - vibrateStep = createVibrateStep(filterByAvailableVibrators( - ((CombinedVibrationEffect.Stereo) effect).getEffects())); - } - return vibrateStep == null ? Lists.newArrayList() : Lists.newArrayList(vibrateStep); } - @Nullable - private VibrateStep createVibrateStep(SparseArray effects) { - if (effects.size() == 0) { - return null; + private void noteVibratorOff() { + try { + mBatteryStatsService.noteVibratorOff(mVibration.uid); + FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED, + mVibration.uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF, + /* duration= */ 0); + } catch (RemoteException e) { } - if (effects.size() == 1) { - // Create simplified step that handles a single vibrator. - return new SingleVibrateStep(mVibrators.get(effects.keyAt(0)), effects.valueAt(0)); - } - return new SyncedVibrateStep(effects); - } - - private SparseArray mapToAvailableVibrators(VibrationEffect effect) { - SparseArray mappedEffects = new SparseArray<>(mVibrators.size()); - for (int i = 0; i < mVibrators.size(); i++) { - mappedEffects.put(mVibrators.keyAt(i), effect); - } - return mappedEffects; - } - - private SparseArray filterByAvailableVibrators( - SparseArray effects) { - SparseArray filteredEffects = new SparseArray<>(); - for (int i = 0; i < effects.size(); i++) { - if (mVibrators.contains(effects.keyAt(i))) { - filteredEffects.put(effects.keyAt(i), effects.valueAt(i)); - } - } - return filteredEffects; } /** @@ -306,341 +272,240 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { return timing; } - /** - * Sleeps until given {@code wakeUpTime}. - * - *

This stops immediately when {@link #cancel()} is called. - * - * @return true if waited until wake-up time, false if it was cancelled. - */ - private boolean waitUntil(long wakeUpTime) { - synchronized (mLock) { - long durationRemaining = wakeUpTime - SystemClock.uptimeMillis(); - while (durationRemaining > 0) { - try { - mLock.wait(durationRemaining); - } catch (InterruptedException e) { - } - if (mForceStop) { - return false; - } - durationRemaining = wakeUpTime - SystemClock.uptimeMillis(); - } + private static CombinedVibrationEffect.Sequential toSequential(CombinedVibrationEffect effect) { + if (effect instanceof CombinedVibrationEffect.Sequential) { + return (CombinedVibrationEffect.Sequential) effect; } - return true; + return (CombinedVibrationEffect.Sequential) CombinedVibrationEffect.startSequential() + .addNext(effect) + .combine(); } - /** - * Sleeps until given {@link VibrateStep#isVibrationComplete()}, or until {@code wakeUpTime}. - * - *

This stops immediately when {@link #cancel()} is called. - * - * @return true if finished on vibration complete, false if it was cancelled or timed out. - */ - private boolean waitForVibrationComplete(VibrateStep step, long wakeUpTime) { - synchronized (mLock) { - long durationRemaining = wakeUpTime - SystemClock.uptimeMillis(); - while (!step.isVibrationComplete() && durationRemaining > 0) { - try { - mLock.wait(durationRemaining); - } catch (InterruptedException e) { - } - if (mForceStop) { - return false; - } - durationRemaining = wakeUpTime - SystemClock.uptimeMillis(); - } - } - return step.isVibrationComplete(); - } - - private void noteVibratorOn(long duration) { - try { - mBatteryStatsService.noteVibratorOn(mVibration.uid, duration); - FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED, - mVibration.uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON, - duration); - } catch (RemoteException e) { - } - } - - private void noteVibratorOff() { - try { - mBatteryStatsService.noteVibratorOff(mVibration.uid); - FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED, - mVibration.uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF, - /* duration= */ 0); - } catch (RemoteException e) { - } - } - - /** Represent a single synchronized step while playing a {@link CombinedVibrationEffect}. */ - private interface Step { - Vibration.Status play(); - } - - /** Represent a synchronized vibration step. */ - private interface VibrateStep extends Step { - /** Callback to notify a vibrator has finished playing a effect. */ - void vibratorComplete(int vibratorId); - - /** Returns true if the vibration played by this step is complete. */ - boolean isVibrationComplete(); - } - - /** Represent a vibration on a single vibrator. */ - private final class SingleVibrateStep implements VibrateStep { - private final VibratorController mVibrator; - private final VibrationEffect mEffect; + /** Queue for {@link Step Steps}, sorted by their start time. */ + private final class StepQueue { + @GuardedBy("mLock") + private final PriorityQueue mNextSteps = new PriorityQueue<>(); @GuardedBy("mLock") - private boolean mVibrationComplete; - - SingleVibrateStep(VibratorController vibrator, VibrationEffect effect) { - mVibrator = vibrator; - mEffect = effect; + public void offer(@NonNull Step step) { + mNextSteps.offer(step); } @GuardedBy("mLock") - @Override - public boolean isVibrationComplete() { - return mVibrationComplete; + @Nullable + public Step peek() { + return mNextSteps.peek(); + } + + /** + * Play and remove the step at the top of this queue, and also adds the next steps + * generated to be played next. + * + * @return the number of steps played + */ + @GuardedBy("mLock") + public int consume() { + Step nextStep = mNextSteps.poll(); + if (nextStep != null) { + mNextSteps.addAll(nextStep.play()); + return 1; + } + return 0; + } + + /** + * Play and remove the step in this queue that should be anticipated by the vibrator + * completion callback. + * + *

This assumes only one of the next steps is waiting on this given vibrator, so the + * first step found is played by this method, in no particular order. + */ + @GuardedBy("mLock") + public void consumeOnVibratorComplete(int vibratorId) { + Iterator it = mNextSteps.iterator(); + List nextSteps = EMPTY_STEP_LIST; + while (it.hasNext()) { + Step step = it.next(); + if (step.shouldPlayWhenVibratorComplete(vibratorId)) { + it.remove(); + nextSteps = step.play(); + break; + } + } + mNextSteps.addAll(nextSteps); + } + + /** + * Cancel the current queue, clearing all remaining steps. + * + *

This will remove and trigger {@link Step#cancel()} in all steps, in order. + */ + @GuardedBy("mLock") + public void cancel() { + Step step; + while ((step = mNextSteps.poll()) != null) { + step.cancel(); + } + } + } + + /** + * Represent a single step for playing a vibration. + * + *

Every step has a start time, which can be used to apply delays between steps while + * executing them in sequence. + */ + private abstract class Step implements Comparable { + public final long startTime; + + Step(long startTime) { + this.startTime = startTime; + } + + /** Play this step, returning a (possibly empty) list of next steps. */ + @NonNull + public abstract List play(); + + /** Cancel this pending step. */ + public void cancel() { + } + + /** + * Return true to play this step right after a vibrator has notified vibration completed, + * used to anticipate steps waiting on vibrator callbacks with a timeout. + */ + public boolean shouldPlayWhenVibratorComplete(int vibratorId) { + return false; + } + + /** Returns the time in millis to wait before playing this step. */ + public long calculateWaitTime() { + if (startTime == Long.MAX_VALUE) { + // This step don't have a predefined start time, it's just marked to be executed + // after all other steps have finished. + return 0; + } + return Math.max(0, startTime - SystemClock.uptimeMillis()); } @Override - public void vibratorComplete(int vibratorId) { - if (mVibrator.getVibratorInfo().getId() != vibratorId) { - return; - } - if (mEffect instanceof VibrationEffect.OneShot - || mEffect instanceof VibrationEffect.Waveform) { - // Oneshot and Waveform are controlled by amplitude steps, ignore callbacks. - return; - } - mVibrator.off(); - synchronized (mLock) { - mVibrationComplete = true; - mLock.notify(); - } + public int compareTo(Step o) { + return Long.compare(startTime, o.startTime); + } + } + + /** + * Starts a sync vibration. + * + *

If this step has successfully started playing a vibration on any vibrator, it will always + * add a {@link FinishVibrateStep} to the queue, to be played after all vibrators have finished + * all their individual steps. + * + * If this step does not start any vibrator, it will add a {@link StartVibrateStep} if the + * sequential effect isn't finished yet. + */ + private final class StartVibrateStep extends Step { + public final CombinedVibrationEffect.Sequential sequentialEffect; + public final int currentIndex; + + StartVibrateStep(CombinedVibrationEffect.Sequential effect) { + this(SystemClock.uptimeMillis() + effect.getDelays().get(0), effect, /* index= */ 0); + } + + StartVibrateStep(long startTime, CombinedVibrationEffect.Sequential effect, int index) { + super(startTime); + sequentialEffect = effect; + currentIndex = index; } @Override - public Vibration.Status play() { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "SingleVibrateStep"); + public List play() { + Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "StartVibrateStep"); + List nextSteps = new ArrayList<>(); long duration = -1; try { if (DEBUG) { - Slog.d(TAG, "SingleVibrateStep starting..."); + Slog.d(TAG, "StartVibrateStep for effect #" + currentIndex); } - long startTime = SystemClock.uptimeMillis(); - duration = vibratePredefined(mEffect); - - if (duration > 0) { - noteVibratorOn(duration); - // Vibration is playing with no need to control amplitudes, just wait for native - // callback or timeout. - if (waitForVibrationComplete(this, - startTime + duration + CALLBACKS_EXTRA_TIMEOUT)) { - return Vibration.Status.FINISHED; - } - // Timed out or vibration cancelled. Stop vibrator anyway. - mVibrator.off(); - return mForceStop ? Vibration.Status.CANCELLED : Vibration.Status.FINISHED; + CombinedVibrationEffect effect = sequentialEffect.getEffects().get(currentIndex); + DeviceEffectMap effectMapping = createEffectToVibratorMapping(effect); + if (effectMapping == null) { + // Unable to map effects to vibrators, ignore this step. + return nextSteps; } - startTime = SystemClock.uptimeMillis(); - AmplitudeStep amplitudeStep = vibrateWithAmplitude(mEffect, startTime); - if (amplitudeStep == null) { - // Vibration could not be played with or without amplitude steps. - return Vibration.Status.IGNORED_UNSUPPORTED; - } - - duration = mEffect instanceof VibrationEffect.Prebaked - ? ((VibrationEffect.Prebaked) mEffect).getFallbackEffect().getDuration() - : mEffect.getDuration(); - if (duration < Long.MAX_VALUE) { - // Only report vibration stats if we know how long we will be vibrating. - noteVibratorOn(duration); - } - while (amplitudeStep != null) { - if (!waitUntil(amplitudeStep.startTime)) { - mVibrator.off(); - return Vibration.Status.CANCELLED; - } - amplitudeStep.play(); - amplitudeStep = amplitudeStep.nextStep(); - } - - return Vibration.Status.FINISHED; + duration = startVibrating(effectMapping, nextSteps); + noteVibratorOn(duration); } finally { - if (duration > 0 && duration < Long.MAX_VALUE) { - noteVibratorOff(); - } - if (DEBUG) { - Slog.d(TAG, "SingleVibrateStep done."); + // If this step triggered any vibrator then add a finish step to wait for all + // active vibrators to finish their individual steps before going to the next. + Step nextStep = duration > 0 ? new FinishVibrateStep(this) : nextStep(); + if (nextStep != null) { + nextSteps.add(nextStep); } Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); } + return nextSteps; } /** - * Try to vibrate given effect using prebaked or composed predefined effects. - * - * @return the duration, in millis, expected for the vibration, or -1 if effect cannot be - * played with predefined effects. + * Create the next {@link StartVibrateStep} to play this sequential effect, starting at the + * time this method is called, or null if sequence is complete. */ - private long vibratePredefined(VibrationEffect effect) { - if (effect instanceof VibrationEffect.Prebaked) { - VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect; - long duration = mVibrator.on(prebaked, mVibration.id); - if (duration > 0) { - return duration; - } - if (prebaked.getFallbackEffect() != null) { - return vibratePredefined(prebaked.getFallbackEffect()); - } - } else if (effect instanceof VibrationEffect.Composed) { - VibrationEffect.Composed composed = (VibrationEffect.Composed) effect; - return mVibrator.on(composed, mVibration.id); + @Nullable + private Step nextStep() { + int nextIndex = currentIndex + 1; + if (nextIndex >= sequentialEffect.getEffects().size()) { + return null; } - // OneShot and Waveform effects require amplitude change after calling vibrator.on. - return -1; + long nextEffectDelay = sequentialEffect.getDelays().get(nextIndex); + long nextStartTime = SystemClock.uptimeMillis() + nextEffectDelay; + return new StartVibrateStep(nextStartTime, sequentialEffect, nextIndex); } - /** - * Try to vibrate given effect using {@link AmplitudeStep} to control vibration amplitude. - * - * @return the {@link AmplitudeStep} to start this vibration, or {@code null} if vibration - * do not require amplitude control. - */ - private AmplitudeStep vibrateWithAmplitude(VibrationEffect effect, long startTime) { - int vibratorId = mVibrator.getVibratorInfo().getId(); - if (effect instanceof VibrationEffect.OneShot) { - VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect; - return new AmplitudeStep(vibratorId, oneShot, startTime, startTime); - } else if (effect instanceof VibrationEffect.Waveform) { - VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect; - return new AmplitudeStep(vibratorId, waveform, startTime, startTime); - } else if (effect instanceof VibrationEffect.Prebaked) { - VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect; - if (prebaked.getFallbackEffect() != null) { - return vibrateWithAmplitude(prebaked.getFallbackEffect(), startTime); - } + /** Create a mapping of individual {@link VibrationEffect} to available vibrators. */ + @Nullable + private DeviceEffectMap createEffectToVibratorMapping( + CombinedVibrationEffect effect) { + if (effect instanceof CombinedVibrationEffect.Mono) { + return new DeviceEffectMap((CombinedVibrationEffect.Mono) effect); + } + if (effect instanceof CombinedVibrationEffect.Stereo) { + return new DeviceEffectMap((CombinedVibrationEffect.Stereo) effect); } return null; } - } - - /** Represent a synchronized vibration step on multiple vibrators. */ - private final class SyncedVibrateStep implements VibrateStep { - private final SparseArray mEffects; - private final long mRequiredCapabilities; - private final int[] mVibratorIds; - - @GuardedBy("mLock") - private int mActiveVibratorCounter; - - SyncedVibrateStep(SparseArray effects) { - mEffects = effects; - mActiveVibratorCounter = mEffects.size(); - mRequiredCapabilities = calculateRequiredSyncCapabilities(effects); - mVibratorIds = new int[effects.size()]; - for (int i = 0; i < effects.size(); i++) { - mVibratorIds[i] = effects.keyAt(i); - } - } - - @GuardedBy("mLock") - @Override - public boolean isVibrationComplete() { - return mActiveVibratorCounter <= 0; - } - - @Override - public void vibratorComplete(int vibratorId) { - VibrationEffect effect = mEffects.get(vibratorId); - if (effect == null) { - return; - } - if (effect instanceof VibrationEffect.OneShot - || effect instanceof VibrationEffect.Waveform) { - // Oneshot and Waveform are controlled by amplitude steps, ignore callbacks. - return; - } - mVibrators.get(vibratorId).off(); - synchronized (mLock) { - --mActiveVibratorCounter; - mLock.notify(); - } - } - - @Override - public Vibration.Status play() { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "SyncedVibrateStep"); - long duration = -1; - try { - if (DEBUG) { - Slog.d(TAG, "SyncedVibrateStep starting..."); - } - final PriorityQueue nextSteps = new PriorityQueue<>(mEffects.size()); - long startTime = SystemClock.uptimeMillis(); - duration = startVibratingSynced(startTime, nextSteps); - - if (duration <= 0) { - // Vibrate step failed, vibrator could not be turned on for this step. - return Vibration.Status.IGNORED; - } - - noteVibratorOn(duration); - while (!nextSteps.isEmpty()) { - AmplitudeStep step = nextSteps.poll(); - if (!waitUntil(step.startTime)) { - stopAllVibrators(); - return Vibration.Status.CANCELLED; - } - step.play(); - AmplitudeStep nextStep = step.nextStep(); - if (nextStep == null) { - // This vibrator has finished playing the effect for this step. - synchronized (mLock) { - mActiveVibratorCounter--; - } - } else { - nextSteps.add(nextStep); - } - } - - synchronized (mLock) { - // All OneShot and Waveform effects have finished. Just wait for the other - // effects to end via native callbacks before finishing this synced step. - final long wakeUpTime = startTime + duration + CALLBACKS_EXTRA_TIMEOUT; - if (mActiveVibratorCounter <= 0 || waitForVibrationComplete(this, wakeUpTime)) { - return Vibration.Status.FINISHED; - } - - // Timed out or vibration cancelled. Stop all vibrators anyway. - stopAllVibrators(); - return mForceStop ? Vibration.Status.CANCELLED : Vibration.Status.FINISHED; - } - } finally { - if (duration > 0) { - noteVibratorOff(); - } - if (DEBUG) { - Slog.d(TAG, "SyncedVibrateStep done."); - } - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } /** * Starts playing effects on designated vibrators, in sync. * - * @return A positive duration, in millis, to wait for the completion of this effect. - * Non-positive values indicate the vibrator has ignored this effect. Repeating waveform - * returns the duration of a single run to be used as timeout for callbacks. + * @param effectMapping The {@link CombinedVibrationEffect} mapped to this device vibrators + * @param nextSteps An output list to accumulate the future {@link Step Steps} created + * by this method, typically one for each vibrator that has + * successfully started vibrating on this step. + * @return The duration, in millis, of the {@link CombinedVibrationEffect}. Repeating + * waveforms return {@link Long#MAX_VALUE}. Zero or negative values indicate the vibrators + * have ignored all effects. */ - private long startVibratingSynced(long startTime, PriorityQueue nextSteps) { + private long startVibrating(DeviceEffectMap effectMapping, List nextSteps) { + int vibratorCount = effectMapping.size(); + if (vibratorCount == 0) { + // No effect was mapped to any available vibrator. + return 0; + } + + VibratorOnStep[] steps = new VibratorOnStep[vibratorCount]; + long vibrationStartTime = SystemClock.uptimeMillis(); + for (int i = 0; i < vibratorCount; i++) { + steps[i] = new VibratorOnStep(vibrationStartTime, + mVibrators.get(effectMapping.vibratorIdAt(i)), effectMapping.effectAt(i)); + } + + if (steps.length == 1) { + // No need to prepare and trigger sync effects on a single vibrator. + return startVibrating(steps[0], nextSteps); + } + // This synchronization of vibrators should be executed one at a time, even if we are // vibrating different sets of vibrators in parallel. The manager can only prepareSynced // one set of vibrators at a time. @@ -648,17 +513,24 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { boolean hasPrepared = false; boolean hasTriggered = false; try { - hasPrepared = mCallbacks.prepareSyncedVibration(mRequiredCapabilities, - mVibratorIds); - long timeout = startVibrating(startTime, nextSteps); + hasPrepared = mCallbacks.prepareSyncedVibration( + effectMapping.getRequiredSyncCapabilities(), + effectMapping.getVibratorIds()); - // Check if preparation was successful, otherwise devices area already vibrating - if (hasPrepared) { + long duration = 0; + for (VibratorOnStep step : steps) { + duration = Math.max(duration, startVibrating(step, nextSteps)); + } + + // Check if sync was prepared and if any step was accepted by a vibrator, + // otherwise there is nothing to trigger here. + if (hasPrepared && duration > 0) { hasTriggered = mCallbacks.triggerSyncedVibration(mVibration.id); } - return timeout; + return duration; } finally { if (hasPrepared && !hasTriggered) { + // Trigger has failed or all steps were ignored by the vibrators. mCallbacks.cancelSyncedVibration(); return 0; } @@ -666,77 +538,365 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { } } - /** - * Starts playing effects on designated vibrators. - * - *

This includes the {@link VibrationEffect.OneShot} and {@link VibrationEffect.Waveform} - * effects, that should start in sync with all other effects in this step. The waveforms are - * controlled by {@link AmplitudeStep} added to the {@code nextSteps} queue. - * - * @return A positive duration, in millis, to wait for the completion of this effect. - * Non-positive values indicate the vibrator has ignored this effect. Repeating waveform - * returns the duration of a single run to be used as timeout for callbacks. - */ - private long startVibrating(long startTime, PriorityQueue nextSteps) { - long maxDuration = 0; - for (int i = 0; i < mEffects.size(); i++) { - VibratorController controller = mVibrators.get(mEffects.keyAt(i)); - VibrationEffect effect = mEffects.valueAt(i); - maxDuration = Math.max(maxDuration, - startVibrating(controller, effect, startTime, nextSteps)); + private long startVibrating(VibratorOnStep step, List nextSteps) { + nextSteps.addAll(step.play()); + return step.getDuration(); + } + } + + /** + * Finish a sync vibration started by a {@link StartVibrateStep}. + * + *

This only plays after all active vibrators steps have finished, and adds a {@link + * StartVibrateStep} to the queue if the sequential effect isn't finished yet. + */ + private final class FinishVibrateStep extends Step { + public final StartVibrateStep startedStep; + + FinishVibrateStep(StartVibrateStep startedStep) { + super(Long.MAX_VALUE); // No predefined startTime, just wait for all steps in the queue. + this.startedStep = startedStep; + } + + @Override + public List play() { + Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "FinishVibrateStep"); + try { + if (DEBUG) { + Slog.d(TAG, "FinishVibrateStep for effect #" + startedStep.currentIndex); + } + noteVibratorOff(); + Step nextStep = startedStep.nextStep(); + return nextStep == null ? EMPTY_STEP_LIST : Arrays.asList(nextStep); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); } - return maxDuration; + } + + @Override + public void cancel() { + noteVibratorOff(); + } + } + + /** + * Represent a step turn the vibrator on. + * + *

No other calls to the vibrator is made from this step, so this can be played in between + * calls to 'prepare' and 'trigger' for synchronized vibrations. + */ + private final class VibratorOnStep extends Step { + public final VibratorController controller; + public final VibrationEffect effect; + private long mDuration; + + VibratorOnStep(long startTime, VibratorController controller, VibrationEffect effect) { + super(startTime); + this.controller = controller; + this.effect = effect; } /** - * Play a single effect on a single vibrator. - * - * @return A positive duration, in millis, to wait for the completion of this effect. - * Non-positive values indicate the vibrator has ignored this effect. Repeating waveform - * returns the duration of a single run to be used as timeout for callbacks. + * Return the duration, in millis, of this effect. Repeating waveforms return {@link + * Long#MAX_VALUE}. Zero or negative values indicate the vibrator has ignored this effect. */ - private long startVibrating(VibratorController controller, VibrationEffect effect, - long startTime, PriorityQueue nextSteps) { - int vibratorId = controller.getVibratorInfo().getId(); - long duration; + public long getDuration() { + return mDuration; + } + + @Override + public List play() { + Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorOnStep"); + try { + if (DEBUG) { + Slog.d(TAG, "Turning on vibrator " + controller.getVibratorInfo().getId()); + } + List nextSteps = new ArrayList<>(); + mDuration = startVibrating(effect, nextSteps); + return nextSteps; + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); + } + } + + private long startVibrating(VibrationEffect effect, List nextSteps) { + final long duration; + final long now = SystemClock.uptimeMillis(); if (effect instanceof VibrationEffect.OneShot) { VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect; duration = oneShot.getDuration(); + // Do NOT set amplitude here. This might be called between prepareSynced and + // triggerSynced, so the vibrator is not actually turned on here. + // The next steps will handle the amplitude after the vibrator has turned on. controller.on(duration, mVibration.id); - nextSteps.add( - new AmplitudeStep(vibratorId, oneShot, startTime, startTime + duration)); + nextSteps.add(new VibratorAmplitudeStep(now, controller, oneShot, + now + duration + CALLBACKS_EXTRA_TIMEOUT)); } else if (effect instanceof VibrationEffect.Waveform) { VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect; - duration = getVibratorOnDuration(waveform, 0); - if (duration > 0) { - // Waveform starts by turning vibrator on. Do it in this sync vibrate step. - controller.on(duration, mVibration.id); + // Return the full duration of this waveform effect. + duration = waveform.getDuration(); + long onDuration = getVibratorOnDuration(waveform, 0); + if (onDuration > 0) { + // Do NOT set amplitude here. This might be called between prepareSynced and + // triggerSynced, so the vibrator is not actually turned on here. + // The next steps will handle the amplitudes after the vibrator has turned on. + controller.on(onDuration, mVibration.id); } - nextSteps.add( - new AmplitudeStep(vibratorId, waveform, startTime, startTime + duration)); + long offTime = onDuration > 0 ? now + onDuration + CALLBACKS_EXTRA_TIMEOUT : now; + nextSteps.add(new VibratorAmplitudeStep(now, controller, waveform, offTime)); } else if (effect instanceof VibrationEffect.Prebaked) { VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect; duration = controller.on(prebaked, mVibration.id); - if (duration <= 0 && prebaked.getFallbackEffect() != null) { - return startVibrating(controller, prebaked.getFallbackEffect(), startTime, - nextSteps); + if (duration > 0) { + nextSteps.add(new VibratorOffStep(now + duration + CALLBACKS_EXTRA_TIMEOUT, + controller)); + } else if (prebaked.getFallbackEffect() != null) { + return startVibrating(prebaked.getFallbackEffect(), nextSteps); } } else if (effect instanceof VibrationEffect.Composed) { VibrationEffect.Composed composed = (VibrationEffect.Composed) effect; duration = controller.on(composed, mVibration.id); + if (duration > 0) { + nextSteps.add(new VibratorOffStep(now + duration + CALLBACKS_EXTRA_TIMEOUT, + controller)); + } } else { duration = 0; } return duration; } + } - private void stopAllVibrators() { - for (int vibratorId : mVibratorIds) { - VibratorController controller = mVibrators.get(vibratorId); - if (controller != null) { - controller.off(); + /** + * Represents a step to turn the vibrator off. + * + *

This runs after a timeout on the expected time the vibrator should have finished playing, + * and can anticipated by vibrator complete callbacks. + */ + private final class VibratorOffStep extends Step { + public final VibratorController controller; + + VibratorOffStep(long startTime, VibratorController controller) { + super(startTime); + this.controller = controller; + } + + @Override + public boolean shouldPlayWhenVibratorComplete(int vibratorId) { + return controller.getVibratorInfo().getId() == vibratorId; + } + + @Override + public List play() { + Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorOffStep"); + try { + stopVibrating(); + return EMPTY_STEP_LIST; + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); + } + } + + @Override + public void cancel() { + stopVibrating(); + } + + private void stopVibrating() { + if (DEBUG) { + Slog.d(TAG, "Turning off vibrator " + controller.getVibratorInfo().getId()); + } + controller.off(); + } + } + + /** Represents a step to change the amplitude of the vibrator. */ + private final class VibratorAmplitudeStep extends Step { + public final VibratorController controller; + public final VibrationEffect.Waveform waveform; + public final int currentIndex; + public final long expectedVibratorStopTime; + + private long mNextVibratorStopTime; + + VibratorAmplitudeStep(long startTime, VibratorController controller, + VibrationEffect.OneShot oneShot, long expectedVibratorStopTime) { + this(startTime, controller, + (VibrationEffect.Waveform) VibrationEffect.createWaveform( + new long[]{oneShot.getDuration()}, new int[]{oneShot.getAmplitude()}, + /* repeat= */ -1), + expectedVibratorStopTime); + } + + VibratorAmplitudeStep(long startTime, VibratorController controller, + VibrationEffect.Waveform waveform, long expectedVibratorStopTime) { + this(startTime, controller, waveform, /* index= */ 0, expectedVibratorStopTime); + } + + VibratorAmplitudeStep(long startTime, VibratorController controller, + VibrationEffect.Waveform waveform, int index, long expectedVibratorStopTime) { + super(startTime); + this.controller = controller; + this.waveform = waveform; + this.currentIndex = index; + this.expectedVibratorStopTime = expectedVibratorStopTime; + mNextVibratorStopTime = expectedVibratorStopTime; + } + + @Override + public boolean shouldPlayWhenVibratorComplete(int vibratorId) { + if (controller.getVibratorInfo().getId() == vibratorId) { + mNextVibratorStopTime = SystemClock.uptimeMillis(); + } + return false; + } + + @Override + public List play() { + Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorAmplitudeStep"); + try { + if (DEBUG) { + long latency = SystemClock.uptimeMillis() - startTime; + Slog.d(TAG, "Running amplitude step with " + latency + "ms latency."); + } + if (waveform.getTimings()[currentIndex] == 0) { + // Skip waveform entries with zero timing. + return nextSteps(); + } + int amplitude = waveform.getAmplitudes()[currentIndex]; + if (amplitude == 0) { + stopVibrating(); + return nextSteps(); + } + if (startTime >= mNextVibratorStopTime) { + // Vibrator has stopped. Turn vibrator back on for the duration of another + // cycle before setting the amplitude. + long onDuration = getVibratorOnDuration(waveform, currentIndex); + if (onDuration > 0) { + startVibrating(onDuration); + mNextVibratorStopTime = + SystemClock.uptimeMillis() + onDuration + CALLBACKS_EXTRA_TIMEOUT; + } + } + changeAmplitude(amplitude); + return nextSteps(); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); + } + } + + @Override + public void cancel() { + stopVibrating(); + } + + private void stopVibrating() { + if (DEBUG) { + Slog.d(TAG, "Turning off vibrator " + controller.getVibratorInfo().getId()); + } + controller.off(); + mNextVibratorStopTime = SystemClock.uptimeMillis(); + } + + private void startVibrating(long duration) { + if (DEBUG) { + Slog.d(TAG, "Turning on vibrator " + controller.getVibratorInfo().getId() + " for " + + duration + "ms"); + } + controller.on(duration, mVibration.id); + } + + private void changeAmplitude(int amplitude) { + if (DEBUG) { + Slog.d(TAG, "Amplitude changed on vibrator " + controller.getVibratorInfo().getId() + + " to " + amplitude); + } + controller.setAmplitude(amplitude); + } + + @NonNull + private List nextSteps() { + long nextStartTime = startTime + waveform.getTimings()[currentIndex]; + int nextIndex = currentIndex + 1; + if (nextIndex >= waveform.getTimings().length) { + nextIndex = waveform.getRepeatIndex(); + } + if (nextIndex < 0) { + return Arrays.asList(new VibratorOffStep(nextStartTime, controller)); + } + return Arrays.asList(new VibratorAmplitudeStep(nextStartTime, controller, waveform, + nextIndex, mNextVibratorStopTime)); + } + } + + /** + * Map a {@link CombinedVibrationEffect} to the vibrators available on the device. + * + *

This contains the logic to find the capabilities required from {@link IVibratorManager} to + * play all of the effects in sync. + */ + private final class DeviceEffectMap { + private final SparseArray mVibratorEffects; + private final int[] mVibratorIds; + private final long mRequiredSyncCapabilities; + + DeviceEffectMap(CombinedVibrationEffect.Mono mono) { + mVibratorEffects = new SparseArray<>(mVibrators.size()); + mVibratorIds = new int[mVibrators.size()]; + for (int i = 0; i < mVibrators.size(); i++) { + int vibratorId = mVibrators.keyAt(i); + mVibratorEffects.put(vibratorId, mono.getEffect()); + mVibratorIds[i] = vibratorId; + } + mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects); + } + + DeviceEffectMap(CombinedVibrationEffect.Stereo stereo) { + SparseArray stereoEffects = stereo.getEffects(); + mVibratorEffects = new SparseArray<>(); + for (int i = 0; i < stereoEffects.size(); i++) { + int vibratorId = stereoEffects.keyAt(i); + if (mVibrators.contains(vibratorId)) { + mVibratorEffects.put(vibratorId, stereoEffects.valueAt(i)); } } + mVibratorIds = new int[mVibratorEffects.size()]; + for (int i = 0; i < mVibratorEffects.size(); i++) { + mVibratorIds[i] = mVibratorEffects.keyAt(i); + } + mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects); + } + + /** + * Return the number of vibrators mapped to play the {@link CombinedVibrationEffect} on this + * device. + */ + public int size() { + return mVibratorIds.length; + } + + /** + * Return all capabilities required to play the {@link CombinedVibrationEffect} in + * between calls to {@link IVibratorManager#prepareSynced} and + * {@link IVibratorManager#triggerSynced}. + */ + public long getRequiredSyncCapabilities() { + return mRequiredSyncCapabilities; + } + + /** Return all vibrator ids mapped to play the {@link CombinedVibrationEffect}. */ + public int[] getVibratorIds() { + return mVibratorIds; + } + + /** Return the id of the vibrator at given index. */ + public int vibratorIdAt(int index) { + return mVibratorEffects.keyAt(index); + } + + /** Return the {@link VibrationEffect} at given index. */ + public VibrationEffect effectAt(int index) { + return mVibratorEffects.valueAt(index); } /** @@ -782,145 +942,4 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { && (prepareCapabilities & ~capability) != 0; } } - - /** Represent a step to set amplitude on a single vibrator. */ - private final class AmplitudeStep implements Step, Comparable { - public final int vibratorId; - public final VibrationEffect.Waveform waveform; - public final int currentIndex; - public final long startTime; - public final long vibratorStopTime; - - AmplitudeStep(int vibratorId, VibrationEffect.OneShot oneShot, - long startTime, long vibratorStopTime) { - this(vibratorId, (VibrationEffect.Waveform) VibrationEffect.createWaveform( - new long[]{oneShot.getDuration()}, - new int[]{oneShot.getAmplitude()}, /* repeat= */ -1), - startTime, - vibratorStopTime); - } - - AmplitudeStep(int vibratorId, VibrationEffect.Waveform waveform, - long startTime, long vibratorStopTime) { - this(vibratorId, waveform, /* index= */ 0, startTime, vibratorStopTime); - } - - AmplitudeStep(int vibratorId, VibrationEffect.Waveform waveform, - int index, long startTime, long vibratorStopTime) { - this.vibratorId = vibratorId; - this.waveform = waveform; - this.currentIndex = index; - this.startTime = startTime; - this.vibratorStopTime = vibratorStopTime; - } - - @Override - public Vibration.Status play() { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "AmplitudeStep"); - try { - if (DEBUG) { - Slog.d(TAG, "AmplitudeStep starting on vibrator " + vibratorId + "..."); - } - VibratorController controller = mVibrators.get(vibratorId); - if (currentIndex < 0) { - controller.off(); - if (DEBUG) { - Slog.d(TAG, "Vibrator turned off and finishing"); - } - return Vibration.Status.FINISHED; - } - if (waveform.getTimings()[currentIndex] == 0) { - // Skip waveform entries with zero timing. - return Vibration.Status.FINISHED; - } - int amplitude = waveform.getAmplitudes()[currentIndex]; - if (amplitude == 0) { - controller.off(); - if (DEBUG) { - Slog.d(TAG, "Vibrator turned off"); - } - return Vibration.Status.FINISHED; - } - if (startTime >= vibratorStopTime) { - // Vibrator has stopped. Turn vibrator back on for the duration of another - // cycle before setting the amplitude. - long onDuration = getVibratorOnDuration(waveform, currentIndex); - if (onDuration > 0) { - controller.on(onDuration, mVibration.id); - if (DEBUG) { - Slog.d(TAG, "Vibrator turned on for " + onDuration + "ms"); - } - } - } - controller.setAmplitude(amplitude); - if (DEBUG) { - Slog.d(TAG, "Amplitude changed to " + amplitude); - } - return Vibration.Status.FINISHED; - } finally { - if (DEBUG) { - Slog.d(TAG, "AmplitudeStep done."); - } - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - - @Override - public int compareTo(AmplitudeStep o) { - return Long.compare(startTime, o.startTime); - } - - /** Return next {@link AmplitudeStep} from this waveform, of {@code null} if finished. */ - @Nullable - public AmplitudeStep nextStep() { - if (currentIndex < 0) { - // Waveform has ended, no more steps to run. - return null; - } - long nextStartTime = startTime + waveform.getTimings()[currentIndex]; - int nextIndex = currentIndex + 1; - if (nextIndex >= waveform.getTimings().length) { - nextIndex = waveform.getRepeatIndex(); - } - return new AmplitudeStep(vibratorId, waveform, nextIndex, nextStartTime, - nextVibratorStopTime()); - } - - /** Return next time the vibrator will stop after this step is played. */ - private long nextVibratorStopTime() { - if (currentIndex < 0 || waveform.getTimings()[currentIndex] == 0 - || startTime < vibratorStopTime) { - return vibratorStopTime; - } - return startTime + getVibratorOnDuration(waveform, currentIndex); - } - } - - /** Represent a delay step with fixed duration, that starts counting when it starts playing. */ - private final class DelayStep implements Step { - private final int mDelay; - - DelayStep(int delay) { - mDelay = delay; - } - - @Override - public Vibration.Status play() { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "DelayStep"); - try { - if (DEBUG) { - Slog.d(TAG, "DelayStep of " + mDelay + "ms starting..."); - } - if (waitUntil(SystemClock.uptimeMillis() + mDelay)) { - return Vibration.Status.FINISHED; - } - return Vibration.Status.CANCELLED; - } finally { - if (DEBUG) { - Slog.d(TAG, "DelayStep done."); - } - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - } } 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 1b7e1ca6a0145..7d5eec0834a17 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java @@ -107,7 +107,8 @@ public class VibrationThreadTest { waitForCompletion(thread); verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId)); - verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.IGNORED)); + verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), + eq(Vibration.Status.IGNORED_UNSUPPORTED)); } @Test @@ -121,7 +122,8 @@ public class VibrationThreadTest { waitForCompletion(thread); verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId)); - verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.IGNORED)); + verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), + eq(Vibration.Status.IGNORED_UNSUPPORTED)); } @Test @@ -206,8 +208,8 @@ public class VibrationThreadTest { thread.cancel(); waitForCompletion(thread); - verify(mIBatteryStatsMock, never()).noteVibratorOn(eq(UID), anyLong()); - verify(mIBatteryStatsMock, never()).noteVibratorOff(eq(UID)); + verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), anyLong()); + verify(mIBatteryStatsMock).noteVibratorOff(eq(UID)); verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED)); assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());