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

Commit f1955f64 authored by Simon Bowden's avatar Simon Bowden Committed by Presubmit Automerger Backend
Browse files

[automerge] Don't take the conductor lock for syncing vibrations. 2p: f609ed9f

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

Bug: 193792066
Change-Id: Id9135bb4109419d4d32a271ae334d3a388a8f35b
parents c2ec66c3 f609ed9f
Loading
Loading
Loading
Loading
+148 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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 android.os.SystemClock;
import android.os.VibrationEffect;
import android.util.Slog;

import java.util.Arrays;
import java.util.List;

/**
 * Represent a step on a single vibrator that plays one or more segments from a
 * {@link VibrationEffect.Composed} effect.
 */
abstract class AbstractVibratorStep extends Step {
    public final VibratorController controller;
    public final VibrationEffect.Composed effect;
    public final int segmentIndex;
    public final long previousStepVibratorOffTimeout;

    long mVibratorOnResult;
    boolean mVibratorCompleteCallbackReceived;

    /**
     * @param conductor          The VibrationStepConductor for these steps.
     * @param startTime          The time to schedule this step in the
     *                           {@link VibrationStepConductor}.
     * @param controller         The vibrator that is playing the effect.
     * @param effect             The effect being played in this step.
     * @param index              The index of the next segment to be played by this step
     * @param previousStepVibratorOffTimeout The time the vibrator is expected to complete any
     *                           previous vibration and turn off. This is used to allow this step to
     *                           be triggered when the completion callback is received, and can
     *                           be used to play effects back-to-back.
     */
    AbstractVibratorStep(VibrationStepConductor conductor, long startTime,
            VibratorController controller, VibrationEffect.Composed effect, int index,
            long previousStepVibratorOffTimeout) {
        super(conductor, startTime);
        this.controller = controller;
        this.effect = effect;
        this.segmentIndex = index;
        this.previousStepVibratorOffTimeout = previousStepVibratorOffTimeout;
    }

    public int getVibratorId() {
        return controller.getVibratorInfo().getId();
    }

    @Override
    public long getVibratorOnDuration() {
        return mVibratorOnResult;
    }

    @Override
    public boolean acceptVibratorCompleteCallback(int vibratorId) {
        boolean isSameVibrator = controller.getVibratorInfo().getId() == vibratorId;
        mVibratorCompleteCallbackReceived |= isSameVibrator;
        // Only activate this step if a timeout was set to wait for the vibration to complete,
        // otherwise we are waiting for the correct time to play the next step.
        return isSameVibrator && (previousStepVibratorOffTimeout > SystemClock.uptimeMillis());
    }

    @Override
    public List<Step> cancel() {
        return Arrays.asList(new CompleteEffectVibratorStep(conductor, SystemClock.uptimeMillis(),
                /* cancelled= */ true, controller, previousStepVibratorOffTimeout));
    }

    @Override
    public void cancelImmediately() {
        if (previousStepVibratorOffTimeout > SystemClock.uptimeMillis()) {
            // Vibrator might be running from previous steps, so turn it off while canceling.
            stopVibrating();
        }
    }

    protected void stopVibrating() {
        if (VibrationThread.DEBUG) {
            Slog.d(VibrationThread.TAG,
                    "Turning off vibrator " + getVibratorId());
        }
        controller.off();
    }

    protected void changeAmplitude(float amplitude) {
        if (VibrationThread.DEBUG) {
            Slog.d(VibrationThread.TAG,
                    "Amplitude changed on vibrator " + getVibratorId() + " to " + amplitude);
        }
        controller.setAmplitude(amplitude);
    }

    /**
     * Return the {@link VibrationStepConductor#nextVibrateStep} with same timings, only jumping
     * the segments.
     */
    protected List<Step> skipToNextSteps(int segmentsSkipped) {
        return nextSteps(startTime, previousStepVibratorOffTimeout, segmentsSkipped);
    }

    /**
     * Return the {@link VibrationStepConductor#nextVibrateStep} with same start and off timings
     * calculated from {@link #getVibratorOnDuration()}, jumping all played segments.
     *
     * <p>This method has same behavior as {@link #skipToNextSteps(int)} when the vibrator
     * result is non-positive, meaning the vibrator has either ignored or failed to turn on.
     */
    protected List<Step> nextSteps(int segmentsPlayed) {
        if (mVibratorOnResult <= 0) {
            // Vibration was not started, so just skip the played segments and keep timings.
            return skipToNextSteps(segmentsPlayed);
        }
        long nextStartTime = SystemClock.uptimeMillis() + mVibratorOnResult;
        long nextVibratorOffTimeout =
                nextStartTime + VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT;
        return nextSteps(nextStartTime, nextVibratorOffTimeout, segmentsPlayed);
    }

