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

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

Merge "Split vibration effect adapters into separate files" into sc-dev am:...

Merge "Split vibration effect adapters into separate files" into sc-dev am: b2198d3a am: 11492386

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

Change-Id: I6b44d7eade33f3acaeb43c4fd1b174c7600f8c01
parents 9f5c04c9 11492386
Loading
Loading
Loading
Loading
+80 −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 android.util.MathUtils;

import java.util.List;

/**
 * Adapter that clips frequency values to {@link VibratorInfo#getFrequencyRange()} and
 * amplitude values to respective {@link VibratorInfo#getMaxAmplitude}.
 *
 * <p>Devices with no frequency control will collapse all frequencies to zero and leave
 * amplitudes unchanged.
 *
 * <p>The frequency value returned in segments will be absolute, converted with
 * {@link VibratorInfo#getAbsoluteFrequency(float)}.
 */
final class ClippingAmplitudeAndFrequencyAdapter
        implements VibrationEffectAdapters.SegmentsAdapter<VibratorInfo> {

    @Override
    public int apply(List<VibrationEffectSegment> segments, int repeatIndex, VibratorInfo info) {
        int segmentCount = segments.size();
        for (int i = 0; i < segmentCount; i++) {
            VibrationEffectSegment segment = segments.get(i);
            if (segment instanceof StepSegment) {
                segments.set(i, apply((StepSegment) segment, info));
            } else if (segment instanceof RampSegment) {
                segments.set(i, apply((RampSegment) segment, info));
            }
        }
        return repeatIndex;
    }

    private StepSegment apply(StepSegment segment, VibratorInfo info) {
        float clampedFrequency = clampFrequency(info, segment.getFrequency());
        return new StepSegment(
                clampAmplitude(info, clampedFrequency, segment.getAmplitude()),
                info.getAbsoluteFrequency(clampedFrequency),
                (int) segment.getDuration());
    }

    private RampSegment apply(RampSegment segment, VibratorInfo info) {
        float clampedStartFrequency = clampFrequency(info, segment.getStartFrequency());
        float clampedEndFrequency = clampFrequency(info, segment.getEndFrequency());
        return new RampSegment(
                clampAmplitude(info, clampedStartFrequency, segment.getStartAmplitude()),
                clampAmplitude(info, clampedEndFrequency, segment.getEndAmplitude()),
                info.getAbsoluteFrequency(clampedStartFrequency),
                info.getAbsoluteFrequency(clampedEndFrequency),
                (int) segment.getDuration());
    }

    private float clampFrequency(VibratorInfo info, float frequency) {
        return info.getFrequencyRange().clamp(frequency);
    }

    private float clampAmplitude(VibratorInfo info, float frequency, float amplitude) {
        return MathUtils.min(amplitude, info.getMaxAmplitude(frequency));
    }
}
+12 −210
Original line number Diff line number Diff line
@@ -16,231 +16,33 @@

package com.android.server.vibrator;

import android.hardware.vibrator.IVibrator;
import android.os.VibrationEffect;
import android.os.VibratorInfo;
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
import android.util.MathUtils;
import android.util.Range;

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

