All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.server.vibrator.RampDownAdapter Maven / Gradle / Ivy

/*
 * 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.
 *
 * 

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. * *

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. * *

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 { private final int mRampDownDuration; private final int mStepDuration; RampDownAdapter(int rampDownDuration, int stepDuration) { mRampDownDuration = rampDownDuration; mStepDuration = stepDuration; } @Override public int apply(List 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: * *

    *
  1. Remove the OFF segment that follows a segment of non-zero amplitude; *
  2. 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; *
  3. Add a zero amplitude segment following the steps, if necessary, to fill the remaining * duration; *
*/ private int addRampDownToZeroAmplitudeSegments(List segments, int repeatIndex) { int segmentCount = segments.size(); for (int i = 1; i < segmentCount; i++) { VibrationEffectSegment previousSegment = segments.get(i - 1); if (!isOffSegment(segments.get(i)) || !endsWithNonZeroAmplitude(previousSegment)) { continue; } List replacementSegments = null; long offDuration = segments.get(i).getDuration(); if (previousSegment instanceof StepSegment) { float previousAmplitude = ((StepSegment) previousSegment).getAmplitude(); float previousFrequency = ((StepSegment) previousSegment).getFrequencyHz(); replacementSegments = createStepsDown(previousAmplitude, previousFrequency, offDuration); } else if (previousSegment instanceof RampSegment) { float previousAmplitude = ((RampSegment) previousSegment).getEndAmplitude(); float previousFrequency = ((RampSegment) previousSegment).getEndFrequencyHz(); 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; VibrationEffectSegment originalOffSegment = segments.remove(i); segments.addAll(i, replacementSegments); if (repeatIndex >= i) { if (repeatIndex == i) { // This effect is repeating to the removed off segment: add it back at the // end of the vibration so the loop timings are preserved, and skip it. segments.add(originalOffSegment); repeatIndex++; segmentCount++; } repeatIndex += segmentsAdded; } i += segmentsAdded; segmentCount += segmentsAdded; } } return repeatIndex; } /** * 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: * *
    *
  1. 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; *
  2. Skip the zero-amplitude segment by incrementing the repeat index, splitting it if * necessary to skip the correct amount; *
*/ private int addRampDownToLoop(List 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).getFrequencyHz(); segments.addAll(createStepsDown(previousAmplitude, previousFrequency, Math.min(offDuration, mRampDownDuration))); } else if (lastSegment instanceof RampSegment) { float previousAmplitude = ((RampSegment) lastSegment).getEndAmplitude(); float previousFrequency = ((RampSegment) lastSegment).getEndFrequencyHz(); segments.add(createRampDown(previousAmplitude, previousFrequency, Math.min(offDuration, mRampDownDuration))); } return repeatIndex; } private List 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 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.getStartFrequencyHz(), ramp.getEndFrequencyHz(), (int) newDuration); } else if (segment instanceof StepSegment) { StepSegment step = (StepSegment) segment; return new StepSegment(step.getAmplitude(), step.getFrequencyHz(), (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; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy