Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5955825c authored by Lais Andrade's avatar Lais Andrade Committed by Android (Google) Code Review
Browse files

Merge "Add cancelation reason to vibration status" into tm-dev

parents 0e77eac9 1d0e7393
Loading
Loading
Loading
Loading
+6 −2
Original line number Diff line number Diff line
@@ -47,12 +47,16 @@ final class Vibration {
        FINISHED,
        FINISHED_UNEXPECTED,  // Didn't terminate in the usual way.
        FORWARDED_TO_INPUT_DEVICES,
        CANCELLED,
        CANCELLED_BINDER_DIED,
        CANCELLED_BY_SCREEN_OFF,
        CANCELLED_BY_SETTINGS_UPDATE,
        CANCELLED_BY_USER,
        CANCELLED_BY_UNKNOWN_REASON,
        CANCELLED_SUPERSEDED,
        IGNORED_ERROR_APP_OPS,
        IGNORED_ERROR_CANCELLING,
        IGNORED_ERROR_SCHEDULING,
        IGNORED_ERROR_TOKEN,
        IGNORED,
        IGNORED_APP_OPS,
        IGNORED_BACKGROUND,
        IGNORED_UNKNOWN_VIBRATION,
+45 −23
Original line number Diff line number Diff line
@@ -79,12 +79,14 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
    private final Object mLock = new Object();
    @GuardedBy("mLock")
    private final IntArray mSignalVibratorsComplete;
    @Nullable
    @GuardedBy("mLock")
    private boolean mSignalCancel = false;
    private Vibration.Status mSignalCancelStatus = null;
    @GuardedBy("mLock")
    private boolean mSignalCancelImmediate = false;

    private boolean mCancelled = false;
    @Nullable
    private Vibration.Status mCancelStatus = null;
    private boolean mCancelledImmediately = false;  // hard stop
    private int mPendingVibrateSteps;
    private int mRemainingStartSequentialEffectSteps;
@@ -185,8 +187,8 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
            expectIsVibrationThread(true);
        }

        if (mCancelled) {
            return Vibration.Status.CANCELLED;
        if (mCancelStatus != null) {
            return mCancelStatus;
        }
        if (mPendingVibrateSteps > 0
                || mRemainingStartSequentialEffectSteps > 0) {
@@ -303,7 +305,7 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
        if (DEBUG) {
            Slog.d(TAG, "Binder died, cancelling vibration...");
        }
        notifyCancelled(/* immediate= */ false);
        notifyCancelled(Vibration.Status.CANCELLED_BINDER_DIED, /* immediate= */ false);
    }

    /**
@@ -312,21 +314,40 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
     *
     * @param immediate indicates whether cancellation should abort urgently and skip cleanup steps.
     */
    public void notifyCancelled(boolean immediate) {
    public void notifyCancelled(@NonNull Vibration.Status cancelStatus, boolean immediate) {
        if (Build.IS_DEBUGGABLE) {
            expectIsVibrationThread(false);
        }
        if (DEBUG) {
            Slog.d(TAG, "Vibration cancel requested with status=" + cancelStatus
                    + ", immediate=" + immediate);
        }
        if ((cancelStatus == null) || !cancelStatus.name().startsWith("CANCEL")) {
            Slog.w(TAG, "Vibration cancel requested with bad status=" + cancelStatus
                    + ", using CANCELLED_UNKNOWN_REASON to ensure cancellation.");
            cancelStatus = Vibration.Status.CANCELLED_BY_UNKNOWN_REASON;
        }
        synchronized (mLock) {
            if (immediate && mSignalCancelImmediate || mSignalCancel) {
                // Nothing to update: already cancelled previously.
            if (immediate && mSignalCancelImmediate || (mSignalCancelStatus != null)) {
                if (DEBUG) {
                    Slog.d(TAG, "Vibration cancel request ignored as the vibration "
                            + mVibration.id + "is already being cancelled with status="
                            + mSignalCancelStatus + ", immediate=" + mSignalCancelImmediate);
                }
                return;
            }
            mSignalCancelImmediate |= immediate;
            mSignalCancel = true;
            mLock.notify();
        }
            if (mSignalCancelStatus == null) {
                mSignalCancelStatus = cancelStatus;
            } else {
                if (DEBUG) {
            Slog.d(TAG, "Vibration cancel requested, immediate=" + immediate);
                    Slog.d(TAG, "Vibration cancel request new status=" + cancelStatus
                            + " ignored as the vibration was already cancelled with status="
                            + mSignalCancelStatus + ", but immediate flag was updated to "
                            + mSignalCancelImmediate);
                }
            }
            mLock.notify();
        }
    }

@@ -380,7 +401,7 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
        if (Build.IS_DEBUGGABLE) {
            expectIsVibrationThread(true);  // Reads VibrationThread variables as well as signals.
        }
        return (mSignalCancel && !mCancelled)
        return (mSignalCancelStatus != mCancelStatus)
            || (mSignalCancelImmediate && !mCancelledImmediately)
            || (mSignalVibratorsComplete.size() > 0);
    }
@@ -395,7 +416,7 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
        }

        int[] vibratorsToProcess = null;
        boolean doCancel = false;
        Vibration.Status doCancelStatus = null;
        boolean doCancelImmediate = false;
        // Collect signals to process, but don't keep the lock while processing them.
        synchronized (mLock) {
@@ -405,9 +426,10 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
                }
                // This should only happen once.
                doCancelImmediate = true;
                doCancelStatus = mSignalCancelStatus;
            }
            if (mSignalCancel && !mCancelled) {
                doCancel = true;
            if (mSignalCancelStatus != mCancelStatus) {
                doCancelStatus = mSignalCancelStatus;
            }
            if (!doCancelImmediate && mSignalVibratorsComplete.size() > 0) {
                // Swap out the queue of completions to process.
@@ -421,11 +443,11 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
        // completion signals that were collected in this call, but we won't process them
        // anyway as all steps are cancelled.
        if (doCancelImmediate) {
            processCancelImmediately();
            processCancelImmediately(doCancelStatus);
            return;
        }
        if (doCancel) {
            processCancel();
        if (doCancelStatus != null) {
            processCancel(doCancelStatus);
        }
        if (vibratorsToProcess != null) {
            processVibratorsComplete(vibratorsToProcess);
@@ -438,12 +460,12 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
     * <p>This will remove all steps and replace them with respective results of
     * {@link Step#cancel()}.
     */
    public void processCancel() {
    public void processCancel(Vibration.Status cancelStatus) {
        if (Build.IS_DEBUGGABLE) {
            expectIsVibrationThread(true);
        }

        mCancelled = true;
        mCancelStatus = cancelStatus;
        // Vibrator callbacks should wait until all steps from the queue are properly cancelled
        // and clean up steps are added back to the queue, so they can handle the callback.
        List<Step> cleanUpSteps = new ArrayList<>();
@@ -461,13 +483,13 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
     *
     * <p>This will remove and trigger {@link Step#cancelImmediately()} in all steps, in order.
     */
    public void processCancelImmediately() {
    public void processCancelImmediately(Vibration.Status cancelStatus) {
        if (Build.IS_DEBUGGABLE) {
            expectIsVibrationThread(true);
        }

        mCancelledImmediately = true;
        mCancelled = true;
        mCancelStatus = cancelStatus;
        Step step;
        while ((step = pollNext()) != null) {
            step.cancelImmediately();
+15 −10
Original line number Diff line number Diff line
@@ -162,10 +162,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                    // When the system is entering a non-interactive state, we want to cancel
                    // vibrations in case a misbehaving app has abandoned them.
                    if (shouldCancelOnScreenOffLocked(mNextVibration)) {
                        clearNextVibrationLocked(Vibration.Status.CANCELLED);
                        clearNextVibrationLocked(Vibration.Status.CANCELLED_BY_SCREEN_OFF);
                    }
                    if (shouldCancelOnScreenOffLocked(mCurrentVibration)) {
                        mCurrentVibration.notifyCancelled(/* immediate= */ false);
                        mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_BY_SCREEN_OFF,
                                /* immediate= */ false);
                    }
                }
            }
@@ -426,7 +427,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                final long ident = Binder.clearCallingIdentity();
                try {
                    if (mCurrentVibration != null) {
                        mCurrentVibration.notifyCancelled(/* immediate= */ false);
                        mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED,
                                /* immediate= */ false);
                    }
                    Vibration.Status status = startVibrationLocked(vib);
                    if (status != Vibration.Status.RUNNING) {
@@ -459,19 +461,20 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                    if (mNextVibration != null
                            && shouldCancelVibration(mNextVibration.getVibration(),
                            usageFilter, token)) {
                        clearNextVibrationLocked(Vibration.Status.CANCELLED);
                        clearNextVibrationLocked(Vibration.Status.CANCELLED_BY_USER);
                    }
                    if (mCurrentVibration != null
                            && shouldCancelVibration(mCurrentVibration.getVibration(),
                            usageFilter, token)) {
                        mCurrentVibration.notifyCancelled(/* immediate= */false);
                        mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_BY_USER,
                                /* immediate= */false);
                    }
                    if (mCurrentExternalVibration != null
                            && shouldCancelVibration(
                            mCurrentExternalVibration.externalVibration.getVibrationAttributes(),
                            usageFilter)) {
                        mCurrentExternalVibration.externalVibration.mute();
                        endExternalVibrateLocked(Vibration.Status.CANCELLED,
                        endExternalVibrateLocked(Vibration.Status.CANCELLED_BY_USER,
                                /* continueExternalControl= */ false);
                    }
                } finally {
@@ -600,7 +603,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                    Slog.d(TAG, "Canceling vibration because settings changed: "
                            + (inputDevicesChanged ? "input devices changed" : ignoreStatus));
                }
                mCurrentVibration.notifyCancelled(/* immediate= */ false);
                mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE,
                        /* immediate= */ false);
            }
        }
    }
@@ -1319,7 +1323,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                    if (DEBUG) {
                        Slog.d(TAG, "External vibration finished because binder died");
                    }
                    endExternalVibrateLocked(Vibration.Status.CANCELLED,
                    endExternalVibrateLocked(Vibration.Status.CANCELLED_BINDER_DIED,
                            /* continueExternalControl= */ false);
                }
            }
@@ -1543,7 +1547,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                    // vibration that may be playing and ready the vibrator for external control.
                    if (mCurrentVibration != null) {
                        clearNextVibrationLocked(Vibration.Status.IGNORED_FOR_EXTERNAL);
                        mCurrentVibration.notifyCancelled(/* immediate= */ true);
                        mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED,
                                /* immediate= */ true);
                        waitForCompletion = true;
                    }
                } else {
@@ -1557,7 +1562,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                    // would need to mute the old one still if it came from a different controller.
                    alreadyUnderExternalControl = true;
                    mCurrentExternalVibration.externalVibration.mute();
                    endExternalVibrateLocked(Vibration.Status.CANCELLED,
                    endExternalVibrateLocked(Vibration.Status.CANCELLED_SUPERSEDED,
                            /* continueExternalControl= */ true);
                }
                mCurrentExternalVibration = new ExternalVibrationHolder(vib);
+34 −28
Original line number Diff line number Diff line
@@ -257,13 +257,13 @@ public class VibrationThreadTest {
        assertTrue(mThread.isRunningVibrationId(vibrationId));
        assertTrue(mControllers.get(VIBRATOR_ID).isVibrating());

        conductor.notifyCancelled(/* immediate= */ false);
        conductor.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ false);
        waitForCompletion();
        assertFalse(mThread.isRunningVibrationId(vibrationId));

        verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong());
        verify(mManagerHooks).noteVibratorOff(eq(UID));
        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_SUPERSEDED);
        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());

        List<Float> playedAmplitudes = fakeVibrator.getAmplitudes();
