Merge "Fix VibrationThread with repeating waveforms of positive amplitudes" into sc-dev am: 9ab91ae6f4
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15245532 Change-Id: I28e6638db65fad4644a8f2007f7796ac52a8db37
This commit is contained in:
@@ -1294,21 +1294,32 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
|
|||||||
@Override
|
@Override
|
||||||
public boolean shouldPlayWhenVibratorComplete(int vibratorId) {
|
public boolean shouldPlayWhenVibratorComplete(int vibratorId) {
|
||||||
if (controller.getVibratorInfo().getId() == vibratorId) {
|
if (controller.getVibratorInfo().getId() == vibratorId) {
|
||||||
|
mVibratorCallbackReceived = true;
|
||||||
mNextOffTime = SystemClock.uptimeMillis();
|
mNextOffTime = SystemClock.uptimeMillis();
|
||||||
}
|
}
|
||||||
// Timings are tightly controlled here, so never anticipate when vibrator is complete.
|
// Timings are tightly controlled here, so only anticipate if the vibrator was supposed
|
||||||
return false;
|
// to be ON but has completed prematurely, to turn it back on as soon as possible.
|
||||||
|
return mNextOffTime < startTime && controller.getCurrentAmplitude() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Step> play() {
|
public List<Step> play() {
|
||||||
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "AmplitudeStep");
|
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "AmplitudeStep");
|
||||||
try {
|
try {
|
||||||
|
long now = SystemClock.uptimeMillis();
|
||||||
|
long latency = now - startTime;
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
long latency = SystemClock.uptimeMillis() - startTime;
|
|
||||||
Slog.d(TAG, "Running amplitude step with " + latency + "ms latency.");
|
Slog.d(TAG, "Running amplitude step with " + latency + "ms latency.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mVibratorCallbackReceived && latency < 0) {
|
||||||
|
// This step was anticipated because the vibrator turned off prematurely.
|
||||||
|
// Turn it back on and return this same step to run at the exact right time.
|
||||||
|
mNextOffTime = turnVibratorBackOn(/* remainingDuration= */ -latency);
|
||||||
|
return Arrays.asList(new AmplitudeStep(startTime, controller, effect,
|
||||||
|
segmentIndex, mNextOffTime));
|
||||||
|
}
|
||||||
|
|
||||||
VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
|
VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
|
||||||
if (!(segment instanceof StepSegment)) {
|
if (!(segment instanceof StepSegment)) {
|
||||||
Slog.w(TAG, "Ignoring wrong segment for a AmplitudeStep: " + segment);
|
Slog.w(TAG, "Ignoring wrong segment for a AmplitudeStep: " + segment);
|
||||||
@@ -1321,17 +1332,16 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
|
|||||||
return skipToNextSteps(/* segmentsSkipped= */ 1);
|
return skipToNextSteps(/* segmentsSkipped= */ 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
long now = SystemClock.uptimeMillis();
|
|
||||||
float amplitude = stepSegment.getAmplitude();
|
float amplitude = stepSegment.getAmplitude();
|
||||||
if (amplitude == 0) {
|
if (amplitude == 0) {
|
||||||
if (mNextOffTime > now) {
|
if (vibratorOffTimeout > now) {
|
||||||
// Amplitude cannot be set to zero, so stop the vibrator.
|
// Amplitude cannot be set to zero, so stop the vibrator.
|
||||||
stopVibrating();
|
stopVibrating();
|
||||||
mNextOffTime = now;
|
mNextOffTime = now;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (startTime >= mNextOffTime) {
|
if (startTime >= mNextOffTime) {
|
||||||
// Vibrator has stopped. Turn vibrator back on for the duration of another
|
// Vibrator is OFF. Turn vibrator back on for the duration of another
|
||||||
// cycle before setting the amplitude.
|
// cycle before setting the amplitude.
|
||||||
long onDuration = getVibratorOnDuration(effect, segmentIndex);
|
long onDuration = getVibratorOnDuration(effect, segmentIndex);
|
||||||
if (onDuration > 0) {
|
if (onDuration > 0) {
|
||||||
@@ -1350,6 +1360,22 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private long turnVibratorBackOn(long remainingDuration) {
|
||||||
|
long onDuration = getVibratorOnDuration(effect, segmentIndex);
|
||||||
|
if (onDuration <= 0) {
|
||||||
|
// Vibrator is supposed to go back off when this step starts, so just leave it off.
|
||||||
|
return vibratorOffTimeout;
|
||||||
|
}
|
||||||
|
onDuration += remainingDuration;
|
||||||
|
float expectedAmplitude = controller.getCurrentAmplitude();
|
||||||
|
mVibratorOnResult = startVibrating(onDuration);
|
||||||
|
if (mVibratorOnResult > 0) {
|
||||||
|
// Set the amplitude back to the value it was supposed to be playing at.
|
||||||
|
changeAmplitude(expectedAmplitude);
|
||||||
|
}
|
||||||
|
return SystemClock.uptimeMillis() + onDuration + CALLBACKS_EXTRA_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
private long startVibrating(long duration) {
|
private long startVibrating(long duration) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Slog.d(TAG, "Turning on vibrator " + controller.getVibratorInfo().getId() + " for "
|
Slog.d(TAG, "Turning on vibrator " + controller.getVibratorInfo().getId() + " for "
|
||||||
@@ -1383,7 +1409,10 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
|
|||||||
repeatIndex = -1;
|
repeatIndex = -1;
|
||||||
}
|
}
|
||||||
if (i == startIndex) {
|
if (i == startIndex) {
|
||||||
return 1000;
|
// The repeating waveform keeps the vibrator ON all the time. Use a minimum
|
||||||
|
// of 1s duration to prevent short patterns from turning the vibrator ON too
|
||||||
|
// frequently.
|
||||||
|
return Math.max(timing, 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (i == segmentCount && effect.getRepeatIndex() < 0) {
|
if (i == segmentCount && effect.getRepeatIndex() < 0) {
|
||||||
|
|||||||
@@ -245,6 +245,81 @@ public class VibrationThreadTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void vibrate_singleVibratorRepeatingShortAlwaysOnWaveform_turnsVibratorOnForASecond()
|
||||||
|
throws Exception {
|
||||||
|
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
|
||||||
|
fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
|
||||||
|
|
||||||
|
long vibrationId = 1;
|
||||||
|
int[] amplitudes = new int[]{1, 2, 3};
|
||||||
|
VibrationEffect effect = VibrationEffect.createWaveform(
|
||||||
|
new long[]{1, 10, 100}, amplitudes, 0);
|
||||||
|
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
|
||||||
|
|
||||||
|
assertTrue(waitUntil(t -> !fakeVibrator.getAmplitudes().isEmpty(), thread,
|
||||||
|
TEST_TIMEOUT_MILLIS));
|
||||||
|
thread.cancel();
|
||||||
|
waitForCompletion(thread);
|
||||||
|
|
||||||
|
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
|
||||||
|
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
|
||||||
|
assertEquals(Arrays.asList(expectedOneShot(1000)), fakeVibrator.getEffectSegments());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void vibrate_singleVibratorRepeatingLongAlwaysOnWaveform_turnsVibratorOnForACycle()
|
||||||
|
throws Exception {
|
||||||
|
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
|
||||||
|
fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
|
||||||
|
|
||||||
|
long vibrationId = 1;
|
||||||
|
int[] amplitudes = new int[]{1, 2, 3};
|
||||||
|
VibrationEffect effect = VibrationEffect.createWaveform(
|
||||||
|
new long[]{5000, 500, 50}, amplitudes, 0);
|
||||||
|
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
|
||||||
|
|
||||||
|
assertTrue(waitUntil(t -> !fakeVibrator.getAmplitudes().isEmpty(), thread,
|
||||||
|
TEST_TIMEOUT_MILLIS));
|
||||||
|
thread.cancel();
|
||||||
|
waitForCompletion(thread);
|
||||||
|
|
||||||
|
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
|
||||||
|
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
|
||||||
|
assertEquals(Arrays.asList(expectedOneShot(5550)), fakeVibrator.getEffectSegments());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void vibrate_singleVibratorRepeatingAlwaysOnWaveform_turnsVibratorBackOn()
|
||||||
|
throws Exception {
|
||||||
|
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
|
||||||
|
fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
|
||||||
|
|
||||||
|
long vibrationId = 1;
|
||||||
|
int[] amplitudes = new int[]{1, 2};
|
||||||
|
VibrationEffect effect = VibrationEffect.createWaveform(
|
||||||
|
new long[]{900, 50}, amplitudes, 0);
|
||||||
|
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
|
||||||
|
|
||||||
|
assertTrue(waitUntil(t -> fakeVibrator.getAmplitudes().size() > 2 * amplitudes.length,
|
||||||
|
thread, 1000 + TEST_TIMEOUT_MILLIS));
|
||||||
|
thread.cancel();
|
||||||
|
waitForCompletion(thread);
|
||||||
|
|
||||||
|
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
|
||||||
|
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
|
||||||
|
assertEquals(2, fakeVibrator.getEffectSegments().size());
|
||||||
|
// First time turn vibrator ON for minimum of 1s.
|
||||||
|
assertEquals(1000L, fakeVibrator.getEffectSegments().get(0).getDuration());
|
||||||
|
// Vibrator turns off in the middle of the second execution of first step, turn it back ON
|
||||||
|
// for another 1s + remaining of 850ms.
|
||||||
|
assertEquals(1850, fakeVibrator.getEffectSegments().get(1).getDuration(), /* delta= */ 20);
|
||||||
|
// Set amplitudes for a cycle {1, 2}, start second loop then turn it back on to same value.
|
||||||
|
assertEquals(expectedAmplitudes(1, 2, 1, 1),
|
||||||
|
mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().subList(0, 4));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void vibrate_singleVibratorPredefinedCancel_cancelsVibrationImmediately()
|
public void vibrate_singleVibratorPredefinedCancel_cancelsVibrationImmediately()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
|||||||
Reference in New Issue
Block a user