/* * 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 android.os.vibrator; import android.annotation.NonNull; import android.annotation.TestApi; import android.os.Parcel; import android.os.VibrationEffect; import android.os.VibratorInfo; import com.android.internal.util.Preconditions; import java.util.Objects; /** * Representation of {@link VibrationEffectSegment} that ramps vibration amplitude and/or frequency * for a specified duration. * *

The amplitudes are expressed by float values in the range [0, 1], representing the relative * output acceleration for the vibrator. The frequencies are expressed in hertz by positive finite * float values. The special value zero is used here for an unspecified frequency, and will be * automatically mapped to the device's default vibration frequency (usually the resonant * frequency). * * @hide */ @TestApi public final class RampSegment extends VibrationEffectSegment { private final float mStartAmplitude; private final float mStartFrequencyHz; private final float mEndAmplitude; private final float mEndFrequencyHz; private final int mDuration; RampSegment(@NonNull Parcel in) { this(in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat(), in.readInt()); } /** @hide */ public RampSegment(float startAmplitude, float endAmplitude, float startFrequencyHz, float endFrequencyHz, int duration) { mStartAmplitude = startAmplitude; mEndAmplitude = endAmplitude; mStartFrequencyHz = startFrequencyHz; mEndFrequencyHz = endFrequencyHz; mDuration = duration; } @Override public boolean equals(Object o) { if (!(o instanceof RampSegment)) { return false; } RampSegment other = (RampSegment) o; return Float.compare(mStartAmplitude, other.mStartAmplitude) == 0 && Float.compare(mEndAmplitude, other.mEndAmplitude) == 0 && Float.compare(mStartFrequencyHz, other.mStartFrequencyHz) == 0 && Float.compare(mEndFrequencyHz, other.mEndFrequencyHz) == 0 && mDuration == other.mDuration; } public float getStartAmplitude() { return mStartAmplitude; } public float getEndAmplitude() { return mEndAmplitude; } public float getStartFrequencyHz() { return mStartFrequencyHz; } public float getEndFrequencyHz() { return mEndFrequencyHz; } @Override public long getDuration() { return mDuration; } /** @hide */ @Override public boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo) { boolean areFeaturesSupported = true; // If the start/end frequencies are not the same, require frequency control since we need to // ramp up/down the frequency. if ((mStartFrequencyHz != mEndFrequencyHz) // If there is no frequency ramping, make sure that the one frequency used does not // require frequency control. || frequencyRequiresFrequencyControl(mStartFrequencyHz)) { areFeaturesSupported &= vibratorInfo.hasFrequencyControl(); } // If the start/end amplitudes are not the same, require amplitude control since we need to // ramp up/down the amplitude. if ((mStartAmplitude != mEndAmplitude) // If there is no amplitude ramping, make sure that the amplitude used does not // require amplitude control. || amplitudeRequiresAmplitudeControl(mStartAmplitude)) { areFeaturesSupported &= vibratorInfo.hasAmplitudeControl(); } return areFeaturesSupported; } /** @hide */ @Override public boolean isHapticFeedbackCandidate() { return true; } /** @hide */ @Override public void validate() { VibrationEffectSegment.checkFrequencyArgument(mStartFrequencyHz, "startFrequencyHz"); VibrationEffectSegment.checkFrequencyArgument(mEndFrequencyHz, "endFrequencyHz"); VibrationEffectSegment.checkDurationArgument(mDuration, "duration"); Preconditions.checkArgumentInRange(mStartAmplitude, 0f, 1f, "startAmplitude"); Preconditions.checkArgumentInRange(mEndAmplitude, 0f, 1f, "endAmplitude"); } /** @hide */ @NonNull @Override public RampSegment resolve(int defaultAmplitude) { // Default amplitude is not supported for ramping. return this; } /** @hide */ @NonNull @Override public RampSegment scale(float scaleFactor) { float newStartAmplitude = VibrationEffect.scale(mStartAmplitude, scaleFactor); float newEndAmplitude = VibrationEffect.scale(mEndAmplitude, scaleFactor); if (Float.compare(mStartAmplitude, newStartAmplitude) == 0 && Float.compare(mEndAmplitude, newEndAmplitude) == 0) { return this; } return new RampSegment(newStartAmplitude, newEndAmplitude, mStartFrequencyHz, mEndFrequencyHz, mDuration); } /** @hide */ @NonNull @Override public RampSegment scaleLinearly(float scaleFactor) { float newStartAmplitude = VibrationEffect.scaleLinearly(mStartAmplitude, scaleFactor); float newEndAmplitude = VibrationEffect.scaleLinearly(mEndAmplitude, scaleFactor); if (Float.compare(mStartAmplitude, newStartAmplitude) == 0 && Float.compare(mEndAmplitude, newEndAmplitude) == 0) { return this; } return new RampSegment(newStartAmplitude, newEndAmplitude, mStartFrequencyHz, mEndFrequencyHz, mDuration); } /** @hide */ @NonNull @Override public RampSegment applyEffectStrength(int effectStrength) { return this; } @Override public int hashCode() { return Objects.hash(mStartAmplitude, mEndAmplitude, mStartFrequencyHz, mEndFrequencyHz, mDuration); } @Override public String toString() { return "Ramp{startAmplitude=" + mStartAmplitude + ", endAmplitude=" + mEndAmplitude + ", startFrequencyHz=" + mStartFrequencyHz + ", endFrequencyHz=" + mEndFrequencyHz + ", duration=" + mDuration + "}"; } /** @hide */ @Override public String toDebugString() { return String.format("Ramp=%dms(amplitude=%.2f%s to %.2f%s)", mDuration, mStartAmplitude, Float.compare(mStartFrequencyHz, 0) == 0 ? "" : " @ " + mStartFrequencyHz + "Hz", mEndAmplitude, Float.compare(mEndFrequencyHz, 0) == 0 ? "" : " @ " + mEndFrequencyHz + "Hz"); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(@NonNull Parcel out, int flags) { out.writeInt(PARCEL_TOKEN_RAMP); out.writeFloat(mStartAmplitude); out.writeFloat(mEndAmplitude); out.writeFloat(mStartFrequencyHz); out.writeFloat(mEndFrequencyHz); out.writeInt(mDuration); } @NonNull public static final Creator CREATOR = new Creator() { @Override public RampSegment createFromParcel(Parcel in) { // Skip the type token in.readInt(); return new RampSegment(in); } @Override public RampSegment[] newArray(int size) { return new RampSegment[size]; } }; }