Allow upcoming vibration when current one if being cancelled
The vibration cancellation at VibratorManagerService is asynchronous and might take some time before it sets the current Vibration status to the cancelled one requested. If a new binder call to vibrate arrived before the status is updated then the new vibration might be ignored for the current one, even though it's already being cancelled. Add a check to the cancel signal sent to the VibrationStepConductor before deciding to ignore new vibrations in favor of the ongoing. Bug: 249032223 Test: VibratorManagerServiceTest Change-Id: I8151d9ee0e30b7115e9cb3d6bcd272da2806b45c
This commit is contained in:
@@ -65,6 +65,9 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
|
|||||||
public final DeviceVibrationEffectAdapter deviceEffectAdapter;
|
public final DeviceVibrationEffectAdapter deviceEffectAdapter;
|
||||||
public final VibrationThread.VibratorManagerHooks vibratorManagerHooks;
|
public final VibrationThread.VibratorManagerHooks vibratorManagerHooks;
|
||||||
|
|
||||||
|
// Not guarded by lock because they're not modified by this conductor, it's used here only to
|
||||||
|
// check immutable attributes. The status and other mutable states are changed by the service or
|
||||||
|
// by the vibrator steps.
|
||||||
private final Vibration mVibration;
|
private final Vibration mVibration;
|
||||||
private final SparseArray<VibratorController> mVibrators = new SparseArray<>();
|
private final SparseArray<VibratorController> mVibrators = new SparseArray<>();
|
||||||
|
|
||||||
@@ -412,6 +415,16 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns true if a cancellation signal was sent via {@link #notifyCancelled}. */
|
||||||
|
public boolean wasNotifiedToCancel() {
|
||||||
|
if (Build.IS_DEBUGGABLE) {
|
||||||
|
expectIsVibrationThread(false);
|
||||||
|
}
|
||||||
|
synchronized (mLock) {
|
||||||
|
return mSignalCancel != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
private boolean hasPendingNotifySignalLocked() {
|
private boolean hasPendingNotifySignalLocked() {
|
||||||
if (Build.IS_DEBUGGABLE) {
|
if (Build.IS_DEBUGGABLE) {
|
||||||
|
|||||||
@@ -864,8 +864,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Vibration currentVibration = mCurrentVibration.getVibration();
|
Vibration currentVibration = mCurrentVibration.getVibration();
|
||||||
if (currentVibration.hasEnded()) {
|
if (currentVibration.hasEnded() || mCurrentVibration.wasNotifiedToCancel()) {
|
||||||
// Current vibration is finishing up, it should not block incoming vibrations.
|
// Current vibration has ended or is cancelling, should not block incoming vibrations.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ final class FakeVibratorControllerProvider {
|
|||||||
|
|
||||||
private boolean mIsAvailable = true;
|
private boolean mIsAvailable = true;
|
||||||
private boolean mIsInfoLoadSuccessful = true;
|
private boolean mIsInfoLoadSuccessful = true;
|
||||||
private long mLatency;
|
private long mOnLatency;
|
||||||
|
private long mOffLatency;
|
||||||
private int mOffCount;
|
private int mOffCount;
|
||||||
|
|
||||||
private int mCapabilities;
|
private int mCapabilities;
|
||||||
@@ -97,7 +98,7 @@ final class FakeVibratorControllerProvider {
|
|||||||
public long on(long milliseconds, long vibrationId) {
|
public long on(long milliseconds, long vibrationId) {
|
||||||
recordEffectSegment(vibrationId, new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE,
|
recordEffectSegment(vibrationId, new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE,
|
||||||
/* frequencyHz= */ 0, (int) milliseconds));
|
/* frequencyHz= */ 0, (int) milliseconds));
|
||||||
applyLatency();
|
applyLatency(mOnLatency);
|
||||||
scheduleListener(milliseconds, vibrationId);
|
scheduleListener(milliseconds, vibrationId);
|
||||||
return milliseconds;
|
return milliseconds;
|
||||||
}
|
}
|
||||||
@@ -105,12 +106,13 @@ final class FakeVibratorControllerProvider {
|
|||||||
@Override
|
@Override
|
||||||
public void off() {
|
public void off() {
|
||||||
mOffCount++;
|
mOffCount++;
|
||||||
|
applyLatency(mOffLatency);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setAmplitude(float amplitude) {
|
public void setAmplitude(float amplitude) {
|
||||||
mAmplitudes.add(amplitude);
|
mAmplitudes.add(amplitude);
|
||||||
applyLatency();
|
applyLatency(mOnLatency);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -121,7 +123,7 @@ final class FakeVibratorControllerProvider {
|
|||||||
}
|
}
|
||||||
recordEffectSegment(vibrationId,
|
recordEffectSegment(vibrationId,
|
||||||
new PrebakedSegment((int) effect, false, (int) strength));
|
new PrebakedSegment((int) effect, false, (int) strength));
|
||||||
applyLatency();
|
applyLatency(mOnLatency);
|
||||||
scheduleListener(EFFECT_DURATION, vibrationId);
|
scheduleListener(EFFECT_DURATION, vibrationId);
|
||||||
return EFFECT_DURATION;
|
return EFFECT_DURATION;
|
||||||
}
|
}
|
||||||
@@ -141,7 +143,7 @@ final class FakeVibratorControllerProvider {
|
|||||||
duration += EFFECT_DURATION + primitive.getDelay();
|
duration += EFFECT_DURATION + primitive.getDelay();
|
||||||
recordEffectSegment(vibrationId, primitive);
|
recordEffectSegment(vibrationId, primitive);
|
||||||
}
|
}
|
||||||
applyLatency();
|
applyLatency(mOnLatency);
|
||||||
scheduleListener(duration, vibrationId);
|
scheduleListener(duration, vibrationId);
|
||||||
return duration;
|
return duration;
|
||||||
}
|
}
|
||||||
@@ -154,7 +156,7 @@ final class FakeVibratorControllerProvider {
|
|||||||
recordEffectSegment(vibrationId, primitive);
|
recordEffectSegment(vibrationId, primitive);
|
||||||
}
|
}
|
||||||
recordBraking(vibrationId, braking);
|
recordBraking(vibrationId, braking);
|
||||||
applyLatency();
|
applyLatency(mOnLatency);
|
||||||
scheduleListener(duration, vibrationId);
|
scheduleListener(duration, vibrationId);
|
||||||
return duration;
|
return duration;
|
||||||
}
|
}
|
||||||
@@ -193,10 +195,10 @@ final class FakeVibratorControllerProvider {
|
|||||||
return mIsInfoLoadSuccessful;
|
return mIsInfoLoadSuccessful;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyLatency() {
|
private void applyLatency(long latencyMillis) {
|
||||||
try {
|
try {
|
||||||
if (mLatency > 0) {
|
if (latencyMillis > 0) {
|
||||||
Thread.sleep(mLatency);
|
Thread.sleep(latencyMillis);
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
}
|
}
|
||||||
@@ -240,10 +242,15 @@ final class FakeVibratorControllerProvider {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the latency this controller should fake for turning the vibrator hardware on or setting
|
* Sets the latency this controller should fake for turning the vibrator hardware on or setting
|
||||||
* it's vibration amplitude.
|
* the vibration amplitude.
|
||||||
*/
|
*/
|
||||||
public void setLatency(long millis) {
|
public void setOnLatency(long millis) {
|
||||||
mLatency = millis;
|
mOnLatency = millis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the latency this controller should fake for turning the vibrator off. */
|
||||||
|
public void setOffLatency(long millis) {
|
||||||
|
mOffLatency = millis;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Set the capabilities of the fake vibrator hardware. */
|
/** Set the capabilities of the fake vibrator hardware. */
|
||||||
|
|||||||
@@ -1159,7 +1159,7 @@ public class VibrationThreadTest {
|
|||||||
|
|
||||||
// 25% of the first waveform step will be spent on the native on() call.
|
// 25% of the first waveform step will be spent on the native on() call.
|
||||||
// 25% of each waveform step will be spent on the native setAmplitude() call..
|
// 25% of each waveform step will be spent on the native setAmplitude() call..
|
||||||
mVibratorProviders.get(VIBRATOR_ID).setLatency(stepDuration / 4);
|
mVibratorProviders.get(VIBRATOR_ID).setOnLatency(stepDuration / 4);
|
||||||
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
|
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
|
||||||
|
|
||||||
int stepCount = totalDuration / stepDuration;
|
int stepCount = totalDuration / stepDuration;
|
||||||
@@ -1190,7 +1190,7 @@ public class VibrationThreadTest {
|
|||||||
fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
|
fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
|
||||||
|
|
||||||
long latency = 5_000; // 5s
|
long latency = 5_000; // 5s
|
||||||
fakeVibrator.setLatency(latency);
|
fakeVibrator.setOnLatency(latency);
|
||||||
|
|
||||||
long vibrationId = 1;
|
long vibrationId = 1;
|
||||||
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
|
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
|
||||||
@@ -1204,8 +1204,7 @@ public class VibrationThreadTest {
|
|||||||
// fail at waitForCompletion(cancellingThread).
|
// fail at waitForCompletion(cancellingThread).
|
||||||
Thread cancellingThread = new Thread(
|
Thread cancellingThread = new Thread(
|
||||||
() -> conductor.notifyCancelled(
|
() -> conductor.notifyCancelled(
|
||||||
new Vibration.EndInfo(
|
new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
|
||||||
Vibration.Status.CANCELLED_BY_USER),
|
|
||||||
/* immediate= */ false));
|
/* immediate= */ false));
|
||||||
cancellingThread.start();
|
cancellingThread.start();
|
||||||
|
|
||||||
|
|||||||
@@ -826,12 +826,39 @@ public class VibratorManagerServiceTest {
|
|||||||
// The second vibration shouldn't have recorded that the vibrators were turned on.
|
// The second vibration shouldn't have recorded that the vibrators were turned on.
|
||||||
verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
|
verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
|
||||||
// No segment played is the prebaked CLICK from the second vibration.
|
// No segment played is the prebaked CLICK from the second vibration.
|
||||||
assertFalse(
|
assertFalse(mVibratorProviders.get(1).getAllEffectSegments().stream()
|
||||||
mVibratorProviders.get(1).getAllEffectSegments().stream()
|
.anyMatch(PrebakedSegment.class::isInstance));
|
||||||
.anyMatch(segment -> segment instanceof PrebakedSegment));
|
|
||||||
cancelVibrate(service); // Clean up repeating effect.
|
cancelVibrate(service); // Clean up repeating effect.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void vibrate_withOngoingRepeatingVibrationBeingCancelled_playsAfterPreviousIsCancelled()
|
||||||
|
throws Exception {
|
||||||
|
mockVibrators(1);
|
||||||
|
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
|
||||||
|
fakeVibrator.setOffLatency(50); // Add latency so cancellation is slow.
|
||||||
|
fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
|
||||||
|
fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
|
||||||
|
VibratorManagerService service = createSystemReadyService();
|
||||||
|
|
||||||
|
VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
|
||||||
|
new long[]{10, 10_000}, new int[]{255, 0}, 1);
|
||||||
|
vibrate(service, repeatingEffect, ALARM_ATTRS);
|
||||||
|
|
||||||
|
// VibrationThread will start this vibration async, wait until the off waveform step.
|
||||||
|
assertTrue(waitUntil(s -> fakeVibrator.getOffCount() > 0, service, TEST_TIMEOUT_MILLIS));
|
||||||
|
|
||||||
|
// Cancel vibration right before requesting a new one.
|
||||||
|
// This should trigger slow IVibrator.off before setting the vibration status to cancelled.
|
||||||
|
cancelVibrate(service);
|
||||||
|
vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
|
||||||
|
ALARM_ATTRS);
|
||||||
|
|
||||||
|
// Check that second vibration was played.
|
||||||
|
assertTrue(fakeVibrator.getAllEffectSegments().stream()
|
||||||
|
.anyMatch(PrebakedSegment.class::isInstance));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void vibrate_withNewRepeatingVibration_cancelsOngoingEffect() throws Exception {
|
public void vibrate_withNewRepeatingVibration_cancelsOngoingEffect() throws Exception {
|
||||||
mockVibrators(1);
|
mockVibrators(1);
|
||||||
@@ -880,10 +907,8 @@ public class VibratorManagerServiceTest {
|
|||||||
// The second vibration shouldn't have recorded that the vibrators were turned on.
|
// The second vibration shouldn't have recorded that the vibrators were turned on.
|
||||||
verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
|
verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
|
||||||
// The second vibration shouldn't have played any prebaked segment.
|
// The second vibration shouldn't have played any prebaked segment.
|
||||||
assertFalse(
|
assertFalse(mVibratorProviders.get(1).getAllEffectSegments().stream()
|
||||||
mVibratorProviders.get(1).getAllEffectSegments().stream()
|
.anyMatch(PrebakedSegment.class::isInstance));
|
||||||
.anyMatch(segment -> segment instanceof PrebakedSegment));
|
|
||||||
|
|
||||||
cancelVibrate(service); // Clean up long effect.
|
cancelVibrate(service); // Clean up long effect.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user