Merge "Add support to slowly bring the vibrator down in VibrationThread" into sc-dev

This commit is contained in:
Lais Andrade
2021-07-05 17:01:38 +00:00
committed by Android (Google) Code Review
7 changed files with 686 additions and 143 deletions

View File

@@ -3370,8 +3370,7 @@
<integer name="config_vibrationWaveformRampStepDuration">5</integer> <integer name="config_vibrationWaveformRampStepDuration">5</integer>
<!-- The duration (in milliseconds) that should be applied to waveform vibrations that ends in <!-- The duration (in milliseconds) that should be applied to waveform vibrations that ends in
non-zero amplitudes, . The waveform will non-zero amplitudes, to bring the vibrator amplitude down to zero using this timing. -->
be played as a PWLE instead of on/off calls if this value is set. -->
<integer name="config_vibrationWaveformRampDownDuration">0</integer> <integer name="config_vibrationWaveformRampDownDuration">0</integer>
<!-- Number of retries Cell Data should attempt for a given error code before <!-- Number of retries Cell Data should attempt for a given error code before

View File

@@ -99,16 +99,24 @@ final class VibrationSettings {
private boolean mLowPowerMode; private boolean mLowPowerMode;
VibrationSettings(Context context, Handler handler) { VibrationSettings(Context context, Handler handler) {
this(context, handler,
context.getResources().getInteger(
com.android.internal.R.integer.config_vibrationWaveformRampDownDuration),
context.getResources().getInteger(
com.android.internal.R.integer.config_vibrationWaveformRampStepDuration));
}
@VisibleForTesting
VibrationSettings(Context context, Handler handler, int rampDownDuration,
int rampStepDuration) {
mContext = context; mContext = context;
mSettingObserver = new SettingsObserver(handler); mSettingObserver = new SettingsObserver(handler);
mUidObserver = new UidObserver(); mUidObserver = new UidObserver();
mUserReceiver = new UserObserver(); mUserReceiver = new UserObserver();
// TODO(b/191150049): move these to vibrator static config file // TODO(b/191150049): move these to vibrator static config file
mRampStepDuration = context.getResources().getInteger( mRampDownDuration = rampDownDuration;
com.android.internal.R.integer.config_vibrationWaveformRampStepDuration); mRampStepDuration = rampStepDuration;
mRampDownDuration = context.getResources().getInteger(
com.android.internal.R.integer.config_vibrationWaveformRampDownDuration);
VibrationEffect clickEffect = createEffectFromResource( VibrationEffect clickEffect = createEffectFromResource(
com.android.internal.R.array.config_virtualKeyVibePattern); com.android.internal.R.array.config_virtualKeyVibePattern);

View File

@@ -61,6 +61,9 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
*/ */
private static final long CALLBACKS_EXTRA_TIMEOUT = 100; private static final long CALLBACKS_EXTRA_TIMEOUT = 100;
/** Threshold to prevent the ramp off steps from trying to set extremely low amplitudes. */
private static final float RAMP_OFF_AMPLITUDE_MIN = 1e-3f;
/** Fixed large duration used to note repeating vibrations to {@link IBatteryStats}. */ /** 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 long BATTERY_STATS_REPEATING_VIBRATION_DURATION = 5_000;
@@ -87,26 +90,33 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
/** Callback triggered to cancel a prepared synced vibration. */ /** Callback triggered to cancel a prepared synced vibration. */
void cancelSyncedVibration(); void cancelSyncedVibration();
/** Callback triggered when vibration thread is complete. */ /** Callback triggered when the vibration is complete. */
void onVibrationEnded(long vibrationId, Vibration.Status status); void onVibrationCompleted(long vibrationId, Vibration.Status status);
/** Callback triggered when the vibrators are released after the thread is complete. */
void onVibratorsReleased();
} }
private final Object mLock = new Object(); private final Object mLock = new Object();
private final WorkSource mWorkSource = new WorkSource(); private final WorkSource mWorkSource = new WorkSource();
private final PowerManager.WakeLock mWakeLock; private final PowerManager.WakeLock mWakeLock;
private final IBatteryStats mBatteryStatsService; private final IBatteryStats mBatteryStatsService;
private final VibrationSettings mVibrationSettings;
private final DeviceVibrationEffectAdapter mDeviceEffectAdapter; private final DeviceVibrationEffectAdapter mDeviceEffectAdapter;
private final Vibration mVibration; private final Vibration mVibration;
private final VibrationCallbacks mCallbacks; private final VibrationCallbacks mCallbacks;
private final SparseArray<VibratorController> mVibrators = new SparseArray<>(); private final SparseArray<VibratorController> mVibrators = new SparseArray<>();
private final StepQueue mStepQueue = new StepQueue(); private final StepQueue mStepQueue = new StepQueue();
private volatile boolean mStop;
private volatile boolean mForceStop; private volatile boolean mForceStop;
VibrationThread(Vibration vib, DeviceVibrationEffectAdapter effectAdapter, VibrationThread(Vibration vib, VibrationSettings vibrationSettings,
DeviceVibrationEffectAdapter effectAdapter,
SparseArray<VibratorController> availableVibrators, PowerManager.WakeLock wakeLock, SparseArray<VibratorController> availableVibrators, PowerManager.WakeLock wakeLock,
IBatteryStats batteryStatsService, VibrationCallbacks callbacks) { IBatteryStats batteryStatsService, VibrationCallbacks callbacks) {
mVibration = vib; mVibration = vib;
mVibrationSettings = vibrationSettings;
mDeviceEffectAdapter = effectAdapter; mDeviceEffectAdapter = effectAdapter;
mCallbacks = callbacks; mCallbacks = callbacks;
mWakeLock = wakeLock; mWakeLock = wakeLock;
@@ -145,8 +155,8 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
mWakeLock.acquire(); mWakeLock.acquire();
try { try {
mVibration.token.linkToDeath(this, 0); mVibration.token.linkToDeath(this, 0);
Vibration.Status status = playVibration(); playVibration();
mCallbacks.onVibrationEnded(mVibration.id, status); mCallbacks.onVibratorsReleased();
} catch (RemoteException e) { } catch (RemoteException e) {
Slog.e(TAG, "Error linking vibration to token death", e); Slog.e(TAG, "Error linking vibration to token death", e);
} finally { } finally {
@@ -155,9 +165,13 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
} }
} }
/** Cancel current vibration and shuts down the thread gracefully. */ /** Cancel current vibration and ramp down the vibrators gracefully. */
public void cancel() { public void cancel() {
mForceStop = true; if (mStop) {
// Already cancelled, running clean-up steps.
return;
}
mStop = true;
synchronized (mLock) { synchronized (mLock) {
if (DEBUG) { if (DEBUG) {
Slog.d(TAG, "Vibration cancelled"); Slog.d(TAG, "Vibration cancelled");
@@ -166,6 +180,21 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
} }
} }
/** Cancel current vibration and shuts off the vibrators immediately. */
public void cancelImmediately() {
if (mForceStop) {
// Already forced the thread to stop, wait for it to finish.
return;
}
mStop = mForceStop = true;
synchronized (mLock) {
if (DEBUG) {
Slog.d(TAG, "Vibration cancelled immediately");
}
mLock.notify();
}
}
/** Notify current vibration that a synced step has completed. */ /** Notify current vibration that a synced step has completed. */
public void syncedVibrationComplete() { public void syncedVibrationComplete() {
synchronized (mLock) { synchronized (mLock) {
@@ -190,17 +219,18 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
} }
} }
private Vibration.Status playVibration() { private void playVibration() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "playVibration"); Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "playVibration");
try { try {
CombinedVibration.Sequential effect = toSequential(mVibration.getEffect()); CombinedVibration.Sequential sequentialEffect = toSequential(mVibration.getEffect());
mStepQueue.offer(new StartVibrateStep(effect)); final int sequentialEffectSize = sequentialEffect.getEffects().size();
mStepQueue.offer(new StartVibrateStep(sequentialEffect));
int stepsPlayed = 0; Vibration.Status status = null;
while (!mStepQueue.isEmpty()) { while (!mStepQueue.isEmpty()) {
long waitTime = mStepQueue.calculateWaitTime(); long waitTime = mStepQueue.calculateWaitTime();
if (waitTime <= 0) { if (waitTime <= 0) {
stepsPlayed += mStepQueue.consumeNext(); mStepQueue.consumeNext();
} else { } else {
synchronized (mLock) { synchronized (mLock) {
try { try {
@@ -209,17 +239,33 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
} }
} }
} }
if (mForceStop) { Vibration.Status currentStatus = mStop ? Vibration.Status.CANCELLED
: mStepQueue.calculateVibrationStatus(sequentialEffectSize);
if (status == null && currentStatus != Vibration.Status.RUNNING) {
// First time vibration stopped running, start clean-up tasks and notify
// callback immediately.
status = currentStatus;
mCallbacks.onVibrationCompleted(mVibration.id, status);
if (status == Vibration.Status.CANCELLED) {
mStepQueue.cancel(); mStepQueue.cancel();
return Vibration.Status.CANCELLED; }
}
if (mForceStop) {
// Cancel every step and stop playing them right away, even clean-up steps.
mStepQueue.cancelImmediately();
break;
} }
} }
// Some effects might be ignored because the specified vibrator don't exist or doesn't if (status == null) {
// support the effect. We only report ignored here if nothing was played besides the status = mStepQueue.calculateVibrationStatus(sequentialEffectSize);
// StartVibrateStep (which means every attempt to turn on the vibrator was ignored). if (status == Vibration.Status.RUNNING) {
return stepsPlayed > effect.getEffects().size() Slog.w(TAG, "Something went wrong, step queue completed but vibration status"
? Vibration.Status.FINISHED : Vibration.Status.IGNORED_UNSUPPORTED; + " is still RUNNING for vibration " + mVibration.id);
status = Vibration.Status.FINISHED;
}
mCallbacks.onVibrationCompleted(mVibration.id, status);
}
} finally { } finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
} }
@@ -260,12 +306,9 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
segmentIndex = effect.getRepeatIndex(); segmentIndex = effect.getRepeatIndex();
} }
if (segmentIndex < 0) { if (segmentIndex < 0) {
if (vibratorOffTimeout > SystemClock.uptimeMillis()) { // No more segments to play, last step is to complete the vibration on this vibrator.
// No more segments to play, last step is to wait for the vibrator to complete return new CompleteStep(startTime, /* cancelled= */ false, controller,
return new OffStep(vibratorOffTimeout, controller); vibratorOffTimeout);
} else {
return null;
}
} }
VibrationEffectSegment segment = effect.getSegments().get(segmentIndex); VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
@@ -299,8 +342,18 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
@GuardedBy("mLock") @GuardedBy("mLock")
private final Queue<Step> mPendingOnVibratorCompleteSteps = new LinkedList<>(); private final Queue<Step> mPendingOnVibratorCompleteSteps = new LinkedList<>();
@GuardedBy("mLock")
private int mPendingVibrateSteps;
@GuardedBy("mLock")
private int mConsumedStartVibrateSteps;
@GuardedBy("mLock")
private int mSuccessfulVibratorOnSteps;
public void offer(@NonNull Step step) { public void offer(@NonNull Step step) {
synchronized (mLock) { synchronized (mLock) {
if (!step.isCleanUp()) {
mPendingVibrateSteps++;
}
mNextSteps.offer(step); mNextSteps.offer(step);
} }
} }
@@ -311,6 +364,24 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
} }
} }
/**
* Calculate the {@link Vibration.Status} based on the current queue state and the expected
* number of {@link StartVibrateStep} to be played.
*/
public Vibration.Status calculateVibrationStatus(int expectedStartVibrateSteps) {
synchronized (mLock) {
if (mPendingVibrateSteps > 0
|| mConsumedStartVibrateSteps < expectedStartVibrateSteps) {
return Vibration.Status.RUNNING;
}
if (mSuccessfulVibratorOnSteps > 0) {
return Vibration.Status.FINISHED;
}
// If no step was able to turn the vibrator ON successfully.
return Vibration.Status.IGNORED_UNSUPPORTED;
}
}
/** Returns the time in millis to wait before calling {@link #consumeNext()}. */ /** Returns the time in millis to wait before calling {@link #consumeNext()}. */
public long calculateWaitTime() { public long calculateWaitTime() {
Step nextStep; Step nextStep;
@@ -330,18 +401,28 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
* *
* @return the number of steps played * @return the number of steps played
*/ */
public int consumeNext() { public void consumeNext() {
Step nextStep = pollNext(); Step nextStep = pollNext();
if (nextStep != null) { if (nextStep != null) {
// This might turn on the vibrator and have a HAL latency. Execute this outside any // This might turn on the vibrator and have a HAL latency. Execute this outside any
// lock to avoid blocking other interactions with the thread. // lock to avoid blocking other interactions with the thread.
List<Step> nextSteps = nextStep.play(); List<Step> nextSteps = nextStep.play();
synchronized (mLock) { synchronized (mLock) {
if (nextStep.getVibratorOnDuration() > 0) {
mSuccessfulVibratorOnSteps++;
}
if (nextStep instanceof StartVibrateStep) {
mConsumedStartVibrateSteps++;
}
if (!nextStep.isCleanUp()) {
mPendingVibrateSteps--;
}
for (int i = 0; i < nextSteps.size(); i++) {
mPendingVibrateSteps += nextSteps.get(i).isCleanUp() ? 0 : 1;
}
mNextSteps.addAll(nextSteps); mNextSteps.addAll(nextSteps);
} }
return 1;
} }
return 0;
} }
/** /**
@@ -368,16 +449,38 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
} }
/** /**
* Cancel the current queue, clearing all remaining steps. * Cancel the current queue, replacing all remaining steps with respective clean-up steps.
* *
* <p>This will remove and trigger {@link Step#cancel()} in all steps, in order. * <p>This will remove all steps and replace them with respective
* {@link Step#cancel()}.
*/ */
public void cancel() { public void cancel() {
List<Step> cleanUpSteps = new ArrayList<>();
Step step;
while ((step = pollNext()) != null) {
cleanUpSteps.addAll(step.cancel());
}
synchronized (mLock) {
// All steps generated by Step.cancel() should be clean-up steps.
mPendingVibrateSteps = 0;
mNextSteps.addAll(cleanUpSteps);
}
}
/**
* Cancel the current queue immediately, clearing all remaining steps and skipping clean-up.
*
* <p>This will remove and trigger {@link Step#cancelImmediately()} in all steps, in order.
*/
public void cancelImmediately() {
Step step; Step step;
while ((step = pollNext()) != null) { while ((step = pollNext()) != null) {
// This might turn off the vibrator and have a HAL latency. Execute this outside // This might turn off the vibrator and have a HAL latency. Execute this outside
// any lock to avoid blocking other interactions with the thread. // any lock to avoid blocking other interactions with the thread.
step.cancel(); step.cancelImmediately();
}
synchronized (mLock) {
mPendingVibrateSteps = 0;
} }
} }
@@ -406,12 +509,37 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
this.startTime = startTime; this.startTime = startTime;
} }
/**
* Returns true if this step is a clean up step and not part of a {@link VibrationEffect} or
* {@link CombinedVibration}.
*/
public boolean isCleanUp() {
return false;
}
/** Play this step, returning a (possibly empty) list of next steps. */ /** Play this step, returning a (possibly empty) list of next steps. */
@NonNull @NonNull
public abstract List<Step> play(); public abstract List<Step> play();
/** Cancel this pending step. */ /**
public void cancel() { * Cancel this pending step and return a (possibly empty) list of clean-up steps that should
* be played to gracefully cancel this step.
*/
@NonNull
public abstract List<Step> cancel();
/** Cancel this pending step immediately, skipping any clean-up. */
public abstract void cancelImmediately();
/**
* Return the duration the vibrator was turned on when this step was played.
*
* @return A positive duration that the vibrator was turned on for by this step;
* Zero if the segment is not supported, the step was not played yet or vibrator was never
* turned on by this step; A negative value if the vibrator call has failed.
*/
public long getVibratorOnDuration() {
return 0;
} }
/** /**
@@ -452,6 +580,8 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
public final CombinedVibration.Sequential sequentialEffect; public final CombinedVibration.Sequential sequentialEffect;
public final int currentIndex; public final int currentIndex;
private long mVibratorsOnMaxDuration;
StartVibrateStep(CombinedVibration.Sequential effect) { StartVibrateStep(CombinedVibration.Sequential effect) {
this(SystemClock.uptimeMillis() + effect.getDelays().get(0), effect, /* index= */ 0); this(SystemClock.uptimeMillis() + effect.getDelays().get(0), effect, /* index= */ 0);
} }
@@ -462,11 +592,16 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
currentIndex = index; currentIndex = index;
} }
@Override
public long getVibratorOnDuration() {
return mVibratorsOnMaxDuration;
}
@Override @Override
public List<Step> play() { public List<Step> play() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "StartVibrateStep"); Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "StartVibrateStep");
List<Step> nextSteps = new ArrayList<>(); List<Step> nextSteps = new ArrayList<>();
long duration = -1; mVibratorsOnMaxDuration = -1;
try { try {
if (DEBUG) { if (DEBUG) {
Slog.d(TAG, "StartVibrateStep for effect #" + currentIndex); Slog.d(TAG, "StartVibrateStep for effect #" + currentIndex);
@@ -478,25 +613,33 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
return nextSteps; return nextSteps;
} }
duration = startVibrating(effectMapping, nextSteps); mVibratorsOnMaxDuration = startVibrating(effectMapping, nextSteps);
noteVibratorOn(duration); noteVibratorOn(mVibratorsOnMaxDuration);
} finally { } finally {
if (duration < 0) { if (mVibratorsOnMaxDuration >= 0) {
// Something failed while playing this step so stop playing this sequence.
return EMPTY_STEP_LIST;
}
// It least one vibrator was started then add a finish step to wait for all // It least one vibrator was started then add a finish step to wait for all
// active vibrators to finish their individual steps before going to the next. // active vibrators to finish their individual steps before going to the next.
// Otherwise this step was ignored so just go to the next one. // Otherwise this step was ignored so just go to the next one.
Step nextStep = duration > 0 ? new FinishVibrateStep(this) : nextStep(); Step nextStep =
mVibratorsOnMaxDuration > 0 ? new FinishVibrateStep(this) : nextStep();
if (nextStep != null) { if (nextStep != null) {
nextSteps.add(nextStep); nextSteps.add(nextStep);
} }
}
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
} }
return nextSteps; return nextSteps;
} }
@Override
public List<Step> cancel() {
return EMPTY_STEP_LIST;
}
@Override
public void cancelImmediately() {
}
/** /**
* Create the next {@link StartVibrateStep} to play this sequential effect, starting at the * Create the next {@link StartVibrateStep} to play this sequential effect, starting at the
* time this method is called, or null if sequence is complete. * time this method is called, or null if sequence is complete.
@@ -593,7 +736,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
// Some vibrator failed without being prepared so other vibrators might be // Some vibrator failed without being prepared so other vibrators might be
// active. Cancel and remove every pending step from output list. // active. Cancel and remove every pending step from output list.
for (int i = nextSteps.size() - 1; i >= 0; i--) { for (int i = nextSteps.size() - 1; i >= 0; i--) {
nextSteps.remove(i).cancel(); nextSteps.remove(i).cancelImmediately();
} }
} }
} }
@@ -626,6 +769,12 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
this.startedStep = startedStep; this.startedStep = startedStep;
} }
@Override
public boolean isCleanUp() {
// This step only notes that all the vibrators has been turned off.
return true;
}
@Override @Override
public List<Step> play() { public List<Step> play() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "FinishVibrateStep"); Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "FinishVibrateStep");
@@ -642,7 +791,13 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
} }
@Override @Override
public void cancel() { public List<Step> cancel() {
cancelImmediately();
return EMPTY_STEP_LIST;
}
@Override
public void cancelImmediately() {
noteVibratorOff(); noteVibratorOff();
} }
} }
@@ -658,6 +813,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
public final long vibratorOffTimeout; public final long vibratorOffTimeout;
long mVibratorOnResult; long mVibratorOnResult;
boolean mVibratorCallbackReceived;
/** /**
* @param startTime The time to schedule this step in the {@link StepQueue}. * @param startTime The time to schedule this step in the {@link StepQueue}.
@@ -678,27 +834,28 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
this.vibratorOffTimeout = vibratorOffTimeout; this.vibratorOffTimeout = vibratorOffTimeout;
} }
/** @Override
* Return the duration the vibrator was turned on when this step was played.
*
* @return A positive duration that the vibrator was turned on for by this step;
* Zero if the segment is not supported, the step was not played yet or vibrator was never
* turned on by this step; A negative value if the vibrator call has failed.
*/
public long getVibratorOnDuration() { public long getVibratorOnDuration() {
return mVibratorOnResult; return mVibratorOnResult;
} }
@Override @Override
public boolean shouldPlayWhenVibratorComplete(int vibratorId) { public boolean shouldPlayWhenVibratorComplete(int vibratorId) {
boolean isSameVibrator = controller.getVibratorInfo().getId() == vibratorId;
mVibratorCallbackReceived |= isSameVibrator;
// Only anticipate this step if a timeout was set to wait for the vibration to complete, // Only anticipate 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. // otherwise we are waiting for the correct time to play the next step.
return (controller.getVibratorInfo().getId() == vibratorId) return isSameVibrator && (vibratorOffTimeout > SystemClock.uptimeMillis());
&& (vibratorOffTimeout > SystemClock.uptimeMillis());
} }
@Override @Override
public void cancel() { public List<Step> cancel() {
return Arrays.asList(new CompleteStep(SystemClock.uptimeMillis(),
/* cancelled= */ true, controller, vibratorOffTimeout));
}
@Override
public void cancelImmediately() {
if (vibratorOffTimeout > SystemClock.uptimeMillis()) { if (vibratorOffTimeout > SystemClock.uptimeMillis()) {
// Vibrator might be running from previous steps, so turn it off while canceling. // Vibrator might be running from previous steps, so turn it off while canceling.
stopVibrating(); stopVibrating();
@@ -712,6 +869,14 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
controller.off(); controller.off();
} }
void changeAmplitude(float amplitude) {
if (DEBUG) {
Slog.d(TAG, "Amplitude changed on vibrator " + controller.getVibratorInfo().getId()
+ " to " + amplitude);
}
controller.setAmplitude(amplitude);
}
/** Return the {@link #nextVibrateStep} with same timings, only jumping the segments. */ /** Return the {@link #nextVibrateStep} with same timings, only jumping the segments. */
public List<Step> skipToNextSteps(int segmentsSkipped) { public List<Step> skipToNextSteps(int segmentsSkipped) {
return nextSteps(startTime, vibratorOffTimeout, segmentsSkipped); return nextSteps(startTime, vibratorOffTimeout, segmentsSkipped);
@@ -937,6 +1102,140 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
} }
} }
/**
* Represents a step to complete a {@link VibrationEffect}.
*
* <p>This runs right at the time the vibration is considered to end and will update the pending
* vibrators count. This can turn off the vibrator or slowly ramp it down to zero amplitude.
*/
private final class CompleteStep extends SingleVibratorStep {
private final boolean mCancelled;
CompleteStep(long startTime, boolean cancelled, VibratorController controller,
long vibratorOffTimeout) {
super(startTime, controller, /* effect= */ null, /* index= */ -1, vibratorOffTimeout);
mCancelled = cancelled;
}
@Override
public boolean isCleanUp() {
// If the vibration was cancelled then this is just a clean up to ramp off the vibrator.
// Otherwise this step is part of the vibration.
return mCancelled;
}
@Override
public List<Step> cancel() {
if (mCancelled) {
// Double cancelling will just turn off the vibrator right away.
return Arrays.asList(new OffStep(SystemClock.uptimeMillis(), controller));
}
return super.cancel();
}
@Override
public List<Step> play() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "CompleteStep");
try {
if (DEBUG) {
Slog.d(TAG, "Running " + (mCancelled ? "cancel" : "complete") + " vibration"
+ " step on vibrator " + controller.getVibratorInfo().getId());
}
if (mVibratorCallbackReceived) {
// Vibration completion callback was received by this step, just turn if off
// and skip any clean-up.
stopVibrating();
return EMPTY_STEP_LIST;
}
float currentAmplitude = controller.getCurrentAmplitude();
long remainingOnDuration =
vibratorOffTimeout - CALLBACKS_EXTRA_TIMEOUT - SystemClock.uptimeMillis();
long rampDownDuration =
Math.min(remainingOnDuration, mVibrationSettings.getRampDownDuration());
long stepDownDuration = mVibrationSettings.getRampStepDuration();
if (currentAmplitude < RAMP_OFF_AMPLITUDE_MIN
|| rampDownDuration <= stepDownDuration) {
// No need to ramp down the amplitude, just wait to turn it off.
if (mCancelled) {
// Vibration is completing because it was cancelled, turn off right away.
stopVibrating();
return EMPTY_STEP_LIST;
} else {
return Arrays.asList(new OffStep(vibratorOffTimeout, controller));
}
}
if (DEBUG) {
Slog.d(TAG, "Ramping down vibrator " + controller.getVibratorInfo().getId()
+ " from amplitude " + currentAmplitude
+ " for " + rampDownDuration + "ms");
}
float amplitudeDelta = currentAmplitude / (rampDownDuration / stepDownDuration);
float amplitudeTarget = currentAmplitude - amplitudeDelta;
long newVibratorOffTimeout = mCancelled ? rampDownDuration : vibratorOffTimeout;
return Arrays.asList(new RampOffStep(startTime, amplitudeTarget, amplitudeDelta,
controller, newVibratorOffTimeout));
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
}
}
/** Represents a step to ramp down the vibrator amplitude before turning it off. */
private final class RampOffStep extends SingleVibratorStep {
private final float mAmplitudeTarget;
private final float mAmplitudeDelta;
RampOffStep(long startTime, float amplitudeTarget, float amplitudeDelta,
VibratorController controller, long vibratorOffTimeout) {
super(startTime, controller, /* effect= */ null, /* index= */ -1, vibratorOffTimeout);
mAmplitudeTarget = amplitudeTarget;
mAmplitudeDelta = amplitudeDelta;
}
@Override
public boolean isCleanUp() {
return true;
}
@Override
public List<Step> cancel() {
return Arrays.asList(new OffStep(SystemClock.uptimeMillis(), controller));
}
@Override
public List<Step> play() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "RampOffStep");
try {
if (DEBUG) {
long latency = SystemClock.uptimeMillis() - startTime;
Slog.d(TAG, "Ramp down the vibrator amplitude, step with "
+ latency + "ms latency.");
}
if (mVibratorCallbackReceived) {
// Vibration completion callback was received by this step, just turn if off
// and skip the rest of the steps to ramp down the vibrator amplitude.
stopVibrating();
return EMPTY_STEP_LIST;
}
changeAmplitude(mAmplitudeTarget);
float newAmplitudeTarget = mAmplitudeTarget - mAmplitudeDelta;
if (newAmplitudeTarget < RAMP_OFF_AMPLITUDE_MIN) {
// Vibrator amplitude cannot go further down, just turn it off.
return Arrays.asList(new OffStep(vibratorOffTimeout, controller));
}
return Arrays.asList(new RampOffStep(
startTime + mVibrationSettings.getRampStepDuration(), newAmplitudeTarget,
mAmplitudeDelta, controller, vibratorOffTimeout));
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
}
}
/** /**
* Represents a step to turn the vibrator off. * Represents a step to turn the vibrator off.
* *
@@ -949,6 +1248,21 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
super(startTime, controller, /* effect= */ null, /* index= */ -1, startTime); super(startTime, controller, /* effect= */ null, /* index= */ -1, startTime);
} }
@Override
public boolean isCleanUp() {
return true;
}
@Override
public List<Step> cancel() {
return Arrays.asList(new OffStep(SystemClock.uptimeMillis(), controller));
}
@Override
public void cancelImmediately() {
stopVibrating();
}
@Override @Override
public List<Step> play() { public List<Step> play() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "OffStep"); Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "OffStep");
@@ -1044,14 +1358,6 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
return controller.on(duration, mVibration.id); return controller.on(duration, mVibration.id);
} }
private void changeAmplitude(float amplitude) {
if (DEBUG) {
Slog.d(TAG, "Amplitude changed on vibrator " + controller.getVibratorInfo().getId()
+ " to " + amplitude);
}
controller.setAmplitude(amplitude);
}
/** /**
* Get the duration the vibrator will be on for a waveform, starting at {@code startIndex} * Get the duration the vibrator will be on for a waveform, starting at {@code startIndex}
* until the next time it's vibrating amplitude is zero or a different type of segment is * until the next time it's vibrating amplitude is zero or a different type of segment is
@@ -1080,6 +1386,11 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
return 1000; return 1000;
} }
} }
if (i == segmentCount && effect.getRepeatIndex() < 0) {
// Vibration ending at non-zero amplitude, add extra timings to ramp down after
// vibration is complete.
timing += mVibrationSettings.getRampDownDuration();
}
return timing; return timing;
} }
} }

View File

@@ -54,6 +54,8 @@ final class VibratorController {
private boolean mIsVibrating; private boolean mIsVibrating;
@GuardedBy("mLock") @GuardedBy("mLock")
private boolean mIsUnderExternalControl; private boolean mIsUnderExternalControl;
@GuardedBy("mLock")
private float mCurrentAmplitude;
/** Listener for vibration completion callbacks from native. */ /** Listener for vibration completion callbacks from native. */
public interface OnVibrationCompleteListener { public interface OnVibrationCompleteListener {
@@ -131,6 +133,23 @@ final class VibratorController {
} }
} }
/**
* Returns the current amplitude the device is vibrating.
*
* <p>This value is set to 1 by the method {@link #on(long, long)}, and can be updated via
* {@link #setAmplitude(float)} if called while the device is vibrating.
*
* <p>If the device is vibrating via any other {@link #on} method then the current amplitude is
* unknown and this will return -1.
*
* <p>If {@link #isVibrating()} is false then this will be zero.
*/
public float getCurrentAmplitude() {
synchronized (mLock) {
return mCurrentAmplitude;
}
}
/** Return {@code true} if this vibrator is under external control, false otherwise. */ /** Return {@code true} if this vibrator is under external control, false otherwise. */
public boolean isUnderExternalControl() { public boolean isUnderExternalControl() {
synchronized (mLock) { synchronized (mLock) {
@@ -192,6 +211,9 @@ final class VibratorController {
if (mVibratorInfo.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) { if (mVibratorInfo.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) {
mNativeWrapper.setAmplitude(amplitude); mNativeWrapper.setAmplitude(amplitude);
} }
if (mIsVibrating) {
mCurrentAmplitude = amplitude;
}
} }
} }
@@ -208,6 +230,7 @@ final class VibratorController {
synchronized (mLock) { synchronized (mLock) {
long duration = mNativeWrapper.on(milliseconds, vibrationId); long duration = mNativeWrapper.on(milliseconds, vibrationId);
if (duration > 0) { if (duration > 0) {
mCurrentAmplitude = -1;
notifyVibratorOnLocked(); notifyVibratorOnLocked();
} }
return duration; return duration;
@@ -228,6 +251,7 @@ final class VibratorController {
long duration = mNativeWrapper.perform(prebaked.getEffectId(), long duration = mNativeWrapper.perform(prebaked.getEffectId(),
prebaked.getEffectStrength(), vibrationId); prebaked.getEffectStrength(), vibrationId);
if (duration > 0) { if (duration > 0) {
mCurrentAmplitude = -1;
notifyVibratorOnLocked(); notifyVibratorOnLocked();
} }
return duration; return duration;
@@ -250,6 +274,7 @@ final class VibratorController {
synchronized (mLock) { synchronized (mLock) {
long duration = mNativeWrapper.compose(primitives, vibrationId); long duration = mNativeWrapper.compose(primitives, vibrationId);
if (duration > 0) { if (duration > 0) {
mCurrentAmplitude = -1;
notifyVibratorOnLocked(); notifyVibratorOnLocked();
} }
return duration; return duration;
@@ -272,6 +297,7 @@ final class VibratorController {
int braking = mVibratorInfo.getDefaultBraking(); int braking = mVibratorInfo.getDefaultBraking();
long duration = mNativeWrapper.composePwle(primitives, braking, vibrationId); long duration = mNativeWrapper.composePwle(primitives, braking, vibrationId);
if (duration > 0) { if (duration > 0) {
mCurrentAmplitude = -1;
notifyVibratorOnLocked(); notifyVibratorOnLocked();
} }
return duration; return duration;
@@ -282,20 +308,24 @@ final class VibratorController {
public void off() { public void off() {
synchronized (mLock) { synchronized (mLock) {
mNativeWrapper.off(); mNativeWrapper.off();
mCurrentAmplitude = 0;
notifyVibratorOffLocked(); notifyVibratorOffLocked();
} }
} }
@Override @Override
public String toString() { public String toString() {
synchronized (mLock) {
return "VibratorController{" return "VibratorController{"
+ "mVibratorInfo=" + mVibratorInfo + "mVibratorInfo=" + mVibratorInfo
+ ", mIsVibrating=" + mIsVibrating + ", mIsVibrating=" + mIsVibrating
+ ", mCurrentAmplitude=" + mCurrentAmplitude
+ ", mIsUnderExternalControl=" + mIsUnderExternalControl + ", mIsUnderExternalControl=" + mIsUnderExternalControl
+ ", mVibratorStateListeners count=" + ", mVibratorStateListeners count="
+ mVibratorStateListeners.getRegisteredCallbackCount() + mVibratorStateListeners.getRegisteredCallbackCount()
+ '}'; + '}';
} }
}
@GuardedBy("mLock") @GuardedBy("mLock")
private void notifyVibratorOnLocked() { private void notifyVibratorOnLocked() {

View File

@@ -514,8 +514,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
return Vibration.Status.FORWARDED_TO_INPUT_DEVICES; return Vibration.Status.FORWARDED_TO_INPUT_DEVICES;
} }
VibrationThread vibThread = new VibrationThread(vib, mDeviceVibrationEffectAdapter, VibrationThread vibThread = new VibrationThread(vib, mVibrationSettings,
mVibrators, mWakeLock, mBatteryStatsService, mVibrationCallbacks); mDeviceVibrationEffectAdapter, mVibrators, mWakeLock, mBatteryStatsService,
mVibrationCallbacks);
if (mCurrentVibration == null) { if (mCurrentVibration == null) {
return startVibrationThreadLocked(vibThread); return startVibrationThreadLocked(vibThread);
@@ -569,7 +570,6 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
try { try {
Vibration vib = mCurrentVibration.getVibration(); Vibration vib = mCurrentVibration.getVibration();
mCurrentVibration = null;
endVibrationLocked(vib, status); endVibrationLocked(vib, status);
finishAppOpModeLocked(vib.uid, vib.opPkg); finishAppOpModeLocked(vib.uid, vib.opPkg);
} finally { } finally {
@@ -613,7 +613,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
// Repeating vibrations always take precedence. // Repeating vibrations always take precedence.
return null; return null;
} }
if (mCurrentVibration != null) { if (mCurrentVibration != null && !mCurrentVibration.getVibration().hasEnded()) {
if (mCurrentVibration.getVibration().attrs.getUsage() if (mCurrentVibration.getVibration().attrs.getUsage()
== VibrationAttributes.USAGE_ALARM) { == VibrationAttributes.USAGE_ALARM) {
if (DEBUG) { if (DEBUG) {
@@ -1003,15 +1003,25 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
} }
@Override @Override
public void onVibrationEnded(long vibrationId, Vibration.Status status) { public void onVibrationCompleted(long vibrationId, Vibration.Status status) {
if (DEBUG) { if (DEBUG) {
Slog.d(TAG, "Vibration " + vibrationId + " thread finished with status " + status); Slog.d(TAG, "Vibration " + vibrationId + " finished with status " + status);
} }
synchronized (mLock) { synchronized (mLock) {
if (mCurrentVibration != null if (mCurrentVibration != null
&& mCurrentVibration.getVibration().id == vibrationId) { && mCurrentVibration.getVibration().id == vibrationId) {
reportFinishedVibrationLocked(status); reportFinishedVibrationLocked(status);
}
}
}
@Override
public void onVibratorsReleased() {
if (DEBUG) {
Slog.d(TAG, "Vibrators released after finished vibration");
}
synchronized (mLock) {
mCurrentVibration = null;
if (mNextVibration != null) { if (mNextVibration != null) {
VibrationThread vibThread = mNextVibration; VibrationThread vibThread = mNextVibration;
mNextVibration = null; mNextVibration = null;
@@ -1020,7 +1030,6 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
} }
} }
} }
}
/** Listener for synced vibration completion callbacks from native. */ /** Listener for synced vibration completion callbacks from native. */
@VisibleForTesting @VisibleForTesting
@@ -1337,7 +1346,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
// vibration that may be playing and ready the vibrator for external control. // vibration that may be playing and ready the vibrator for external control.
if (mCurrentVibration != null) { if (mCurrentVibration != null) {
mNextVibration = null; mNextVibration = null;
mCurrentVibration.cancel(); mCurrentVibration.cancelImmediately();
cancelingVibration = mCurrentVibration; cancelingVibration = mCurrentVibration;
} }
} else { } else {

View File

@@ -27,6 +27,7 @@ import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -86,6 +87,7 @@ public class VibrationThreadTest {
private static final int VIBRATOR_ID = 1; private static final int VIBRATOR_ID = 1;
private static final String PACKAGE_NAME = "package"; private static final String PACKAGE_NAME = "package";
private static final VibrationAttributes ATTRS = new VibrationAttributes.Builder().build(); private static final VibrationAttributes ATTRS = new VibrationAttributes.Builder().build();
private static final int TEST_RAMP_STEP_DURATION = 5;
@Rule @Rule
public MockitoRule mMockitoRule = MockitoJUnit.rule(); public MockitoRule mMockitoRule = MockitoJUnit.rule();
@@ -100,6 +102,7 @@ public class VibrationThreadTest {
private IBatteryStats mIBatteryStatsMock; private IBatteryStats mIBatteryStatsMock;
private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>(); private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
private VibrationSettings mVibrationSettings;
private DeviceVibrationEffectAdapter mEffectAdapter; private DeviceVibrationEffectAdapter mEffectAdapter;
private PowerManager.WakeLock mWakeLock; private PowerManager.WakeLock mWakeLock;
private TestLooper mTestLooper; private TestLooper mTestLooper;
@@ -109,9 +112,9 @@ public class VibrationThreadTest {
mTestLooper = new TestLooper(); mTestLooper = new TestLooper();
Context context = InstrumentationRegistry.getContext(); Context context = InstrumentationRegistry.getContext();
VibrationSettings vibrationSettings = new VibrationSettings(context, mVibrationSettings = new VibrationSettings(context, new Handler(mTestLooper.getLooper()),
new Handler(mTestLooper.getLooper())); /* rampDownDuration= */ 0, TEST_RAMP_STEP_DURATION);
mEffectAdapter = new DeviceVibrationEffectAdapter(vibrationSettings); mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);
mWakeLock = context.getSystemService( mWakeLock = context.getSystemService(
PowerManager.class).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*"); PowerManager.class).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
@@ -128,8 +131,7 @@ public class VibrationThreadTest {
waitForCompletion(thread); waitForCompletion(thread);
verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId)); verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
eq(Vibration.Status.IGNORED_UNSUPPORTED));
} }
@Test @Test
@@ -143,8 +145,7 @@ public class VibrationThreadTest {
waitForCompletion(thread); waitForCompletion(thread);
verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId)); verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
eq(Vibration.Status.IGNORED_UNSUPPORTED));
} }
@Test @Test
@@ -159,7 +160,7 @@ public class VibrationThreadTest {
verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L)); verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L));
verify(mIBatteryStatsMock).noteVibratorOff(eq(UID)); verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED)); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(10)), assertEquals(Arrays.asList(expectedOneShot(10)),
@@ -178,7 +179,7 @@ public class VibrationThreadTest {
verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L)); verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L));
verify(mIBatteryStatsMock).noteVibratorOff(eq(UID)); verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED)); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(10)), assertEquals(Arrays.asList(expectedOneShot(10)),
@@ -200,7 +201,7 @@ public class VibrationThreadTest {
verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(15L)); verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(15L));
verify(mIBatteryStatsMock).noteVibratorOff(eq(UID)); verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED)); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(15)), assertEquals(Arrays.asList(expectedOneShot(15)),
@@ -232,7 +233,7 @@ public class VibrationThreadTest {
verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), anyLong()); verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), anyLong());
verify(mIBatteryStatsMock).noteVibratorOff(eq(UID)); verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED)); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
List<Float> playedAmplitudes = fakeVibrator.getAmplitudes(); List<Float> playedAmplitudes = fakeVibrator.getAmplitudes();
@@ -269,7 +270,7 @@ public class VibrationThreadTest {
waitForCompletion(vibrationThread, /* timeout= */ 50); waitForCompletion(vibrationThread, /* timeout= */ 50);
waitForCompletion(cancellingThread); waitForCompletion(cancellingThread);
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED)); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
assertFalse(vibrationThread.getVibrators().get(VIBRATOR_ID).isVibrating()); assertFalse(vibrationThread.getVibrators().get(VIBRATOR_ID).isVibrating());
} }
@@ -294,7 +295,7 @@ public class VibrationThreadTest {
waitForCompletion(vibrationThread, /* timeout= */ 50); waitForCompletion(vibrationThread, /* timeout= */ 50);
waitForCompletion(cancellingThread); waitForCompletion(cancellingThread);
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED)); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
assertFalse(vibrationThread.getVibrators().get(VIBRATOR_ID).isVibrating()); assertFalse(vibrationThread.getVibrators().get(VIBRATOR_ID).isVibrating());
} }
@@ -310,7 +311,7 @@ public class VibrationThreadTest {
verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L)); verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L));
verify(mIBatteryStatsMock).noteVibratorOff(eq(UID)); verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED)); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_THUD)), assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_THUD)),
@@ -333,7 +334,7 @@ public class VibrationThreadTest {
verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L)); verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L));
verify(mIBatteryStatsMock).noteVibratorOff(eq(UID)); verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED)); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(10)), assertEquals(Arrays.asList(expectedOneShot(10)),
@@ -352,8 +353,7 @@ public class VibrationThreadTest {
verify(mIBatteryStatsMock, never()).noteVibratorOn(eq(UID), anyLong()); verify(mIBatteryStatsMock, never()).noteVibratorOn(eq(UID), anyLong());
verify(mIBatteryStatsMock, never()).noteVibratorOff(eq(UID)); verify(mIBatteryStatsMock, never()).noteVibratorOff(eq(UID));
verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
eq(Vibration.Status.IGNORED_UNSUPPORTED));
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty()); assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty());
} }
@@ -373,7 +373,7 @@ public class VibrationThreadTest {
verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(40L)); verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(40L));
verify(mIBatteryStatsMock).noteVibratorOff(eq(UID)); verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED)); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList( assertEquals(Arrays.asList(
expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0), expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0),
@@ -393,8 +393,7 @@ public class VibrationThreadTest {
verify(mIBatteryStatsMock, never()).noteVibratorOn(eq(UID), anyLong()); verify(mIBatteryStatsMock, never()).noteVibratorOn(eq(UID), anyLong());
verify(mIBatteryStatsMock, never()).noteVibratorOff(eq(UID)); verify(mIBatteryStatsMock, never()).noteVibratorOff(eq(UID));
verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
eq(Vibration.Status.IGNORED_UNSUPPORTED));
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty()); assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty());
} }
@@ -413,7 +412,7 @@ public class VibrationThreadTest {
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect); VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
waitForCompletion(thread); waitForCompletion(thread);
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED)); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
// Vibrator compose called twice. // Vibrator compose called twice.
verify(mControllerCallbacks, times(2)).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verify(mControllerCallbacks, times(2)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
assertEquals(3, fakeVibrator.getEffectSegments().size()); assertEquals(3, fakeVibrator.getEffectSegments().size());
@@ -443,7 +442,7 @@ public class VibrationThreadTest {
verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L)); verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L));
verify(mIBatteryStatsMock).noteVibratorOff(eq(UID)); verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
verify(mControllerCallbacks, times(4)).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verify(mControllerCallbacks, times(4)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED)); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList( assertEquals(Arrays.asList(
expectedOneShot(10), expectedOneShot(10),
@@ -479,7 +478,7 @@ public class VibrationThreadTest {
verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(100L)); verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(100L));
verify(mIBatteryStatsMock).noteVibratorOff(eq(UID)); verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED)); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList( assertEquals(Arrays.asList(
expectedRamp(/* amplitude= */ 1, /* frequency= */ 150, /* duration= */ 10), expectedRamp(/* amplitude= */ 1, /* frequency= */ 150, /* duration= */ 10),
@@ -512,7 +511,7 @@ public class VibrationThreadTest {
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect); VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
waitForCompletion(thread); waitForCompletion(thread);
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED)); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
// Vibrator compose called twice. // Vibrator compose called twice.
verify(mControllerCallbacks, times(2)).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verify(mControllerCallbacks, times(2)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
assertEquals(4, fakeVibrator.getEffectSegments().size()); assertEquals(4, fakeVibrator.getEffectSegments().size());
@@ -537,7 +536,7 @@ public class VibrationThreadTest {
waitForCompletion(thread); waitForCompletion(thread);
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED)); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
} }
@Test @Test
@@ -545,10 +544,10 @@ public class VibrationThreadTest {
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
long vibrationId = 1; long vibrationId = 1;
waitForCompletion(startThreadAndDispatcher(vibrationId++, waitForCompletion(startThreadAndDispatcher(vibrationId,
VibrationEffect.createOneShot(10, 100))); VibrationEffect.createOneShot(10, 100)));
verify(mThreadCallbacks).onVibrationEnded(anyLong(), eq(Vibration.Status.FINISHED)); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
verify(mThreadCallbacks, never()).prepareSyncedVibration(anyLong(), any()); verify(mThreadCallbacks, never()).prepareSyncedVibration(anyLong(), any());
verify(mThreadCallbacks, never()).triggerSyncedVibration(anyLong()); verify(mThreadCallbacks, never()).triggerSyncedVibration(anyLong());
verify(mThreadCallbacks, never()).cancelSyncedVibration(); verify(mThreadCallbacks, never()).cancelSyncedVibration();
@@ -571,7 +570,7 @@ public class VibrationThreadTest {
verify(mIBatteryStatsMock).noteVibratorOff(eq(UID)); verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verify(mControllerCallbacks, never()).onComplete(eq(2), eq(vibrationId)); verify(mControllerCallbacks, never()).onComplete(eq(2), eq(vibrationId));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED)); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_TICK)), assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_TICK)),
@@ -596,7 +595,7 @@ public class VibrationThreadTest {
verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId)); verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId)); verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId)); verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED)); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(thread.getVibrators().get(1).isVibrating()); assertFalse(thread.getVibrators().get(1).isVibrating());
assertFalse(thread.getVibrators().get(2).isVibrating()); assertFalse(thread.getVibrators().get(2).isVibrating());
assertFalse(thread.getVibrators().get(3).isVibrating()); assertFalse(thread.getVibrators().get(3).isVibrating());
@@ -635,7 +634,7 @@ public class VibrationThreadTest {
verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId)); verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId)); verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
verify(mControllerCallbacks).onComplete(eq(4), eq(vibrationId)); verify(mControllerCallbacks).onComplete(eq(4), eq(vibrationId));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED)); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(thread.getVibrators().get(1).isVibrating()); assertFalse(thread.getVibrators().get(1).isVibrating());
assertFalse(thread.getVibrators().get(2).isVibrating()); assertFalse(thread.getVibrators().get(2).isVibrating());
assertFalse(thread.getVibrators().get(3).isVibrating()); assertFalse(thread.getVibrators().get(3).isVibrating());
@@ -686,7 +685,7 @@ public class VibrationThreadTest {
batterVerifier.verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L)); batterVerifier.verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L));
batterVerifier.verify(mIBatteryStatsMock).noteVibratorOff(eq(UID)); batterVerifier.verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED)); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(thread.getVibrators().get(1).isVibrating()); assertFalse(thread.getVibrators().get(1).isVibrating());
assertFalse(thread.getVibrators().get(2).isVibrating()); assertFalse(thread.getVibrators().get(2).isVibrating());
assertFalse(thread.getVibrators().get(3).isVibrating()); assertFalse(thread.getVibrators().get(3).isVibrating());
@@ -727,7 +726,7 @@ public class VibrationThreadTest {
verify(mThreadCallbacks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds)); verify(mThreadCallbacks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
verify(mThreadCallbacks).triggerSyncedVibration(eq(vibrationId)); verify(mThreadCallbacks).triggerSyncedVibration(eq(vibrationId));
verify(mThreadCallbacks, never()).cancelSyncedVibration(); verify(mThreadCallbacks, never()).cancelSyncedVibration();
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED)); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
VibrationEffectSegment expected = expectedPrimitive( VibrationEffectSegment expected = expectedPrimitive(
VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100); VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100);
@@ -767,7 +766,7 @@ public class VibrationThreadTest {
verify(mThreadCallbacks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds)); verify(mThreadCallbacks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
verify(mThreadCallbacks).triggerSyncedVibration(eq(vibrationId)); verify(mThreadCallbacks).triggerSyncedVibration(eq(vibrationId));
verify(mThreadCallbacks, never()).cancelSyncedVibration(); verify(mThreadCallbacks, never()).cancelSyncedVibration();
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED)); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
} }
@Test @Test
@@ -858,7 +857,7 @@ public class VibrationThreadTest {
verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId)); verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId)); verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId)); verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED)); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(thread.getVibrators().get(1).isVibrating()); assertFalse(thread.getVibrators().get(1).isVibrating());
assertFalse(thread.getVibrators().get(2).isVibrating()); assertFalse(thread.getVibrators().get(2).isVibrating());
assertFalse(thread.getVibrators().get(3).isVibrating()); assertFalse(thread.getVibrators().get(3).isVibrating());
@@ -935,7 +934,7 @@ public class VibrationThreadTest {
// After the vibrator call ends the vibration is cancelled and the vibrator is turned off. // After the vibrator call ends the vibration is cancelled and the vibrator is turned off.
waitForCompletion(vibrationThread, /* timeout= */ latency + TEST_TIMEOUT_MILLIS); waitForCompletion(vibrationThread, /* timeout= */ latency + TEST_TIMEOUT_MILLIS);
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED)); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
assertFalse(vibrationThread.getVibrators().get(VIBRATOR_ID).isVibrating()); assertFalse(vibrationThread.getVibrators().get(VIBRATOR_ID).isVibrating());
} }
@@ -968,7 +967,7 @@ public class VibrationThreadTest {
waitForCompletion(vibrationThread, /* timeout= */ 50); waitForCompletion(vibrationThread, /* timeout= */ 50);
waitForCompletion(cancellingThread); waitForCompletion(cancellingThread);
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED)); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
assertFalse(vibrationThread.getVibrators().get(1).isVibrating()); assertFalse(vibrationThread.getVibrators().get(1).isVibrating());
assertFalse(vibrationThread.getVibrators().get(2).isVibrating()); assertFalse(vibrationThread.getVibrators().get(2).isVibrating());
} }
@@ -1000,7 +999,7 @@ public class VibrationThreadTest {
waitForCompletion(vibrationThread, /* timeout= */ 50); waitForCompletion(vibrationThread, /* timeout= */ 50);
waitForCompletion(cancellingThread); waitForCompletion(cancellingThread);
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED)); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
assertFalse(vibrationThread.getVibrators().get(1).isVibrating()); assertFalse(vibrationThread.getVibrators().get(1).isVibrating());
assertFalse(vibrationThread.getVibrators().get(2).isVibrating()); assertFalse(vibrationThread.getVibrators().get(2).isVibrating());
} }
@@ -1020,11 +1019,179 @@ public class VibrationThreadTest {
verify(mVibrationToken).linkToDeath(same(thread), eq(0)); verify(mVibrationToken).linkToDeath(same(thread), eq(0));
verify(mVibrationToken).unlinkToDeath(same(thread), eq(0)); verify(mVibrationToken).unlinkToDeath(same(thread), eq(0));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED)); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
assertFalse(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty()); assertFalse(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty());
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
} }
@Test
public void vibrate_waveformWithRampDown_addsRampDownAfterVibrationCompleted() {
int rampDownDuration = 15;
mVibrationSettings = new VibrationSettings(InstrumentationRegistry.getContext(),
new Handler(mTestLooper.getLooper()), rampDownDuration, TEST_RAMP_STEP_DURATION);
mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.createWaveform(
new long[]{5, 5, 5}, new int[]{60, 120, 240}, -1);
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
waitForCompletion(thread);
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
// Duration extended for 5 + 5 + 5 + 15.
assertEquals(Arrays.asList(expectedOneShot(30)),
mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
List<Float> amplitudes = mVibratorProviders.get(VIBRATOR_ID).getAmplitudes();
assertTrue(amplitudes.size() > 3);
assertEquals(expectedAmplitudes(60, 120, 240), amplitudes.subList(0, 3));
for (int i = 3; i < amplitudes.size(); i++) {
assertTrue(amplitudes.get(i) < amplitudes.get(i - 1));
}
}
@Test
public void vibrate_waveformWithRampDown_triggersCallbackWhenOriginalVibrationEnds() {
int rampDownDuration = 10_000;
mVibrationSettings = new VibrationSettings(InstrumentationRegistry.getContext(),
new Handler(mTestLooper.getLooper()), rampDownDuration, TEST_RAMP_STEP_DURATION);
mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.createOneShot(10, 200);
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
// Vibration completed but vibrator not yet released.
verify(mThreadCallbacks, timeout(TEST_TIMEOUT_MILLIS)).onVibrationCompleted(eq(vibrationId),
eq(Vibration.Status.FINISHED));
verify(mThreadCallbacks, never()).onVibratorsReleased();
// Thread still running ramp down.
assertTrue(thread.isAlive());
// Duration extended for 10 + 10000.
assertEquals(Arrays.asList(expectedOneShot(10_010)),
mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
// Will stop the ramp down right away.
thread.cancelImmediately();
waitForCompletion(thread);
// Does not cancel already finished vibration, but releases vibrator.
verify(mThreadCallbacks, never()).onVibrationCompleted(eq(vibrationId),
eq(Vibration.Status.CANCELLED));
verify(mThreadCallbacks).onVibratorsReleased();
}
@Test
public void vibrate_waveformCancelledWithRampDown_addsRampDownAfterVibrationCancelled()
throws Exception {
int rampDownDuration = 15;
mVibrationSettings = new VibrationSettings(InstrumentationRegistry.getContext(),
new Handler(mTestLooper.getLooper()), rampDownDuration, TEST_RAMP_STEP_DURATION);
mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.createOneShot(10_000, 240);
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
assertTrue(waitUntil(t -> t.getVibrators().get(VIBRATOR_ID).isVibrating(), thread,
TEST_TIMEOUT_MILLIS));
thread.cancel();
waitForCompletion(thread);
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
// Duration extended for 10000 + 15.
assertEquals(Arrays.asList(expectedOneShot(10_015)),
mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
List<Float> amplitudes = mVibratorProviders.get(VIBRATOR_ID).getAmplitudes();
assertTrue(amplitudes.size() > 1);
for (int i = 1; i < amplitudes.size(); i++) {
assertTrue(amplitudes.get(i) < amplitudes.get(i - 1));
}
}
@Test
public void vibrate_predefinedWithRampDown_doesNotAddRampDown() {
int rampDownDuration = 15;
mVibrationSettings = new VibrationSettings(InstrumentationRegistry.getContext(),
new Handler(mTestLooper.getLooper()), rampDownDuration, TEST_RAMP_STEP_DURATION);
mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
waitForCompletion(thread);
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().isEmpty());
}
@Test
public void vibrate_composedWithRampDown_doesNotAddRampDown() {
int rampDownDuration = 15;
mVibrationSettings = new VibrationSettings(InstrumentationRegistry.getContext(),
new Handler(mTestLooper.getLooper()), rampDownDuration, TEST_RAMP_STEP_DURATION);
mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
IVibrator.CAP_COMPOSE_EFFECTS);
mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(
VibrationEffect.Composition.PRIMITIVE_CLICK);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
.compose();
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
waitForCompletion(thread);
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertEquals(
Arrays.asList(expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().isEmpty());
}
@Test
public void vibrate_pwleWithRampDown_doesNotAddRampDown() {
int rampDownDuration = 15;
mVibrationSettings = new VibrationSettings(InstrumentationRegistry.getContext(),
new Handler(mTestLooper.getLooper()), rampDownDuration, TEST_RAMP_STEP_DURATION);
mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
fakeVibrator.setMinFrequency(100);
fakeVibrator.setResonantFrequency(150);
fakeVibrator.setFrequencyResolution(50);
fakeVibrator.setMaxAmplitudes(1, 1, 1);
fakeVibrator.setPwleSizeMax(2);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.startWaveform().addRamp(1, 1).build();
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
waitForCompletion(thread);
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertEquals(Arrays.asList(expectedRamp(0, 1, 150, 150, 1)),
fakeVibrator.getEffectSegments());
assertTrue(fakeVibrator.getAmplitudes().isEmpty());
}
private void mockVibrators(int... vibratorIds) { private void mockVibrators(int... vibratorIds) {
for (int vibratorId : vibratorIds) { for (int vibratorId : vibratorIds) {
mVibratorProviders.put(vibratorId, mVibratorProviders.put(vibratorId,
@@ -1042,7 +1209,7 @@ public class VibrationThreadTest {
} }
private VibrationThread startThreadAndDispatcher(Vibration vib) { private VibrationThread startThreadAndDispatcher(Vibration vib) {
VibrationThread thread = new VibrationThread(vib, mEffectAdapter, VibrationThread thread = new VibrationThread(vib, mVibrationSettings, mEffectAdapter,
createVibratorControllers(), mWakeLock, mIBatteryStatsMock, mThreadCallbacks); createVibratorControllers(), mWakeLock, mIBatteryStatsMock, mThreadCallbacks);
doAnswer(answer -> { doAnswer(answer -> {
thread.vibratorComplete(answer.getArgument(0)); thread.vibratorComplete(answer.getArgument(0));
@@ -1117,4 +1284,9 @@ public class VibrationThreadTest {
.mapToObj(amplitude -> amplitude / 255f) .mapToObj(amplitude -> amplitude / 255f)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
private void verifyCallbacksTriggered(long vibrationId, Vibration.Status expectedStatus) {
verify(mThreadCallbacks).onVibrationCompleted(eq(vibrationId), eq(expectedStatus));
verify(mThreadCallbacks).onVibratorsReleased();
}
} }

View File

@@ -67,6 +67,7 @@ import android.os.VibratorInfo;
import android.os.test.TestLooper; import android.os.test.TestLooper;
import android.os.vibrator.PrebakedSegment; import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment; import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.VibrationEffectSegment;
import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.Presubmit;
import android.provider.Settings; import android.provider.Settings;
import android.view.InputDevice; import android.view.InputDevice;
@@ -470,13 +471,14 @@ public class VibratorManagerServiceTest {
mockVibrators(1); mockVibrators(1);
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1); FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
mVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_MEDIUM); mVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_MEDIUM);
fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK,
VibrationEffect.EFFECT_HEAVY_CLICK, VibrationEffect.EFFECT_DOUBLE_CLICK);
setRingerMode(AudioManager.RINGER_MODE_NORMAL); setRingerMode(AudioManager.RINGER_MODE_NORMAL);
setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0); setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
VibratorManagerService service = createSystemReadyService(); VibratorManagerService service = createSystemReadyService();
vibrate(service, VibrationEffect.createOneShot(1, 1), RINGTONE_ATTRS); vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
// Wait before checking it never played. // Wait before checking it never played.
assertFalse(waitUntil(s -> !fakeVibrator.getEffectSegments().isEmpty(), assertFalse(waitUntil(s -> !fakeVibrator.getEffectSegments().isEmpty(),
service, /* timeout= */ 50)); service, /* timeout= */ 50));
@@ -484,43 +486,52 @@ public class VibratorManagerServiceTest {
setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1); setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1);
service = createSystemReadyService(); service = createSystemReadyService();
vibrate(service, VibrationEffect.createOneShot(1, 10), RINGTONE_ATTRS); vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK), RINGTONE_ATTRS);
assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 1, assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 1,
service, TEST_TIMEOUT_MILLIS)); service, TEST_TIMEOUT_MILLIS));
setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1); setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0); setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
service = createSystemReadyService(); service = createSystemReadyService();
vibrate(service, VibrationEffect.createOneShot(1, 100), RINGTONE_ATTRS); vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK), RINGTONE_ATTRS);
assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 2, assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 2,
service, TEST_TIMEOUT_MILLIS)); service, TEST_TIMEOUT_MILLIS));
assertEquals(Arrays.asList(10 / 255f, 100 / 255f), assertEquals(
mVibratorProviders.get(1).getAmplitudes()); Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_HEAVY_CLICK),
expectedPrebaked(VibrationEffect.EFFECT_DOUBLE_CLICK)),
mVibratorProviders.get(1).getEffectSegments());
} }
@Test @Test
public void vibrate_withPowerMode_usesPowerModeState() throws Exception { public void vibrate_withPowerMode_usesPowerModeState() throws Exception {
mockVibrators(1); mockVibrators(1);
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1); FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_TICK, VibrationEffect.EFFECT_CLICK,
VibrationEffect.EFFECT_HEAVY_CLICK, VibrationEffect.EFFECT_DOUBLE_CLICK);
VibratorManagerService service = createSystemReadyService(); VibratorManagerService service = createSystemReadyService();
mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE); mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
vibrate(service, VibrationEffect.createOneShot(1, 1), HAPTIC_FEEDBACK_ATTRS); vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), HAPTIC_FEEDBACK_ATTRS);
vibrate(service, VibrationEffect.createOneShot(2, 2), RINGTONE_ATTRS); vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 1, assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 1,
service, TEST_TIMEOUT_MILLIS)); service, TEST_TIMEOUT_MILLIS));
mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE); mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
vibrate(service, VibrationEffect.createOneShot(3, 3), /* attributes= */ null); vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK),
/* attrs= */ null);
assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 2, assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 2,
service, TEST_TIMEOUT_MILLIS)); service, TEST_TIMEOUT_MILLIS));
vibrate(service, VibrationEffect.createOneShot(4, 4), NOTIFICATION_ATTRS); vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK),
NOTIFICATION_ATTRS);
assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 3, assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 3,
service, TEST_TIMEOUT_MILLIS)); service, TEST_TIMEOUT_MILLIS));
assertEquals(Arrays.asList(2 / 255f, 3 / 255f, 4 / 255f), fakeVibrator.getAmplitudes()); assertEquals(
Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK),
expectedPrebaked(VibrationEffect.EFFECT_HEAVY_CLICK),
expectedPrebaked(VibrationEffect.EFFECT_DOUBLE_CLICK)),
mVibratorProviders.get(1).getEffectSegments());
} }
@Test @Test
@@ -836,7 +847,6 @@ public class VibratorManagerServiceTest {
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS); vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
assertEquals(4, fakeVibrator.getEffectSegments().size()); assertEquals(4, fakeVibrator.getEffectSegments().size());
assertEquals(1, fakeVibrator.getAmplitudes().size());
// Notification vibrations will be scaled with SCALE_VERY_HIGH. // Notification vibrations will be scaled with SCALE_VERY_HIGH.
assertTrue(0.6 < fakeVibrator.getAmplitudes().get(0)); assertTrue(0.6 < fakeVibrator.getAmplitudes().get(0));
@@ -957,6 +967,10 @@ public class VibratorManagerServiceTest {
assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
} }
private VibrationEffectSegment expectedPrebaked(int effectId) {
return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
}
private void mockCapabilities(long... capabilities) { private void mockCapabilities(long... capabilities) {
when(mNativeWrapperMock.getCapabilities()).thenReturn( when(mNativeWrapperMock.getCapabilities()).thenReturn(
Arrays.stream(capabilities).reduce(0, (a, b) -> a | b)); Arrays.stream(capabilities).reduce(0, (a, b) -> a | b));