1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.os.vibrator;
18 
19 import android.annotation.NonNull;
20 import android.annotation.TestApi;
21 import android.os.Parcel;
22 import android.os.VibrationEffect;
23 import android.os.VibratorInfo;
24 
25 import com.android.internal.util.Preconditions;
26 
27 import java.util.Objects;
28 
29 /**
30  * Representation of {@link VibrationEffectSegment} that ramps vibration amplitude and/or frequency
31  * for a specified duration.
32  *
33  * <p>The amplitudes are expressed by float values in the range [0, 1], representing the relative
34  * output acceleration for the vibrator. The frequencies are expressed in hertz by positive finite
35  * float values. The special value zero is used here for an unspecified frequency, and will be
36  * automatically mapped to the device's default vibration frequency (usually the resonant
37  * frequency).
38  *
39  * @hide
40  */
41 @TestApi
42 public final class RampSegment extends VibrationEffectSegment {
43     private final float mStartAmplitude;
44     private final float mStartFrequencyHz;
45     private final float mEndAmplitude;
46     private final float mEndFrequencyHz;
47     private final int mDuration;
48 
RampSegment(@onNull Parcel in)49     RampSegment(@NonNull Parcel in) {
50         this(in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat(), in.readInt());
51     }
52 
53     /** @hide */
RampSegment(float startAmplitude, float endAmplitude, float startFrequencyHz, float endFrequencyHz, int duration)54     public RampSegment(float startAmplitude, float endAmplitude, float startFrequencyHz,
55             float endFrequencyHz, int duration) {
56         mStartAmplitude = startAmplitude;
57         mEndAmplitude = endAmplitude;
58         mStartFrequencyHz = startFrequencyHz;
59         mEndFrequencyHz = endFrequencyHz;
60         mDuration = duration;
61     }
62 
63     @Override
equals(Object o)64     public boolean equals(Object o) {
65         if (!(o instanceof RampSegment)) {
66             return false;
67         }
68         RampSegment other = (RampSegment) o;
69         return Float.compare(mStartAmplitude, other.mStartAmplitude) == 0
70                 && Float.compare(mEndAmplitude, other.mEndAmplitude) == 0
71                 && Float.compare(mStartFrequencyHz, other.mStartFrequencyHz) == 0
72                 && Float.compare(mEndFrequencyHz, other.mEndFrequencyHz) == 0
73                 && mDuration == other.mDuration;
74     }
75 
getStartAmplitude()76     public float getStartAmplitude() {
77         return mStartAmplitude;
78     }
79 
getEndAmplitude()80     public float getEndAmplitude() {
81         return mEndAmplitude;
82     }
83 
getStartFrequencyHz()84     public float getStartFrequencyHz() {
85         return mStartFrequencyHz;
86     }
87 
getEndFrequencyHz()88     public float getEndFrequencyHz() {
89         return mEndFrequencyHz;
90     }
91 
92     @Override
getDuration()93     public long getDuration() {
94         return mDuration;
95     }
96 
97     /** @hide */
98     @Override
areVibrationFeaturesSupported(@onNull VibratorInfo vibratorInfo)99     public boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo) {
100         boolean areFeaturesSupported = true;
101         // If the start/end frequencies are not the same, require frequency control since we need to
102         // ramp up/down the frequency.
103         if ((mStartFrequencyHz != mEndFrequencyHz)
104                 // If there is no frequency ramping, make sure that the one frequency used does not
105                 // require frequency control.
106                 || frequencyRequiresFrequencyControl(mStartFrequencyHz)) {
107             areFeaturesSupported &= vibratorInfo.hasFrequencyControl();
108         }
109         // If the start/end amplitudes are not the same, require amplitude control since we need to
110         // ramp up/down the amplitude.
111         if ((mStartAmplitude != mEndAmplitude)
112                 // If there is no amplitude ramping, make sure that the amplitude used does not
113                 // require amplitude control.
114                 || amplitudeRequiresAmplitudeControl(mStartAmplitude)) {
115             areFeaturesSupported &= vibratorInfo.hasAmplitudeControl();
116         }
117         return areFeaturesSupported;
118     }
119 
120     /** @hide */
121     @Override
isHapticFeedbackCandidate()122     public boolean isHapticFeedbackCandidate() {
123         return true;
124     }
125 
126     /** @hide */
127     @Override
validate()128     public void validate() {
129         VibrationEffectSegment.checkFrequencyArgument(mStartFrequencyHz, "startFrequencyHz");
130         VibrationEffectSegment.checkFrequencyArgument(mEndFrequencyHz, "endFrequencyHz");
131         VibrationEffectSegment.checkDurationArgument(mDuration, "duration");
132         Preconditions.checkArgumentInRange(mStartAmplitude, 0f, 1f, "startAmplitude");
133         Preconditions.checkArgumentInRange(mEndAmplitude, 0f, 1f, "endAmplitude");
134     }
135 
136     /** @hide */
137     @NonNull
138     @Override
resolve(int defaultAmplitude)139     public RampSegment resolve(int defaultAmplitude) {
140         // Default amplitude is not supported for ramping.
141         return this;
142     }
143 
144     /** @hide */
145     @NonNull
146     @Override
scale(float scaleFactor)147     public RampSegment scale(float scaleFactor) {
148         float newStartAmplitude = VibrationEffect.scale(mStartAmplitude, scaleFactor);
149         float newEndAmplitude = VibrationEffect.scale(mEndAmplitude, scaleFactor);
150         if (Float.compare(mStartAmplitude, newStartAmplitude) == 0
151                 && Float.compare(mEndAmplitude, newEndAmplitude) == 0) {
152             return this;
153         }
154         return new RampSegment(newStartAmplitude, newEndAmplitude, mStartFrequencyHz,
155                 mEndFrequencyHz,
156                 mDuration);
157     }
158 
159     /** @hide */
160     @NonNull
161     @Override
scaleLinearly(float scaleFactor)162     public RampSegment scaleLinearly(float scaleFactor) {
163         float newStartAmplitude = VibrationEffect.scaleLinearly(mStartAmplitude, scaleFactor);
164         float newEndAmplitude = VibrationEffect.scaleLinearly(mEndAmplitude, scaleFactor);
165         if (Float.compare(mStartAmplitude, newStartAmplitude) == 0
166                 && Float.compare(mEndAmplitude, newEndAmplitude) == 0) {
167             return this;
168         }
169         return new RampSegment(newStartAmplitude, newEndAmplitude, mStartFrequencyHz,
170                 mEndFrequencyHz,
171                 mDuration);
172     }
173 
174     /** @hide */
175     @NonNull
176     @Override
applyEffectStrength(int effectStrength)177     public RampSegment applyEffectStrength(int effectStrength) {
178         return this;
179     }
180 
181     @Override
hashCode()182     public int hashCode() {
183         return Objects.hash(mStartAmplitude, mEndAmplitude, mStartFrequencyHz, mEndFrequencyHz,
184                 mDuration);
185     }
186 
187     @Override
toString()188     public String toString() {
189         return "Ramp{startAmplitude=" + mStartAmplitude
190                 + ", endAmplitude=" + mEndAmplitude
191                 + ", startFrequencyHz=" + mStartFrequencyHz
192                 + ", endFrequencyHz=" + mEndFrequencyHz
193                 + ", duration=" + mDuration
194                 + "}";
195     }
196 
197     /** @hide */
198     @Override
toDebugString()199     public String toDebugString() {
200         return String.format("Ramp=%dms(amplitude=%.2f%s to %.2f%s)",
201                 mDuration,
202                 mStartAmplitude,
203                 Float.compare(mStartFrequencyHz, 0) == 0 ? "" : " @ " + mStartFrequencyHz + "Hz",
204                 mEndAmplitude,
205                 Float.compare(mEndFrequencyHz, 0) == 0 ? "" : " @ " + mEndFrequencyHz + "Hz");
206     }
207 
208     @Override
describeContents()209     public int describeContents() {
210         return 0;
211     }
212 
213     @Override
writeToParcel(@onNull Parcel out, int flags)214     public void writeToParcel(@NonNull Parcel out, int flags) {
215         out.writeInt(PARCEL_TOKEN_RAMP);
216         out.writeFloat(mStartAmplitude);
217         out.writeFloat(mEndAmplitude);
218         out.writeFloat(mStartFrequencyHz);
219         out.writeFloat(mEndFrequencyHz);
220         out.writeInt(mDuration);
221     }
222 
223     @NonNull
224     public static final Creator<RampSegment> CREATOR =
225             new Creator<RampSegment>() {
226                 @Override
227                 public RampSegment createFromParcel(Parcel in) {
228                     // Skip the type token
229                     in.readInt();
230                     return new RampSegment(in);
231                 }
232 
233                 @Override
234                 public RampSegment[] newArray(int size) {
235                     return new RampSegment[size];
236                 }
237             };
238 }
239