blob: 0d5315f9b062ec435fa7a21795c117a33c053828 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.vibrator;
import android.annotation.Nullable;
import android.os.CombinedVibrationEffect;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
import android.os.VibrationEffect;
import android.os.WorkSource;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
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.List;
import java.util.PriorityQueue;
import java.util.concurrent.atomic.AtomicInteger;
/** Plays a {@link Vibration} in dedicated thread. */
// TODO(b/159207608): Make this package-private once vibrator services are moved to this package
public final class VibrationThread extends Thread implements IBinder.DeathRecipient {
private static final String TAG = "VibrationThread";
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.
*/
private static final long CALLBACKS_EXTRA_TIMEOUT = 100;
/** Callbacks for playing a {@link Vibration}. */
public interface VibrationCallbacks {
/**
* Callback triggered before starting a synchronized vibration step. This will be called
* with {@code requiredCapabilities = 0} if no synchronization is required.
*
* @param requiredCapabilities The required syncing capabilities for this preparation step.
* Expects a combination of values from
* IVibratorManager.CAP_PREPARE_* and
* IVibratorManager.CAP_MIXED_TRIGGER_*.
* @param vibratorIds The id of the vibrators to be prepared.
*/
void prepareSyncedVibration(int requiredCapabilities, int[] vibratorIds);
/** Callback triggered after synchronized vibrations were prepared. */
void triggerSyncedVibration(long vibrationId);
/** Callback triggered when vibration thread is complete. */
void onVibrationEnded(long vibrationId, Vibration.Status status);
}
private final Object mLock = new Object();
private final WorkSource mWorkSource = new WorkSource();
private final PowerManager.WakeLock mWakeLock;
private final IBatteryStats mBatteryStatsService;
private final Vibration mVibration;
private final VibrationCallbacks mCallbacks;
private final SparseArray<VibratorController> mVibrators;
@GuardedBy("mLock")
@Nullable
private VibrateStep mCurrentVibrateStep;
@GuardedBy("this")
private boolean mForceStop;
// TODO(b/159207608): Remove this constructor once VibratorService is removed
public VibrationThread(Vibration vib, VibratorController vibrator,
PowerManager.WakeLock wakeLock, IBatteryStats batteryStatsService,
VibrationCallbacks callbacks) {
this(vib, toSparseArray(vibrator), wakeLock, batteryStatsService, callbacks);
}
public VibrationThread(Vibration vib, SparseArray<VibratorController> availableVibrators,
PowerManager.WakeLock wakeLock, IBatteryStats batteryStatsService,
VibrationCallbacks callbacks) {
mVibration = vib;
mCallbacks = callbacks;
mWakeLock = wakeLock;
mWorkSource.set(vib.uid);
mWakeLock.setWorkSource(mWorkSource);
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));
}
}
}
@Override
public void binderDied() {
cancel();
}
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
mWakeLock.acquire();
try {
mVibration.token.linkToDeath(this, 0);
Vibration.Status status = playVibration();
mCallbacks.onVibrationEnded(mVibration.id, status);
} catch (RemoteException e) {
Slog.e(TAG, "Error linking vibration to token death", e);
} finally {
mVibration.token.unlinkToDeath(this, 0);
mWakeLock.release();
}
}
/** Cancel current vibration and shuts down the thread gracefully. */
public void cancel() {
synchronized (this) {
mForceStop = true;
notify();
}
}
/** Notify current vibration that a step has completed on given vibrator. */
public void vibratorComplete(int vibratorId) {
synchronized (mLock) {
if (mCurrentVibrateStep != null) {
mCurrentVibrateStep.vibratorComplete(vibratorId);
}
}
}
@VisibleForTesting
SparseArray<VibratorController> getVibrators() {
return mVibrators;
}
private Vibration.Status playVibration() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "playVibration");
try {
List<Step> 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;
} else {
mCurrentVibrateStep = null;
}
}
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;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
}
private List<Step> generateSteps(CombinedVibrationEffect effect) {
if (effect instanceof CombinedVibrationEffect.Sequential) {
CombinedVibrationEffect.Sequential sequential =
(CombinedVibrationEffect.Sequential) effect;
List<Step> 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)));
}
final int stepCount = steps.size();
for (int i = 0; i < stepCount; i++) {
if (steps.get(i) instanceof VibrateStep) {
return steps;
}
}
// No valid vibrate step was generated, ignore effect completely.
return Lists.newArrayList();
}
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<VibrationEffect> effects) {
if (effects.size() == 0) {
return null;
}
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<VibrationEffect> mapToAvailableVibrators(VibrationEffect effect) {
SparseArray<VibrationEffect> mappedEffects = new SparseArray<>(mVibrators.size());
for (int i = 0; i < mVibrators.size(); i++) {
mappedEffects.put(mVibrators.keyAt(i), effect);
}
return mappedEffects;
}
private SparseArray<VibrationEffect> filterByAvailableVibrators(
SparseArray<VibrationEffect> effects) {
SparseArray<VibrationEffect> 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;
}
private static SparseArray<VibratorController> toSparseArray(VibratorController controller) {
SparseArray<VibratorController> array = new SparseArray<>(1);
array.put(controller.getVibratorInfo().getId(), controller);
return array;
}
/**
* Get the duration the vibrator will be on for given {@code waveform}, starting at {@code
* startIndex} until the next time it's vibrating amplitude is zero.
*/
private static long getVibratorOnDuration(VibrationEffect.Waveform waveform, int startIndex) {
long[] timings = waveform.getTimings();
int[] amplitudes = waveform.getAmplitudes();
int repeatIndex = waveform.getRepeatIndex();
int i = startIndex;
long timing = 0;
while (timings[i] == 0 || amplitudes[i] != 0) {
timing += timings[i++];
if (i >= timings.length) {
if (repeatIndex >= 0) {
i = repeatIndex;
// prevent infinite loop
repeatIndex = -1;
} else {
break;
}
}
if (i == startIndex) {
return 1000;
}
}
return timing;
}
/**
* Sleeps until given {@code wakeUpTime}.
*
* <p>This stops immediately when {@link #cancel()} is called.
*/
private void waitUntil(long wakeUpTime) {
synchronized (this) {
long durationRemaining = wakeUpTime - SystemClock.uptimeMillis();
while (durationRemaining > 0) {
try {
VibrationThread.this.wait(durationRemaining);
} catch (InterruptedException e) {
}
if (mForceStop) {
break;
}
durationRemaining = wakeUpTime - SystemClock.uptimeMillis();
}
}
}
/**
* Sleeps until given {@code countDown} has reached zero or {@code wakeUpTime} was reached.
*
* <p>This stops immediately when {@link #cancel()} is called.
*/
private void waitUntil(AtomicInteger countDown, long wakeUpTime) {
synchronized (this) {
long durationRemaining = wakeUpTime - SystemClock.uptimeMillis();
while (countDown.get() > 0 && durationRemaining > 0) {
try {
VibrationThread.this.wait(durationRemaining);
} catch (InterruptedException e) {
}
if (mForceStop) {
break;
}
durationRemaining = wakeUpTime - SystemClock.uptimeMillis();
}
}
}
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);
}
/** Represent a vibration on a single vibrator. */
private final class SingleVibrateStep implements VibrateStep {
private final VibratorController mVibrator;
private final VibrationEffect mEffect;
private final AtomicInteger mCounter;
SingleVibrateStep(VibratorController vibrator, VibrationEffect effect) {
mVibrator = vibrator;
mEffect = effect;
mCounter = new AtomicInteger(1);
}
@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();
mCounter.decrementAndGet();
synchronized (VibrationThread.this) {
VibrationThread.this.notify();
}
}
@Override
public Vibration.Status play() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "SingleVibrateStep");
long duration = -1;
try {
if (DEBUG) {
Slog.d(TAG, "SingleVibrateStep starting...");
}
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.
waitUntil(mCounter, startTime + duration + CALLBACKS_EXTRA_TIMEOUT);
if (mForceStop) {
mVibrator.off();
return Vibration.Status.CANCELLED;
}
return Vibration.Status.FINISHED;
}
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) {
waitUntil(amplitudeStep.startTime);
if (mForceStop) {
mVibrator.off();
return Vibration.Status.CANCELLED;
}
amplitudeStep.play();
amplitudeStep = amplitudeStep.nextStep();
}
return Vibration.Status.FINISHED;
} finally {
if (duration > 0 && duration < Long.MAX_VALUE) {
noteVibratorOff();
}
if (DEBUG) {
Slog.d(TAG, "SingleVibrateStep step done.");
}
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
}
/**
* 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.
*/
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);
}
// OneShot and Waveform effects require amplitude change after calling vibrator.on.
return -1;
}
/**
* 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);
}
}
return null;
}
}
/** Represent a synchronized vibration step on multiple vibrators. */
private final class SyncedVibrateStep implements VibrateStep {
private final SparseArray<VibrationEffect> mEffects;
private final AtomicInteger mActiveVibratorCounter;
private final int mRequiredCapabilities;
private final int[] mVibratorIds;
SyncedVibrateStep(SparseArray<VibrationEffect> effects) {
mEffects = effects;
mActiveVibratorCounter = new AtomicInteger(mEffects.size());
// TODO(b/159207608): Calculate required capabilities for syncing this step.
mRequiredCapabilities = 0;
mVibratorIds = new int[effects.size()];
for (int i = 0; i < effects.size(); i++) {
mVibratorIds[i] = effects.keyAt(i);
}
}
@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();
mActiveVibratorCounter.decrementAndGet();
synchronized (VibrationThread.this) {
VibrationThread.this.notify();
}
}
@Override
public Vibration.Status play() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "SyncedVibrateStep");
long timeout = -1;
try {
if (DEBUG) {
Slog.d(TAG, "SyncedVibrateStep starting...");
}
final PriorityQueue<AmplitudeStep> nextSteps = new PriorityQueue<>(mEffects.size());
long startTime = SystemClock.uptimeMillis();
mCallbacks.prepareSyncedVibration(mRequiredCapabilities, mVibratorIds);
timeout = startVibrating(startTime, nextSteps);
mCallbacks.triggerSyncedVibration(mVibration.id);
noteVibratorOn(timeout);
while (!nextSteps.isEmpty()) {
AmplitudeStep step = nextSteps.poll();
waitUntil(step.startTime);
if (mForceStop) {
stopAllVibrators();
return Vibration.Status.CANCELLED;
}
step.play();
AmplitudeStep nextStep = step.nextStep();
if (nextStep == null) {
// This vibrator has finished playing the effect for this step.
mActiveVibratorCounter.decrementAndGet();
} else {
nextSteps.add(nextStep);
}
}
// All OneShot and Waveform effects have finished. Just wait for the other effects
// to end via native callbacks before finishing this synced step.
waitUntil(mActiveVibratorCounter, startTime + timeout + CALLBACKS_EXTRA_TIMEOUT);
if (mForceStop) {
stopAllVibrators();
return Vibration.Status.CANCELLED;
}
return Vibration.Status.FINISHED;
} finally {
if (timeout > 0) {
noteVibratorOff();
}
if (DEBUG) {
Slog.d(TAG, "SyncedVibrateStep done.");
}
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
}
/**
* Starts playing effects on designated vibrators.
*
* <p>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 duration, in millis, to wait for the completion of all vibrations. This ignores
* any repeating waveform duration and returns the duration of a single run.
*/
private long startVibrating(long startTime, PriorityQueue<AmplitudeStep> 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));
}
return maxDuration;
}
/**
* Play a single effect on a single vibrator.
*
* @return A duration, in millis, to wait for the completion of this effect. This ignores
* any repeating waveform duration and returns the duration of a single run to be used as
* timeout for callbacks.
*/
private long startVibrating(VibratorController controller, VibrationEffect effect,
long startTime, PriorityQueue<AmplitudeStep> nextSteps) {
int vibratorId = controller.getVibratorInfo().getId();
long duration;
if (effect instanceof VibrationEffect.OneShot) {
VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect;
duration = oneShot.getDuration();
controller.on(duration, mVibration.id);
nextSteps.add(
new AmplitudeStep(vibratorId, oneShot, startTime, startTime + duration));
} 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);
}
nextSteps.add(
new AmplitudeStep(vibratorId, waveform, startTime, startTime + duration));
} 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);
}
} else if (effect instanceof VibrationEffect.Composed) {
VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
duration = controller.on(composed, mVibration.id);
} else {
duration = 0;
}
return duration;
}
private void stopAllVibrators() {
for (int vibratorId : mVibratorIds) {
VibratorController controller = mVibrators.get(vibratorId);
if (controller != null) {
controller.off();
}
}
}
}
/** Represent a step to set amplitude on a single vibrator. */
private final class AmplitudeStep implements Step, Comparable<AmplitudeStep> {
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 nextWakeUpTime = startTime + waveform.getTimings()[currentIndex];
int nextIndex = currentIndex + 1;
if (nextIndex >= waveform.getTimings().length) {
nextIndex = waveform.getRepeatIndex();
}
return new AmplitudeStep(vibratorId, waveform, nextIndex, nextWakeUpTime,
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...");
}
waitUntil(SystemClock.uptimeMillis() + mDelay);
return mForceStop ? Vibration.Status.CANCELLED : Vibration.Status.FINISHED;
} finally {
if (DEBUG) {
Slog.d(TAG, "DelayStep done.");
}
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
}
}
}