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