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