1 /*
2  * Copyright (C) 2017 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;
18 
19 import android.hardware.vibrator.V1_0.Constants.Effect;
20 
21 import java.util.Arrays;
22 
23 /**
24  * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
25  *
26  * These effects may be any number of things, from single shot vibrations to complex waveforms.
27  */
28 public abstract class VibrationEffect implements Parcelable {
29     private static final int PARCEL_TOKEN_ONE_SHOT = 1;
30     private static final int PARCEL_TOKEN_WAVEFORM = 2;
31     private static final int PARCEL_TOKEN_EFFECT = 3;
32 
33     /**
34      * The default vibration strength of the device.
35      */
36     public static final int DEFAULT_AMPLITUDE = -1;
37 
38     /**
39      * A click effect.
40      *
41      * @see #get(int)
42      * @hide
43      */
44     public static final int EFFECT_CLICK = Effect.CLICK;
45 
46     /**
47      * A double click effect.
48      *
49      * @see #get(int)
50      * @hide
51      */
52     public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK;
53 
54     /** @hide to prevent subclassing from outside of the framework */
VibrationEffect()55     public VibrationEffect() { }
56 
57     /**
58      * Create a one shot vibration.
59      *
60      * One shot vibrations will vibrate constantly for the specified period of time at the
61      * specified amplitude, and then stop.
62      *
63      * @param milliseconds The number of milliseconds to vibrate. This must be a positive number.
64      * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or
65      * {@link #DEFAULT_AMPLITUDE}.
66      *
67      * @return The desired effect.
68      */
createOneShot(long milliseconds, int amplitude)69     public static VibrationEffect createOneShot(long milliseconds, int amplitude) {
70         VibrationEffect effect = new OneShot(milliseconds, amplitude);
71         effect.validate();
72         return effect;
73     }
74 
75     /**
76      * Create a waveform vibration.
77      *
78      * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
79      * each pair, the value in the amplitude array determines the strength of the vibration and the
80      * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
81      * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
82      * <p>
83      * The amplitude array of the generated waveform will be the same size as the given
84      * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE},
85      * starting with 0. Therefore the first timing value will be the period to wait before turning
86      * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE}
87      * strength, etc.
88      * </p><p>
89      * To cause the pattern to repeat, pass the index into the timings array at which to start the
90      * repetition, or -1 to disable repeating.
91      * </p>
92      *
93      * @param timings The pattern of alternating on-off timings, starting with off. Timing values
94      *                of 0 will cause the timing / amplitude pair to be ignored.
95      * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
96      *               want to repeat.
97      *
98      * @return The desired effect.
99      */
createWaveform(long[] timings, int repeat)100     public static VibrationEffect createWaveform(long[] timings, int repeat) {
101         int[] amplitudes = new int[timings.length];
102         for (int i = 0; i < (timings.length / 2); i++) {
103             amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE;
104         }
105         return createWaveform(timings, amplitudes, repeat);
106     }
107 
108     /**
109      * Create a waveform vibration.
110      *
111      * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
112      * each pair, the value in the amplitude array determines the strength of the vibration and the
113      * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
114      * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
115      * </p><p>
116      * To cause the pattern to repeat, pass the index into the timings array at which to start the
117      * repetition, or -1 to disable repeating.
118      * </p>
119      *
120      * @param timings The timing values of the timing / amplitude pairs. Timing values of 0
121      *                will cause the pair to be ignored.
122      * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values
123      *                   must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An
124      *                   amplitude value of 0 implies the motor is off.
125      * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
126      *               want to repeat.
127      *
128      * @return The desired effect.
129      */
createWaveform(long[] timings, int[] amplitudes, int repeat)130     public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
131         VibrationEffect effect = new Waveform(timings, amplitudes, repeat);
132         effect.validate();
133         return effect;
134     }
135 
136     /**
137      * Get a predefined vibration effect.
138      *
139      * Predefined effects are a set of common vibration effects that should be identical, regardless
140      * of the app they come from, in order to provide a cohesive experience for users across
141      * the entire device. They also may be custom tailored to the device hardware in order to
142      * provide a better experience than you could otherwise build using the generic building
143      * blocks.
144      *
145      * @param effectId The ID of the effect to perform:
146      * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}.
147      *
148      * @return The desired effect.
149      * @hide
150      */
get(int effectId)151     public static VibrationEffect get(int effectId) {
152         VibrationEffect effect = new Prebaked(effectId);
153         effect.validate();
154         return effect;
155     }
156 
157     @Override
describeContents()158     public int describeContents() {
159         return 0;
160     }
161 
162     /** @hide */
validate()163     public abstract void validate();
164 
165     /** @hide */
166     public static class OneShot extends VibrationEffect implements Parcelable {
167         private long mTiming;
168         private int mAmplitude;
169 
OneShot(Parcel in)170         public OneShot(Parcel in) {
171             this(in.readLong(), in.readInt());
172         }
173 
OneShot(long milliseconds, int amplitude)174         public OneShot(long milliseconds, int amplitude) {
175             mTiming = milliseconds;
176             mAmplitude = amplitude;
177         }
178 
getTiming()179         public long getTiming() {
180             return mTiming;
181         }
182 
getAmplitude()183         public int getAmplitude() {
184             return mAmplitude;
185         }
186 
187         @Override
validate()188         public void validate() {
189             if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) {
190                 throw new IllegalArgumentException(
191                         "amplitude must either be DEFAULT_AMPLITUDE, " +
192                         "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")");
193             }
194             if (mTiming <= 0) {
195                 throw new IllegalArgumentException(
196                         "timing must be positive (timing=" + mTiming + ")");
197             }
198         }
199 
200         @Override
equals(Object o)201         public boolean equals(Object o) {
202             if (!(o instanceof VibrationEffect.OneShot)) {
203                 return false;
204             }
205             VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
206             return other.mTiming == mTiming && other.mAmplitude == mAmplitude;
207         }
208 
209         @Override
hashCode()210         public int hashCode() {
211             int result = 17;
212             result = 37 * (int) mTiming;
213             result = 37 * mAmplitude;
214             return result;
215         }
216 
217         @Override
toString()218         public String toString() {
219             return "OneShot{mTiming=" + mTiming +", mAmplitude=" + mAmplitude + "}";
220         }
221 
222         @Override
writeToParcel(Parcel out, int flags)223         public void writeToParcel(Parcel out, int flags) {
224             out.writeInt(PARCEL_TOKEN_ONE_SHOT);
225             out.writeLong(mTiming);
226             out.writeInt(mAmplitude);
227         }
228 
229         public static final Parcelable.Creator<OneShot> CREATOR =
230             new Parcelable.Creator<OneShot>() {
231                 @Override
232                 public OneShot createFromParcel(Parcel in) {
233                     // Skip the type token
234                     in.readInt();
235                     return new OneShot(in);
236                 }
237                 @Override
238                 public OneShot[] newArray(int size) {
239                     return new OneShot[size];
240                 }
241             };
242     }
243 
244     /** @hide */
245     public static class Waveform extends VibrationEffect implements Parcelable {
246         private long[] mTimings;
247         private int[] mAmplitudes;
248         private int mRepeat;
249 
Waveform(Parcel in)250         public Waveform(Parcel in) {
251             this(in.createLongArray(), in.createIntArray(), in.readInt());
252         }
253 
Waveform(long[] timings, int[] amplitudes, int repeat)254         public Waveform(long[] timings, int[] amplitudes, int repeat) {
255             mTimings = new long[timings.length];
256             System.arraycopy(timings, 0, mTimings, 0, timings.length);
257             mAmplitudes = new int[amplitudes.length];
258             System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length);
259             mRepeat = repeat;
260         }
261 
getTimings()262         public long[] getTimings() {
263             return mTimings;
264         }
265 
getAmplitudes()266         public int[] getAmplitudes() {
267             return mAmplitudes;
268         }
269 
getRepeatIndex()270         public int getRepeatIndex() {
271             return mRepeat;
272         }
273 
274         @Override
validate()275         public void validate() {
276             if (mTimings.length != mAmplitudes.length) {
277                 throw new IllegalArgumentException(
278                         "timing and amplitude arrays must be of equal length" +
279                         " (timings.length=" + mTimings.length +
280                         ", amplitudes.length=" + mAmplitudes.length + ")");
281             }
282             if (!hasNonZeroEntry(mTimings)) {
283                 throw new IllegalArgumentException("at least one timing must be non-zero" +
284                         " (timings=" + Arrays.toString(mTimings) + ")");
285             }
286             for (long timing : mTimings) {
287                 if (timing < 0) {
288                     throw new IllegalArgumentException("timings must all be >= 0" +
289                             " (timings=" + Arrays.toString(mTimings) + ")");
290                 }
291             }
292             for (int amplitude : mAmplitudes) {
293                 if (amplitude < -1 || amplitude > 255) {
294                     throw new IllegalArgumentException(
295                             "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255" +
296                             " (amplitudes=" + Arrays.toString(mAmplitudes) + ")");
297                 }
298             }
299             if (mRepeat < -1 || mRepeat >= mTimings.length) {
300                 throw new IllegalArgumentException(
301                         "repeat index must be within the bounds of the timings array" +
302                         " (timings.length=" + mTimings.length + ", index=" + mRepeat +")");
303             }
304         }
305 
306         @Override
equals(Object o)307         public boolean equals(Object o) {
308             if (!(o instanceof VibrationEffect.Waveform)) {
309                 return false;
310             }
311             VibrationEffect.Waveform other = (VibrationEffect.Waveform) o;
312             return Arrays.equals(mTimings, other.mTimings) &&
313                 Arrays.equals(mAmplitudes, other.mAmplitudes) &&
314                 mRepeat == other.mRepeat;
315         }
316 
317         @Override
hashCode()318         public int hashCode() {
319             int result = 17;
320             result = 37 * Arrays.hashCode(mTimings);
321             result = 37 * Arrays.hashCode(mAmplitudes);
322             result = 37 * mRepeat;
323             return result;
324         }
325 
326         @Override
toString()327         public String toString() {
328             return "Waveform{mTimings=" + Arrays.toString(mTimings) +
329                 ", mAmplitudes=" + Arrays.toString(mAmplitudes) +
330                 ", mRepeat=" + mRepeat +
331                 "}";
332         }
333 
334         @Override
writeToParcel(Parcel out, int flags)335         public void writeToParcel(Parcel out, int flags) {
336             out.writeInt(PARCEL_TOKEN_WAVEFORM);
337             out.writeLongArray(mTimings);
338             out.writeIntArray(mAmplitudes);
339             out.writeInt(mRepeat);
340         }
341 
hasNonZeroEntry(long[] vals)342         private static boolean hasNonZeroEntry(long[] vals) {
343             for (long val : vals) {
344                 if (val != 0) {
345                     return true;
346                 }
347             }
348             return false;
349         }
350 
351 
352         public static final Parcelable.Creator<Waveform> CREATOR =
353             new Parcelable.Creator<Waveform>() {
354                 @Override
355                 public Waveform createFromParcel(Parcel in) {
356                     // Skip the type token
357                     in.readInt();
358                     return new Waveform(in);
359                 }
360                 @Override
361                 public Waveform[] newArray(int size) {
362                     return new Waveform[size];
363                 }
364             };
365     }
366 
367     /** @hide */
368     public static class Prebaked extends VibrationEffect implements Parcelable {
369         private int mEffectId;
370 
Prebaked(Parcel in)371         public Prebaked(Parcel in) {
372             this(in.readInt());
373         }
374 
Prebaked(int effectId)375         public Prebaked(int effectId) {
376             mEffectId = effectId;
377         }
378 
getId()379         public int getId() {
380             return mEffectId;
381         }
382 
383         @Override
validate()384         public void validate() {
385             if (mEffectId != EFFECT_CLICK) {
386                 throw new IllegalArgumentException(
387                         "Unknown prebaked effect type (value=" + mEffectId + ")");
388             }
389         }
390 
391         @Override
equals(Object o)392         public boolean equals(Object o) {
393             if (!(o instanceof VibrationEffect.Prebaked)) {
394                 return false;
395             }
396             VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
397             return mEffectId == other.mEffectId;
398         }
399 
400         @Override
hashCode()401         public int hashCode() {
402             return mEffectId;
403         }
404 
405         @Override
toString()406         public String toString() {
407             return "Prebaked{mEffectId=" + mEffectId + "}";
408         }
409 
410 
411         @Override
writeToParcel(Parcel out, int flags)412         public void writeToParcel(Parcel out, int flags) {
413             out.writeInt(PARCEL_TOKEN_EFFECT);
414             out.writeInt(mEffectId);
415         }
416 
417         public static final Parcelable.Creator<Prebaked> CREATOR =
418             new Parcelable.Creator<Prebaked>() {
419                 @Override
420                 public Prebaked createFromParcel(Parcel in) {
421                     // Skip the type token
422                     in.readInt();
423                     return new Prebaked(in);
424                 }
425                 @Override
426                 public Prebaked[] newArray(int size) {
427                     return new Prebaked[size];
428                 }
429             };
430     }
431 
432     public static final Parcelable.Creator<VibrationEffect> CREATOR =
433             new Parcelable.Creator<VibrationEffect>() {
434                 @Override
435                 public VibrationEffect createFromParcel(Parcel in) {
436                     int token = in.readInt();
437                     if (token == PARCEL_TOKEN_ONE_SHOT) {
438                         return new OneShot(in);
439                     } else if (token == PARCEL_TOKEN_WAVEFORM) {
440                         return new Waveform(in);
441                     } else if (token == PARCEL_TOKEN_EFFECT) {
442                         return new Prebaked(in);
443                     } else {
444                         throw new IllegalStateException(
445                                 "Unexpected vibration event type token in parcel.");
446                     }
447                 }
448                 @Override
449                 public VibrationEffect[] newArray(int size) {
450                     return new VibrationEffect[size];
451                 }
452             };
453 }
454