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