    /**
     * Return the {@link VibrationStepConductor#nextVibrateStep} with given start and off timings,
     * which might be calculated independently, jumping all played segments.
     *
     * <p>This should be used when the vibrator on/off state is not responsible for the steps
     * execution timings, e.g. while playing the vibrator amplitudes.
     */
    protected List<Step> nextSteps(long nextStartTime, long vibratorOffTimeout,
            int segmentsPlayed) {
        Step nextStep = conductor.nextVibrateStep(nextStartTime, controller, effect,
                segmentIndex + segmentsPlayed, vibratorOffTimeout);
        return nextStep == null ? VibrationStepConductor.EMPTY_STEP_LIST : Arrays.asList(nextStep);
    }
}
+114 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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 android.os.SystemClock;
import android.os.Trace;
import android.os.VibrationEffect;
import android.util.Slog;

import java.util.Arrays;
import java.util.List;

/**
 * Represents a step to complete a {@link VibrationEffect}.
 *
 * <p>This runs right at the time the vibration is considered to end and will update the pending
 * vibrators count. This can turn off the vibrator or slowly ramp it down to zero amplitude.
 */
final class CompleteEffectVibratorStep extends AbstractVibratorStep {
    private final boolean mCancelled;

    CompleteEffectVibratorStep(VibrationStepConductor conductor, long startTime, boolean cancelled,
            VibratorController controller, long previousStepVibratorOffTimeout) {
        super(conductor, startTime, controller, /* effect= */ null, /* index= */ -1,
                previousStepVibratorOffTimeout);
        mCancelled = cancelled;
    }

    @Override
    public boolean isCleanUp() {
        // If the vibration was cancelled then this is just a clean up to ramp off the vibrator.
        // Otherwise this step is part of the vibration.
        return mCancelled;
    }

    @Override
    public List<Step> cancel() {
        if (mCancelled) {
            // Double cancelling will just turn off the vibrator right away.
            return Arrays.asList(
                    new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(), controller));
        }
        return super.cancel();
    }

    @Override
    public List<Step> play() {
        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "CompleteEffectVibratorStep");
        try {
            if (VibrationThread.DEBUG) {
                Slog.d(VibrationThread.TAG,
                        "Running " + (mCancelled ? "cancel" : "complete") + " vibration"
                                + " step on vibrator " + controller.getVibratorInfo().getId());
            }
            if (mVibratorCompleteCallbackReceived) {
                // Vibration completion callback was received by this step, just turn if off
                // and skip any clean-up.
                stopVibrating();
                return VibrationStepConductor.EMPTY_STEP_LIST;
            }

            float currentAmplitude = controller.getCurrentAmplitude();
            long remainingOnDuration =
                    previousStepVibratorOffTimeout - VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT
                            - SystemClock.uptimeMillis();
            long rampDownDuration =
                    Math.min(remainingOnDuration,
                            conductor.vibrationSettings.getRampDownDuration());
            long stepDownDuration = conductor.vibrationSettings.getRampStepDuration();
            if (currentAmplitude < VibrationStepConductor.RAMP_OFF_AMPLITUDE_MIN
                    || rampDownDuration <= stepDownDuration) {
                // No need to ramp down the amplitude, just wait to turn it off.
                if (mCancelled) {
                    // Vibration is completing because it was cancelled, turn off right away.
                    stopVibrating();
                    return VibrationStepConductor.EMPTY_STEP_LIST;
                } else {
                    return Arrays.asList(new TurnOffVibratorStep(
                            conductor, previousStepVibratorOffTimeout, controller));
                }
            }

            if (VibrationThread.DEBUG) {
                Slog.d(VibrationThread.TAG,
                        "Ramping down vibrator " + controller.getVibratorInfo().getId()
                                + " from amplitude " + currentAmplitude
                                + " for " + rampDownDuration + "ms");
            }
            float amplitudeDelta = currentAmplitude / (rampDownDuration / stepDownDuration);
            float amplitudeTarget = currentAmplitude - amplitudeDelta;
            long newVibratorOffTimeout =
                    mCancelled ? rampDownDuration : previousStepVibratorOffTimeout;
            return Arrays.asList(
                    new RampOffVibratorStep(conductor, startTime, amplitudeTarget, amplitudeDelta,
                            controller, newVibratorOffTimeout));
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
        }
    }
}
+84 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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 android.os.Trace;
import android.os.VibrationEffect;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.VibrationEffectSegment;
import android.util.Slog;

import java.util.ArrayList;
import java.util.List;

/**
 * Represents a step to turn the vibrator on using a composition of primitives.
 *
 * <p>This step will use the maximum supported number of consecutive segments of type
 * {@link PrimitiveSegment} starting at the current index.
 */
final class ComposePrimitivesVibratorStep extends AbstractVibratorStep {

    ComposePrimitivesVibratorStep(VibrationStepConductor conductor, long startTime,
            VibratorController controller, VibrationEffect.Composed effect, int index,
            long previousStepVibratorOffTimeout) {
        // This step should wait for the last vibration to finish (with the timeout) and for the
        // intended step start time (to respect the effect delays).
        super(conductor, Math.max(startTime, previousStepVibratorOffTimeout), controller, effect,
                index, previousStepVibratorOffTimeout);
    }

