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

Commit 7ca22de1 authored by Simon Bowden's avatar Simon Bowden
Browse files

Extract Steps and StepQueue out of VibrationThread.

This is groundwork for making VibrationThread long-lived, executing
vibrations managed by a VibrationStepConductor(StepQueue) in turn. The
purpose is to separate state from the two, but I thought we might as
well pull the steps right out to the top-level rather than moving them
to be nested in the conductor, as it's easier to navigate conductor code
then.

Renames:
 - StepQueue to VibrationStepConductor
 - SingleVibratorStep to AbstractVibratorStep
 - all subclasses of SingleVibratorStep to be called
   SomethingVibratorStep.
 - The non-VibratorStep Steps are now SomethingSequentialEffectStep.

This CL is intended to have no logic changes - just path-to-access, but
needs to move fields and methods around a lot, and change visibility to
make it work. In some cases, the field visibility arrangement is not
great (especially the lock used in VibrationThread), but the worst of
those will change in a follow-up that will detach VibrationStepConductor
from VibrationThread that will require non-trivial changes.

One small refactor for neatness was to move "noteVibratorOn/Off" into
the VibratorManagerHooks so that the battery stats service itself isn't
needed.

Bug: 193792066
Test: atest
Change-Id: Iabed5a057b0e0f65450c9ed9ef01186055f21907
parent 45a07122
Loading
Loading
Loading
Loading
+148 −0
Original line number Original line 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 Original line 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 Original line 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 Original line 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 Original line 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