VibrationEffect.java revision f38efd123a3b8ce7ddf85ca22d2195dba02f61c6
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.ContentResolver;
21import android.content.Context;
22import android.hardware.vibrator.V1_0.EffectStrength;
23import android.hardware.vibrator.V1_2.Effect;
24import android.net.Uri;
25import android.util.MathUtils;
26
27import com.android.internal.annotations.VisibleForTesting;
28
29import 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 */
36public 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 */
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     */
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     */
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     */
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     */
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     */
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
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
292    public int describeContents() {
293        return 0;
294    }
295
296    /** @hide */
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     */
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     */
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
326        public OneShot(Parcel in) {
327            mDuration = in.readLong();
328            mAmplitude = in.readInt();
329        }
330
331        public OneShot(long milliseconds, int amplitude) {
332            mDuration = milliseconds;
333            mAmplitude = amplitude;
334        }
335
336        @Override
337        public long getDuration() {
338            return mDuration;
339        }
340
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         */
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         */
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
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
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
401        public int hashCode() {
402            int result = 17;
403            result += 37 * (int) mDuration;
404            result += 37 * mAmplitude;
405            return result;
406        }
407
408        @Override
409        public String toString() {
410            return "OneShot{mDuration=" + mDuration + ", mAmplitude=" + mAmplitude + "}";
411        }
412
413        @Override
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
441        public Waveform(Parcel in) {
442            this(in.createLongArray(), in.createIntArray(), in.readInt());
443        }
444
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
453        public long[] getTimings() {
454            return mTimings;
455        }
456
457        public int[] getAmplitudes() {
458            return mAmplitudes;
459        }
460
461        public int getRepeatIndex() {
462            return mRepeat;
463        }
464
465        @Override
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         */
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         */
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
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
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
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
576        public String toString() {
577            return "Waveform{mTimings=" + Arrays.toString(mTimings)
578                + ", mAmplitudes=" + Arrays.toString(mAmplitudes)
579                + ", mRepeat=" + mRepeat
580                + "}";
581        }
582
583        @Override
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
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
623        public Prebaked(Parcel in) {
624            this(in.readInt(), in.readByte() != 0);
625            mEffectStrength = in.readInt();
626        }
627
628        public Prebaked(int effectId, boolean fallback) {
629            mEffectId = effectId;
630            mFallback = fallback;
631            mEffectStrength = EffectStrength.MEDIUM;
632        }
633
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         */
642        public boolean shouldFallback() {
643            return mFallback;
644        }
645
646        @Override
647        public long getDuration() {
648            return -1;
649        }
650
651        /**
652         * Set the effect strength of the prebaked effect.
653         */
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         */
664        public int getEffectStrength() {
665            return mEffectStrength;
666        }
667
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
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
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
713        public int hashCode() {
714            int result = 17;
715            result += 37 * mEffectId;
716            result += 37 * mEffectStrength;
717            return result;
718        }
719
720        @Override
721        public String toString() {
722            return "Prebaked{mEffectId=" + mEffectId
723                + ", mEffectStrength=" + mEffectStrength
724                + ", mFallback=" + mFallback
725                + "}";
726        }
727
728
729        @Override
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