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

Commit c9f99730 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Add step down ramp to vibration waveforms" into sc-dev am: 50471a76

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

Change-Id: I41cf2d1b9c7820ec7af37361fc14ab88ef56291a
parents 168402f8 50471a76
Loading
Loading
Loading
Loading
+4 −6
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package com.android.server.vibrator;

import android.content.Context;
import android.os.VibrationEffect;
import android.os.VibratorInfo;

@@ -29,14 +28,13 @@ final class DeviceVibrationEffectAdapter

    private final List<VibrationEffectAdapters.SegmentsAdapter<VibratorInfo>> mSegmentAdapters;

    DeviceVibrationEffectAdapter(Context context) {
    DeviceVibrationEffectAdapter(VibrationSettings settings) {
        mSegmentAdapters = Arrays.asList(
                // TODO(b/167947076): add filter that removes unsupported primitives
                // TODO(b/167947076): add filter that replaces unsupported prebaked with fallback
                new RampToStepAdapter(context.getResources().getInteger(
                        com.android.internal.R.integer.config_vibrationWaveformRampStepDuration)),
                new StepToRampAdapter(context.getResources().getInteger(
                        com.android.internal.R.integer.config_vibrationWaveformRampDownDuration)),
                new RampToStepAdapter(settings.getRampStepDuration()),
                new StepToRampAdapter(),
                new RampDownAdapter(settings.getRampDownDuration(), settings.getRampStepDuration()),
                new ClippingAmplitudeAndFrequencyAdapter()
        );
    }
+240 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.VibratorInfo;
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;

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

/**
 * Adapter that applies the ramp down duration config to bring down the vibrator amplitude smoothly.
 *
 * <p>This prevents the device from ringing when it cannot handle abrupt changes between ON and OFF
 * states. This will not change other types of abrupt amplitude changes in the original effect. The
 * effect overall duration is preserved by this transformation.
 *
 * <p>Waveforms with ON/OFF segments are handled gracefully by the ramp down changes. Each OFF
 * segment preceded by an ON segment will be shortened, and a ramp or step down will be added to the
 * transition between ON and OFF. The ramps/steps can be shorter than the configured duration in
 * order to preserve the waveform  timings, but they will still soften the ringing effect.
 *
 * <p>If the segment preceding an OFF segment a {@link RampSegment} then a new ramp segment will be
 * added to bring the amplitude down. If it is a {@link StepSegment} then a sequence of steps will
 * be used to bring the amplitude down to zero. This ensures that the transition from the last
 * amplitude to zero will be handled by the same vibrate method.
 */
final class RampDownAdapter implements VibrationEffectAdapters.SegmentsAdapter<VibratorInfo> {
    private final int mRampDownDuration;
    private final int mStepDuration;

    RampDownAdapter(int rampDownDuration, int stepDuration) {
        mRampDownDuration = rampDownDuration;
        mStepDuration = stepDuration;
    }

    @Override
    public int apply(List<VibrationEffectSegment> segments, int repeatIndex,
            VibratorInfo info) {
        if (mRampDownDuration <= 0) {
            // Nothing to do, no ramp down duration configured.
            return repeatIndex;
        }
        repeatIndex = addRampDownToZeroAmplitudeSegments(segments, repeatIndex);
        repeatIndex = addRampDownToLoop(segments, repeatIndex);
        return repeatIndex;
    }

    /**
     * This will add ramp or steps down to zero as follows:
     *
     * <ol>
     *     <li>Remove the OFF segment that follows a segment of non-zero amplitude;
     *     <li>Add a single {@link RampSegment} or a list of {@link StepSegment} starting at the
     *         previous segment's amplitude and frequency, with min between the configured ramp down
     *         duration or the removed segment's duration;
     *     <li>Add a zero amplitude segment following the steps, if necessary, to fill the remaining
     *         duration;
     * </ol>
     */
    private int addRampDownToZeroAmplitudeSegments(List<VibrationEffectSegment> segments,
            int repeatIndex) {
        int newRepeatIndex = repeatIndex;
        int newSegmentCount = segments.size();
        for (int i = 1; i < newSegmentCount; i++) {
            VibrationEffectSegment previousSegment = segments.get(i - 1);
            if (!isOffSegment(segments.get(i))
                    || !endsWithNonZeroAmplitude(previousSegment)) {
                continue;
            }

            List<VibrationEffectSegment> replacementSegments = null;
            long offDuration = segments.get(i).getDuration();

            if (previousSegment instanceof StepSegment) {
                float previousAmplitude = ((StepSegment) previousSegment).getAmplitude();
                float previousFrequency = ((StepSegment) previousSegment).getFrequency();

                replacementSegments =
                        createStepsDown(previousAmplitude, previousFrequency, offDuration);
            } else if (previousSegment instanceof RampSegment) {
                float previousAmplitude = ((RampSegment) previousSegment).getEndAmplitude();
                float previousFrequency = ((RampSegment) previousSegment).getEndFrequency();

                if (offDuration <= mRampDownDuration) {
                    // Replace the zero amplitude segment with a ramp down of same duration, to
                    // preserve waveform timings and still soften the transition to zero.
                    replacementSegments = Arrays.asList(
                            createRampDown(previousAmplitude, previousFrequency, offDuration));
                } else {
                    // Replace the zero amplitude segment with a ramp down of configured duration
                    // followed by a shorter off segment.
                    replacementSegments = Arrays.asList(
                            createRampDown(previousAmplitude, previousFrequency, mRampDownDuration),
                            createRampDown(0, previousFrequency, offDuration - mRampDownDuration));
                }
            }

            if (replacementSegments != null) {
                int segmentsAdded = replacementSegments.size() - 1;

                segments.remove(i);
                segments.addAll(i, replacementSegments);
                if (repeatIndex > i) {
                    newRepeatIndex += segmentsAdded;
                }
                i += segmentsAdded;
                newSegmentCount += segmentsAdded;
            }
        }
        return newRepeatIndex;
    }

    /**
     * This will ramps down to zero at the repeating index of the given effect, if set, only if
     * the last segment ends at a non-zero amplitude and the repeating segment has zero amplitude.
     * The update is described as:
     *
     * <ol>
     *     <li>Add a ramp or sequence of steps down to zero following the last segment, with the min
     *         between the removed segment duration and the configured ramp down duration;
     *     <li>Skip the zero-amplitude segment by incrementing the repeat index, splitting it if
     *         necessary to skip the correct amount;
     * </ol>
     */
    private int addRampDownToLoop(List<VibrationEffectSegment> segments, int repeatIndex) {
        if (repeatIndex < 0) {
            // Nothing to do, no ramp down duration configured or effect is not repeating.
            return repeatIndex;
        }

        int segmentCount = segments.size();
        if (!endsWithNonZeroAmplitude(segments.get(segmentCount - 1))
                || !isOffSegment(segments.get(repeatIndex))) {
            // Nothing to do, not going back from a positive amplitude to a off segment.
            return repeatIndex;
        }

        VibrationEffectSegment lastSegment = segments.get(segmentCount - 1);
        VibrationEffectSegment offSegment = segments.get(repeatIndex);
        long offDuration = offSegment.getDuration();

        if (offDuration > mRampDownDuration) {
            // Split the zero amplitude segment and start repeating from the second half, to
            // preserve waveform timings. This will update the waveform as follows:
            //  R              R+1
            //  |   ____        |  ____
            // _|__/       => __|_/    \
            segments.set(repeatIndex, updateDuration(offSegment, offDuration - mRampDownDuration));
            segments.add(repeatIndex, updateDuration(offSegment, mRampDownDuration));
        }

        // Skip the zero amplitude segment and append ramp/steps down at the end.
        repeatIndex++;
        if (lastSegment instanceof StepSegment) {
            float previousAmplitude = ((StepSegment) lastSegment).getAmplitude();
            float previousFrequency = ((StepSegment) lastSegment).getFrequency();
            segments.addAll(createStepsDown(previousAmplitude, previousFrequency,
                    Math.min(offDuration, mRampDownDuration)));
        } else if (lastSegment instanceof RampSegment) {
            float previousAmplitude = ((RampSegment) lastSegment).getEndAmplitude();
            float previousFrequency = ((RampSegment) lastSegment).getEndFrequency();
            segments.add(createRampDown(previousAmplitude, previousFrequency,
                    Math.min(offDuration, mRampDownDuration)));
        }

        return repeatIndex;
    }

    private List<VibrationEffectSegment> createStepsDown(float amplitude, float frequency,
            long duration) {
        // Step down for at most the configured ramp duration.
        int stepCount = (int) Math.min(duration, mRampDownDuration) / mStepDuration;
        float amplitudeStep = amplitude / stepCount;
        List<VibrationEffectSegment> steps = new ArrayList<>();
        for (int i = 1; i < stepCount; i++) {
            steps.add(new StepSegment(amplitude - i * amplitudeStep, frequency, mStepDuration));
        }
        int remainingDuration = (int) duration - mStepDuration * (stepCount - 1);
        steps.add(new StepSegment(0, frequency, remainingDuration));
        return steps;
    }

    private static RampSegment createRampDown(float amplitude, float frequency, long duration) {
        return new RampSegment(amplitude, /* endAmplitude= */ 0, frequency, frequency,
                (int) duration);
    }

    private static VibrationEffectSegment updateDuration(VibrationEffectSegment segment,
            long newDuration) {
        if (segment instanceof RampSegment) {
            RampSegment ramp = (RampSegment) segment;
            return new RampSegment(ramp.getStartAmplitude(), ramp.getEndAmplitude(),
                    ramp.getStartFrequency(), ramp.getEndFrequency(), (int) newDuration);
        } else if (segment instanceof StepSegment) {
            StepSegment step = (StepSegment) segment;
            return new StepSegment(step.getAmplitude(), step.getFrequency(), (int) newDuration);
        }
        return segment;
    }

    /** Returns true if the segment is a ramp or a step that starts and ends at zero amplitude. */
    private static boolean isOffSegment(VibrationEffectSegment segment) {
        if (segment instanceof StepSegment) {
            StepSegment ramp = (StepSegment) segment;
            return ramp.getAmplitude() == 0;
        } else if (segment instanceof RampSegment) {
            RampSegment ramp = (RampSegment) segment;
            return ramp.getStartAmplitude() == 0 && ramp.getEndAmplitude() == 0;
        }
        return false;
    }

    /** Returns true if the segment is a ramp or a step that ends at a non-zero amplitude. */
    private static boolean endsWithNonZeroAmplitude(VibrationEffectSegment segment) {
        if (segment instanceof StepSegment) {
            return ((StepSegment) segment).getAmplitude() != 0;
        } else if (segment instanceof RampSegment) {
            return ((RampSegment) segment).getEndAmplitude() != 0;
        }
        return false;
    }
}
+6 −175
Original line number Diff line number Diff line
@@ -31,25 +31,9 @@ import java.util.List;
 * <p>Each replaced {@link StepSegment} will be represented by a {@link RampSegment} with same
 * start and end amplitudes/frequencies, which can then be converted to PWLE compositions. This
 * adapter leaves the segments unchanged if the device doesn't have the PWLE composition capability.
 *
 * <p>This adapter also applies the ramp down duration config on devices with PWLE support. This
 * prevents the device from ringing when it cannot handle abrupt changes between ON and OFF states.
 * This will not change other types of abrupt amplitude changes in the original effect.
 *
 * <p>The effect overall duration is preserved by this transformation. Waveforms with ON/OFF
 * segments are handled gracefully by the ramp down changes. Each OFF segment preceded by an ON
 * segment will be shortened, and a ramp down will be added to the transition between ON and OFF.
 * The ramps can be shorter than the configured duration in order to preserve the waveform timings,
 * but they will still soften the ringing effect.
 */
final class StepToRampAdapter implements VibrationEffectAdapters.SegmentsAdapter<VibratorInfo> {

    private final int mRampDownDuration;

    StepToRampAdapter(int rampDownDuration) {
        mRampDownDuration = rampDownDuration;
    }

    @Override
    public int apply(List<VibrationEffectSegment> segments, int repeatIndex,
            VibratorInfo info) {
@@ -58,28 +42,17 @@ final class StepToRampAdapter implements VibrationEffectAdapters.SegmentsAdapter
            return repeatIndex;
        }
        convertStepsToRamps(segments);
        int newRepeatIndex = addRampDownToZeroAmplitudeSegments(segments, repeatIndex);
        newRepeatIndex = addRampDownToLoop(segments, newRepeatIndex);
        newRepeatIndex = splitLongRampSegments(info, segments, newRepeatIndex);
        return newRepeatIndex;
        repeatIndex = splitLongRampSegments(info, segments, repeatIndex);
        return repeatIndex;
    }

    private void convertStepsToRamps(List<VibrationEffectSegment> segments) {
        int segmentCount = segments.size();
        if (mRampDownDuration > 0) {
            // Convert all steps to ramps if the device requires ramp down.
            for (int i = 0; i < segmentCount; i++) {
                if (isStep(segments.get(i))) {
                    segments.set(i, apply((StepSegment) segments.get(i)));
                }
            }
            return;
        }
        // Convert steps that require frequency control to ramps.
        for (int i = 0; i < segmentCount; i++) {
            VibrationEffectSegment segment = segments.get(i);
            if (isStep(segment) && ((StepSegment) segment).getFrequency() != 0) {
                segments.set(i, apply((StepSegment) segment));
                segments.set(i, convertStepToRamp((StepSegment) segment));
            }
        }
        // Convert steps that are next to ramps to also become ramps, so they can be composed
@@ -87,129 +60,13 @@ final class StepToRampAdapter implements VibrationEffectAdapters.SegmentsAdapter
        for (int i = 0; i < segmentCount; i++) {
            if (segments.get(i) instanceof RampSegment) {
                for (int j = i - 1; j >= 0 && isStep(segments.get(j)); j--) {
                    segments.set(j, apply((StepSegment) segments.get(j)));
                    segments.set(j, convertStepToRamp((StepSegment) segments.get(j)));
                }
                for (int j = i + 1; j < segmentCount && isStep(segments.get(j)); j++) {
                    segments.set(j, apply((StepSegment) segments.get(j)));
                }
            }
        }
    }

    /**
     * This will add a ramp to zero as follows:
     *
     * <ol>
     *     <li>Remove the {@link VibrationEffectSegment} that starts and ends at zero amplitude
     *         and follows a segment that ends at non-zero amplitude;
     *     <li>Add a ramp down to zero starting at the previous segment end amplitude and frequency,
     *         with min between the removed segment duration and the configured ramp down duration;
     *     <li>Add a zero amplitude segment following the ramp with the remaining duration, if
     *         necessary;
     * </ol>
     */
    private int addRampDownToZeroAmplitudeSegments(List<VibrationEffectSegment> segments,
            int repeatIndex) {
        if (mRampDownDuration <= 0) {
            // Nothing to do, no ramp down duration configured.
            return repeatIndex;
        }
        int newRepeatIndex = repeatIndex;
        int newSegmentCount = segments.size();
        for (int i = 1; i < newSegmentCount; i++) {
            if (!isOffRampSegment(segments.get(i))
                    || !endsWithNonZeroAmplitude(segments.get(i - 1))) {
                continue;
                    segments.set(j, convertStepToRamp((StepSegment) segments.get(j)));
                }

            // We know the previous segment is a ramp that ends at non-zero amplitude.
            float previousAmplitude = ((RampSegment) segments.get(i - 1)).getEndAmplitude();
            float previousFrequency = ((RampSegment) segments.get(i - 1)).getEndFrequency();
            RampSegment ramp = (RampSegment) segments.get(i);

            if (ramp.getDuration() <= mRampDownDuration) {
                // Replace the zero amplitude segment with a ramp down of same duration, to
                // preserve waveform timings and still soften the transition to zero.
                segments.set(i, createRampDown(previousAmplitude, previousFrequency,
                        ramp.getDuration()));
            } else {
                // Make the zero amplitude segment shorter, to preserve waveform timings, and add a
                // ramp down to zero segment right before it.
                segments.set(i, updateDuration(ramp, ramp.getDuration() - mRampDownDuration));
                segments.add(i, createRampDown(previousAmplitude, previousFrequency,
                        mRampDownDuration));
                if (repeatIndex > i) {
                    newRepeatIndex++;
                }
                i++;
                newSegmentCount++;
            }
        }
        return newRepeatIndex;
    }

    /**
     * This will add a ramp to zero at the repeating index of the given effect, if set, only if
     * the last segment ends at a non-zero amplitude and the repeating segment starts and ends at
     * zero amplitude. The update is described as:
     *
     * <ol>
     *     <li>Add a ramp down to zero following the last segment, with the min between the
     *         removed segment duration and the configured ramp down duration;
     *     <li>Skip the zero-amplitude segment by incrementing the repeat index, splitting it if
     *         necessary to skip the correct amount;
     * </ol>
     */
    private int addRampDownToLoop(List<VibrationEffectSegment> segments, int repeatIndex) {
        if (repeatIndex < 0) {
            // Non-repeating compositions should remain unchanged so duration will be preserved.
            return repeatIndex;
        }

        int segmentCount = segments.size();
        if (mRampDownDuration <= 0 || !endsWithNonZeroAmplitude(segments.get(segmentCount - 1))) {
            // Nothing to do, no ramp down duration configured or composition already ends at zero.
            return repeatIndex;
        }

        // We know the last segment is a ramp that ends at non-zero amplitude.
        RampSegment lastRamp = (RampSegment) segments.get(segmentCount - 1);
        float previousAmplitude = lastRamp.getEndAmplitude();
        float previousFrequency = lastRamp.getEndFrequency();

        if (isOffRampSegment(segments.get(repeatIndex))) {
            // Repeating from a non-zero to a zero amplitude segment, we know the next segment is a
            // ramp with zero amplitudes.
            RampSegment nextRamp = (RampSegment) segments.get(repeatIndex);

            if (nextRamp.getDuration() <= mRampDownDuration) {
                // Skip the zero amplitude segment and append a ramp down of same duration to the
                // end of the composition, to preserve waveform timings and still soften the
                // transition to zero.
                // This will update the waveform as follows:
                //  R               R+1
                //  |  ____          | ____
                // _|_/       =>   __|/    \
                segments.add(createRampDown(previousAmplitude, previousFrequency,
                        nextRamp.getDuration()));
                repeatIndex++;
            } else {
                // Append a ramp down to the end of the composition, split the zero amplitude
                // segment and start repeating from the second half, to preserve waveform timings.
                // This will update the waveform as follows:
                //  R              R+1
                //  |   ____        |  ____
                // _|__/       => __|_/    \
                segments.add(createRampDown(previousAmplitude, previousFrequency,
                        mRampDownDuration));
                segments.set(repeatIndex, updateDuration(nextRamp,
                        nextRamp.getDuration() - mRampDownDuration));
                segments.add(repeatIndex, updateDuration(nextRamp, mRampDownDuration));
                repeatIndex++;
            }
        }

        return repeatIndex;
    }

    /**
@@ -247,7 +104,7 @@ final class StepToRampAdapter implements VibrationEffectAdapters.SegmentsAdapter
        return repeatIndex;
    }

    private static RampSegment apply(StepSegment segment) {
    private static RampSegment convertStepToRamp(StepSegment segment) {
        return new RampSegment(segment.getAmplitude(), segment.getAmplitude(),
                segment.getFrequency(), segment.getFrequency(), (int) segment.getDuration());
    }
@@ -276,36 +133,10 @@ final class StepToRampAdapter implements VibrationEffectAdapters.SegmentsAdapter
        return ramps;
    }

    private static RampSegment createRampDown(float amplitude, float frequency, long duration) {
        return new RampSegment(amplitude, /* endAmplitude= */ 0, frequency, frequency,
                (int) duration);
    }

    private static RampSegment updateDuration(RampSegment ramp, long newDuration) {
        return new RampSegment(ramp.getStartAmplitude(), ramp.getEndAmplitude(),
                ramp.getStartFrequency(), ramp.getEndFrequency(), (int) newDuration);
    }

    private static boolean isStep(VibrationEffectSegment segment) {
        return segment instanceof StepSegment;
    }

    /** Returns true if the segment is a ramp that starts and ends at zero amplitude. */
    private static boolean isOffRampSegment(VibrationEffectSegment segment) {
        if (segment instanceof RampSegment) {
            RampSegment ramp = (RampSegment) segment;
            return ramp.getStartAmplitude() == 0 && ramp.getEndAmplitude() == 0;
        }
        return false;
    }

    private static boolean endsWithNonZeroAmplitude(VibrationEffectSegment segment) {
        if (segment instanceof RampSegment) {
            return ((RampSegment) segment).getEndAmplitude() != 0;
        }
        return false;
    }

    private static float interpolateAmplitude(RampSegment ramp, long duration) {
        return interpolate(ramp.getStartAmplitude(), ramp.getEndAmplitude(), duration,
                ramp.getDuration());
+28 −1

File changed.

Preview size limit exceeded, changes collapsed.

+1 −1
Original line number Diff line number Diff line
@@ -176,7 +176,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
        mVibrationSettings = new VibrationSettings(mContext, mHandler);
        mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings);
        mInputDeviceDelegate = new InputDeviceDelegate(mContext, mHandler);
        mDeviceVibrationEffectAdapter = new DeviceVibrationEffectAdapter(mContext);
        mDeviceVibrationEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);

        VibrationCompleteListener listener = new VibrationCompleteListener(this);
        mNativeWrapper = injector.getNativeWrapper();
Loading