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.annotation.Nullable;
20 import android.content.ContentResolver;
21 import android.content.Context;
22 import android.hardware.vibrator.V1_0.EffectStrength;
23 import android.hardware.vibrator.V1_2.Effect;
24 import android.net.Uri;
25 import android.util.MathUtils;
26 
27 import com.android.internal.annotations.VisibleForTesting;
28 
29 import java.util.Arrays;
30 
31 /**
32  * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
33  *
34  * These effects may be any number of things, from single shot vibrations to complex waveforms.
35  */
36 public abstract class VibrationEffect implements Parcelable {
37     private static final int PARCEL_TOKEN_ONE_SHOT = 1;
38     private static final int PARCEL_TOKEN_WAVEFORM = 2;
39     private static final int PARCEL_TOKEN_EFFECT = 3;
40 
41     /**
42      * The default vibration strength of the device.
43      */
44     public static final int DEFAULT_AMPLITUDE = -1;
45 
46     /**
47      * The maximum amplitude value
48      * @hide
49      */
50     public static final int MAX_AMPLITUDE = 255;
51 
52     /**
53      * A click effect.
54      *
55      * @see #get(int)
56      * @hide
57      */
58     public static final int EFFECT_CLICK = Effect.CLICK;
59 
60     /**
61      * A double click effect.
62      *
63      * @see #get(int)
64      * @hide
65      */
66     public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK;
67 
68     /**
69      * A tick effect.
70      * @see #get(int)
71      * @hide
72      */
73     public static final int EFFECT_TICK = Effect.TICK;
74 
75     /**
76      * A thud effect.
77      * @see #get(int)
78      * @hide
79      */
80     public static final int EFFECT_THUD = Effect.THUD;
81 
82     /**
83      * A pop effect.
84      * @see #get(int)
85      * @hide
86      */
87     public static final int EFFECT_POP = Effect.POP;
88 
89     /**
90      * A heavy click effect.
91      * @see #get(int)
92      * @hide
93      */
94     public static final int EFFECT_HEAVY_CLICK = Effect.HEAVY_CLICK;
95 
96 
97     /**
98      * Ringtone patterns. They may correspond with the device's ringtone audio, or may just be a
99      * pattern that can be played as a ringtone with any audio, depending on the device.
100      *
101      * @see #get(Uri, Context)
102      * @hide
103      */
104     @VisibleForTesting
105     public static final int[] RINGTONES = {
106         Effect.RINGTONE_1,
107         Effect.RINGTONE_2,
108         Effect.RINGTONE_3,
109         Effect.RINGTONE_4,
110         Effect.RINGTONE_5,
111         Effect.RINGTONE_6,
112         Effect.RINGTONE_7,
113         Effect.RINGTONE_8,
114         Effect.RINGTONE_9,
115         Effect.RINGTONE_10,
116         Effect.RINGTONE_11,
117         Effect.RINGTONE_12,
118         Effect.RINGTONE_13,
119         Effect.RINGTONE_14,
120         Effect.RINGTONE_15
121     };
122 
123     /** @hide to prevent subclassing from outside of the framework */
VibrationEffect()124     public VibrationEffect() { }
125 
126     /**
127      * Create a one shot vibration.
128      *
129      * One shot vibrations will vibrate constantly for the specified period of time at the
130      * specified amplitude, and then stop.
131      *
132      * @param milliseconds The number of milliseconds to vibrate. This must be a positive number.
133      * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or
134      * {@link #DEFAULT_AMPLITUDE}.
135      *
136      * @return The desired effect.
137      */
createOneShot(long milliseconds, int amplitude)138     public static VibrationEffect createOneShot(long milliseconds, int amplitude) {
139         VibrationEffect effect = new OneShot(milliseconds, amplitude);
140         effect.validate();
141         return effect;
142     }
143 
144     /**
145      * Create a waveform vibration.
146      *
147      * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
148      * each pair, the value in the amplitude array determines the strength of the vibration and the
149      * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
150      * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
151      * <p>
152      * The amplitude array of the generated waveform will be the same size as the given
153      * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE},
154      * starting with 0. Therefore the first timing value will be the period to wait before turning
155      * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE}
156      * strength, etc.
157      * </p><p>
158      * To cause the pattern to repeat, pass the index into the timings array at which to start the
159      * repetition, or -1 to disable repeating.
160      * </p>
161      *
162      * @param timings The pattern of alternating on-off timings, starting with off. Timing values
163      *                of 0 will cause the timing / amplitude pair to be ignored.
164      * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
165      *               want to repeat.
166      *
167      * @return The desired effect.
168      */
createWaveform(long[] timings, int repeat)169     public static VibrationEffect createWaveform(long[] timings, int repeat) {
170         int[] amplitudes = new int[timings.length];
171         for (int i = 0; i < (timings.length / 2); i++) {
172             amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE;
173         }
174         return createWaveform(timings, amplitudes, repeat);
175     }
176 
177     /**
178      * Create a waveform vibration.
179      *
180      * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
181      * each pair, the value in the amplitude array determines the strength of the vibration and the
182      * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
183      * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
184      * </p><p>
185      * To cause the pattern to repeat, pass the index into the timings array at which to start the
186      * repetition, or -1 to disable repeating.
187      * </p>
188      *
189      * @param timings The timing values of the timing / amplitude pairs. Timing values of 0
190      *                will cause the pair to be ignored.
191      * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values
192      *                   must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An
193      *                   amplitude value of 0 implies the motor is off.
194      * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
195      *               want to repeat.
196      *
197      * @return The desired effect.
198      */
createWaveform(long[] timings, int[] amplitudes, int repeat)199     public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
200         VibrationEffect effect = new Waveform(timings, amplitudes, repeat);
201         effect.validate();
202         return effect;
203     }
204 
205     /**
206      * Get a predefined vibration effect.
207      *
208      * Predefined effects are a set of common vibration effects that should be identical, regardless
209      * of the app they come from, in order to provide a cohesive experience for users across
210      * the entire device. They also may be custom tailored to the device hardware in order to
211      * provide a better experience than you could otherwise build using the generic building
212      * blocks.
213      *
214      * This will fallback to a generic pattern if one exists and there does not exist a
215      * hardware-specific implementation of the effect.
216      *
217      * @param effectId The ID of the effect to perform:
218      *                 {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
219      *
220      * @return The desired effect.
221      * @hide
222      */
get(int effectId)223     public static VibrationEffect get(int effectId) {
224         return get(effectId, true);
225     }
226 
227     /**
228      * Get a predefined vibration effect.
229      *
230      * Predefined effects are a set of common vibration effects that should be identical, regardless
231      * of the app they come from, in order to provide a cohesive experience for users across
232      * the entire device. They also may be custom tailored to the device hardware in order to
233      * provide a better experience than you could otherwise build using the generic building
234      * blocks.
235      *
236      * Some effects you may only want to play if there's a hardware specific implementation because
237      * they may, for example, be too disruptive to the user without tuning. The {@code fallback}
238      * parameter allows you to decide whether you want to fallback to the generic implementation or
239      * only play if there's a tuned, hardware specific one available.
240      *
241      * @param effectId The ID of the effect to perform:
242      *                 {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
243      * @param fallback Whether to fallback to a generic pattern if a hardware specific
244      *                 implementation doesn't exist.
245      *
246      * @return The desired effect.
247      * @hide
248      */
get(int effectId, boolean fallback)249     public static VibrationEffect get(int effectId, boolean fallback) {
250         VibrationEffect effect = new Prebaked(effectId, fallback);
251         effect.validate();
252         return effect;
253     }
254 
255     /**
256      * Get a predefined vibration effect associated with a given URI.
257      *
258      * Predefined effects are a set of common vibration effects that should be identical, regardless
259      * of the app they come from, in order to provide a cohesive experience for users across
260      * the entire device. They also may be custom tailored to the device hardware in order to
261      * provide a better experience than you could otherwise build using the generic building
262      * blocks.
263      *
264      * @param uri The URI associated with the haptic effect.
265      * @param context The context used to get the URI to haptic effect association.
266      *
267      * @return The desired effect, or {@code null} if there's no associated effect.
268      *
269      * @hide
270      */
271     @Nullable
get(Uri uri, Context context)272     public static VibrationEffect get(Uri uri, Context context) {
273         String[] uris = context.getResources().getStringArray(
274                 com.android.internal.R.array.config_ringtoneEffectUris);
275         for (int i = 0; i < uris.length && i < RINGTONES.length; i++) {
276             if (uris[i] == null) {
277                 continue;
278             }
279             ContentResolver cr = context.getContentResolver();
280             Uri mappedUri = cr.uncanonicalize(Uri.parse(uris[i]));
281             if (mappedUri == null) {
282                 continue;
283             }
284             if (mappedUri.equals(uri)) {
285                 return get(RINGTONES[i]);
286             }
287         }
288         return null;
289     }
290 
291     @Override
describeContents()292     public int describeContents() {
293         return 0;
294     }
295 
296     /** @hide */
validate()297     public abstract void validate();
298 
299     /**
300      * Gets the estimated duration of the vibration in milliseconds.
301      *
302      * For effects without a defined end (e.g. a Waveform with a non-negative repeat index), this
303      * returns Long.MAX_VALUE. For effects with an unknown duration (e.g. Prebaked effects where
304      * the length is device and potentially run-time dependent), this returns -1.
305      *
306      * @hide
307      */
getDuration()308     public abstract long getDuration();
309 
310     /**
311      * Scale the amplitude with the given constraints.
312      *
313      * This assumes that the previous value was in the range [0, MAX_AMPLITUDE]
314      * @hide
315      */
scale(int amplitude, float gamma, int maxAmplitude)316     protected static int scale(int amplitude, float gamma, int maxAmplitude) {
317         float val = MathUtils.pow(amplitude / (float) MAX_AMPLITUDE, gamma);
318         return (int) (val * maxAmplitude);
319     }
320 
321     /** @hide */
322     public static class OneShot extends VibrationEffect implements Parcelable {
323         private final long mDuration;
324         private final int mAmplitude;
325 
OneShot(Parcel in)326         public OneShot(Parcel in) {
327             mDuration = in.readLong();
328             mAmplitude = in.readInt();
329         }
330 
OneShot(long milliseconds, int amplitude)331         public OneShot(long milliseconds, int amplitude) {
332             mDuration = milliseconds;
333             mAmplitude = amplitude;
334         }
335 
336         @Override
getDuration()337         public long getDuration() {
338             return mDuration;
339         }
340 
getAmplitude()341         public int getAmplitude() {
342             return mAmplitude;
343         }
344 
345         /**
346          * Scale the amplitude of this effect.
347          *
348          * @param gamma the gamma adjustment to apply
349          * @param maxAmplitude the new maximum amplitude of the effect
350          *
351          * @return A {@link OneShot} effect with the same timing but scaled amplitude.
352          */
scale(float gamma, int maxAmplitude)353         public VibrationEffect scale(float gamma, int maxAmplitude) {
354             int newAmplitude = scale(mAmplitude, gamma, maxAmplitude);
355             return new OneShot(mDuration, newAmplitude);
356         }
357 
358         /**
359          * Resolve default values into integer amplitude numbers.
360          *
361          * @param defaultAmplitude the default amplitude to apply, must be between 0 and
362          *         MAX_AMPLITUDE
363          * @return A {@link OneShot} effect with same physical meaning but explicitly set amplitude
364          *
365          * @hide
366          */
resolve(int defaultAmplitude)367         public OneShot resolve(int defaultAmplitude) {
368             if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
369                 throw new IllegalArgumentException(
370                         "Amplitude is negative or greater than MAX_AMPLITUDE");
371             }
372             if (mAmplitude == DEFAULT_AMPLITUDE) {
373                 return new OneShot(mDuration, defaultAmplitude);
374             }
375             return this;
376         }
377 
378         @Override
validate()379         public void validate() {
380             if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) {
381                 throw new IllegalArgumentException(
382                         "amplitude must either be DEFAULT_AMPLITUDE, "
383                         + "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")");
384             }
385             if (mDuration <= 0) {
386                 throw new IllegalArgumentException(
387                         "duration must be positive (duration=" + mDuration + ")");
388             }
389         }
390 
391         @Override
equals(Object o)392         public boolean equals(Object o) {
393             if (!(o instanceof VibrationEffect.OneShot)) {
394                 return false;
395             }
396             VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
397             return other.mDuration == mDuration && other.mAmplitude == mAmplitude;
398         }
399 
400         @Override
hashCode()401         public int hashCode() {
402             int result = 17;
403             result += 37 * (int) mDuration;
404             result += 37 * mAmplitude;
405             return result;
406         }
407 
408         @Override
toString()409         public String toString() {
410             return "OneShot{mDuration=" + mDuration + ", mAmplitude=" + mAmplitude + "}";
411         }
412 
413         @Override
writeToParcel(Parcel out, int flags)414         public void writeToParcel(Parcel out, int flags) {
415             out.writeInt(PARCEL_TOKEN_ONE_SHOT);
416             out.writeLong(mDuration);
417             out.writeInt(mAmplitude);
418         }
419 
420         public static final Parcelable.Creator<OneShot> CREATOR =
421             new Parcelable.Creator<OneShot>() {
422                 @Override
423                 public OneShot createFromParcel(Parcel in) {
424                     // Skip the type token
425                     in.readInt();
426                     return new OneShot(in);
427                 }
428                 @Override
429                 public OneShot[] newArray(int size) {
430                     return new OneShot[size];
431                 }
432             };
433     }
434 
435     /** @hide */
436     public static class Waveform extends VibrationEffect implements Parcelable {
437         private final long[] mTimings;
438         private final int[] mAmplitudes;
439         private final int mRepeat;
440 
Waveform(Parcel in)441         public Waveform(Parcel in) {
442             this(in.createLongArray(), in.createIntArray(), in.readInt());
443         }
444 
Waveform(long[] timings, int[] amplitudes, int repeat)445         public Waveform(long[] timings, int[] amplitudes, int repeat) {
446             mTimings = new long[timings.length];
447             System.arraycopy(timings, 0, mTimings, 0, timings.length);
448             mAmplitudes = new int[amplitudes.length];
449             System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length);
450             mRepeat = repeat;
451         }
452 
getTimings()453         public long[] getTimings() {
454             return mTimings;
455         }
456 
getAmplitudes()457         public int[] getAmplitudes() {
458             return mAmplitudes;
459         }
460 
getRepeatIndex()461         public int getRepeatIndex() {
462             return mRepeat;
463         }
464 
465         @Override
getDuration()466         public long getDuration() {
467             if (mRepeat >= 0) {
468                 return Long.MAX_VALUE;
469             }
470             long duration = 0;
471             for (long d : mTimings) {
472                 duration += d;
473             }
474             return duration;
475         }
476 
477         /**
478          * Scale the Waveform with the given gamma and new max amplitude.
479          *
480          * @param gamma the gamma adjustment to apply
481          * @param maxAmplitude the new maximum amplitude of the effect
482          *
483          * @return A {@link Waveform} effect with the same timings and repeat index
484          *         but scaled amplitude.
485          */
scale(float gamma, int maxAmplitude)486         public VibrationEffect scale(float gamma, int maxAmplitude) {
487             if (gamma == 1.0f && maxAmplitude == MAX_AMPLITUDE) {
488                 // Just return a copy of the original if there's no scaling to be done.
489                 return new Waveform(mTimings, mAmplitudes, mRepeat);
490             }
491 
492             int[] scaledAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
493             for (int i = 0; i < scaledAmplitudes.length; i++) {
494                 scaledAmplitudes[i] = scale(scaledAmplitudes[i], gamma, maxAmplitude);
495             }
496             return new Waveform(mTimings, scaledAmplitudes, mRepeat);
497         }
498 
499         /**
500          * Resolve default values into integer amplitude numbers.
501          *
502          * @param defaultAmplitude the default amplitude to apply, must be between 0 and
503          *         MAX_AMPLITUDE
504          * @return A {@link Waveform} effect with same physical meaning but explicitly set
505          *         amplitude
506          *
507          * @hide
508          */
resolve(int defaultAmplitude)509         public Waveform resolve(int defaultAmplitude) {
510             if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
511                 throw new IllegalArgumentException(
512                         "Amplitude is negative or greater than MAX_AMPLITUDE");
513             }
514             int[] resolvedAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
515             for (int i = 0; i < resolvedAmplitudes.length; i++) {
516                 if (resolvedAmplitudes[i] == DEFAULT_AMPLITUDE) {
517                     resolvedAmplitudes[i] = defaultAmplitude;
518                 }
519             }
520             return new Waveform(mTimings, resolvedAmplitudes, mRepeat);
521         }
522 
523         @Override
validate()524         public void validate() {
525             if (mTimings.length != mAmplitudes.length) {
526                 throw new IllegalArgumentException(
527                         "timing and amplitude arrays must be of equal length"
528                         + " (timings.length=" + mTimings.length
529                         + ", amplitudes.length=" + mAmplitudes.length + ")");
530             }
531             if (!hasNonZeroEntry(mTimings)) {
532                 throw new IllegalArgumentException("at least one timing must be non-zero"
533                         + " (timings=" + Arrays.toString(mTimings) + ")");
534             }
535             for (long timing : mTimings) {
536                 if (timing < 0) {
537                     throw new IllegalArgumentException("timings must all be >= 0"
538                             + " (timings=" + Arrays.toString(mTimings) + ")");
539                 }
540             }
541             for (int amplitude : mAmplitudes) {
542                 if (amplitude < -1 || amplitude > 255) {
543                     throw new IllegalArgumentException(
544                             "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255"
545                             + " (amplitudes=" + Arrays.toString(mAmplitudes) + ")");
546                 }
547             }
548             if (mRepeat < -1 || mRepeat >= mTimings.length) {
549                 throw new IllegalArgumentException(
550                         "repeat index must be within the bounds of the timings array"
551                         + " (timings.length=" + mTimings.length + ", index=" + mRepeat + ")");
552             }
553         }
554 
555         @Override
equals(Object o)556         public boolean equals(Object o) {
557             if (!(o instanceof VibrationEffect.Waveform)) {
558                 return false;
559             }
560             VibrationEffect.Waveform other = (VibrationEffect.Waveform) o;
561             return Arrays.equals(mTimings, other.mTimings)
562                 && Arrays.equals(mAmplitudes, other.mAmplitudes)
563                 && mRepeat == other.mRepeat;
564         }
565 
566         @Override
hashCode()567         public int hashCode() {
568             int result = 17;
569             result += 37 * Arrays.hashCode(mTimings);
570             result += 37 * Arrays.hashCode(mAmplitudes);
571             result += 37 * mRepeat;
572             return result;
573         }
574 
575         @Override
toString()576         public String toString() {
577             return "Waveform{mTimings=" + Arrays.toString(mTimings)
578                 + ", mAmplitudes=" + Arrays.toString(mAmplitudes)
579                 + ", mRepeat=" + mRepeat
580                 + "}";
581         }
582 
583         @Override
writeToParcel(Parcel out, int flags)584         public void writeToParcel(Parcel out, int flags) {
585             out.writeInt(PARCEL_TOKEN_WAVEFORM);
586             out.writeLongArray(mTimings);
587             out.writeIntArray(mAmplitudes);
588             out.writeInt(mRepeat);
589         }
590 
hasNonZeroEntry(long[] vals)591         private static boolean hasNonZeroEntry(long[] vals) {
592             for (long val : vals) {
593                 if (val != 0) {
594                     return true;
595                 }
596             }
597             return false;
598         }
599 
600 
601         public static final Parcelable.Creator<Waveform> CREATOR =
602             new Parcelable.Creator<Waveform>() {
603                 @Override
604                 public Waveform createFromParcel(Parcel in) {
605                     // Skip the type token
606                     in.readInt();
607                     return new Waveform(in);
608                 }
609                 @Override
610                 public Waveform[] newArray(int size) {
611                     return new Waveform[size];
612                 }
613             };
614     }
615 
616     /** @hide */
617     public static class Prebaked extends VibrationEffect implements Parcelable {
618         private final int mEffectId;
619         private final boolean mFallback;
620 
621         private int mEffectStrength;
622 
Prebaked(Parcel in)623         public Prebaked(Parcel in) {
624             this(in.readInt(), in.readByte() != 0);
625             mEffectStrength = in.readInt();
626         }
627 
Prebaked(int effectId, boolean fallback)628         public Prebaked(int effectId, boolean fallback) {
629             mEffectId = effectId;
630             mFallback = fallback;
631             mEffectStrength = EffectStrength.MEDIUM;
632         }
633 
getId()634         public int getId() {
635             return mEffectId;
636         }
637 
638         /**
639          * Whether the effect should fall back to a generic pattern if there's no hardware specific
640          * implementation of it.
641          */
shouldFallback()642         public boolean shouldFallback() {
643             return mFallback;
644         }
645 
646         @Override
getDuration()647         public long getDuration() {
648             return -1;
649         }
650 
651         /**
652          * Set the effect strength of the prebaked effect.
653          */
setEffectStrength(int strength)654         public void setEffectStrength(int strength) {
655             if (!isValidEffectStrength(strength)) {
656                 throw new IllegalArgumentException("Invalid effect strength: " + strength);
657             }
658             mEffectStrength = strength;
659         }
660 
661         /**
662          * Set the effect strength.
663          */
getEffectStrength()664         public int getEffectStrength() {
665             return mEffectStrength;
666         }
667 
isValidEffectStrength(int strength)668         private static boolean isValidEffectStrength(int strength) {
669             switch (strength) {
670                 case EffectStrength.LIGHT:
671                 case EffectStrength.MEDIUM:
672                 case EffectStrength.STRONG:
673                     return true;
674                 default:
675                     return false;
676             }
677         }
678 
679         @Override
validate()680         public void validate() {
681             switch (mEffectId) {
682                 case EFFECT_CLICK:
683                 case EFFECT_DOUBLE_CLICK:
684                 case EFFECT_TICK:
685                 case EFFECT_THUD:
686                 case EFFECT_POP:
687                 case EFFECT_HEAVY_CLICK:
688                     break;
689                 default:
690                     if (mEffectId < RINGTONES[0] || mEffectId > RINGTONES[RINGTONES.length - 1]) {
691                         throw new IllegalArgumentException(
692                                 "Unknown prebaked effect type (value=" + mEffectId + ")");
693                     }
694             }
695             if (!isValidEffectStrength(mEffectStrength)) {
696                 throw new IllegalArgumentException(
697                         "Unknown prebaked effect strength (value=" + mEffectStrength + ")");
698             }
699         }
700 
701         @Override
equals(Object o)702         public boolean equals(Object o) {
703             if (!(o instanceof VibrationEffect.Prebaked)) {
704                 return false;
705             }
706             VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
707             return mEffectId == other.mEffectId
708                 && mFallback == other.mFallback
709                 && mEffectStrength == other.mEffectStrength;
710         }
711 
712         @Override
hashCode()713         public int hashCode() {
714             int result = 17;
715             result += 37 * mEffectId;
716             result += 37 * mEffectStrength;
717             return result;
718         }
719 
720         @Override
toString()721         public String toString() {
722             return "Prebaked{mEffectId=" + mEffectId
723                 + ", mEffectStrength=" + mEffectStrength
724                 + ", mFallback=" + mFallback
725                 + "}";
726         }
727 
728 
729         @Override
writeToParcel(Parcel out, int flags)730         public void writeToParcel(Parcel out, int flags) {
731             out.writeInt(PARCEL_TOKEN_EFFECT);
732             out.writeInt(mEffectId);
733             out.writeByte((byte) (mFallback ? 1 : 0));
734             out.writeInt(mEffectStrength);
735         }
736 
737         public static final Parcelable.Creator<Prebaked> CREATOR =
738             new Parcelable.Creator<Prebaked>() {
739                 @Override
740                 public Prebaked createFromParcel(Parcel in) {
741                     // Skip the type token
742                     in.readInt();
743                     return new Prebaked(in);
744                 }
745                 @Override
746                 public Prebaked[] newArray(int size) {
747                     return new Prebaked[size];
748                 }
749             };
750     }
751 
752     public static final Parcelable.Creator<VibrationEffect> CREATOR =
753             new Parcelable.Creator<VibrationEffect>() {
754                 @Override
755                 public VibrationEffect createFromParcel(Parcel in) {
756                     int token = in.readInt();
757                     if (token == PARCEL_TOKEN_ONE_SHOT) {
758                         return new OneShot(in);
759                     } else if (token == PARCEL_TOKEN_WAVEFORM) {
760                         return new Waveform(in);
761                     } else if (token == PARCEL_TOKEN_EFFECT) {
762                         return new Prebaked(in);
763                     } else {
764                         throw new IllegalStateException(
765                                 "Unexpected vibration event type token in parcel.");
766                     }
767                 }
768                 @Override
769                 public VibrationEffect[] newArray(int size) {
770                     return new VibrationEffect[size];
771                 }
772             };
773 }
774