Merge "Cancel current vibration asynchronously in VibratorService" into sc-dev

This commit is contained in:
TreeHugger Robot
2021-01-28 11:37:40 +00:00
committed by Android (Google) Code Review
2 changed files with 144 additions and 88 deletions

View File

@@ -102,7 +102,10 @@ public class VibratorService extends IVibratorService.Stub {
private VibrationScaler mVibrationScaler; private VibrationScaler mVibrationScaler;
private InputDeviceDelegate mInputDeviceDelegate; private InputDeviceDelegate mInputDeviceDelegate;
private volatile VibrationThread mThread; @GuardedBy("mLock")
private VibrationThread mThread;
@GuardedBy("mLock")
private VibrationThread mNextVibrationThread;
@GuardedBy("mLock") @GuardedBy("mLock")
private Vibration mCurrentVibration; private Vibration mCurrentVibration;
@@ -132,6 +135,10 @@ public class VibratorService extends IVibratorService.Stub {
if (mCurrentVibration != null && mCurrentVibration.id == vibrationId) { if (mCurrentVibration != null && mCurrentVibration.id == vibrationId) {
mThread = null; mThread = null;
reportFinishVibrationLocked(status); reportFinishVibrationLocked(status);
if (mNextVibrationThread != null) {
startVibrationThreadLocked(mNextVibrationThread);
mNextVibrationThread = null;
}
} }
} }
} }
@@ -258,18 +265,14 @@ public class VibratorService extends IVibratorService.Stub {
@VisibleForTesting @VisibleForTesting
public void onVibrationComplete(int vibratorId, long vibrationId) { public void onVibrationComplete(int vibratorId, long vibrationId) {
synchronized (mLock) { synchronized (mLock) {
if (mCurrentVibration != null && mCurrentVibration.id == vibrationId) { if (mCurrentVibration != null && mCurrentVibration.id == vibrationId
&& mThread != null) {
if (DEBUG) { if (DEBUG) {
Slog.d(TAG, "Vibration onComplete callback, notifying VibrationThread"); Slog.d(TAG, "Vibration onComplete callback, notifying VibrationThread");
} }
if (mThread != null) { // Let the thread playing the vibration handle the callback, since it might be
// Let the thread playing the vibration handle the callback, since it might be // expecting the vibrator to turn off multiple times during a single vibration.
// expecting the vibrator to turn off multiple times during a single vibration. mThread.vibratorComplete(vibratorId);
mThread.vibratorComplete(vibratorId);
} else {
// No vibration is playing in the thread, but clean up service just in case.
doCancelVibrateLocked(Vibration.Status.FINISHED);
}
} }
} }
} }
@@ -462,8 +465,10 @@ public class VibratorService extends IVibratorService.Stub {
try { try {
doCancelVibrateLocked(Vibration.Status.CANCELLED); doCancelVibrateLocked(Vibration.Status.CANCELLED);
startVibrationLocked(vib); startVibrationLocked(vib);
boolean isNextVibration = mNextVibrationThread != null
&& vib.equals(mNextVibrationThread.getVibration());
if (!vib.hasEnded() && mCurrentVibration.id != vib.id) { if (!vib.hasEnded() && !vib.equals(mCurrentVibration) && !isNextVibration) {
// Vibration was unexpectedly ignored: add to list for debugging // Vibration was unexpectedly ignored: add to list for debugging
endVibrationLocked(vib, Vibration.Status.IGNORED); endVibrationLocked(vib, Vibration.Status.IGNORED);
} }
@@ -532,6 +537,7 @@ public class VibratorService extends IVibratorService.Stub {
} }
final long ident = Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity();
try { try {
mNextVibrationThread = null;
doCancelVibrateLocked(Vibration.Status.CANCELLED); doCancelVibrateLocked(Vibration.Status.CANCELLED);
} finally { } finally {
Binder.restoreCallingIdentity(ident); Binder.restoreCallingIdentity(ident);
@@ -546,16 +552,14 @@ public class VibratorService extends IVibratorService.Stub {
try { try {
if (mThread != null) { if (mThread != null) {
mThread.cancel(); mThread.cancel();
mThread = null;
} }
mInputDeviceDelegate.cancelVibrateIfAvailable();
if (mCurrentExternalVibration != null) { if (mCurrentExternalVibration != null) {
endVibrationLocked(mCurrentExternalVibration, status); endVibrationLocked(mCurrentExternalVibration, status);
mCurrentExternalVibration.externalVibration.mute(); mCurrentExternalVibration.externalVibration.mute();
mCurrentExternalVibration = null; mCurrentExternalVibration = null;
mVibratorController.setExternalControl(false); mVibratorController.setExternalControl(false);
} }
doVibratorOff();
reportFinishVibrationLocked(status);
} finally { } finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
} }
@@ -579,28 +583,30 @@ public class VibratorService extends IVibratorService.Stub {
private void startVibrationInnerLocked(Vibration vib) { private void startVibrationInnerLocked(Vibration vib) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationInnerLocked"); Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationInnerLocked");
try { try {
// Set current vibration before starting it, so callback will work.
mCurrentVibration = vib;
VibrationEffect effect = getEffect(vib);
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable( boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
vib.uid, vib.opPkg, vib.getEffect(), vib.reason, vib.attrs); vib.uid, vib.opPkg, vib.getEffect(), vib.reason, vib.attrs);
if (inputDevicesAvailable) { if (inputDevicesAvailable) {
// The set current vibration is no longer being played by this service, so drop it.
mCurrentVibration = null;
endVibrationLocked(vib, Vibration.Status.FORWARDED_TO_INPUT_DEVICES); endVibrationLocked(vib, Vibration.Status.FORWARDED_TO_INPUT_DEVICES);
} else if (mThread == null) {
startVibrationThreadLocked(new VibrationThread(vib, mVibratorController, mWakeLock,
mBatteryStatsService, mVibrationCallbacks));
} else { } else {
// mThread better be null here. doCancelVibrate should always be mNextVibrationThread = new VibrationThread(vib, mVibratorController, mWakeLock,
// called before startVibrationInnerLocked
mThread = new VibrationThread(vib, mVibratorController, mWakeLock,
mBatteryStatsService, mVibrationCallbacks); mBatteryStatsService, mVibrationCallbacks);
mThread.start();
} }
} finally { } finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
} }
} }
@GuardedBy("mLock")
private void startVibrationThreadLocked(VibrationThread thread) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
mCurrentVibration = thread.getVibration();
mThread = thread;
mThread.start();
}
/** Scale the vibration effect by the intensity as appropriate based its intent. */ /** Scale the vibration effect by the intensity as appropriate based its intent. */
private void applyVibrationIntensityScalingLocked(Vibration vib) { private void applyVibrationIntensityScalingLocked(Vibration vib) {
vib.updateEffect(mVibrationScaler.scale(vib.getEffect(), vib.attrs.getUsage())); vib.updateEffect(mVibrationScaler.scale(vib.getEffect(), vib.attrs.getUsage()));
@@ -665,13 +671,14 @@ public class VibratorService extends IVibratorService.Stub {
@GuardedBy("mLock") @GuardedBy("mLock")
private void reportFinishVibrationLocked(Vibration.Status status) { private void reportFinishVibrationLocked(Vibration.Status status) {
Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked"); Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
try { try {
if (mCurrentVibration != null) { if (mCurrentVibration != null) {
endVibrationLocked(mCurrentVibration, status); endVibrationLocked(mCurrentVibration, status);
mAppOps.finishOp(AppOpsManager.OP_VIBRATE, mCurrentVibration.uid, mAppOps.finishOp(AppOpsManager.OP_VIBRATE, mCurrentVibration.uid,
mCurrentVibration.opPkg); mCurrentVibration.opPkg);
Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
mCurrentVibration = null; mCurrentVibration = null;
} }
} finally { } finally {
@@ -697,21 +704,6 @@ public class VibratorService extends IVibratorService.Stub {
} }
} }
private void doVibratorOff() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorOff");
try {
if (DEBUG) {
Slog.d(TAG, "Turning vibrator off.");
}
boolean inputDevicesAvailable = mInputDeviceDelegate.cancelVibrateIfAvailable();
if (!inputDevicesAvailable) {
mVibratorController.off();
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
}
private boolean isSystemHapticFeedback(Vibration vib) { private boolean isSystemHapticFeedback(Vibration vib) {
if (vib.attrs.getUsage() != VibrationAttributes.USAGE_TOUCH) { if (vib.attrs.getUsage() != VibrationAttributes.USAGE_TOUCH) {
return false; return false;
@@ -844,6 +836,7 @@ public class VibratorService extends IVibratorService.Stub {
// haptic feedback as part of the transition. So we don't cancel // haptic feedback as part of the transition. So we don't cancel
// system vibrations. // system vibrations.
if (mCurrentVibration != null && !isSystemHapticFeedback(mCurrentVibration)) { if (mCurrentVibration != null && !isSystemHapticFeedback(mCurrentVibration)) {
mNextVibrationThread = null;
doCancelVibrateLocked(Vibration.Status.CANCELLED); doCancelVibrateLocked(Vibration.Status.CANCELLED);
} }
} }
@@ -910,6 +903,8 @@ public class VibratorService extends IVibratorService.Stub {
return IExternalVibratorService.SCALE_MUTE; return IExternalVibratorService.SCALE_MUTE;
} }
VibrationThread cancelingVibration = null;
int scale;
synchronized (mLock) { synchronized (mLock) {
if (mCurrentExternalVibration != null if (mCurrentExternalVibration != null
&& mCurrentExternalVibration.externalVibration.equals(vib)) { && mCurrentExternalVibration.externalVibration.equals(vib)) {
@@ -920,11 +915,9 @@ public class VibratorService extends IVibratorService.Stub {
if (mCurrentExternalVibration == null) { if (mCurrentExternalVibration == null) {
// If we're not under external control right now, then cancel any normal // If we're not under external control right now, then cancel any normal
// 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 (DEBUG) { mNextVibrationThread = null;
Slog.d(TAG, "Vibrator going under external control.");
}
doCancelVibrateLocked(Vibration.Status.CANCELLED); doCancelVibrateLocked(Vibration.Status.CANCELLED);
mVibratorController.setExternalControl(true); cancelingVibration = mThread;
} else { } else {
endVibrationLocked(mCurrentExternalVibration, Vibration.Status.CANCELLED); endVibrationLocked(mCurrentExternalVibration, Vibration.Status.CANCELLED);
} }
@@ -941,11 +934,24 @@ public class VibratorService extends IVibratorService.Stub {
vib.linkToDeath(mCurrentExternalDeathRecipient); vib.linkToDeath(mCurrentExternalDeathRecipient);
mCurrentExternalVibration.scale = mVibrationScaler.getExternalVibrationScale( mCurrentExternalVibration.scale = mVibrationScaler.getExternalVibrationScale(
vib.getVibrationAttributes().getUsage()); vib.getVibrationAttributes().getUsage());
if (DEBUG) { scale = mCurrentExternalVibration.scale;
Slog.e(TAG, "Playing external vibration: " + vib);
}
return mCurrentExternalVibration.scale;
} }
if (cancelingVibration != null) {
try {
cancelingVibration.join();
} catch (InterruptedException e) {
Slog.w("Interrupted while waiting current vibration to be cancelled before "
+ "starting external vibration", e);
}
}
if (DEBUG) {
Slog.d(TAG, "Vibrator going under external control.");
}
mVibratorController.setExternalControl(true);
if (DEBUG) {
Slog.e(TAG, "Playing external vibration: " + vib);
}
return scale;
} }
@Override @Override

View File

@@ -74,6 +74,7 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
void onVibrationEnded(long vibrationId, Vibration.Status status); void onVibrationEnded(long vibrationId, Vibration.Status status);
} }
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;
@@ -81,10 +82,10 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
private final VibrationCallbacks mCallbacks; private final VibrationCallbacks mCallbacks;
private final SparseArray<VibratorController> mVibrators; private final SparseArray<VibratorController> mVibrators;
@GuardedBy("this") @GuardedBy("mLock")
@Nullable @Nullable
private VibrateStep mCurrentVibrateStep; private VibrateStep mCurrentVibrateStep;
@GuardedBy("this") @GuardedBy("mLock")
private boolean mForceStop; private boolean mForceStop;
// TODO(b/159207608): Remove this constructor once VibratorService is removed // TODO(b/159207608): Remove this constructor once VibratorService is removed
@@ -113,6 +114,10 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
} }
} }
public Vibration getVibration() {
return mVibration;
}
@Override @Override
public void binderDied() { public void binderDied() {
cancel(); cancel();
@@ -136,15 +141,15 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
/** Cancel current vibration and shuts down the thread gracefully. */ /** Cancel current vibration and shuts down the thread gracefully. */
public void cancel() { public void cancel() {
synchronized (this) { synchronized (mLock) {
mForceStop = true; mForceStop = true;
notify(); mLock.notify();
} }
} }
/** Notify current vibration that a step has completed on given vibrator. */ /** Notify current vibration that a step has completed on given vibrator. */
public void vibratorComplete(int vibratorId) { public void vibratorComplete(int vibratorId) {
synchronized (this) { synchronized (mLock) {
if (mCurrentVibrateStep != null) { if (mCurrentVibrateStep != null) {
mCurrentVibrateStep.vibratorComplete(vibratorId); mCurrentVibrateStep.vibratorComplete(vibratorId);
} }
@@ -168,7 +173,7 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
final int stepCount = steps.size(); final int stepCount = steps.size();
for (int i = 0; i < stepCount; i++) { for (int i = 0; i < stepCount; i++) {
Step step = steps.get(i); Step step = steps.get(i);
synchronized (this) { synchronized (mLock) {
if (step instanceof VibrateStep) { if (step instanceof VibrateStep) {
mCurrentVibrateStep = (VibrateStep) step; mCurrentVibrateStep = (VibrateStep) step;
} else { } else {
@@ -295,21 +300,48 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
* Sleeps until given {@code wakeUpTime}. * Sleeps until given {@code wakeUpTime}.
* *
* <p>This stops immediately when {@link #cancel()} is called. * <p>This stops immediately when {@link #cancel()} is called.
*
* @return true if waited until wake-up time, false if it was cancelled.
*/ */
private void waitUntil(long wakeUpTime) { private boolean waitUntil(long wakeUpTime) {
synchronized (this) { synchronized (mLock) {
long durationRemaining = wakeUpTime - SystemClock.uptimeMillis(); long durationRemaining = wakeUpTime - SystemClock.uptimeMillis();
while (durationRemaining > 0) { while (durationRemaining > 0) {
try { try {
VibrationThread.this.wait(durationRemaining); mLock.wait(durationRemaining);
} catch (InterruptedException e) { } catch (InterruptedException e) {
} }
if (mForceStop) { if (mForceStop) {
break; return false;
} }
durationRemaining = wakeUpTime - SystemClock.uptimeMillis(); durationRemaining = wakeUpTime - SystemClock.uptimeMillis();
} }
} }
return true;
}
/**
* Sleeps until given {@link VibrateStep#isVibrationComplete()}, or until {@code wakeUpTime}.
*
* <p>This stops immediately when {@link #cancel()} is called.
*
* @return true if finished on vibration complete, false if it was cancelled or timed out.
*/
private boolean waitForVibrationComplete(VibrateStep step, long wakeUpTime) {
synchronized (mLock) {
long durationRemaining = wakeUpTime - SystemClock.uptimeMillis();
while (!step.isVibrationComplete() && durationRemaining > 0) {
try {
mLock.wait(durationRemaining);
} catch (InterruptedException e) {
}
if (mForceStop) {
return false;
}
durationRemaining = wakeUpTime - SystemClock.uptimeMillis();
}
}
return step.isVibrationComplete();
} }
private void noteVibratorOn(long duration) { private void noteVibratorOn(long duration) {
@@ -341,6 +373,9 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
private interface VibrateStep extends Step { private interface VibrateStep extends Step {
/** Callback to notify a vibrator has finished playing a effect. */ /** Callback to notify a vibrator has finished playing a effect. */
void vibratorComplete(int vibratorId); void vibratorComplete(int vibratorId);
/** Returns true if the vibration played by this step is complete. */
boolean isVibrationComplete();
} }
/** Represent a vibration on a single vibrator. */ /** Represent a vibration on a single vibrator. */
@@ -348,11 +383,20 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
private final VibratorController mVibrator; private final VibratorController mVibrator;
private final VibrationEffect mEffect; private final VibrationEffect mEffect;
@GuardedBy("mLock")
private boolean mVibrationComplete;
SingleVibrateStep(VibratorController vibrator, VibrationEffect effect) { SingleVibrateStep(VibratorController vibrator, VibrationEffect effect) {
mVibrator = vibrator; mVibrator = vibrator;
mEffect = effect; mEffect = effect;
} }
@GuardedBy("mLock")
@Override
public boolean isVibrationComplete() {
return mVibrationComplete;
}
@Override @Override
public void vibratorComplete(int vibratorId) { public void vibratorComplete(int vibratorId) {
if (mVibrator.getVibratorInfo().getId() != vibratorId) { if (mVibrator.getVibratorInfo().getId() != vibratorId) {
@@ -364,8 +408,9 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
return; return;
} }
mVibrator.off(); mVibrator.off();
synchronized (VibrationThread.this) { synchronized (mLock) {
VibrationThread.this.notify(); mVibrationComplete = true;
mLock.notify();
} }
} }
@@ -384,12 +429,13 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
noteVibratorOn(duration); noteVibratorOn(duration);
// Vibration is playing with no need to control amplitudes, just wait for native // Vibration is playing with no need to control amplitudes, just wait for native
// callback or timeout. // callback or timeout.
waitUntil(startTime + duration + CALLBACKS_EXTRA_TIMEOUT); if (waitForVibrationComplete(this,
if (mForceStop) { startTime + duration + CALLBACKS_EXTRA_TIMEOUT)) {
mVibrator.off(); return Vibration.Status.FINISHED;
return Vibration.Status.CANCELLED;
} }
return Vibration.Status.FINISHED; // Timed out or vibration cancelled. Stop vibrator anyway.
mVibrator.off();
return mForceStop ? Vibration.Status.CANCELLED : Vibration.Status.FINISHED;
} }
startTime = SystemClock.uptimeMillis(); startTime = SystemClock.uptimeMillis();
@@ -407,8 +453,7 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
noteVibratorOn(duration); noteVibratorOn(duration);
} }
while (amplitudeStep != null) { while (amplitudeStep != null) {
waitUntil(amplitudeStep.startTime); if (!waitUntil(amplitudeStep.startTime)) {
if (mForceStop) {
mVibrator.off(); mVibrator.off();
return Vibration.Status.CANCELLED; return Vibration.Status.CANCELLED;
} }
@@ -482,7 +527,7 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
private final int mRequiredCapabilities; private final int mRequiredCapabilities;
private final int[] mVibratorIds; private final int[] mVibratorIds;
@GuardedBy("VibrationThread.this") @GuardedBy("mLock")
private int mActiveVibratorCounter; private int mActiveVibratorCounter;
SyncedVibrateStep(SparseArray<VibrationEffect> effects) { SyncedVibrateStep(SparseArray<VibrationEffect> effects) {
@@ -496,6 +541,12 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
} }
} }
@GuardedBy("mLock")
@Override
public boolean isVibrationComplete() {
return mActiveVibratorCounter <= 0;
}
@Override @Override
public void vibratorComplete(int vibratorId) { public void vibratorComplete(int vibratorId) {
VibrationEffect effect = mEffects.get(vibratorId); VibrationEffect effect = mEffects.get(vibratorId);
@@ -508,10 +559,9 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
return; return;
} }
mVibrators.get(vibratorId).off(); mVibrators.get(vibratorId).off();
synchronized (VibrationThread.this) { synchronized (mLock) {
if (--mActiveVibratorCounter <= 0) { --mActiveVibratorCounter;
VibrationThread.this.notify(); mLock.notify();
}
} }
} }
@@ -532,8 +582,7 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
while (!nextSteps.isEmpty()) { while (!nextSteps.isEmpty()) {
AmplitudeStep step = nextSteps.poll(); AmplitudeStep step = nextSteps.poll();
waitUntil(step.startTime); if (!waitUntil(step.startTime)) {
if (mForceStop) {
stopAllVibrators(); stopAllVibrators();
return Vibration.Status.CANCELLED; return Vibration.Status.CANCELLED;
} }
@@ -541,7 +590,7 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
AmplitudeStep nextStep = step.nextStep(); AmplitudeStep nextStep = step.nextStep();
if (nextStep == null) { if (nextStep == null) {
// This vibrator has finished playing the effect for this step. // This vibrator has finished playing the effect for this step.
synchronized (VibrationThread.this) { synchronized (mLock) {
mActiveVibratorCounter--; mActiveVibratorCounter--;
} }
} else { } else {
@@ -549,19 +598,18 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
} }
} }
// All OneShot and Waveform effects have finished. Just wait for the other effects synchronized (mLock) {
// to end via native callbacks before finishing this synced step. // All OneShot and Waveform effects have finished. Just wait for the other
synchronized (VibrationThread.this) { // effects to end via native callbacks before finishing this synced step.
if (mActiveVibratorCounter > 0) { final long wakeUpTime = startTime + timeout + CALLBACKS_EXTRA_TIMEOUT;
waitUntil(startTime + timeout + CALLBACKS_EXTRA_TIMEOUT); if (mActiveVibratorCounter <= 0 || waitForVibrationComplete(this, wakeUpTime)) {
return Vibration.Status.FINISHED;
} }
}
if (mForceStop) {
stopAllVibrators();
return Vibration.Status.CANCELLED;
}
return Vibration.Status.FINISHED; // Timed out or vibration cancelled. Stop all vibrators anyway.
stopAllVibrators();
return mForceStop ? Vibration.Status.CANCELLED : Vibration.Status.FINISHED;
}
} finally { } finally {
if (timeout > 0) { if (timeout > 0) {
noteVibratorOff(); noteVibratorOff();
@@ -774,8 +822,10 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi
if (DEBUG) { if (DEBUG) {
Slog.d(TAG, "DelayStep of " + mDelay + "ms starting..."); Slog.d(TAG, "DelayStep of " + mDelay + "ms starting...");
} }
waitUntil(SystemClock.uptimeMillis() + mDelay); if (waitUntil(SystemClock.uptimeMillis() + mDelay)) {
return mForceStop ? Vibration.Status.CANCELLED : Vibration.Status.FINISHED; return Vibration.Status.FINISHED;
}
return Vibration.Status.CANCELLED;
} finally { } finally {
if (DEBUG) { if (DEBUG) {
Slog.d(TAG, "DelayStep done."); Slog.d(TAG, "DelayStep done.");