/** Adapts a {@link VibrationEffect} to a specific device, taking into account its capabilities. */
final class DeviceVibrationEffectAdapter implements VibrationEffectModifier<VibratorInfo> {
final class DeviceVibrationEffectAdapter
        implements VibrationEffectAdapters.EffectAdapter<VibratorInfo> {

    /** Duration of each step created to simulate a ramp segment. */
    private static final int RAMP_STEP_DURATION_MILLIS = 5;

    /** Adapts a sequence of {@link VibrationEffectSegment} to device's capabilities. */
    interface SegmentsAdapter {

        /**
         * Modifies the given segments list by adding/removing segments to it based on the
         * device capabilities specified by given {@link VibratorInfo}.
         *
         * @param segments    List of {@link VibrationEffectSegment} to be modified.
         * @param repeatIndex Repeat index of the vibration with given segment list.
         * @param info        The device vibrator info that the segments must be adapted to.
         * @return The new repeat index to be used for the modified list.
         */
        int apply(List<VibrationEffectSegment> segments, int repeatIndex, VibratorInfo info);
    }

    private final SegmentsAdapter mAmplitudeFrequencyAdapter;
    private final SegmentsAdapter mStepToRampAdapter;
    private final SegmentsAdapter mRampToStepsAdapter;
    private final List<VibrationEffectAdapters.SegmentsAdapter<VibratorInfo>> mSegmentAdapters;

    DeviceVibrationEffectAdapter() {
        this(new ClippingAmplitudeFrequencyAdapter());
    }

    DeviceVibrationEffectAdapter(SegmentsAdapter amplitudeFrequencyAdapter) {
        mAmplitudeFrequencyAdapter = amplitudeFrequencyAdapter;
        mStepToRampAdapter = new StepToRampAdapter();
        mRampToStepsAdapter = new RampToStepsAdapter(RAMP_STEP_DURATION_MILLIS);
    }

    @Override
    public VibrationEffect apply(VibrationEffect effect, VibratorInfo info) {
        if (!(effect instanceof VibrationEffect.Composed)) {
            return effect;
        }

        VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
        List<VibrationEffectSegment> newSegments = new ArrayList<>(composed.getSegments());
        int newRepeatIndex = composed.getRepeatIndex();

        // Replace ramps with a sequence of fixed steps, or no-op if PWLE capability present.
        newRepeatIndex = mRampToStepsAdapter.apply(newSegments, newRepeatIndex, info);

        // Replace steps that should be handled by PWLE to ramps, or no-op if capability missing.
        // This should be done before frequency is converted from relative to absolute values.
        newRepeatIndex = mStepToRampAdapter.apply(newSegments, newRepeatIndex, info);

        // Adapt amplitude and frequency values to device supported ones, converting frequency
        // to absolute values in Hertz.
        newRepeatIndex = mAmplitudeFrequencyAdapter.apply(newSegments, newRepeatIndex, info);

        mSegmentAdapters = Arrays.asList(
                // TODO(b/167947076): add filter that removes unsupported primitives
                // TODO(b/167947076): add filter that replaces unsupported prebaked with fallback

        return new VibrationEffect.Composed(newSegments, newRepeatIndex);
                new RampToStepAdapter(RAMP_STEP_DURATION_MILLIS),
                new StepToRampAdapter(),
                new ClippingAmplitudeAndFrequencyAdapter()
        );
    }

    /**
     * Adapter that converts step segments that should be handled as PWLEs to ramp segments.
     *
     * <p>This leaves the list unchanged if the device do not have compose PWLE capability.
     */
    private static final class StepToRampAdapter implements SegmentsAdapter {
    @Override
        public int apply(List<VibrationEffectSegment> segments, int repeatIndex,
                VibratorInfo info) {
            if (!info.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
                // The vibrator do not have PWLE capability, so keep the segments unchanged.
                return repeatIndex;
            }
            int segmentCount = segments.size();
            // Convert steps that require frequency control to ramps.
            for (int i = 0; i < segmentCount; i++) {
                VibrationEffectSegment segment = segments.get(i);
                if ((segment instanceof StepSegment)
                        && ((StepSegment) segment).getFrequency() != 0) {
                    segments.set(i, apply((StepSegment) segment));
                }
            }
            // Convert steps that are next to ramps to also become ramps, so they can be composed
            // together in the same PWLE waveform.
            for (int i = 1; i < segmentCount; i++) {
                if (segments.get(i) instanceof RampSegment) {
                    for (int j = i - 1; j >= 0 && (segments.get(j) instanceof StepSegment); j--) {
                        segments.set(j, apply((StepSegment) segments.get(j)));
                    }
                }
            }
            return repeatIndex;
        }

        private RampSegment apply(StepSegment segment) {
            return new RampSegment(segment.getAmplitude(), segment.getAmplitude(),
                    segment.getFrequency(), segment.getFrequency(), (int) segment.getDuration());
        }
    }

    /**
     * Adapter that converts ramp segments that to a sequence of fixed step segments.
     *
     * <p>This leaves the list unchanged if the device have compose PWLE capability.
     */
    private static final class RampToStepsAdapter implements SegmentsAdapter {
        private final int mStepDuration;

        RampToStepsAdapter(int stepDuration) {
            mStepDuration = stepDuration;
        }

        @Override
        public int apply(List<VibrationEffectSegment> segments, int repeatIndex,
                VibratorInfo info) {
            if (info.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
                // The vibrator have PWLE capability, so keep the segments unchanged.
                return repeatIndex;
            }
            int segmentCount = segments.size();
            for (int i = 0; i < segmentCount; i++) {
                VibrationEffectSegment segment = segments.get(i);
                if (!(segment instanceof RampSegment)) {
                    continue;
                }
                List<StepSegment> steps = apply((RampSegment) segment);
                segments.remove(i);
                segments.addAll(i, steps);
                int addedSegments = steps.size() - 1;
                if (repeatIndex > i) {
                    repeatIndex += addedSegments;
                }
                i += addedSegments;
                segmentCount += addedSegments;
            }
            return repeatIndex;
        }

        private List<StepSegment> apply(RampSegment ramp) {
            if (Float.compare(ramp.getStartAmplitude(), ramp.getEndAmplitude()) == 0) {
                // Amplitude is the same, so return a single step to simulate this ramp.
                return Arrays.asList(
                        new StepSegment(ramp.getStartAmplitude(), ramp.getStartFrequency(),
                                (int) ramp.getDuration()));
            }

            List<StepSegment> steps = new ArrayList<>();
            int stepCount = (int) (ramp.getDuration() + mStepDuration - 1) / mStepDuration;
            for (int i = 0; i < stepCount - 1; i++) {
                float pos = (float) i / stepCount;
                steps.add(new StepSegment(
                        interpolate(ramp.getStartAmplitude(), ramp.getEndAmplitude(), pos),
                        interpolate(ramp.getStartFrequency(), ramp.getEndFrequency(), pos),
                        mStepDuration));
            }
            int duration = (int) ramp.getDuration() - mStepDuration * (stepCount - 1);
            steps.add(new StepSegment(ramp.getEndAmplitude(), ramp.getEndFrequency(), duration));
            return steps;
        }

        private static float interpolate(float start, float end, float position) {
            return start + position * (end - start);
        }
    }

    /**
     * Adapter that clips frequency values to {@link VibratorInfo#getFrequencyRange()} and
     * amplitude values to respective {@link VibratorInfo#getMaxAmplitude}.
     *
     * <p>Devices with no frequency control will collapse all frequencies to zero and leave
     * amplitudes unchanged.
     *
     * <p>The frequency value returned in segments will be absolute, conveted with
     * {@link VibratorInfo#getAbsoluteFrequency(float)}.
     */
    private static final class ClippingAmplitudeFrequencyAdapter implements SegmentsAdapter {
        @Override
        public int apply(List<VibrationEffectSegment> segments, int repeatIndex,
                VibratorInfo info) {
            int segmentCount = segments.size();
            for (int i = 0; i < segmentCount; i++) {
                VibrationEffectSegment segment = segments.get(i);
                if (segment instanceof StepSegment) {
                    segments.set(i, apply((StepSegment) segment, info));
                } else if (segment instanceof RampSegment) {
                    segments.set(i, apply((RampSegment) segment, info));
                }
            }
            return repeatIndex;
        }

        private StepSegment apply(StepSegment segment, VibratorInfo info) {
            float clampedFrequency = info.getFrequencyRange().clamp(segment.getFrequency());
            return new StepSegment(
                    MathUtils.min(segment.getAmplitude(), info.getMaxAmplitude(clampedFrequency)),
                    info.getAbsoluteFrequency(clampedFrequency),
                    (int) segment.getDuration());
        }

        private RampSegment apply(RampSegment segment, VibratorInfo info) {
            Range<Float> frequencyRange = info.getFrequencyRange();
            float clampedStartFrequency = frequencyRange.clamp(segment.getStartFrequency());
            float clampedEndFrequency = frequencyRange.clamp(segment.getEndFrequency());
            return new RampSegment(
                    MathUtils.min(segment.getStartAmplitude(),
                            info.getMaxAmplitude(clampedStartFrequency)),
                    MathUtils.min(segment.getEndAmplitude(),
                            info.getMaxAmplitude(clampedEndFrequency)),
                    info.getAbsoluteFrequency(clampedStartFrequency),
                    info.getAbsoluteFrequency(clampedEndFrequency),
                    (int) segment.getDuration());
        }
    public VibrationEffect apply(VibrationEffect effect, VibratorInfo info) {
        return VibrationEffectAdapters.apply(effect, mSegmentAdapters, info);
    }
}
+93 −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.hardware.vibrator.IVibrator;
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 converts ramp segments that to a sequence of fixed step segments.
 *
 * <p>This leaves the list unchanged if the device have compose PWLE capability.
 */
final class RampToStepAdapter implements VibrationEffectAdapters.SegmentsAdapter<VibratorInfo> {

    private final int mStepDuration;

    RampToStepAdapter(int stepDuration) {
        mStepDuration = stepDuration;
    }

    @Override
    public int apply(List<VibrationEffectSegment> segments, int repeatIndex,
            VibratorInfo info) {
        if (info.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
            // The vibrator have PWLE capability, so keep the segments unchanged.
            return repeatIndex;
        }
        int segmentCount = segments.size();
        for (int i = 0; i < segmentCount; i++) {
            VibrationEffectSegment segment = segments.get(i);
            if (!(segment instanceof RampSegment)) {
                continue;
            }
            List<StepSegment> steps = apply((RampSegment) segment);
            segments.remove(i);
            segments.addAll(i, steps);
            int addedSegments = steps.size() - 1;
            if (repeatIndex > i) {
                repeatIndex += addedSegments;
            }
            i += addedSegments;
            segmentCount += addedSegments;
        }
        return repeatIndex;
    }

    private List<StepSegment> apply(RampSegment ramp) {
        if (Float.compare(ramp.getStartAmplitude(), ramp.getEndAmplitude()) == 0) {
            // Amplitude is the same, so return a single step to simulate this ramp.
            return Arrays.asList(
                    new StepSegment(ramp.getStartAmplitude(), ramp.getStartFrequency(),
                            (int) ramp.getDuration()));
        }

        List<StepSegment> steps = new ArrayList<>();
        int stepCount = (int) (ramp.getDuration() + mStepDuration - 1) / mStepDuration;
        for (int i = 0; i < stepCount - 1; i++) {
            float pos = (float) i / stepCount;
            steps.add(new StepSegment(
                    interpolate(ramp.getStartAmplitude(), ramp.getEndAmplitude(), pos),
                    interpolate(ramp.getStartFrequency(), ramp.getEndFrequency(), pos),
                    mStepDuration));
        }
        int duration = (int) ramp.getDuration() - mStepDuration * (stepCount - 1);
        steps.add(new StepSegment(ramp.getEndAmplitude(), ramp.getEndFrequency(), duration));
        return steps;
    }

    private static float interpolate(float start, float end, float position) {
        return start + position * (end - start);
    }
}
+65 −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.hardware.vibrator.IVibrator;
import android.os.VibratorInfo;
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;

import java.util.List;

/**
 * Adapter that converts step segments that should be handled as PWLEs to ramp segments.
 *
 * <p>This leaves the list unchanged if the device do not have compose PWLE capability.
 */
final class StepToRampAdapter implements VibrationEffectAdapters.SegmentsAdapter<VibratorInfo> {
    @Override
    public int apply(List<VibrationEffectSegment> segments, int repeatIndex,
            VibratorInfo info) {
        if (!info.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
            // The vibrator do not have PWLE capability, so keep the segments unchanged.
            return repeatIndex;
        }
        int segmentCount = segments.size();
        // Convert steps that require frequency control to ramps.
        for (int i = 0; i < segmentCount; i++) {
            VibrationEffectSegment segment = segments.get(i);
            if ((segment instanceof StepSegment)
                    && ((StepSegment) segment).getFrequency() != 0) {
                segments.set(i, apply((StepSegment) segment));
            }
        }
        // Convert steps that are next to ramps to also become ramps, so they can be composed
        // together in the same PWLE waveform.
        for (int i = 1; i < segmentCount; i++) {
            if (segments.get(i) instanceof RampSegment) {
                for (int j = i - 1; j >= 0 && (segments.get(j) instanceof StepSegment); j--) {
                    segments.set(j, apply((StepSegment) segments.get(j)));
                }
            }
        }
        return repeatIndex;
    }

    private RampSegment apply(StepSegment segment) {
        return new RampSegment(segment.getAmplitude(), segment.getAmplitude(),
                segment.getFrequency(), segment.getFrequency(), (int) segment.getDuration());
    }
}
+92 −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.VibrationEffect;
import android.os.vibrator.VibrationEffectSegment;

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

/**
 * Helpers to adapt a {@link VibrationEffect} to generic modifiers (e.g. device capabilities,
 * user settings, etc).
 */
public final class VibrationEffectAdapters {

    /**
     * Function that applies a generic modifier to a sequence of {@link VibrationEffectSegment}.
     *
     * @param <T> The type of modifiers this adapter accepts.
     */
    public interface SegmentsAdapter<T> {

        /**
         * Add and/or remove segments to the given {@link VibrationEffectSegment} list based on the
         * given modifier.
         *
         * <p>This returns the new {@code repeatIndex} to be used together with the updated list to
         * specify an equivalent {@link VibrationEffect}.
         *
         * @param segments    List of {@link VibrationEffectSegment} to be modified.
         * @param repeatIndex Repeat index on the current segment list.
         * @param modifier    The modifier to be applied to the sequence of segments.
         * @return The new repeat index on the modifies list.
         */
        int apply(List<VibrationEffectSegment> segments, int repeatIndex, T modifier);
    }

    /**
     * Function that applies a generic modifier to a {@link VibrationEffect}.
     *
     * @param <T> The type of modifiers this adapter accepts.
     */
    public interface EffectAdapter<T> {

        /** Applies the modifier to given {@link VibrationEffect}, returning the new effect. */
        VibrationEffect apply(VibrationEffect effect, T modifier);
    }

    /**
     * Applies a sequence of {@link SegmentsAdapter} to the segments of a given
     * {@link VibrationEffect}, in order.
     *
     * @param effect   The effect to be adapted to given modifier.
     * @param adapters The sequence of adapters to be applied to given {@link VibrationEffect}.
     * @param modifier The modifier to be passed to each adapter that describes the conditions the
     *                 {@link VibrationEffect} needs to be adapted to (e.g. device capabilities,
     *                 user settings, etc).
     */
    public static <T> VibrationEffect apply(VibrationEffect effect,
            List<SegmentsAdapter<T>> adapters, T modifier) {
        if (!(effect instanceof VibrationEffect.Composed)) {
            // Segments adapters can only be applied to Composed effects.
            return effect;
        }

        VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
        List<VibrationEffectSegment> newSegments = new ArrayList<>(composed.getSegments());
        int newRepeatIndex = composed.getRepeatIndex();

        int adapterCount = adapters.size();
        for (int i = 0; i < adapterCount; i++) {
            newRepeatIndex = adapters.get(i).apply(newSegments, newRepeatIndex, modifier);
        }

        return new VibrationEffect.Composed(newSegments, newRepeatIndex);
    }
}
Loading