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

Commit 220e3849 authored by Lais Andrade's avatar Lais Andrade Committed by Automerger Merge Worker
Browse files

Merge "Add cancelation reason to vibration status" into tm-dev am: 5955825c

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17298483

Change-Id: I928cb2285e64e905576ad9b6f9ee25b41a805e47
parents 9e7e0fd8 5955825c
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