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

Commit d4187fdd authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Fix VibratorService waveform accumulated delay"

parents 97c97911 934559ac
Loading
Loading
Loading
Loading
+26 −24
Original line number Diff line number Diff line
@@ -1794,25 +1794,20 @@ public class VibratorService extends IVibratorService.Stub
            mWakeLock.setWorkSource(mTmpWorkSource);
        }

        private long delayLocked(long duration) {
        private void delayLocked(long wakeUpTime) {
            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "delayLocked");
            try {
                long durationRemaining = duration;
                if (duration > 0) {
                    final long bedtime = duration + SystemClock.uptimeMillis();
                    do {
                long durationRemaining = wakeUpTime - SystemClock.uptimeMillis();
                while (durationRemaining > 0) {
                    try {
                        this.wait(durationRemaining);
                    } catch (InterruptedException e) {
                    }
                        catch (InterruptedException e) { }
                    if (mForceStop) {
                        break;
                    }
                        durationRemaining = bedtime - SystemClock.uptimeMillis();
                    } while (durationRemaining > 0);
                    return duration - durationRemaining;
                    durationRemaining = wakeUpTime - SystemClock.uptimeMillis();
                }
                return 0;
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
            }
@@ -1846,7 +1841,8 @@ public class VibratorService extends IVibratorService.Stub
                    final int repeat = mWaveform.getRepeatIndex();

                    int index = 0;
                    long onDuration = 0;
                    long nextStepStartTime = SystemClock.uptimeMillis();
                    long nextVibratorStopTime = 0;
                    while (!mForceStop) {
                        if (index < len) {
                            final int amplitude = amplitudes[index];
@@ -1855,27 +1851,33 @@ public class VibratorService extends IVibratorService.Stub
                                continue;
                            }
                            if (amplitude != 0) {
                                if (onDuration <= 0) {
                                long now = SystemClock.uptimeMillis();
                                if (nextVibratorStopTime <= now) {
                                    // Telling the vibrator to start multiple times usually causes
                                    // effects to feel "choppy" because the motor resets at every on
                                    // command.  Instead we figure out how long our next "on" period
                                    // is going to be, tell the motor to stay on for the full
                                    // duration, and then wake up to change the amplitude at the
                                    // appropriate intervals.
                                    onDuration = getTotalOnDuration(timings, amplitudes, index - 1,
                                            repeat);
                                    long onDuration = getTotalOnDuration(
                                            timings, amplitudes, index - 1, repeat);
                                    mVibration.effect = VibrationEffect.createOneShot(
                                            onDuration, amplitude);
                                    doVibratorOn(mVibration);
                                    nextVibratorStopTime = now + onDuration;
                                } else {
                                    // Vibrator is already ON, so just change its amplitude.
                                    doVibratorSetAmplitude(amplitude);
                                }
                            }

                            long waitTime = delayLocked(duration);
                            if (amplitude != 0) {
                                onDuration -= waitTime;
                            }
                            // We wait until the time this waveform step was supposed to end,
                            // calculated from the time it was supposed to start. All start times
                            // are calculated from the waveform original start time by adding the
                            // input durations. Any scheduling or processing delay should not affect
                            // this step's perceived total duration. They will be amortized here.
                            nextStepStartTime += duration;
                            delayLocked(nextStepStartTime);
                        } else if (repeat < 0) {
                            break;
                        } else {
+72 −5
Original line number Diff line number Diff line
@@ -52,6 +52,8 @@ import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
@@ -76,7 +78,11 @@ import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Tests for {@link VibratorService}.
@@ -443,6 +449,42 @@ public class VibratorServiceTest {
        verify(mNativeWrapperMock).vibratorSetAmplitude(eq(50));
    }

    @Test
    public void vibrate_withWaveform_totalVibrationTimeRespected() throws Exception {
        int totalDuration = 10_000; // 10s
        int stepDuration = 25; // 25ms

        // 25% of the first waveform step will be spent on the native on() call.
        mockVibratorCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
        doAnswer(invocation -> {
            Thread.currentThread().sleep(stepDuration / 4);
            return null;
        }).when(mNativeWrapperMock).vibratorOn(anyLong(), anyLong());
        // 25% of each waveform step will be spent on the native setAmplitude() call..
        doAnswer(invocation -> {
            Thread.currentThread().sleep(stepDuration / 4);
            return null;
        }).when(mNativeWrapperMock).vibratorSetAmplitude(anyInt());

        VibratorService service = createService();

        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);

        int perceivedDuration = vibrateAndMeasure(service, effect, /* timeoutSecs= */ 15);
        int delay = Math.abs(perceivedDuration - 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);
    }

    @Test
    public void vibrate_withOneShotAndNativeCallbackTriggered_finishesVibration() {
        VibratorService service = createService();
@@ -699,16 +741,41 @@ public class VibratorServiceTest {
        vibrate(service, effect, ALARM_ATTRS);
    }

    private void vibrate(VibratorService service, VibrationEffect effect, AudioAttributes attrs) {
        VibrationAttributes attributes = new VibrationAttributes.Builder(attrs, effect).build();
        vibrate(service, effect, attributes);
    }

    private void vibrate(VibratorService service, VibrationEffect effect,
            VibrationAttributes attributes) {
        service.vibrate(UID, PACKAGE_NAME, effect, attributes, "some reason", service);
    }

    private int vibrateAndMeasure(
            VibratorService service, VibrationEffect effect, long timeoutSecs) throws Exception {
        AtomicLong startTime = new AtomicLong(0);
        AtomicLong endTime = new AtomicLong(0);
        CountDownLatch startedCount = new CountDownLatch(1);
        CountDownLatch finishedCount = new CountDownLatch(1);
        service.registerVibratorStateListener(new IVibratorStateListener() {
            @Override
            public void onVibrating(boolean vibrating) throws RemoteException {
                if (vibrating) {
                    startTime.set(SystemClock.uptimeMillis());
                    startedCount.countDown();
                } else if (startedCount.getCount() == 0) {
                    endTime.set(SystemClock.uptimeMillis());
                    finishedCount.countDown();
                }
            }

            @Override
            public IBinder asBinder() {
                return mVibratorStateListenerBinderMock;
            }
        });

        vibrate(service, effect);

        assertTrue(finishedCount.await(timeoutSecs, TimeUnit.SECONDS));
        return (int) (endTime.get() - startTime.get());
    }

    private void mockVibratorCapabilities(int capabilities) {
        when(mNativeWrapperMock.vibratorGetCapabilities()).thenReturn((long) capabilities);
    }