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.FloatRange;
20 import android.annotation.IntDef;
21 import android.annotation.IntRange;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.TestApi;
25 import android.compat.annotation.UnsupportedAppUsage;
26 import android.content.ContentResolver;
27 import android.content.Context;
28 import android.hardware.vibrator.V1_0.EffectStrength;
29 import android.hardware.vibrator.V1_3.Effect;
30 import android.net.Uri;
31 import android.util.MathUtils;
32 
33 import com.android.internal.util.Preconditions;
34 
35 import java.lang.annotation.Retention;
36 import java.lang.annotation.RetentionPolicy;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.List;
40 import java.util.Objects;
41 
42 /**
43  * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
44  *
45  * These effects may be any number of things, from single shot vibrations to complex waveforms.
46  */
47 public abstract class VibrationEffect implements Parcelable {
48     private static final int PARCEL_TOKEN_ONE_SHOT = 1;
49     private static final int PARCEL_TOKEN_WAVEFORM = 2;
50     private static final int PARCEL_TOKEN_EFFECT = 3;
51     private static final int PARCEL_TOKEN_COMPOSITION = 4;
52 
53 
54     /**
55      * The default vibration strength of the device.
56      */
57     public static final int DEFAULT_AMPLITUDE = -1;
58 
59     /**
60      * The maximum amplitude value
61      * @hide
62      */
63     public static final int MAX_AMPLITUDE = 255;
64 
65     /**
66      * A click effect. Use this effect as a baseline, as it's the most common type of click effect.
67      */
68     public static final int EFFECT_CLICK = Effect.CLICK;
69 
70     /**
71      * A double click effect.
72      */
73     public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK;
74 
75     /**
76      * A tick effect. This effect is less strong compared to {@link #EFFECT_CLICK}.
77      */
78     public static final int EFFECT_TICK = Effect.TICK;
79 
80     /**
81      * A thud effect.
82      * @see #get(int)
83      * @hide
84      */
85     @UnsupportedAppUsage
86     @TestApi
87     public static final int EFFECT_THUD = Effect.THUD;
88 
89     /**
90      * A pop effect.
91      * @see #get(int)
92      * @hide
93      */
94     @UnsupportedAppUsage
95     @TestApi
96     public static final int EFFECT_POP = Effect.POP;
97 
98     /**
99      * A heavy click effect. This effect is stronger than {@link #EFFECT_CLICK}.
100      */
101     public static final int EFFECT_HEAVY_CLICK = Effect.HEAVY_CLICK;
102 
103     /**
104      * A texture effect meant to replicate soft ticks.
105      *
106      * Unlike normal effects, texture effects are meant to be called repeatedly, generally in
107      * response to some motion, in order to replicate the feeling of some texture underneath the
108      * user's fingers.
109      *
110      * @see #get(int)
111      * @hide
112      */
113     @TestApi
114     public static final int EFFECT_TEXTURE_TICK = Effect.TEXTURE_TICK;
115 
116     /** {@hide} */
117     @TestApi
118     public static final int EFFECT_STRENGTH_LIGHT = EffectStrength.LIGHT;
119 
120     /** {@hide} */
121     @TestApi
122     public static final int EFFECT_STRENGTH_MEDIUM = EffectStrength.MEDIUM;
123 
124     /** {@hide} */
125     @TestApi
126     public static final int EFFECT_STRENGTH_STRONG = EffectStrength.STRONG;
127 
128     /**
129      * Ringtone patterns. They may correspond with the device's ringtone audio, or may just be a
130      * pattern that can be played as a ringtone with any audio, depending on the device.
131      *
132      * @see #get(Uri, Context)
133      * @hide
134      */
135     @UnsupportedAppUsage
136     @TestApi
137     public static final int[] RINGTONES = {
138         Effect.RINGTONE_1,
139         Effect.RINGTONE_2,
140         Effect.RINGTONE_3,
141         Effect.RINGTONE_4,
142         Effect.RINGTONE_5,
143         Effect.RINGTONE_6,
144         Effect.RINGTONE_7,
145         Effect.RINGTONE_8,
146         Effect.RINGTONE_9,
147         Effect.RINGTONE_10,
148         Effect.RINGTONE_11,
149         Effect.RINGTONE_12,
150         Effect.RINGTONE_13,
151         Effect.RINGTONE_14,
152         Effect.RINGTONE_15
153     };
154 
155     /** @hide */
156     @IntDef(prefix = { "EFFECT_" }, value = {
157             EFFECT_TICK,
158             EFFECT_CLICK,
159             EFFECT_HEAVY_CLICK,
160             EFFECT_DOUBLE_CLICK,
161     })
162     @Retention(RetentionPolicy.SOURCE)
163     public @interface EffectType {}
164 
165     /** @hide to prevent subclassing from outside of the framework */
VibrationEffect()166     public VibrationEffect() { }
167 
168     /**
169      * Create a one shot vibration.
170      *
171      * One shot vibrations will vibrate constantly for the specified period of time at the
172      * specified amplitude, and then stop.
173      *
174      * @param milliseconds The number of milliseconds to vibrate. This must be a positive number.
175      * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or
176      * {@link #DEFAULT_AMPLITUDE}.
177      *
178      * @return The desired effect.
179      */
createOneShot(long milliseconds, int amplitude)180     public static VibrationEffect createOneShot(long milliseconds, int amplitude) {
181         VibrationEffect effect = new OneShot(milliseconds, amplitude);
182         effect.validate();
183         return effect;
184     }
185 
186     /**
187      * Create a waveform vibration.
188      *
189      * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
190      * each pair, the value in the amplitude array determines the strength of the vibration and the
191      * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
192      * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
193      * <p>
194      * The amplitude array of the generated waveform will be the same size as the given
195      * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE},
196      * starting with 0. Therefore the first timing value will be the period to wait before turning
197      * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE}
198      * strength, etc.
199      * </p><p>
200      * To cause the pattern to repeat, pass the index into the timings array at which to start the
201      * repetition, or -1 to disable repeating.
202      * </p>
203      *
204      * @param timings The pattern of alternating on-off timings, starting with off. Timing values
205      *                of 0 will cause the timing / amplitude pair to be ignored.
206      * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
207      *               want to repeat.
208      *
209      * @return The desired effect.
210      */
createWaveform(long[] timings, int repeat)211     public static VibrationEffect createWaveform(long[] timings, int repeat) {
212         int[] amplitudes = new int[timings.length];
213         for (int i = 0; i < (timings.length / 2); i++) {
214             amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE;
215         }
216         return createWaveform(timings, amplitudes, repeat);
217     }
218 
219     /**
220      * Create a waveform vibration.
221      *
222      * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
223      * each pair, the value in the amplitude array determines the strength of the vibration and the
224      * value in the timing array determines how long it vibrates for, in milliseconds. Amplitude
225      * values must be between 0 and 255, and an amplitude of 0 implies no vibration (i.e. off). Any
226      * pairs with a timing value of 0 will be ignored.
227      * </p><p>
228      * To cause the pattern to repeat, pass the index into the timings array at which to start the
229      * repetition, or -1 to disable repeating.
230      * </p>
231      *
232      * @param timings The timing values, in milliseconds, of the timing / amplitude pairs. Timing
233      *                values of 0 will cause the pair to be ignored.
234      * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values
235      *                   must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An
236      *                   amplitude value of 0 implies the motor is off.
237      * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
238      *               want to repeat.
239      *
240      * @return The desired effect.
241      */
createWaveform(long[] timings, int[] amplitudes, int repeat)242     public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
243         VibrationEffect effect = new Waveform(timings, amplitudes, repeat);
244         effect.validate();
245         return effect;
246     }
247 
248     /**
249      * Create a predefined vibration effect.
250      *
251      * Predefined effects are a set of common vibration effects that should be identical, regardless
252      * of the app they come from, in order to provide a cohesive experience for users across
253      * the entire device. They also may be custom tailored to the device hardware in order to
254      * provide a better experience than you could otherwise build using the generic building
255      * blocks.
256      *
257      * This will fallback to a generic pattern if one exists and there does not exist a
258      * hardware-specific implementation of the effect.
259      *
260      * @param effectId The ID of the effect to perform:
261      *                 {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
262      *
263      * @return The desired effect.
264      */
265     @NonNull
createPredefined(@ffectType int effectId)266     public static VibrationEffect createPredefined(@EffectType int effectId) {
267         return get(effectId, true);
268     }
269 
270     /**
271      * Get a predefined vibration effect.
272      *
273      * Predefined effects are a set of common vibration effects that should be identical, regardless
274      * of the app they come from, in order to provide a cohesive experience for users across
275      * the entire device. They also may be custom tailored to the device hardware in order to
276      * provide a better experience than you could otherwise build using the generic building
277      * blocks.
278      *
279      * This will fallback to a generic pattern if one exists and there does not exist a
280      * hardware-specific implementation of the effect.
281      *
282      * @param effectId The ID of the effect to perform:
283      *                 {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
284      *
285      * @return The desired effect.
286      * @hide
287      */
288     @TestApi
get(int effectId)289     public static VibrationEffect get(int effectId) {
290         return get(effectId, true);
291     }
292 
293     /**
294      * Get a predefined vibration effect.
295      *
296      * Predefined effects are a set of common vibration effects that should be identical, regardless
297      * of the app they come from, in order to provide a cohesive experience for users across
298      * the entire device. They also may be custom tailored to the device hardware in order to
299      * provide a better experience than you could otherwise build using the generic building
300      * blocks.
301      *
302      * Some effects you may only want to play if there's a hardware specific implementation because
303      * they may, for example, be too disruptive to the user without tuning. The {@code fallback}
304      * parameter allows you to decide whether you want to fallback to the generic implementation or
305      * only play if there's a tuned, hardware specific one available.
306      *
307      * @param effectId The ID of the effect to perform:
308      *                 {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
309      * @param fallback Whether to fallback to a generic pattern if a hardware specific
310      *                 implementation doesn't exist.
311      *
312      * @return The desired effect.
313      * @hide
314      */
315     @TestApi
get(int effectId, boolean fallback)316     public static VibrationEffect get(int effectId, boolean fallback) {
317         VibrationEffect effect = new Prebaked(effectId, fallback);
318         effect.validate();
319         return effect;
320     }
321 
322     /**
323      * Get a predefined vibration effect associated with a given URI.
324      *
325      * Predefined effects are a set of common vibration effects that should be identical, regardless
326      * of the app they come from, in order to provide a cohesive experience for users across
327      * the entire device. They also may be custom tailored to the device hardware in order to
328      * provide a better experience than you could otherwise build using the generic building
329      * blocks.
330      *
331      * @param uri The URI associated with the haptic effect.
332      * @param context The context used to get the URI to haptic effect association.
333      *
334      * @return The desired effect, or {@code null} if there's no associated effect.
335      *
336      * @hide
337      */
338     @TestApi
339     @Nullable
get(Uri uri, Context context)340     public static VibrationEffect get(Uri uri, Context context) {
341         String[] uris = context.getResources().getStringArray(
342                 com.android.internal.R.array.config_ringtoneEffectUris);
343 
344         // Skip doing any IPC if we don't have any effects configured.
345         if (uris.length == 0) {
346             return null;
347         }
348 
349         final ContentResolver cr = context.getContentResolver();
350         Uri uncanonicalUri = cr.uncanonicalize(uri);
351         if (uncanonicalUri == null) {
352             // If we already had an uncanonical URI, it's possible we'll get null back here. In
353             // this case, just use the URI as passed in since it wasn't canonicalized in the first
354             // place.
355             uncanonicalUri = uri;
356         }
357 
358         for (int i = 0; i < uris.length && i < RINGTONES.length; i++) {
359             if (uris[i] == null) {
360                 continue;
361             }
362             Uri mappedUri = cr.uncanonicalize(Uri.parse(uris[i]));
363             if (mappedUri == null) {
364                 continue;
365             }
366             if (mappedUri.equals(uncanonicalUri)) {
367                 return get(RINGTONES[i]);
368             }
369         }
370         return null;
371     }
372 
373     /**
374      * Start composing a haptic effect.
375      *
376      * @see VibrationEffect.Composition
377      */
378     @NonNull
startComposition()379     public static VibrationEffect.Composition startComposition() {
380         return new VibrationEffect.Composition();
381     }
382 
383     @Override
describeContents()384     public int describeContents() {
385         return 0;
386     }
387 
388     /** @hide */
validate()389     public abstract void validate();
390 
391     /**
392      * Gets the estimated duration of the vibration in milliseconds.
393      *
394      * For effects without a defined end (e.g. a Waveform with a non-negative repeat index), this
395      * returns Long.MAX_VALUE. For effects with an unknown duration (e.g. Prebaked effects where
396      * the length is device and potentially run-time dependent), this returns -1.
397      *
398      * @hide
399      */
400     @TestApi
getDuration()401     public abstract long getDuration();
402 
403     /**
404      * Scale the amplitude with the given constraints.
405      *
406      * This assumes that the previous value was in the range [0, MAX_AMPLITUDE]
407      * @hide
408      */
409     @TestApi
scale(int amplitude, float gamma, int maxAmplitude)410     protected static int scale(int amplitude, float gamma, int maxAmplitude) {
411         float val = MathUtils.pow(amplitude / (float) MAX_AMPLITUDE, gamma);
412         return (int) (val * maxAmplitude);
413     }
414 
415     /** @hide */
416     @TestApi
417     public static class OneShot extends VibrationEffect implements Parcelable {
418         private final long mDuration;
419         private final int mAmplitude;
420 
OneShot(Parcel in)421         public OneShot(Parcel in) {
422             mDuration = in.readLong();
423             mAmplitude = in.readInt();
424         }
425 
OneShot(long milliseconds, int amplitude)426         public OneShot(long milliseconds, int amplitude) {
427             mDuration = milliseconds;
428             mAmplitude = amplitude;
429         }
430 
431         @Override
getDuration()432         public long getDuration() {
433             return mDuration;
434         }
435 
getAmplitude()436         public int getAmplitude() {
437             return mAmplitude;
438         }
439 
440         /**
441          * Scale the amplitude of this effect.
442          *
443          * @param gamma the gamma adjustment to apply
444          * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and
445          *         MAX_AMPLITUDE
446          * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE
447          *
448          * @return A {@link OneShot} effect with the same timing but scaled amplitude.
449          */
scale(float gamma, int maxAmplitude)450         public OneShot scale(float gamma, int maxAmplitude) {
451             if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) {
452                 throw new IllegalArgumentException(
453                         "Amplitude is negative or greater than MAX_AMPLITUDE");
454             }
455             int newAmplitude = scale(mAmplitude, gamma, maxAmplitude);
456             return new OneShot(mDuration, newAmplitude);
457         }
458 
459         /**
460          * Resolve default values into integer amplitude numbers.
461          *
462          * @param defaultAmplitude the default amplitude to apply, must be between 0 and
463          *         MAX_AMPLITUDE
464          * @return A {@link OneShot} effect with same physical meaning but explicitly set amplitude
465          *
466          * @hide
467          */
resolve(int defaultAmplitude)468         public OneShot resolve(int defaultAmplitude) {
469             if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
470                 throw new IllegalArgumentException(
471                         "Amplitude is negative or greater than MAX_AMPLITUDE");
472             }
473             if (mAmplitude == DEFAULT_AMPLITUDE) {
474                 return new OneShot(mDuration, defaultAmplitude);
475             }
476             return this;
477         }
478 
479         @Override
validate()480         public void validate() {
481             if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) {
482                 throw new IllegalArgumentException(
483                         "amplitude must either be DEFAULT_AMPLITUDE, "
484                         + "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")");
485             }
486             if (mDuration <= 0) {
487                 throw new IllegalArgumentException(
488                         "duration must be positive (duration=" + mDuration + ")");
489             }
490         }
491 
492         @Override
equals(Object o)493         public boolean equals(Object o) {
494             if (!(o instanceof VibrationEffect.OneShot)) {
495                 return false;
496             }
497             VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
498             return other.mDuration == mDuration && other.mAmplitude == mAmplitude;
499         }
500 
501         @Override
hashCode()502         public int hashCode() {
503             int result = 17;
504             result += 37 * (int) mDuration;
505             result += 37 * mAmplitude;
506             return result;
507         }
508 
509         @Override
toString()510         public String toString() {
511             return "OneShot{mDuration=" + mDuration + ", mAmplitude=" + mAmplitude + "}";
512         }
513 
514         @Override
writeToParcel(Parcel out, int flags)515         public void writeToParcel(Parcel out, int flags) {
516             out.writeInt(PARCEL_TOKEN_ONE_SHOT);
517             out.writeLong(mDuration);
518             out.writeInt(mAmplitude);
519         }
520 
521         @UnsupportedAppUsage
522         public static final @android.annotation.NonNull Parcelable.Creator<OneShot> CREATOR =
523             new Parcelable.Creator<OneShot>() {
524                 @Override
525                 public OneShot createFromParcel(Parcel in) {
526                     // Skip the type token
527                     in.readInt();
528                     return new OneShot(in);
529                 }
530                 @Override
531                 public OneShot[] newArray(int size) {
532                     return new OneShot[size];
533                 }
534             };
535     }
536 
537     /** @hide */
538     @TestApi
539     public static class Waveform extends VibrationEffect implements Parcelable {
540         private final long[] mTimings;
541         private final int[] mAmplitudes;
542         private final int mRepeat;
543 
Waveform(Parcel in)544         public Waveform(Parcel in) {
545             this(in.createLongArray(), in.createIntArray(), in.readInt());
546         }
547 
Waveform(long[] timings, int[] amplitudes, int repeat)548         public Waveform(long[] timings, int[] amplitudes, int repeat) {
549             mTimings = new long[timings.length];
550             System.arraycopy(timings, 0, mTimings, 0, timings.length);
551             mAmplitudes = new int[amplitudes.length];
552             System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length);
553             mRepeat = repeat;
554         }
555 
getTimings()556         public long[] getTimings() {
557             return mTimings;
558         }
559 
getAmplitudes()560         public int[] getAmplitudes() {
561             return mAmplitudes;
562         }
563 
getRepeatIndex()564         public int getRepeatIndex() {
565             return mRepeat;
566         }
567 
568         @Override
getDuration()569         public long getDuration() {
570             if (mRepeat >= 0) {
571                 return Long.MAX_VALUE;
572             }
573             long duration = 0;
574             for (long d : mTimings) {
575                 duration += d;
576             }
577             return duration;
578         }
579 
580         /**
581          * Scale the Waveform with the given gamma and new max amplitude.
582          *
583          * @param gamma the gamma adjustment to apply
584          * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and
585          *         MAX_AMPLITUDE
586          * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE
587          *
588          * @return A {@link Waveform} effect with the same timings and repeat index
589          *         but scaled amplitude.
590          */
scale(float gamma, int maxAmplitude)591         public Waveform scale(float gamma, int maxAmplitude) {
592             if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) {
593                 throw new IllegalArgumentException(
594                         "Amplitude is negative or greater than MAX_AMPLITUDE");
595             }
596             if (gamma == 1.0f && maxAmplitude == MAX_AMPLITUDE) {
597                 // Just return a copy of the original if there's no scaling to be done.
598                 return new Waveform(mTimings, mAmplitudes, mRepeat);
599             }
600 
601             int[] scaledAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
602             for (int i = 0; i < scaledAmplitudes.length; i++) {
603                 scaledAmplitudes[i] = scale(scaledAmplitudes[i], gamma, maxAmplitude);
604             }
605             return new Waveform(mTimings, scaledAmplitudes, mRepeat);
606         }
607 
608         /**
609          * Resolve default values into integer amplitude numbers.
610          *
611          * @param defaultAmplitude the default amplitude to apply, must be between 0 and
612          *         MAX_AMPLITUDE
613          * @return A {@link Waveform} effect with same physical meaning but explicitly set
614          *         amplitude
615          *
616          * @hide
617          */
resolve(int defaultAmplitude)618         public Waveform resolve(int defaultAmplitude) {
619             if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
620                 throw new IllegalArgumentException(
621                         "Amplitude is negative or greater than MAX_AMPLITUDE");
622             }
623             int[] resolvedAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
624             for (int i = 0; i < resolvedAmplitudes.length; i++) {
625                 if (resolvedAmplitudes[i] == DEFAULT_AMPLITUDE) {
626                     resolvedAmplitudes[i] = defaultAmplitude;
627                 }
628             }
629             return new Waveform(mTimings, resolvedAmplitudes, mRepeat);
630         }
631 
632         @Override
validate()633         public void validate() {
634             if (mTimings.length != mAmplitudes.length) {
635                 throw new IllegalArgumentException(
636                         "timing and amplitude arrays must be of equal length"
637                         + " (timings.length=" + mTimings.length
638                         + ", amplitudes.length=" + mAmplitudes.length + ")");
639             }
640             if (!hasNonZeroEntry(mTimings)) {
641                 throw new IllegalArgumentException("at least one timing must be non-zero"
642                         + " (timings=" + Arrays.toString(mTimings) + ")");
643             }
644             for (long timing : mTimings) {
645                 if (timing < 0) {
646                     throw new IllegalArgumentException("timings must all be >= 0"
647                             + " (timings=" + Arrays.toString(mTimings) + ")");
648                 }
649             }
650             for (int amplitude : mAmplitudes) {
651                 if (amplitude < -1 || amplitude > 255) {
652                     throw new IllegalArgumentException(
653                             "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255"
654                             + " (amplitudes=" + Arrays.toString(mAmplitudes) + ")");
655                 }
656             }
657             if (mRepeat < -1 || mRepeat >= mTimings.length) {
658                 throw new IllegalArgumentException(
659                         "repeat index must be within the bounds of the timings array"
660                         + " (timings.length=" + mTimings.length + ", index=" + mRepeat + ")");
661             }
662         }
663 
664         @Override
equals(Object o)665         public boolean equals(Object o) {
666             if (!(o instanceof VibrationEffect.Waveform)) {
667                 return false;
668             }
669             VibrationEffect.Waveform other = (VibrationEffect.Waveform) o;
670             return Arrays.equals(mTimings, other.mTimings)
671                 && Arrays.equals(mAmplitudes, other.mAmplitudes)
672                 && mRepeat == other.mRepeat;
673         }
674 
675         @Override
hashCode()676         public int hashCode() {
677             int result = 17;
678             result += 37 * Arrays.hashCode(mTimings);
679             result += 37 * Arrays.hashCode(mAmplitudes);
680             result += 37 * mRepeat;
681             return result;
682         }
683 
684         @Override
toString()685         public String toString() {
686             return "Waveform{mTimings=" + Arrays.toString(mTimings)
687                 + ", mAmplitudes=" + Arrays.toString(mAmplitudes)
688                 + ", mRepeat=" + mRepeat
689                 + "}";
690         }
691 
692         @Override
writeToParcel(Parcel out, int flags)693         public void writeToParcel(Parcel out, int flags) {
694             out.writeInt(PARCEL_TOKEN_WAVEFORM);
695             out.writeLongArray(mTimings);
696             out.writeIntArray(mAmplitudes);
697             out.writeInt(mRepeat);
698         }
699 
hasNonZeroEntry(long[] vals)700         private static boolean hasNonZeroEntry(long[] vals) {
701             for (long val : vals) {
702                 if (val != 0) {
703                     return true;
704                 }
705             }
706             return false;
707         }
708 
709 
710         public static final @android.annotation.NonNull Parcelable.Creator<Waveform> CREATOR =
711             new Parcelable.Creator<Waveform>() {
712                 @Override
713                 public Waveform createFromParcel(Parcel in) {
714                     // Skip the type token
715                     in.readInt();
716                     return new Waveform(in);
717                 }
718                 @Override
719                 public Waveform[] newArray(int size) {
720                     return new Waveform[size];
721                 }
722             };
723     }
724 
725     /** @hide */
726     @TestApi
727     public static class Prebaked extends VibrationEffect implements Parcelable {
728         private final int mEffectId;
729         private final boolean mFallback;
730 
731         private int mEffectStrength;
732 
Prebaked(Parcel in)733         public Prebaked(Parcel in) {
734             this(in.readInt(), in.readByte() != 0);
735             mEffectStrength = in.readInt();
736         }
737 
Prebaked(int effectId, boolean fallback)738         public Prebaked(int effectId, boolean fallback) {
739             mEffectId = effectId;
740             mFallback = fallback;
741             mEffectStrength = EffectStrength.MEDIUM;
742         }
743 
getId()744         public int getId() {
745             return mEffectId;
746         }
747 
748         /**
749          * Whether the effect should fall back to a generic pattern if there's no hardware specific
750          * implementation of it.
751          */
shouldFallback()752         public boolean shouldFallback() {
753             return mFallback;
754         }
755 
756         @Override
getDuration()757         public long getDuration() {
758             return -1;
759         }
760 
761         /**
762          * Set the effect strength of the prebaked effect.
763          */
setEffectStrength(int strength)764         public void setEffectStrength(int strength) {
765             if (!isValidEffectStrength(strength)) {
766                 throw new IllegalArgumentException("Invalid effect strength: " + strength);
767             }
768             mEffectStrength = strength;
769         }
770 
771         /**
772          * Set the effect strength.
773          */
getEffectStrength()774         public int getEffectStrength() {
775             return mEffectStrength;
776         }
777 
isValidEffectStrength(int strength)778         private static boolean isValidEffectStrength(int strength) {
779             switch (strength) {
780                 case EffectStrength.LIGHT:
781                 case EffectStrength.MEDIUM:
782                 case EffectStrength.STRONG:
783                     return true;
784                 default:
785                     return false;
786             }
787         }
788 
789         @Override
validate()790         public void validate() {
791             switch (mEffectId) {
792                 case EFFECT_CLICK:
793                 case EFFECT_DOUBLE_CLICK:
794                 case EFFECT_TICK:
795                 case EFFECT_TEXTURE_TICK:
796                 case EFFECT_THUD:
797                 case EFFECT_POP:
798                 case EFFECT_HEAVY_CLICK:
799                     break;
800                 default:
801                     if (mEffectId < RINGTONES[0] || mEffectId > RINGTONES[RINGTONES.length - 1]) {
802                         throw new IllegalArgumentException(
803                                 "Unknown prebaked effect type (value=" + mEffectId + ")");
804                     }
805             }
806             if (!isValidEffectStrength(mEffectStrength)) {
807                 throw new IllegalArgumentException(
808                         "Unknown prebaked effect strength (value=" + mEffectStrength + ")");
809             }
810         }
811 
812         @Override
equals(Object o)813         public boolean equals(Object o) {
814             if (!(o instanceof VibrationEffect.Prebaked)) {
815                 return false;
816             }
817             VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
818             return mEffectId == other.mEffectId
819                 && mFallback == other.mFallback
820                 && mEffectStrength == other.mEffectStrength;
821         }
822 
823         @Override
hashCode()824         public int hashCode() {
825             int result = 17;
826             result += 37 * mEffectId;
827             result += 37 * mEffectStrength;
828             return result;
829         }
830 
831         @Override
toString()832         public String toString() {
833             return "Prebaked{mEffectId=" + mEffectId
834                 + ", mEffectStrength=" + mEffectStrength
835                 + ", mFallback=" + mFallback
836                 + "}";
837         }
838 
839 
840         @Override
writeToParcel(Parcel out, int flags)841         public void writeToParcel(Parcel out, int flags) {
842             out.writeInt(PARCEL_TOKEN_EFFECT);
843             out.writeInt(mEffectId);
844             out.writeByte((byte) (mFallback ? 1 : 0));
845             out.writeInt(mEffectStrength);
846         }
847 
848         public static final @NonNull Parcelable.Creator<Prebaked> CREATOR =
849             new Parcelable.Creator<Prebaked>() {
850                 @Override
851                 public Prebaked createFromParcel(Parcel in) {
852                     // Skip the type token
853                     in.readInt();
854                     return new Prebaked(in);
855                 }
856                 @Override
857                 public Prebaked[] newArray(int size) {
858                     return new Prebaked[size];
859                 }
860             };
861     }
862 
863     /** @hide */
864     public static final class Composed extends VibrationEffect implements Parcelable {
865         private final ArrayList<Composition.PrimitiveEffect> mPrimitiveEffects;
866 
867         /**
868          * @hide
869          */
870         @SuppressWarnings("unchecked")
Composed(@onNull Parcel in)871         public Composed(@NonNull Parcel in) {
872             this(in.readArrayList(Composed.class.getClassLoader()));
873         }
874 
875         /**
876          * @hide
877          */
Composed(List<Composition.PrimitiveEffect> effects)878         public Composed(List<Composition.PrimitiveEffect> effects) {
879             mPrimitiveEffects = new ArrayList<>(Objects.requireNonNull(effects));
880         }
881 
882         /**
883          * @hide
884          */
885         @NonNull
getPrimitiveEffects()886         public List<Composition.PrimitiveEffect> getPrimitiveEffects() {
887             return mPrimitiveEffects;
888         }
889 
890         @Override
getDuration()891         public long getDuration() {
892             return -1;
893         }
894 
895         /**
896          * Scale all primitives of this effect.
897          *
898          * @param gamma the gamma adjustment to apply
899          * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and
900          *         MAX_AMPLITUDE
901          * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE
902          *
903          * @return A {@link Composed} effect with same but scaled primitives.
904          */
scale(float gamma, int maxAmplitude)905         public Composed scale(float gamma, int maxAmplitude) {
906             if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) {
907                 throw new IllegalArgumentException(
908                         "Amplitude is negative or greater than MAX_AMPLITUDE");
909             }
910             if (gamma == 1.0f && maxAmplitude == MAX_AMPLITUDE) {
911                 // Just return a copy of the original if there's no scaling to be done.
912                 return new Composed(mPrimitiveEffects);
913             }
914             List<Composition.PrimitiveEffect> scaledPrimitives = new ArrayList<>();
915             for (Composition.PrimitiveEffect primitive : mPrimitiveEffects) {
916                 float adjustedScale = MathUtils.pow(primitive.scale, gamma);
917                 float newScale = adjustedScale * maxAmplitude / (float) MAX_AMPLITUDE;
918                 scaledPrimitives.add(new Composition.PrimitiveEffect(
919                         primitive.id, newScale, primitive.delay));
920             }
921             return new Composed(scaledPrimitives);
922         }
923 
924         /**
925          * @hide
926          */
927         @Override
validate()928         public void validate() {
929             for (Composition.PrimitiveEffect effect : mPrimitiveEffects) {
930                 Composition.checkPrimitive(effect.id);
931                 Preconditions.checkArgumentInRange(
932                         effect.scale, 0.0f, 1.0f, "scale");
933             }
934         }
935 
936         @Override
writeToParcel(@onNull Parcel out, int flags)937         public void writeToParcel(@NonNull Parcel out, int flags) {
938             out.writeInt(PARCEL_TOKEN_COMPOSITION);
939             out.writeList(mPrimitiveEffects);
940         }
941 
942         @Override
describeContents()943         public int describeContents() {
944             return 0;
945         }
946 
947         @Override
equals(Object o)948         public boolean equals(Object o) {
949             if (this == o) return true;
950             if (o == null || getClass() != o.getClass()) return false;
951             Composed composed = (Composed) o;
952             return mPrimitiveEffects.equals(composed.mPrimitiveEffects);
953         }
954 
955         @Override
hashCode()956         public int hashCode() {
957             return Objects.hash(mPrimitiveEffects);
958         }
959 
960         @Override
toString()961         public String toString() {
962             return "Composed{mPrimitiveEffects=" + mPrimitiveEffects + '}';
963         }
964 
965         public static final @NonNull Parcelable.Creator<Composed> CREATOR =
966                 new Parcelable.Creator<Composed>() {
967                     @Override
968                     public Composed createFromParcel(@NonNull Parcel in) {
969                         // Skip the type token
970                         in.readInt();
971                         return new Composed(in);
972                     }
973 
974                     @Override
975                     @NonNull
976                     public Composed[] newArray(int size) {
977                         return new Composed[size];
978                     }
979                 };
980     }
981 
982     /**
983      * A composition of haptic primitives that, when combined, create a single haptic effect.
984      *
985      * @see VibrationEffect#startComposition()
986      */
987     public static final class Composition {
988         /** @hide */
989         @IntDef(prefix = { "PRIMITIVE_" }, value = {
990                 PRIMITIVE_CLICK,
991                 PRIMITIVE_THUD,
992                 PRIMITIVE_SPIN,
993                 PRIMITIVE_QUICK_RISE,
994                 PRIMITIVE_SLOW_RISE,
995                 PRIMITIVE_QUICK_FALL,
996                 PRIMITIVE_TICK,
997         })
998         @Retention(RetentionPolicy.SOURCE)
999         public @interface Primitive {}
1000 
1001         /**
1002          * No haptic effect. Used to generate extended delays between primitives.
1003          * @hide
1004          */
1005         public static final int PRIMITIVE_NOOP = 0;
1006         /**
1007          * This effect should produce a sharp, crisp click sensation.
1008          */
1009         public static final int PRIMITIVE_CLICK = 1;
1010         /**
1011          * A haptic effect that simulates downwards movement with gravity. Often
1012          * followed by extra energy of hitting and reverberation to augment
1013          * physicality.
1014          *
1015          * @hide Not confident enough to expose publicly yet
1016          */
1017         public static final int PRIMITIVE_THUD = 2;
1018         /**
1019          * A haptic effect that simulates spinning momentum.
1020          *
1021          * @hide Not confident enough to expose publicly yet
1022          */
1023         public static final int PRIMITIVE_SPIN = 3;
1024         /**
1025          * A haptic effect that simulates quick upward movement against gravity.
1026          */
1027         public static final int PRIMITIVE_QUICK_RISE = 4;
1028         /**
1029          * A haptic effect that simulates slow upward movement against gravity.
1030          */
1031         public static final int PRIMITIVE_SLOW_RISE = 5;
1032         /**
1033          * A haptic effect that simulates quick downwards movement with gravity.
1034          */
1035         public static final int PRIMITIVE_QUICK_FALL = 6;
1036         /**
1037          * This very short effect should produce a light crisp sensation intended
1038          * to be used repetitively for dynamic feedback.
1039          */
1040         // Internally this maps to the HAL constant CompositePrimitive::LIGHT_TICK
1041         public static final int PRIMITIVE_TICK = 7;
1042 
1043 
1044         private ArrayList<PrimitiveEffect> mEffects = new ArrayList<>();
1045 
Composition()1046         Composition() { }
1047 
1048         /**
1049          * Add a haptic primitive to the end of the current composition.
1050          *
1051          * Similar to {@link #addPrimitive(int, float, int)}, but with no delay and a
1052          * default scale applied.
1053          *
1054          * @param primitiveId The primitive to add
1055          *
1056          * @return The {@link Composition} object to enable adding multiple primitives in one chain.
1057          */
1058         @NonNull
addPrimitive(@rimitive int primitiveId)1059         public Composition addPrimitive(@Primitive int primitiveId) {
1060             addPrimitive(primitiveId, /*scale*/ 1.0f, /*delay*/ 0);
1061             return this;
1062         }
1063 
1064         /**
1065          * Add a haptic primitive to the end of the current composition.
1066          *
1067          * Similar to {@link #addPrimitive(int, float, int)}, but with no delay.
1068          *
1069          * @param primitiveId The primitive to add
1070          * @param scale The scale to apply to the intensity of the primitive.
1071          *
1072          * @return The {@link Composition} object to enable adding multiple primitives in one chain.
1073          */
1074         @NonNull
addPrimitive(@rimitive int primitiveId, @FloatRange(from = 0f, to = 1f) float scale)1075         public Composition addPrimitive(@Primitive int primitiveId,
1076                 @FloatRange(from = 0f, to = 1f) float scale) {
1077             addPrimitive(primitiveId, scale, /*delay*/ 0);
1078             return this;
1079         }
1080 
1081         /**
1082          * Add a haptic primitive to the end of the current composition.
1083          *
1084          * @param primitiveId The primitive to add
1085          * @param scale The scale to apply to the intensity of the primitive.
1086          * @param delay The amount of time, in milliseconds, to wait between playing the prior
1087          *              primitive and this one
1088          * @return The {@link Composition} object to enable adding multiple primitives in one chain.
1089          */
1090         @NonNull
addPrimitive(@rimitive int primitiveId, @FloatRange(from = 0f, to = 1f) float scale, @IntRange(from = 0) int delay)1091         public Composition addPrimitive(@Primitive int primitiveId,
1092                 @FloatRange(from = 0f, to = 1f) float scale, @IntRange(from = 0) int delay) {
1093             mEffects.add(new PrimitiveEffect(checkPrimitive(primitiveId), scale, delay));
1094             return this;
1095         }
1096 
1097         /**
1098          * Compose all of the added primitives together into a single {@link VibrationEffect}.
1099          *
1100          * The {@link Composition} object is still valid after this call, so you can continue adding
1101          * more primitives to it and generating more {@link VibrationEffect}s by calling this method
1102          * again.
1103          *
1104          * @return The {@link VibrationEffect} resulting from the composition of the primitives.
1105          */
1106         @NonNull
compose()1107         public VibrationEffect compose() {
1108             if (mEffects.isEmpty()) {
1109                 throw new IllegalStateException(
1110                         "Composition must have at least one element to compose.");
1111             }
1112             return new VibrationEffect.Composed(mEffects);
1113         }
1114 
1115         /**
1116          * @throws IllegalArgumentException throws if the primitive ID is not within the valid range
1117          * @hide
1118          *
1119          */
checkPrimitive(int primitiveId)1120         static int checkPrimitive(int primitiveId) {
1121             Preconditions.checkArgumentInRange(primitiveId, PRIMITIVE_NOOP, PRIMITIVE_TICK,
1122                     "primitiveId");
1123             return primitiveId;
1124         }
1125 
1126         /**
1127          * Convert the primitive ID to a human readable string for debugging
1128          * @param id The ID to convert
1129          * @return The ID in a human readable format.
1130          * @hide
1131          */
primitiveToString(@rimitive int id)1132         public static String primitiveToString(@Primitive int id) {
1133             switch (id) {
1134                 case PRIMITIVE_NOOP:
1135                     return "PRIMITIVE_NOOP";
1136                 case PRIMITIVE_CLICK:
1137                     return "PRIMITIVE_CLICK";
1138                 case PRIMITIVE_THUD:
1139                     return "PRIMITIVE_THUD";
1140                 case PRIMITIVE_SPIN:
1141                     return "PRIMITIVE_SPIN";
1142                 case PRIMITIVE_QUICK_RISE:
1143                     return "PRIMITIVE_QUICK_RISE";
1144                 case PRIMITIVE_SLOW_RISE:
1145                     return "PRIMITIVE_SLOW_RISE";
1146                 case PRIMITIVE_QUICK_FALL:
1147                     return "PRIMITIVE_QUICK_FALL";
1148                 case PRIMITIVE_TICK:
1149                     return "PRIMITIVE_TICK";
1150 
1151                 default:
1152                     return Integer.toString(id);
1153 
1154             }
1155         }
1156 
1157 
1158         /**
1159          * @hide
1160          */
1161         public static class PrimitiveEffect implements Parcelable {
1162             public int id;
1163             public float scale;
1164             public int delay;
1165 
PrimitiveEffect(int id, float scale, int delay)1166             PrimitiveEffect(int id, float scale, int delay) {
1167                 this.id = id;
1168                 this.scale = scale;
1169                 this.delay = delay;
1170             }
1171 
1172             @Override
writeToParcel(Parcel dest, int flags)1173             public void writeToParcel(Parcel dest, int flags) {
1174                 dest.writeInt(id);
1175                 dest.writeFloat(scale);
1176                 dest.writeInt(delay);
1177             }
1178 
1179             @Override
describeContents()1180             public int describeContents() {
1181                 return 0;
1182             }
1183 
1184             @Override
toString()1185             public String toString() {
1186                 return "PrimitiveEffect{"
1187                         + "id=" + primitiveToString(id)
1188                         + ", scale=" + scale
1189                         + ", delay=" + delay
1190                         + '}';
1191             }
1192 
1193             @Override
equals(Object o)1194             public boolean equals(Object o) {
1195                 if (this == o) return true;
1196                 if (o == null || getClass() != o.getClass()) return false;
1197                 PrimitiveEffect that = (PrimitiveEffect) o;
1198                 return id == that.id
1199                         && Float.compare(that.scale, scale) == 0
1200                         && delay == that.delay;
1201             }
1202 
1203             @Override
hashCode()1204             public int hashCode() {
1205                 return Objects.hash(id, scale, delay);
1206             }
1207 
1208 
1209             public static final @NonNull Parcelable.Creator<PrimitiveEffect> CREATOR =
1210                     new Parcelable.Creator<PrimitiveEffect>() {
1211                         @Override
1212                         public PrimitiveEffect createFromParcel(Parcel in) {
1213                             return new PrimitiveEffect(in.readInt(), in.readFloat(), in.readInt());
1214                         }
1215                         @Override
1216                         public PrimitiveEffect[] newArray(int size) {
1217                             return new PrimitiveEffect[size];
1218                         }
1219                     };
1220         }
1221     }
1222 
1223     public static final @NonNull Parcelable.Creator<VibrationEffect> CREATOR =
1224             new Parcelable.Creator<VibrationEffect>() {
1225                 @Override
1226                 public VibrationEffect createFromParcel(Parcel in) {
1227                     int token = in.readInt();
1228                     if (token == PARCEL_TOKEN_ONE_SHOT) {
1229                         return new OneShot(in);
1230                     } else if (token == PARCEL_TOKEN_WAVEFORM) {
1231                         return new Waveform(in);
1232                     } else if (token == PARCEL_TOKEN_EFFECT) {
1233                         return new Prebaked(in);
1234                     } else if (token == PARCEL_TOKEN_COMPOSITION) {
1235                         return new Composed(in);
1236                     } else {
1237                         throw new IllegalStateException(
1238                                 "Unexpected vibration event type token in parcel.");
1239                     }
1240                 }
1241                 @Override
1242                 public VibrationEffect[] newArray(int size) {
1243                     return new VibrationEffect[size];
1244                 }
1245             };
1246 }
1247