@@ -288,10 +288,10 @@ public class VibrationThreadTest {
        VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);

        assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS));
        conductor.notifyCancelled(/* immediate= */ false);
        conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
        waitForCompletion();

        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
        assertEquals(Arrays.asList(expectedOneShot(1000)),
                fakeVibrator.getEffectSegments(vibrationId));
@@ -310,10 +310,10 @@ public class VibrationThreadTest {
        VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);

        assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS));
        conductor.notifyCancelled(/* immediate= */ false);
        conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
        waitForCompletion();

        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
        assertEquals(Arrays.asList(expectedOneShot(5550)),
                fakeVibrator.getEffectSegments(vibrationId));
@@ -334,10 +334,10 @@ public class VibrationThreadTest {

        assertTrue(waitUntil(() -> fakeVibrator.getAmplitudes().size() > 2 * amplitudes.length,
                1000 + TEST_TIMEOUT_MILLIS));
        conductor.notifyCancelled(/* immediate= */ false);
        conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
        waitForCompletion();

        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
        assertEquals(2, fakeVibrator.getEffectSegments(vibrationId).size());
        // First time turn vibrator ON for minimum of 1s.
@@ -371,13 +371,14 @@ public class VibrationThreadTest {
        // 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(/* immediate= */ false));
                new Thread(() -> conductor.notifyCancelled(
                        Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE, /* immediate= */ false));
        cancellingThread.start();

        waitForCompletion(/* timeout= */ 50);
        cancellingThread.join();

        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE);
        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
    }

