blob: 0551bfc70bdade32f3d4e3085eddcccf5727e920 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.vibrator;
import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManagerInternal;
import android.hardware.vibrator.Braking;
import android.hardware.vibrator.IVibrator;
import android.hardware.vibrator.IVibratorManager;
import android.os.CombinedVibration;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.Process;
import android.os.SystemClock;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.test.TestLooper;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
import android.platform.test.annotations.LargeTest;
import android.platform.test.annotations.Presubmit;
import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
import com.android.server.LocalServices;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.time.Duration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;
/**
* Tests for {@link VibrationThread}.
*
* Build/Install/Run:
* atest FrameworksServicesTests:VibrationThreadTest
*/
@Presubmit
public class VibrationThreadTest {
private static final int TEST_TIMEOUT_MILLIS = 900;
private static final int UID = Process.ROOT_UID;
private static final int DISPLAY_ID = 10;
private static final int VIBRATOR_ID = 1;
private static final String PACKAGE_NAME = "package";
private static final VibrationAttributes ATTRS = new VibrationAttributes.Builder().build();
private static final int TEST_RAMP_STEP_DURATION = 5;
@Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
@Mock private PackageManagerInternal mPackageManagerInternalMock;
@Mock private VibrationThread.VibratorManagerHooks mManagerHooks;
@Mock private VibratorController.OnVibrationCompleteListener mControllerCallbacks;
@Mock private IBinder mVibrationToken;
@Mock private VibrationConfig mVibrationConfigMock;
private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
private VibrationSettings mVibrationSettings;
private DeviceVibrationEffectAdapter mEffectAdapter;
private TestLooper mTestLooper;
private TestLooperAutoDispatcher mCustomTestLooperDispatcher;
private VibrationThread mThread;
// Setup from the providers when VibrationThread is initialized.
private SparseArray<VibratorController> mControllers;
@Before
public void setUp() throws Exception {
mTestLooper = new TestLooper();
when(mVibrationConfigMock.getDefaultVibrationIntensity(anyInt()))
.thenReturn(Vibrator.VIBRATION_INTENSITY_MEDIUM);
when(mVibrationConfigMock.getRampStepDurationMs()).thenReturn(TEST_RAMP_STEP_DURATION);
when(mPackageManagerInternalMock.getSystemUiServiceComponent())
.thenReturn(new ComponentName("", ""));
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
Context context = InstrumentationRegistry.getContext();
mVibrationSettings = new VibrationSettings(context, new Handler(mTestLooper.getLooper()),
mVibrationConfigMock);
mockVibrators(VIBRATOR_ID);
mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);
PowerManager.WakeLock wakeLock = context.getSystemService(
PowerManager.class).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
mThread = new VibrationThread(wakeLock, mManagerHooks);
mThread.start();
}
@After
public void tearDown() {
if (mCustomTestLooperDispatcher != null) {
mCustomTestLooperDispatcher.cancel();
}
}
@Test
public void vibrate_noVibrator_ignoresVibration() {
mVibratorProviders.clear();
long vibrationId = 1;
CombinedVibration effect = CombinedVibration.createParallel(
VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
startThreadAndDispatcher(vibrationId, effect);
waitForCompletion();
verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
}
@Test
public void vibrate_missingVibrators_ignoresVibration() {
long vibrationId = 1;
CombinedVibration effect = CombinedVibration.startSequential()
.addNext(2, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
.addNext(3, VibrationEffect.get(VibrationEffect.EFFECT_TICK))
.combine();
startThreadAndDispatcher(vibrationId, effect);
waitForCompletion();
verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
}
@Test
public void vibrate_singleVibratorOneShot_runsVibrationAndSetsAmplitude() throws Exception {
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.createOneShot(10, 100);
startThreadAndDispatcher(vibrationId, effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(10)),
mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
}
@Test
public void vibrate_oneShotWithoutAmplitudeControl_runsVibrationWithDefaultAmplitude()
throws Exception {
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.createOneShot(10, 100);
startThreadAndDispatcher(vibrationId, effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(10)),
mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().isEmpty());
}
@Test
public void vibrate_singleVibratorWaveform_runsVibrationAndChangesAmplitudes()
throws Exception {
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.createWaveform(
new long[]{5, 5, 5}, new int[]{1, 2, 3}, -1);
startThreadAndDispatcher(vibrationId, effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(15L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(15)),
mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
assertEquals(expectedAmplitudes(1, 2, 3),
mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
}
@Test
public void vibrate_singleVibratorRepeatingWaveform_runsVibrationUntilThreadCancelled()
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[]{5, 5, 5}, amplitudes, 0);
VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
assertTrue(
waitUntil(() -> fakeVibrator.getAmplitudes().size() > 2 * amplitudes.length,
TEST_TIMEOUT_MILLIS));
// Vibration still running after 2 cycles.
assertTrue(mThread.isRunningVibrationId(vibrationId));
assertTrue(mControllers.get(VIBRATOR_ID).isVibrating());
Vibration.EndInfo cancelVibrationInfo = new Vibration.EndInfo(
Vibration.Status.CANCELLED_SUPERSEDED, /* endedByUid= */ 1,
/* endedByUsage= */ VibrationAttributes.USAGE_ALARM);
conductor.notifyCancelled(
cancelVibrationInfo,
/* immediate= */ false);
waitForCompletion();
assertFalse(mThread.isRunningVibrationId(vibrationId));
verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong());
verify(mManagerHooks).noteVibratorOff(eq(UID));
verifyCallbacksTriggered(vibrationId, cancelVibrationInfo);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
List<Float> playedAmplitudes = fakeVibrator.getAmplitudes();
assertFalse(fakeVibrator.getEffectSegments(vibrationId).isEmpty());
assertFalse(playedAmplitudes.isEmpty());
for (int i = 0; i < playedAmplitudes.size(); i++) {
assertEquals(amplitudes[i % amplitudes.length] / 255f, playedAmplitudes.get(i), 1e-5);
}
}
@Test
public void vibrate_singleVibratorRepeatingShortAlwaysOnWaveform_turnsVibratorOnForLonger()
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);
VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS));
conductor.notifyCancelled(
new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
/* immediate= */ false);
waitForCompletion();
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(5000)),
fakeVibrator.getEffectSegments(vibrationId));
}
@Test
public void vibrate_singleVibratorRepeatingPwle_generatesLargestPwles() throws Exception {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
fakeVibrator.setMinFrequency(100);
fakeVibrator.setResonantFrequency(150);
fakeVibrator.setFrequencyResolution(50);
fakeVibrator.setMaxAmplitudes(1, 1, 1);
fakeVibrator.setPwleSizeMax(10);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.startWaveform(targetAmplitude(1))
// Very long segment so thread will be cancelled after first PWLE is triggered.
.addTransition(Duration.ofMillis(100), targetFrequency(100))
.build();
VibrationEffect repeatingEffect = VibrationEffect.startComposition()
.repeatEffectIndefinitely(effect)
.compose();
VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, repeatingEffect);
assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
TEST_TIMEOUT_MILLIS));
conductor.notifyCancelled(
new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
/* immediate= */ false);
waitForCompletion();
// PWLE size max was used to generate a single vibrate call with 10 segments.
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(10, fakeVibrator.getEffectSegments(vibrationId).size());
}
@Test
public void vibrate_singleVibratorRepeatingPrimitives_generatesLargestComposition()
throws Exception {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK);
fakeVibrator.setCompositionSizeMax(10);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.startComposition()
// Very long delay so thread will be cancelled after first PWLE is triggered.
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
.compose();
VibrationEffect repeatingEffect = VibrationEffect.startComposition()
.repeatEffectIndefinitely(effect)
.compose();
VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, repeatingEffect);
assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
TEST_TIMEOUT_MILLIS));
conductor.notifyCancelled(
new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
/* immediate= */ false);
waitForCompletion();
// Composition size max was used to generate a single vibrate call with 10 primitives.
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(10, fakeVibrator.getEffectSegments(vibrationId).size());
}
@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);
VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS));
conductor.notifyCancelled(
new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
/* immediate= */ false);
waitForCompletion();
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(5550)),
fakeVibrator.getEffectSegments(vibrationId));
}
@LargeTest
@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[]{4900, 50}, amplitudes, 0);
VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibrationId).size() > 1,
5000 + TEST_TIMEOUT_MILLIS));
conductor.notifyCancelled(
new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
/* immediate= */ false);
waitForCompletion();
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
// First time turn vibrator ON for minimum of 5s.
assertEquals(5000L, fakeVibrator.getEffectSegments(vibrationId).get(0).getDuration());
// Vibrator turns off in the middle of the second execution of first step, turn it back ON
// for another 5s + remaining of 850ms.
assertEquals(4900 + 50 + 4900,
fakeVibrator.getEffectSegments(vibrationId).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
public void vibrate_singleVibratorPredefinedCancel_cancelsVibrationImmediately()
throws Exception {
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(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, 1f, 100)
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
.compose();
VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
TEST_TIMEOUT_MILLIS));
assertTrue(mThread.isRunningVibrationId(vibrationId));
// Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
// fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
Thread cancellingThread =
new Thread(() -> conductor.notifyCancelled(
new Vibration.EndInfo(
Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
/* immediate= */ false));
cancellingThread.start();
waitForCompletion(/* timeout= */ 50);
cancellingThread.join();
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
}
@Test
public void vibrate_singleVibratorWaveformCancel_cancelsVibrationImmediately()
throws Exception {
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.createWaveform(new long[]{100}, new int[]{100}, 0);
VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
TEST_TIMEOUT_MILLIS));
assertTrue(mThread.isRunningVibrationId(vibrationId));
// Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
// fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
Thread cancellingThread =
new Thread(() -> conductor.notifyCancelled(
new Vibration.EndInfo(
Vibration.Status.CANCELLED_BY_SCREEN_OFF),
/* immediate= */ false));
cancellingThread.start();
waitForCompletion(/* timeout= */ 50);
cancellingThread.join();
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
}
@Test
public void vibrate_singleVibratorPrebaked_runsVibration() throws Exception {
mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_THUD);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_THUD);
startThreadAndDispatcher(vibrationId, effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_THUD)),
mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
}
@Test
public void vibrate_singleVibratorPrebakedAndUnsupportedEffectWithFallback_runsFallback()
throws Exception {
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
long vibrationId = 1;
VibrationEffect fallback = VibrationEffect.createOneShot(10, 100);
Vibration vibration = createVibration(vibrationId, CombinedVibration.createParallel(
VibrationEffect.get(VibrationEffect.EFFECT_CLICK)));
vibration.addFallback(VibrationEffect.EFFECT_CLICK, fallback);
startThreadAndDispatcher(vibration);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(10)),
mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
}
@Test
public void vibrate_singleVibratorPrebakedAndUnsupportedEffect_ignoresVibration()
throws Exception {
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
startThreadAndDispatcher(vibrationId, effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).isEmpty());
}
@Test
public void vibrate_singleVibratorComposed_runsVibration() throws Exception {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK,
VibrationEffect.Composition.PRIMITIVE_TICK);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
.compose();
startThreadAndDispatcher(vibrationId, effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(40L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(
expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0),
expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0)),
fakeVibrator.getEffectSegments(vibrationId));
}
@Test
public void vibrate_singleVibratorComposedAndNoCapability_ignoresVibration() throws Exception {
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
.compose();
startThreadAndDispatcher(vibrationId, effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).isEmpty());
}
@Test
public void vibrate_singleVibratorLargeComposition_splitsVibratorComposeCalls() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
fakeVibrator.setSupportedPrimitives(
VibrationEffect.Composition.PRIMITIVE_CLICK,
VibrationEffect.Composition.PRIMITIVE_TICK,
VibrationEffect.Composition.PRIMITIVE_SPIN);
fakeVibrator.setCompositionSizeMax(2);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.8f)
.compose();
startThreadAndDispatcher(vibrationId, effect);
waitForCompletion();
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
// Vibrator compose called twice.
verify(mControllerCallbacks, times(2)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
assertEquals(3, fakeVibrator.getEffectSegments(vibrationId).size());
}
@Test
public void vibrate_singleVibratorComposedEffects_runsDifferentVibrations() throws Exception {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
fakeVibrator.setSupportedPrimitives(
VibrationEffect.Composition.PRIMITIVE_CLICK,
VibrationEffect.Composition.PRIMITIVE_TICK);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS,
IVibrator.CAP_COMPOSE_PWLE_EFFECTS, IVibrator.CAP_AMPLITUDE_CONTROL);
fakeVibrator.setMinFrequency(100);
fakeVibrator.setResonantFrequency(150);
fakeVibrator.setFrequencyResolution(50);
fakeVibrator.setMaxAmplitudes(
0.5f /* 100Hz*/, 1 /* 150Hz */, 0.6f /* 200Hz */);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.startComposition()
.addEffect(VibrationEffect.createOneShot(10, 100))
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
.addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
.addEffect(VibrationEffect.startWaveform()
.addTransition(Duration.ofMillis(10),
targetAmplitude(1), targetFrequency(100))
.addTransition(Duration.ofMillis(20), targetFrequency(120))
.build())
.addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
.compose();
startThreadAndDispatcher(vibrationId, effect);
waitForCompletion();
// Use first duration the vibrator is turned on since we cannot estimate the clicks.
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks, times(5)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(
expectedOneShot(10),
expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0),
expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0),
expectedPrebaked(VibrationEffect.EFFECT_CLICK),
expectedRamp(/* startAmplitude= */ 0, /* endAmplitude= */ 0.5f,
/* startFrequencyHz= */ 150, /* endFrequencyHz= */ 100, /* duration= */ 10),
expectedRamp(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.7f,
/* startFrequencyHz= */ 100, /* endFrequencyHz= */ 120, /* duration= */ 20),
expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
}
@Test
public void vibrate_singleVibratorPwle_runsComposePwle() throws Exception {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
fakeVibrator.setSupportedBraking(Braking.CLAB);
fakeVibrator.setMinFrequency(100);
fakeVibrator.setResonantFrequency(150);
fakeVibrator.setFrequencyResolution(50);
fakeVibrator.setMaxAmplitudes(
0.5f /* 100Hz*/, 1 /* 150Hz */, 0.6f /* 200Hz */);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.startWaveform(targetAmplitude(1))
.addSustain(Duration.ofMillis(10))
.addTransition(Duration.ofMillis(20), targetAmplitude(0))
.addTransition(Duration.ZERO, targetAmplitude(0.8f), targetFrequency(100))
.addSustain(Duration.ofMillis(30))
.addTransition(Duration.ofMillis(40), targetAmplitude(0.6f), targetFrequency(200))
.build();
startThreadAndDispatcher(vibrationId, effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(
expectedRamp(/* amplitude= */ 1, /* frequencyHz= */ 150, /* duration= */ 10),
expectedRamp(/* startAmplitude= */ 1, /* endAmplitude= */ 0,
/* startFrequencyHz= */ 150, /* endFrequencyHz= */ 150, /* duration= */ 20),
expectedRamp(/* amplitude= */ 0.5f, /* frequencyHz= */ 100, /* duration= */ 30),
expectedRamp(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.6f,
/* startFrequencyHz= */ 100, /* endFrequencyHz= */ 200,
/* duration= */ 40)),
fakeVibrator.getEffectSegments(vibrationId));
assertEquals(Arrays.asList(Braking.CLAB), fakeVibrator.getBraking(vibrationId));
}
@Test
public void vibrate_singleVibratorLargePwle_splitsComposeCallWhenAmplitudeIsLowest() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
fakeVibrator.setMinFrequency(100);
fakeVibrator.setResonantFrequency(150);
fakeVibrator.setFrequencyResolution(50);
fakeVibrator.setMaxAmplitudes(1, 1, 1);
fakeVibrator.setPwleSizeMax(3);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.startWaveform(targetAmplitude(1))
.addSustain(Duration.ofMillis(10))
.addTransition(Duration.ofMillis(20), targetAmplitude(0))
// Waveform will be split here, after vibration goes to zero amplitude
.addTransition(Duration.ZERO, targetAmplitude(0.8f), targetFrequency(100))
.addSustain(Duration.ofMillis(30))
.addTransition(Duration.ofMillis(40), targetAmplitude(0.6f), targetFrequency(200))
// Waveform will be split here at lowest amplitude.
.addTransition(Duration.ofMillis(40), targetAmplitude(0.7f), targetFrequency(200))
.addTransition(Duration.ofMillis(40), targetAmplitude(0.6f), targetFrequency(200))
.build();
startThreadAndDispatcher(vibrationId, effect);
waitForCompletion();
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
// Vibrator compose called 3 times with 2 segments instead of 2 times with 3 segments.
// Using best split points instead of max-packing PWLEs.
verify(mControllerCallbacks, times(3)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
assertEquals(6, fakeVibrator.getEffectSegments(vibrationId).size());
}
@Test
public void vibrate_singleVibratorCancelled_vibratorStopped() throws Exception {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.createWaveform(new long[]{5}, new int[]{100}, 0);
VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
assertTrue(waitUntil(() -> fakeVibrator.getAmplitudes().size() > 2, TEST_TIMEOUT_MILLIS));
// Vibration still running after 2 cycles.
assertTrue(mThread.isRunningVibrationId(vibrationId));
assertTrue(mControllers.get(VIBRATOR_ID).isVibrating());
conductor.binderDied();
waitForCompletion();
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BINDER_DIED);
}
@Test
public void vibrate_singleVibrator_skipsSyncedCallbacks() {
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
long vibrationId = 1;
startThreadAndDispatcher(vibrationId,
VibrationEffect.createOneShot(10, 100));
waitForCompletion();
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
verify(mManagerHooks, never()).prepareSyncedVibration(anyLong(), any());
verify(mManagerHooks, never()).triggerSyncedVibration(anyLong());
verify(mManagerHooks, never()).cancelSyncedVibration();
}
@Test
public void vibrate_multipleExistingAndMissingVibrators_vibratesOnlyExistingOnes()
throws Exception {
mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_TICK);
long vibrationId = 1;
CombinedVibration effect = CombinedVibration.startParallel()
.addVibrator(VIBRATOR_ID, VibrationEffect.get(VibrationEffect.EFFECT_TICK))
.addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_TICK))
.combine();
startThreadAndDispatcher(vibrationId, effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verify(mControllerCallbacks, never()).onComplete(eq(2), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_TICK)),
mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
}
@Test
public void vibrate_multipleMono_runsSameEffectInAllVibrators() throws Exception {
mockVibrators(1, 2, 3);
mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
mVibratorProviders.get(2).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
mVibratorProviders.get(3).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
long vibrationId = 1;
CombinedVibration effect = CombinedVibration.createParallel(
VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
startThreadAndDispatcher(vibrationId, effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(mControllers.get(1).isVibrating());
assertFalse(mControllers.get(2).isVibrating());
assertFalse(mControllers.get(3).isVibrating());
VibrationEffectSegment expected = expectedPrebaked(VibrationEffect.EFFECT_CLICK);
assertEquals(Arrays.asList(expected),
mVibratorProviders.get(1).getEffectSegments(vibrationId));
assertEquals(Arrays.asList(expected),
mVibratorProviders.get(2).getEffectSegments(vibrationId));
assertEquals(Arrays.asList(expected),
mVibratorProviders.get(3).getEffectSegments(vibrationId));
}
@Test
public void vibrate_multipleStereo_runsVibrationOnRightVibrators() throws Exception {
mockVibrators(1, 2, 3, 4);
mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
mVibratorProviders.get(3).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
mVibratorProviders.get(4).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
mVibratorProviders.get(4).setSupportedPrimitives(
VibrationEffect.Composition.PRIMITIVE_CLICK);
long vibrationId = 1;
VibrationEffect composed = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
.compose();
CombinedVibration effect = CombinedVibration.startParallel()
.addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
.addVibrator(2, VibrationEffect.createOneShot(10, 100))
.addVibrator(3, VibrationEffect.createWaveform(
new long[]{10, 10}, new int[]{1, 2}, -1))
.addVibrator(4, composed)
.combine();
startThreadAndDispatcher(vibrationId, effect);
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
verify(mControllerCallbacks).onComplete(eq(4), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(mControllers.get(1).isVibrating());
assertFalse(mControllers.get(2).isVibrating());
assertFalse(mControllers.get(3).isVibrating());
assertFalse(mControllers.get(4).isVibrating());
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
mVibratorProviders.get(1).getEffectSegments(vibrationId));
assertEquals(Arrays.asList(expectedOneShot(10)),
mVibratorProviders.get(2).getEffectSegments(vibrationId));
assertEquals(expectedAmplitudes(100), mVibratorProviders.get(2).getAmplitudes());
assertEquals(Arrays.asList(expectedOneShot(20)),
mVibratorProviders.get(3).getEffectSegments(vibrationId));
assertEquals(expectedAmplitudes(1, 2), mVibratorProviders.get(3).getAmplitudes());
assertEquals(Arrays.asList(
expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
mVibratorProviders.get(4).getEffectSegments(vibrationId));
}
@Test
public void vibrate_multipleSequential_runsVibrationInOrderWithDelays() throws Exception {
mockVibrators(1, 2, 3);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
mVibratorProviders.get(2).setSupportedPrimitives(
VibrationEffect.Composition.PRIMITIVE_CLICK);
mVibratorProviders.get(3).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
long vibrationId = 1;
VibrationEffect composed = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
.compose();
CombinedVibration effect = CombinedVibration.startSequential()
.addNext(3, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), /* delay= */ 50)
.addNext(1, VibrationEffect.createOneShot(10, 100), /* delay= */ 50)
.addNext(2, composed, /* delay= */ 50)
.combine();
startThreadAndDispatcher(vibrationId, effect);
waitForCompletion();
InOrder controllerVerifier = inOrder(mControllerCallbacks);
controllerVerifier.verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
controllerVerifier.verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
controllerVerifier.verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
InOrder batteryVerifier = inOrder(mManagerHooks);
batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID));
batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID));
batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(mControllers.get(1).isVibrating());
assertFalse(mControllers.get(2).isVibrating());
assertFalse(mControllers.get(3).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(10)),
mVibratorProviders.get(1).getEffectSegments(vibrationId));
assertEquals(expectedAmplitudes(100), mVibratorProviders.get(1).getAmplitudes());
assertEquals(Arrays.asList(
expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
mVibratorProviders.get(2).getEffectSegments(vibrationId));
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
mVibratorProviders.get(3).getEffectSegments(vibrationId));
}
@Test
public void vibrate_multipleSyncedCallbackTriggered_finishSteps() throws Exception {
int[] vibratorIds = new int[]{1, 2};
long vibrationId = 1;
mockVibrators(vibratorIds);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
mVibratorProviders.get(1).setSupportedPrimitives(
VibrationEffect.Composition.PRIMITIVE_CLICK);
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
mVibratorProviders.get(2).setSupportedPrimitives(
VibrationEffect.Composition.PRIMITIVE_CLICK);
when(mManagerHooks.prepareSyncedVibration(anyLong(), eq(vibratorIds))).thenReturn(true);
when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true);
VibrationEffect composed = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100)
.compose();
CombinedVibration effect = CombinedVibration.createParallel(composed);
VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
assertTrue(waitUntil(
() -> !mVibratorProviders.get(1).getEffectSegments(vibrationId).isEmpty()
&& !mVibratorProviders.get(2).getEffectSegments(vibrationId).isEmpty(),
TEST_TIMEOUT_MILLIS));
conductor.notifySyncedVibrationComplete();
waitForCompletion();
long expectedCap = IVibratorManager.CAP_SYNC | IVibratorManager.CAP_PREPARE_COMPOSE;
verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
verify(mManagerHooks).triggerSyncedVibration(eq(vibrationId));
verify(mManagerHooks, never()).cancelSyncedVibration();
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
VibrationEffectSegment expected = expectedPrimitive(
VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100);
assertEquals(Arrays.asList(expected),
mVibratorProviders.get(1).getEffectSegments(vibrationId));
assertEquals(Arrays.asList(expected),
mVibratorProviders.get(2).getEffectSegments(vibrationId));
}
@Test
public void vibrate_multipleSynced_callsPrepareAndTriggerCallbacks() {
int[] vibratorIds = new int[]{1, 2, 3, 4};
mockVibrators(vibratorIds);
mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
mVibratorProviders.get(4).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
mVibratorProviders.get(4).setSupportedPrimitives(
VibrationEffect.Composition.PRIMITIVE_CLICK);
when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true);
when(mManagerHooks.triggerSyncedVibration(anyLong())).thenReturn(true);
long vibrationId = 1;
VibrationEffect composed = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
.compose();
CombinedVibration effect = CombinedVibration.startParallel()
.addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
.addVibrator(2, VibrationEffect.createOneShot(10, 100))
.addVibrator(3, VibrationEffect.createWaveform(new long[]{10}, new int[]{100}, -1))
.addVibrator(4, composed)
.combine();
startThreadAndDispatcher(vibrationId, effect);
waitForCompletion();
long expectedCap = IVibratorManager.CAP_SYNC
| IVibratorManager.CAP_PREPARE_ON
| IVibratorManager.CAP_PREPARE_PERFORM
| IVibratorManager.CAP_PREPARE_COMPOSE
| IVibratorManager.CAP_MIXED_TRIGGER_ON
| IVibratorManager.CAP_MIXED_TRIGGER_PERFORM
| IVibratorManager.CAP_MIXED_TRIGGER_COMPOSE;
verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
verify(mManagerHooks).triggerSyncedVibration(eq(vibrationId));
verify(mManagerHooks, never()).cancelSyncedVibration();
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
}
@Test
public void vibrate_multipleSyncedPrepareFailed_skipTriggerStepAndVibrates() {
int[] vibratorIds = new int[]{1, 2};
mockVibrators(vibratorIds);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(false);
long vibrationId = 1;
CombinedVibration effect = CombinedVibration.startParallel()
.addVibrator(1, VibrationEffect.createOneShot(10, 100))
.addVibrator(2, VibrationEffect.createWaveform(new long[]{5}, new int[]{200}, -1))
.combine();
startThreadAndDispatcher(vibrationId, effect);
waitForCompletion();
long expectedCap = IVibratorManager.CAP_SYNC | IVibratorManager.CAP_PREPARE_ON;
verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
verify(mManagerHooks, never()).triggerSyncedVibration(eq(vibrationId));
verify(mManagerHooks, never()).cancelSyncedVibration();
assertEquals(Arrays.asList(expectedOneShot(10)),
mVibratorProviders.get(1).getEffectSegments(vibrationId));
assertEquals(expectedAmplitudes(100), mVibratorProviders.get(1).getAmplitudes());
assertEquals(Arrays.asList(expectedOneShot(5)),
mVibratorProviders.get(2).getEffectSegments(vibrationId));
assertEquals(expectedAmplitudes(200), mVibratorProviders.get(2).getAmplitudes());
}
@Test
public void vibrate_multipleSyncedTriggerFailed_cancelPreparedVibrationAndSkipSetAmplitude() {
int[] vibratorIds = new int[]{1, 2};
mockVibrators(vibratorIds);
mVibratorProviders.get(2).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true);
when(mManagerHooks.triggerSyncedVibration(anyLong())).thenReturn(false);
long vibrationId = 1;
CombinedVibration effect = CombinedVibration.startParallel()
.addVibrator(1, VibrationEffect.createOneShot(10, 100))
.addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
.combine();
startThreadAndDispatcher(vibrationId, effect);
waitForCompletion();
long expectedCap = IVibratorManager.CAP_SYNC
| IVibratorManager.CAP_PREPARE_ON
| IVibratorManager.CAP_PREPARE_PERFORM
| IVibratorManager.CAP_MIXED_TRIGGER_ON
| IVibratorManager.CAP_MIXED_TRIGGER_PERFORM;
verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
verify(mManagerHooks).triggerSyncedVibration(eq(vibrationId));
verify(mManagerHooks).cancelSyncedVibration();
assertTrue(mVibratorProviders.get(1).getAmplitudes().isEmpty());
}
@Test
public void vibrate_multipleWaveforms_playsWaveformsInParallel() throws Exception {
mockVibrators(1, 2, 3);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
mVibratorProviders.get(3).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
long vibrationId = 1;
CombinedVibration effect = CombinedVibration.startParallel()
.addVibrator(1, VibrationEffect.createWaveform(
new long[]{5, 10, 10}, new int[]{1, 2, 3}, -1))
.addVibrator(2, VibrationEffect.createWaveform(
new long[]{20, 60}, new int[]{4, 5}, -1))
.addVibrator(3, VibrationEffect.createWaveform(
new long[]{60}, new int[]{6}, -1))
.combine();
startThreadAndDispatcher(vibrationId, effect);
// All vibrators are turned on in parallel.
assertTrue(waitUntil(
() -> mControllers.get(1).isVibrating()
&& mControllers.get(2).isVibrating()
&& mControllers.get(3).isVibrating(),
TEST_TIMEOUT_MILLIS));
waitForCompletion();
verify(mManagerHooks).noteVibratorOn(eq(UID), eq(80L));
verify(mManagerHooks).noteVibratorOff(eq(UID));
verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertFalse(mControllers.get(1).isVibrating());
assertFalse(mControllers.get(2).isVibrating());
assertFalse(mControllers.get(3).isVibrating());
assertEquals(Arrays.asList(expectedOneShot(25)),
mVibratorProviders.get(1).getEffectSegments(vibrationId));
assertEquals(Arrays.asList(expectedOneShot(80)),
mVibratorProviders.get(2).getEffectSegments(vibrationId));
assertEquals(Arrays.asList(expectedOneShot(60)),
mVibratorProviders.get(3).getEffectSegments(vibrationId));
assertEquals(expectedAmplitudes(1, 2, 3), mVibratorProviders.get(1).getAmplitudes());
assertEquals(expectedAmplitudes(4, 5), mVibratorProviders.get(2).getAmplitudes());
assertEquals(expectedAmplitudes(6), mVibratorProviders.get(3).getAmplitudes());
}
@LargeTest
@Test
public void vibrate_withWaveform_totalVibrationTimeRespected() {
int totalDuration = 10_000; // 10s
int stepDuration = 25; // 25ms
// 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..
mVibratorProviders.get(VIBRATOR_ID).setLatency(stepDuration / 4);
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
int stepCount = totalDuration / stepDuration;
long[] timings = new long[stepCount];
int[] amplitudes = new int[stepCount];
Arrays.fill(timings, stepDuration);
Arrays.fill(amplitudes, VibrationEffect.DEFAULT_AMPLITUDE);
VibrationEffect effect = VibrationEffect.createWaveform(timings, amplitudes, -1);
long vibrationId = 1;
startThreadAndDispatcher(vibrationId, effect);
long startTime = SystemClock.elapsedRealtime();
waitForCompletion(totalDuration + TEST_TIMEOUT_MILLIS);
long delay = Math.abs(SystemClock.elapsedRealtime() - startTime - totalDuration);
// Allow some delay for thread scheduling and callback triggering.
int maxDelay = (int) (0.05 * totalDuration); // < 5% of total duration
assertTrue("Waveform with perceived delay of " + delay + "ms,"
+ " expected less than " + maxDelay + "ms",
delay < maxDelay);
}
@LargeTest
@Test
public void vibrate_cancelSlowVibrator_cancelIsNotBlockedByVibrationThread() throws Exception {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
long latency = 5_000; // 5s
fakeVibrator.setLatency(latency);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
TEST_TIMEOUT_MILLIS));
assertTrue(mThread.isRunningVibrationId(vibrationId));
// Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
// fail at waitForCompletion(cancellingThread).
Thread cancellingThread = new Thread(
() -> conductor.notifyCancelled(
new Vibration.EndInfo(
Vibration.Status.CANCELLED_BY_USER),
/* immediate= */ false));
cancellingThread.start();
// Cancelling the vibration should be fast and return right away, even if the thread is
// stuck at the slow call to the vibrator.
waitForCompletion(/* timeout= */ 50);
// After the vibrator call ends the vibration is cancelled and the vibrator is turned off.
waitForCompletion(/* timeout= */ latency + TEST_TIMEOUT_MILLIS);
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
}
@Test
public void vibrate_multiplePredefinedCancel_cancelsVibrationImmediately() throws Exception {
mockVibrators(1, 2);
mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
mVibratorProviders.get(2).setSupportedPrimitives(
VibrationEffect.Composition.PRIMITIVE_CLICK);
long vibrationId = 1;
CombinedVibration effect = CombinedVibration.startParallel()
.addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
.addVibrator(2, VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
.compose())
.combine();
VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
assertTrue(waitUntil(() -> mControllers.get(2).isVibrating(),
TEST_TIMEOUT_MILLIS));
assertTrue(mThread.isRunningVibrationId(vibrationId));
// Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
// fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
Thread cancellingThread = new Thread(
() -> conductor.notifyCancelled(
new Vibration.EndInfo(
Vibration.Status.CANCELLED_BY_SCREEN_OFF),
/* immediate= */ false));
cancellingThread.start();
waitForCompletion(/* timeout= */ 50);
cancellingThread.join();
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
assertFalse(mControllers.get(1).isVibrating());
assertFalse(mControllers.get(2).isVibrating());
}
@Test
public void vibrate_multipleWaveformCancel_cancelsVibrationImmediately() throws Exception {
mockVibrators(1, 2);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
long vibrationId = 1;
CombinedVibration effect = CombinedVibration.startParallel()
.addVibrator(1, VibrationEffect.createWaveform(
new long[]{100, 100}, new int[]{1, 2}, 0))
.addVibrator(2, VibrationEffect.createOneShot(100, 100))
.combine();
VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
assertTrue(waitUntil(() -> mControllers.get(1).isVibrating()
&& mControllers.get(2).isVibrating(),
TEST_TIMEOUT_MILLIS));
assertTrue(mThread.isRunningVibrationId(vibrationId));
// Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
// fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
Thread cancellingThread = new Thread(
() -> conductor.notifyCancelled(
new Vibration.EndInfo(
Vibration.Status.CANCELLED_BY_SCREEN_OFF),
/* immediate= */ false));
cancellingThread.start();
waitForCompletion(/* timeout= */ 50);
cancellingThread.join();
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
assertFalse(mControllers.get(1).isVibrating());
assertFalse(mControllers.get(2).isVibrating());
}
@Test
public void vibrate_binderDied_cancelsVibration() throws Exception {
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.createWaveform(new long[]{5}, new int[]{100}, 0);
VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
TEST_TIMEOUT_MILLIS));
assertTrue(mThread.isRunningVibrationId(vibrationId));
conductor.binderDied();
waitForCompletion();
verify(mVibrationToken).linkToDeath(same(conductor), eq(0));
verify(mVibrationToken).unlinkToDeath(same(conductor), eq(0));
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BINDER_DIED);
assertFalse(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).isEmpty());
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
}
@Test
public void vibrate_waveformWithRampDown_addsRampDownAfterVibrationCompleted() {
when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
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);
startThreadAndDispatcher(vibrationId, effect);
waitForCompletion();
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(vibrationId));
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() {
when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(10_000);
mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.createOneShot(10, 200);
VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
// Vibration completed but vibrator not yet released.
verify(mManagerHooks, timeout(TEST_TIMEOUT_MILLIS)).onVibrationCompleted(eq(vibrationId),
eq(new Vibration.EndInfo(Vibration.Status.FINISHED)));
verify(mManagerHooks, never()).onVibrationThreadReleased(anyLong());
// Thread still running ramp down.
assertTrue(mThread.isRunningVibrationId(vibrationId));
// Duration extended for 10 + 10000.
assertEquals(Arrays.asList(expectedOneShot(10_010)),
mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
// Will stop the ramp down right away.
conductor.notifyCancelled(
new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
/* immediate= */ true);
waitForCompletion();
// Does not cancel already finished vibration, but releases vibrator.
verify(mManagerHooks, never()).onVibrationCompleted(eq(vibrationId),
eq(new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE)));
verify(mManagerHooks).onVibrationThreadReleased(vibrationId);
}
@Test
public void vibrate_waveformCancelledWithRampDown_addsRampDownAfterVibrationCancelled()
throws Exception {
when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.createOneShot(10_000, 240);
VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
TEST_TIMEOUT_MILLIS));
conductor.notifyCancelled(
new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
/* immediate= */ false);
waitForCompletion();
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
// Duration extended for 10000 + 15.
assertEquals(Arrays.asList(expectedOneShot(10_015)),
mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
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() {
when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
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);
startThreadAndDispatcher(vibrationId, effect);
waitForCompletion();
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(vibrationId));
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().isEmpty());
}
@Test
public void vibrate_composedWithRampDown_doesNotAddRampDown() {
when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
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();
startThreadAndDispatcher(vibrationId, effect);
waitForCompletion();
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(vibrationId));
assertTrue(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().isEmpty());
}
@Test
public void vibrate_pwleWithRampDown_doesNotAddRampDown() {
when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
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()
.addTransition(Duration.ofMillis(1), targetAmplitude(1))
.build();
startThreadAndDispatcher(vibrationId, effect);
waitForCompletion();
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
assertEquals(Arrays.asList(expectedRamp(0, 1, 150, 150, 1)),
fakeVibrator.getEffectSegments(vibrationId));
assertTrue(fakeVibrator.getAmplitudes().isEmpty());
}
@Test
public void vibrate_multipleVibrations_withCancel() throws Exception {
mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects(
VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_TICK);
mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(
VibrationEffect.Composition.PRIMITIVE_CLICK);
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
IVibrator.CAP_COMPOSE_EFFECTS);
long vibrationId1 = 1;
long vibrationId2 = 2;
long vibrationId3 = 3;
long vibrationId4 = 4;
long vibrationId5 = 5;
// A simple effect, followed by a repeating effect that gets cancelled, followed by another
// simple effect.
VibrationEffect effect1 = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
VibrationEffect effect2 = VibrationEffect.startComposition()
.repeatEffectIndefinitely(VibrationEffect.get(VibrationEffect.EFFECT_TICK))
.compose();
VibrationEffect effect3 = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
.compose();
VibrationEffect effect4 = VibrationEffect.createOneShot(8000, 100);
VibrationEffect effect5 = VibrationEffect.createOneShot(20, 222);
startThreadAndDispatcher(vibrationId1, effect1);
waitForCompletion();
verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibrationId1);
verifyCallbacksTriggered(vibrationId1, Vibration.Status.FINISHED);
VibrationStepConductor conductor2 = startThreadAndDispatcher(vibrationId2, effect2);
// Effect2 won't complete on its own. Cancel it after a couple of repeats.
Thread.sleep(150); // More than two TICKs.
conductor2.notifyCancelled(
new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
/* immediate= */ false);
waitForCompletion();
startThreadAndDispatcher(vibrationId3, effect3);
waitForCompletion();
// Effect4 is a long oneshot, but it gets cancelled as fast as possible.
long start4 = System.currentTimeMillis();
VibrationStepConductor conductor4 = startThreadAndDispatcher(vibrationId4, effect4);
conductor4.notifyCancelled(
new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
/* immediate= */ true);
waitForCompletion();
long duration4 = System.currentTimeMillis() - start4;
// Effect5 is to show that things keep going after the immediate cancel.
startThreadAndDispatcher(vibrationId5, effect5);
waitForCompletion();
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
// Effect1
verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibrationId1);
verifyCallbacksTriggered(vibrationId1, Vibration.Status.FINISHED);
assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
fakeVibrator.getEffectSegments(vibrationId1));
// Effect2: repeating, cancelled.
verify(mControllerCallbacks, atLeast(2)).onComplete(VIBRATOR_ID, vibrationId2);
verifyCallbacksTriggered(vibrationId2, Vibration.Status.CANCELLED_BY_USER);
// The exact count of segments might vary, so just check that there's more than 2 and
// all elements are the same segment.
List<VibrationEffectSegment> actualSegments2 = fakeVibrator.getEffectSegments(vibrationId2);
assertTrue(actualSegments2.size() + " > 2", actualSegments2.size() > 2);
for (VibrationEffectSegment segment : actualSegments2) {
assertEquals(expectedPrebaked(VibrationEffect.EFFECT_TICK), segment);
}
// Effect3
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId3));
verifyCallbacksTriggered(vibrationId3, Vibration.Status.FINISHED);
assertEquals(Arrays.asList(
expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
fakeVibrator.getEffectSegments(vibrationId3));
// Effect4: cancelled quickly.
verifyCallbacksTriggered(vibrationId4, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
assertTrue("Tested duration=" + duration4, duration4 < 2000);
// Effect5: normal oneshot. Don't worry about amplitude, as effect4 may or may not have
// started.
verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId5));
verifyCallbacksTriggered(vibrationId5, Vibration.Status.FINISHED);
assertEquals(Arrays.asList(expectedOneShot(20)),
fakeVibrator.getEffectSegments(vibrationId5));
}
private void mockVibrators(int... vibratorIds) {
for (int vibratorId : vibratorIds) {
mVibratorProviders.put(vibratorId,
new FakeVibratorControllerProvider(mTestLooper.getLooper()));
}
}
private VibrationStepConductor startThreadAndDispatcher(
long vibrationId, VibrationEffect effect) {
return startThreadAndDispatcher(vibrationId, CombinedVibration.createParallel(effect));
}
private VibrationStepConductor startThreadAndDispatcher(long vibrationId,
CombinedVibration effect) {
return startThreadAndDispatcher(createVibration(vibrationId, effect));
}
private VibrationStepConductor startThreadAndDispatcher(Vibration vib) {
mControllers = createVibratorControllers();
VibrationStepConductor conductor = new VibrationStepConductor(vib, mVibrationSettings,
mEffectAdapter, mControllers, mManagerHooks);
doAnswer(answer -> {
conductor.notifyVibratorComplete(answer.getArgument(0));
return null;
}).when(mControllerCallbacks).onComplete(anyInt(), eq(vib.id));
assertTrue(mThread.runVibrationOnVibrationThread(conductor));
return conductor;
}
private boolean waitUntil(BooleanSupplier predicate, long timeout)
throws InterruptedException {
long timeoutTimestamp = SystemClock.uptimeMillis() + timeout;
boolean predicateResult = false;
while (!predicateResult && SystemClock.uptimeMillis() < timeoutTimestamp) {
Thread.sleep(10);
predicateResult = predicate.getAsBoolean();
}
return predicateResult;
}
private void waitForCompletion() {
waitForCompletion(TEST_TIMEOUT_MILLIS);
}
private void waitForCompletion(long timeout) {
mThread.waitForThreadIdle(timeout);
mTestLooper.dispatchAll(); // Flush callbacks
}
private Vibration createVibration(long id, CombinedVibration effect) {
return new Vibration(mVibrationToken, (int) id, effect, ATTRS, UID, DISPLAY_ID,
PACKAGE_NAME, "reason");
}
private SparseArray<VibratorController> createVibratorControllers() {
SparseArray<VibratorController> array = new SparseArray<>();
for (Map.Entry<Integer, FakeVibratorControllerProvider> e : mVibratorProviders.entrySet()) {
int id = e.getKey();
array.put(id, e.getValue().newVibratorController(id, mControllerCallbacks));
}
// Start a looper for the vibrationcontrollers if it's not already running.
// TestLooper.AutoDispatchThread has a fixed 1s duration. Use a custom auto-dispatcher.
if (mCustomTestLooperDispatcher == null) {
mCustomTestLooperDispatcher = new TestLooperAutoDispatcher(mTestLooper);
mCustomTestLooperDispatcher.start();
}
return array;
}
private VibrationEffectSegment expectedOneShot(long millis) {
return new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE,
/* frequencyHz= */ 0, (int) millis);
}
private VibrationEffectSegment expectedPrebaked(int effectId) {
return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
}
private VibrationEffectSegment expectedPrimitive(int primitiveId, float scale, int delay) {
return new PrimitiveSegment(primitiveId, scale, delay);
}
private VibrationEffectSegment expectedRamp(float amplitude, float frequencyHz, int duration) {
return expectedRamp(amplitude, amplitude, frequencyHz, frequencyHz, duration);
}
private VibrationEffectSegment expectedRamp(float startAmplitude, float endAmplitude,
float startFrequencyHz, float endFrequencyHz, int duration) {
return new RampSegment(startAmplitude, endAmplitude, startFrequencyHz, endFrequencyHz,
duration);
}
private List<Float> expectedAmplitudes(int... amplitudes) {
return Arrays.stream(amplitudes)
.mapToObj(amplitude -> amplitude / 255f)
.collect(Collectors.toList());
}
private void verifyCallbacksTriggered(long vibrationId, Vibration.Status expectedStatus) {
verifyCallbacksTriggered(vibrationId, new Vibration.EndInfo(expectedStatus));
}
private void verifyCallbacksTriggered(long vibrationId, Vibration.EndInfo expectedEndInfo) {
verify(mManagerHooks).onVibrationCompleted(eq(vibrationId), eq(expectedEndInfo));
verify(mManagerHooks).onVibrationThreadReleased(vibrationId);
}
private static final class TestLooperAutoDispatcher extends Thread {
private final TestLooper mTestLooper;
private boolean mCancelled;
TestLooperAutoDispatcher(TestLooper testLooper) {
mTestLooper = testLooper;
}
@Override
public void run() {
while (!mCancelled) {
mTestLooper.dispatchAll();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
return;
}
}
}
public void cancel() {
mCancelled = true;
}
}
}