    @Override
    public List<Step> play() {
        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePrimitivesStep");
        try {
            // Load the next PrimitiveSegments to create a single compose call to the vibrator,
            // limited to the vibrator composition maximum size.
            int limit = controller.getVibratorInfo().getCompositionSizeMax();
            int segmentCount = limit > 0
                    ? Math.min(effect.getSegments().size(), segmentIndex + limit)
                    : effect.getSegments().size();
            List<PrimitiveSegment> primitives = new ArrayList<>();
            for (int i = segmentIndex; i < segmentCount; i++) {
                VibrationEffectSegment segment = effect.getSegments().get(i);
                if (segment instanceof PrimitiveSegment) {
                    primitives.add((PrimitiveSegment) segment);
                } else {
                    break;
                }
            }

            if (primitives.isEmpty()) {
                Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePrimitivesStep: "
                        + effect.getSegments().get(segmentIndex));
                return skipToNextSteps(/* segmentsSkipped= */ 1);
            }

            if (VibrationThread.DEBUG) {
                Slog.d(VibrationThread.TAG, "Compose " + primitives + " primitives on vibrator "
                        + controller.getVibratorInfo().getId());
            }
            mVibratorOnResult = controller.on(
                    primitives.toArray(new PrimitiveSegment[primitives.size()]),
                    getVibration().id);

            return nextSteps(/* segmentsPlayed= */ primitives.size());
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
        }
    }
}
+84 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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 android.os.Trace;
import android.os.VibrationEffect;
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
import android.util.Slog;

import java.util.ArrayList;
import java.util.List;

/**
 * Represents a step to turn the vibrator on using a composition of PWLE segments.
 *
 * <p>This step will use the maximum supported number of consecutive segments of type
 * {@link StepSegment} or {@link RampSegment} starting at the current index.
 */
final class ComposePwleVibratorStep extends AbstractVibratorStep {

    ComposePwleVibratorStep(VibrationStepConductor conductor, long startTime,
            VibratorController controller, VibrationEffect.Composed effect, int index,
            long previousStepVibratorOffTimeout) {
        // This step should wait for the last vibration to finish (with the timeout) and for the
        // intended step start time (to respect the effect delays).
        super(conductor, Math.max(startTime, previousStepVibratorOffTimeout), controller, effect,
                index, previousStepVibratorOffTimeout);
    }

    @Override
    public List<Step> play() {
        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePwleStep");
        try {
            // Load the next RampSegments to create a single composePwle call to the vibrator,
            // limited to the vibrator PWLE maximum size.
            int limit = controller.getVibratorInfo().getPwleSizeMax();
            int segmentCount = limit > 0
                    ? Math.min(effect.getSegments().size(), segmentIndex + limit)
                    : effect.getSegments().size();
            List<RampSegment> pwles = new ArrayList<>();
            for (int i = segmentIndex; i < segmentCount; i++) {
                VibrationEffectSegment segment = effect.getSegments().get(i);
                if (segment instanceof RampSegment) {
                    pwles.add((RampSegment) segment);
                } else {
                    break;
                }
            }

            if (pwles.isEmpty()) {
                Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePwleStep: "
                        + effect.getSegments().get(segmentIndex));
                return skipToNextSteps(/* segmentsSkipped= */ 1);
            }

            if (VibrationThread.DEBUG) {
                Slog.d(VibrationThread.TAG, "Compose " + pwles + " PWLEs on vibrator "
                        + controller.getVibratorInfo().getId());
            }
            mVibratorOnResult = controller.on(pwles.toArray(new RampSegment[pwles.size()]),
                    getVibration().id);

            return nextSteps(/* segmentsPlayed= */ pwles.size());
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
        }
    }
}
+73 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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 android.os.Trace;
import android.util.Slog;

import java.util.Arrays;
import java.util.List;

/**
 * Finish a sync vibration started by a {@link StartSequentialEffectStep}.
 *
 * <p>This only plays after all active vibrators steps have finished, and adds a {@link
 * StartSequentialEffectStep} to the queue if the sequential effect isn't finished yet.
 */
final class FinishSequentialEffectStep extends Step {
    public final StartSequentialEffectStep startedStep;

    FinishSequentialEffectStep(StartSequentialEffectStep startedStep) {
        // No predefined startTime, just wait for all steps in the queue.
        super(startedStep.conductor, Long.MAX_VALUE);
        this.startedStep = startedStep;
    }

    @Override
    public boolean isCleanUp() {
        // This step only notes that all the vibrators has been turned off.
        return true;
    }

    @Override
    public List<Step> play() {
        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "FinishSequentialEffectStep");
        try {
            if (VibrationThread.DEBUG) {
                Slog.d(VibrationThread.TAG,
                        "FinishSequentialEffectStep for effect #" + startedStep.currentIndex);
            }
            conductor.vibratorManagerHooks.noteVibratorOff(conductor.getVibration().uid);
            Step nextStep = startedStep.nextStep();
            return nextStep == null ? VibrationStepConductor.EMPTY_STEP_LIST
                    : Arrays.asList(nextStep);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
        }
    }

    @Override
    public List<Step> cancel() {
        cancelImmediately();
        return VibrationStepConductor.EMPTY_STEP_LIST;
    }

    @Override
    public void cancelImmediately() {
        conductor.vibratorManagerHooks.noteVibratorOff(conductor.getVibration().uid);
    }
}
Loading