@@ -397,13 +398,14 @@ public class VibrationThreadTest {
        // 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(/* immediate= */ false));
                new Thread(() -> conductor.notifyCancelled(
                        Vibration.Status.CANCELLED_BY_SCREEN_OFF, /* immediate= */ false));
        cancellingThread.start();

        waitForCompletion(/* timeout= */ 50);
        cancellingThread.join();

        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
    }

@@ -647,7 +649,7 @@ public class VibrationThreadTest {
        waitForCompletion();
        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());

        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BINDER_DIED);
    }

    @Test
@@ -1043,7 +1045,8 @@ public class VibrationThreadTest {
        // 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(/* immediate= */ false));
                () -> conductor.notifyCancelled(
                        Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false));
        cancellingThread.start();

        // Cancelling the vibration should be fast and return right away, even if the thread is
@@ -1052,7 +1055,7 @@ public class VibrationThreadTest {

        // 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);
        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
    }

@@ -1080,13 +1083,14 @@ public class VibrationThreadTest {
        // 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(/* immediate= */ false));
                () -> conductor.notifyCancelled(
                        Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ false));
        cancellingThread.start();

        waitForCompletion(/* timeout= */ 50);
        cancellingThread.join();

        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_SUPERSEDED);
        assertFalse(mControllers.get(1).isVibrating());
        assertFalse(mControllers.get(2).isVibrating());
    }
@@ -1113,13 +1117,14 @@ public class VibrationThreadTest {
        // 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(/* immediate= */ false));
                new Thread(() -> conductor.notifyCancelled(
                        Vibration.Status.CANCELLED_BY_SCREEN_OFF, /* immediate= */ false));
        cancellingThread.start();

        waitForCompletion(/* timeout= */ 50);
        cancellingThread.join();

        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
        assertFalse(mControllers.get(1).isVibrating());
        assertFalse(mControllers.get(2).isVibrating());
    }
@@ -1139,7 +1144,7 @@ public class VibrationThreadTest {

        verify(mVibrationToken).linkToDeath(same(conductor), eq(0));
        verify(mVibrationToken).unlinkToDeath(same(conductor), eq(0));
        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BINDER_DIED);
        assertFalse(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).isEmpty());
        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
    }
@@ -1193,12 +1198,13 @@ public class VibrationThreadTest {
                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));

        // Will stop the ramp down right away.
        conductor.notifyCancelled(/* immediate= */ true);
        conductor.notifyCancelled(
                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(Vibration.Status.CANCELLED));
                eq(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE));
        verify(mManagerHooks).onVibrationThreadReleased(vibrationId);
    }

@@ -1214,10 +1220,10 @@ public class VibrationThreadTest {
        VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
        assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
                TEST_TIMEOUT_MILLIS));
        conductor.notifyCancelled(/* immediate= */ false);
        conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
        waitForCompletion();

        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);

        // Duration extended for 10000 + 15.
        assertEquals(Arrays.asList(expectedOneShot(10_015)),
@@ -1337,7 +1343,7 @@ public class VibrationThreadTest {
        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(/* immediate= */ false);
        conductor2.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
        waitForCompletion();

        startThreadAndDispatcher(vibrationId3, effect3);
@@ -1346,7 +1352,7 @@ public class VibrationThreadTest {
        // Effect4 is a long oneshot, but it gets cancelled as fast as possible.
        long start4 = System.currentTimeMillis();
        VibrationStepConductor conductor4 = startThreadAndDispatcher(vibrationId4, effect4);
        conductor4.notifyCancelled(/* immediate= */ true);
        conductor4.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ true);
        waitForCompletion();
        long duration4 = System.currentTimeMillis() - start4;

@@ -1366,7 +1372,7 @@ public class VibrationThreadTest {

        // Effect2: repeating, cancelled.
        verify(mControllerCallbacks, atLeast(2)).onComplete(VIBRATOR_ID, vibrationId2);
        verifyCallbacksTriggered(vibrationId2, Vibration.Status.CANCELLED);
        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.
@@ -1384,7 +1390,7 @@ public class VibrationThreadTest {
                fakeVibrator.getEffectSegments(vibrationId3));

        // Effect4: cancelled quickly.
        verifyCallbacksTriggered(vibrationId4, Vibration.Status.CANCELLED);
        verifyCallbacksTriggered(vibrationId4, Vibration.Status.CANCELLED_SUPERSEDED);
        assertTrue("Tested duration=" + duration4, duration4 < 2000);

        // Effect5: normal oneshot. Don't worry about amplitude, as effect4 may or may not have