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.os.Vibrator;
32 import android.os.vibrator.PrebakedSegment;
33 import android.os.vibrator.PrimitiveSegment;
34 import android.os.vibrator.RampSegment;
35 import android.os.vibrator.StepSegment;
36 import android.os.vibrator.VibrationEffectSegment;
37 import android.util.MathUtils;
38 
39 import com.android.internal.util.Preconditions;
40 
41 import java.lang.annotation.Retention;
42 import java.lang.annotation.RetentionPolicy;
43 import java.time.Duration;
44 import java.util.ArrayList;
45 import java.util.Arrays;
46 import java.util.List;
47 import java.util.Objects;
48 
49 /**
50  * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
51  *
52  * <p>These effects may be any number of things, from single shot vibrations to complex waveforms.
53  */
54 public abstract class VibrationEffect implements Parcelable {
55     // Stevens' coefficient to scale the perceived vibration intensity.
56     private static final float SCALE_GAMMA = 0.65f;
57     // If a vibration is playing for longer than 1s, it's probably not haptic feedback
58     private static final long MAX_HAPTIC_FEEDBACK_DURATION = 1000;
59     // If a vibration is playing more than 3 constants, it's probably not haptic feedback
60     private static final long MAX_HAPTIC_FEEDBACK_COMPOSITION_SIZE = 3;
61 
62     /**
63      * The default vibration strength of the device.
64      */
65     public static final int DEFAULT_AMPLITUDE = -1;
66 
67     /**
68      * The maximum amplitude value
69      * @hide
70      */
71     public static final int MAX_AMPLITUDE = 255;
72 
73     /**
74      * A click effect. Use this effect as a baseline, as it's the most common type of click effect.
75      */
76     public static final int EFFECT_CLICK = Effect.CLICK;
77 
78     /**
79      * A double click effect.
80      */
81     public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK;
82 
83     /**
84      * A tick effect. This effect is less strong compared to {@link #EFFECT_CLICK}.
85      */
86     public static final int EFFECT_TICK = Effect.TICK;
87 
88     /**
89      * A thud effect.
90      * @see #get(int)
91      * @hide
92      */
93     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
94     @TestApi
95     public static final int EFFECT_THUD = Effect.THUD;
96 
97     /**
98      * A pop effect.
99      * @see #get(int)
100      * @hide
101      */
102     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
103     @TestApi
104     public static final int EFFECT_POP = Effect.POP;
105 
106     /**
107      * A heavy click effect. This effect is stronger than {@link #EFFECT_CLICK}.
108      */
109     public static final int EFFECT_HEAVY_CLICK = Effect.HEAVY_CLICK;
110 
111     /**
112      * A texture effect meant to replicate soft ticks.
113      *
114      * <p>Unlike normal effects, texture effects are meant to be called repeatedly, generally in
115      * response to some motion, in order to replicate the feeling of some texture underneath the
116      * user's fingers.
117      *
118      * @see #get(int)
119      * @hide
120      */
121     @TestApi
122     public static final int EFFECT_TEXTURE_TICK = Effect.TEXTURE_TICK;
123 
124     /** {@hide} */
125     @TestApi
126     public static final int EFFECT_STRENGTH_LIGHT = EffectStrength.LIGHT;
127 
128     /** {@hide} */
129     @TestApi
130     public static final int EFFECT_STRENGTH_MEDIUM = EffectStrength.MEDIUM;
131 
132     /** {@hide} */
133     @TestApi
134     public static final int EFFECT_STRENGTH_STRONG = EffectStrength.STRONG;
135 
136     /**
137      * Ringtone patterns. They may correspond with the device's ringtone audio, or may just be a
138      * pattern that can be played as a ringtone with any audio, depending on the device.
139      *
140      * @see #get(Uri, Context)
141      * @hide
142      */
143     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
144     @TestApi
145     public static final int[] RINGTONES = {
146         Effect.RINGTONE_1,
147         Effect.RINGTONE_2,
148         Effect.RINGTONE_3,
149         Effect.RINGTONE_4,
150         Effect.RINGTONE_5,
151         Effect.RINGTONE_6,
152         Effect.RINGTONE_7,
153         Effect.RINGTONE_8,
154         Effect.RINGTONE_9,
155         Effect.RINGTONE_10,
156         Effect.RINGTONE_11,
157         Effect.RINGTONE_12,
158         Effect.RINGTONE_13,
159         Effect.RINGTONE_14,
160         Effect.RINGTONE_15
161     };
162 
163     /** @hide */
164     @IntDef(prefix = { "EFFECT_" }, value = {
165             EFFECT_TICK,
166             EFFECT_CLICK,
167             EFFECT_HEAVY_CLICK,
168             EFFECT_DOUBLE_CLICK,
169     })
170     @Retention(RetentionPolicy.SOURCE)
171     public @interface EffectType {}
172 
173     /** @hide to prevent subclassing from outside of the framework */
VibrationEffect()174     public VibrationEffect() { }
175 
176     /**
177      * Create a one shot vibration.
178      *
179      * <p>One shot vibrations will vibrate constantly for the specified period of time at the
180      * specified amplitude, and then stop.
181      *
182      * @param milliseconds The number of milliseconds to vibrate. This must be a positive number.
183      * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or
184      * {@link #DEFAULT_AMPLITUDE}.
185      *
186      * @return The desired effect.
187      */
createOneShot(long milliseconds, int amplitude)188     public static VibrationEffect createOneShot(long milliseconds, int amplitude) {
189         if (amplitude == 0) {
190             throw new IllegalArgumentException(
191                     "amplitude must either be DEFAULT_AMPLITUDE, "
192                             + "or between 1 and 255 inclusive (amplitude=" + amplitude + ")");
193         }
194         return createWaveform(new long[]{milliseconds}, new int[]{amplitude}, -1 /* repeat */);
195     }
196 
197     /**
198      * Create a waveform vibration, using only off/on transitions at the provided time intervals,
199      * and potentially repeating.
200      *
201      * <p>In effect, the timings array represents the number of milliseconds <em>before</em> turning
202      * the vibrator on, followed by the number of milliseconds to keep the vibrator on, then
203      * the number of milliseconds turned off, and so on. Consequently, the first timing value will
204      * often be 0, so that the effect will start vibrating immediately.
205      *
206      * <p>This method is equivalent to calling {@link #createWaveform(long[], int[], int)} with
207      * corresponding amplitude values alternating between 0 and {@link #DEFAULT_AMPLITUDE},
208      * beginning with 0.
209      *
210      * <p>To cause the pattern to repeat, pass the index into the timings array at which to start
211      * the repetition, or -1 to disable repeating. Repeating effects will be played indefinitely
212      * and should be cancelled via {@link Vibrator#cancel()}.
213      *
214      * @param timings The pattern of alternating on-off timings, starting with an 'off' timing, and
215      *               representing the length of time to sustain the individual item (not
216      *               cumulative).
217      * @param repeat The index into the timings array at which to repeat, or -1 if you don't
218      *               want to repeat indefinitely.
219      *
220      * @return The desired effect.
221      */
createWaveform(long[] timings, int repeat)222     public static VibrationEffect createWaveform(long[] timings, int repeat) {
223         int[] amplitudes = new int[timings.length];
224         for (int i = 0; i < (timings.length / 2); i++) {
225             amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE;
226         }
227         return createWaveform(timings, amplitudes, repeat);
228     }
229 
230     /**
231      * Computes a legacy vibration pattern (i.e. a pattern with duration values for "off/on"
232      * vibration components) that is equivalent to this VibrationEffect.
233      *
234      * <p>All non-repeating effects created with {@link #createWaveform(int[], int)} are convertible
235      * into an equivalent vibration pattern with this method. It is not guaranteed that an effect
236      * created with other means becomes converted into an equivalent legacy vibration pattern, even
237      * if it has an equivalent vibration pattern. If this method is unable to create an equivalent
238      * vibration pattern for such effects, it will return {@code null}.
239      *
240      * <p>Note that a valid equivalent long[] pattern cannot be created for an effect that has any
241      * form of repeating behavior, regardless of how the effect was created. For repeating effects,
242      * the method will always return {@code null}.
243      *
244      * @return a long array representing a vibration pattern equivalent to the VibrationEffect, if
245      *               the method successfully derived a vibration pattern equivalent to the effect
246      *               (this will always be the case if the effect was created via
247      *               {@link #createWaveform(int[], int)} and is non-repeating). Otherwise, returns
248      *               {@code null}.
249      * @hide
250      */
251     @TestApi
252     @Nullable
computeCreateWaveformOffOnTimingsOrNull()253     public abstract long[] computeCreateWaveformOffOnTimingsOrNull();
254 
255     /**
256      * Create a waveform vibration.
257      *
258      * <p>Waveform vibrations are a potentially repeating series of timing and amplitude pairs,
259      * provided in separate arrays. For each pair, the value in the amplitude array determines
260      * the strength of the vibration and the value in the timing array determines how long it
261      * vibrates for, in milliseconds.
262      *
263      * <p>To cause the pattern to repeat, pass the index into the timings array at which to start
264      * the repetition, or -1 to disable repeating. Repeating effects will be played indefinitely
265      * and should be cancelled via {@link Vibrator#cancel()}.
266      *
267      * @param timings The timing values, in milliseconds, of the timing / amplitude pairs. Timing
268      *                values of 0 will cause the pair to be ignored.
269      * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values
270      *                   must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An
271      *                   amplitude value of 0 implies the motor is off.
272      * @param repeat The index into the timings array at which to repeat, or -1 if you don't
273      *               want to repeat indefinitely.
274      *
275      * @return The desired effect.
276      */
createWaveform(long[] timings, int[] amplitudes, int repeat)277     public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
278         if (timings.length != amplitudes.length) {
279             throw new IllegalArgumentException(
280                     "timing and amplitude arrays must be of equal length"
281                             + " (timings.length=" + timings.length
282                             + ", amplitudes.length=" + amplitudes.length + ")");
283         }
284         List<StepSegment> segments = new ArrayList<>();
285         for (int i = 0; i < timings.length; i++) {
286             float parsedAmplitude = amplitudes[i] == DEFAULT_AMPLITUDE
287                     ? DEFAULT_AMPLITUDE : (float) amplitudes[i] / MAX_AMPLITUDE;
288             segments.add(new StepSegment(parsedAmplitude, /* frequencyHz= */ 0, (int) timings[i]));
289         }
290         VibrationEffect effect = new Composed(segments, repeat);
291         effect.validate();
292         return effect;
293     }
294 
295     /**
296      * Create a predefined vibration effect.
297      *
298      * <p>Predefined effects are a set of common vibration effects that should be identical,
299      * regardless of the app they come from, in order to provide a cohesive experience for users
300      * across the entire device. They also may be custom tailored to the device hardware in order to
301      * provide a better experience than you could otherwise build using the generic building
302      * blocks.
303      *
304      * <p>This will fallback to a generic pattern if one exists and there does not exist a
305      * hardware-specific implementation of the effect.
306      *
307      * @param effectId The ID of the effect to perform:
308      *                 {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
309      *
310      * @return The desired effect.
311      */
312     @NonNull
createPredefined(@ffectType int effectId)313     public static VibrationEffect createPredefined(@EffectType int effectId) {
314         return get(effectId, true);
315     }
316 
317     /**
318      * Get a predefined vibration effect.
319      *
320      * <p>Predefined effects are a set of common vibration effects that should be identical,
321      * regardless of the app they come from, in order to provide a cohesive experience for users
322      * across the entire device. They also may be custom tailored to the device hardware in order to
323      * provide a better experience than you could otherwise build using the generic building
324      * blocks.
325      *
326      * <p>This will fallback to a generic pattern if one exists and there does not exist a
327      * hardware-specific implementation of the effect.
328      *
329      * @param effectId The ID of the effect to perform:
330      *                 {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
331      *
332      * @return The desired effect.
333      * @hide
334      */
335     @TestApi
get(int effectId)336     public static VibrationEffect get(int effectId) {
337         return get(effectId, true);
338     }
339 
340     /**
341      * Get a predefined vibration effect.
342      *
343      * <p>Predefined effects are a set of common vibration effects that should be identical,
344      * regardless of the app they come from, in order to provide a cohesive experience for users
345      * across the entire device. They also may be custom tailored to the device hardware in order to
346      * provide a better experience than you could otherwise build using the generic building
347      * blocks.
348      *
349      * <p>Some effects you may only want to play if there's a hardware specific implementation
350      * because they may, for example, be too disruptive to the user without tuning. The
351      * {@code fallback} parameter allows you to decide whether you want to fallback to the generic
352      * implementation or only play if there's a tuned, hardware specific one available.
353      *
354      * @param effectId The ID of the effect to perform:
355      *                 {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
356      * @param fallback Whether to fallback to a generic pattern if a hardware specific
357      *                 implementation doesn't exist.
358      *
359      * @return The desired effect.
360      * @hide
361      */
362     @TestApi
get(int effectId, boolean fallback)363     public static VibrationEffect get(int effectId, boolean fallback) {
364         VibrationEffect effect = new Composed(
365                 new PrebakedSegment(effectId, fallback, EffectStrength.MEDIUM));
366         effect.validate();
367         return effect;
368     }
369 
370     /**
371      * Get a predefined vibration effect associated with a given URI.
372      *
373      * <p>Predefined effects are a set of common vibration effects that should be identical,
374      * regardless of the app they come from, in order to provide a cohesive experience for users
375      * across the entire device. They also may be custom tailored to the device hardware in order to
376      * provide a better experience than you could otherwise build using the generic building
377      * blocks.
378      *
379      * @param uri The URI associated with the haptic effect.
380      * @param context The context used to get the URI to haptic effect association.
381      *
382      * @return The desired effect, or {@code null} if there's no associated effect.
383      *
384      * @hide
385      */
386     @TestApi
387     @Nullable
get(Uri uri, Context context)388     public static VibrationEffect get(Uri uri, Context context) {
389         String[] uris = context.getResources().getStringArray(
390                 com.android.internal.R.array.config_ringtoneEffectUris);
391 
392         // Skip doing any IPC if we don't have any effects configured.
393         if (uris.length == 0) {
394             return null;
395         }
396 
397         final ContentResolver cr = context.getContentResolver();
398         Uri uncanonicalUri = cr.uncanonicalize(uri);
399         if (uncanonicalUri == null) {
400             // If we already had an uncanonical URI, it's possible we'll get null back here. In
401             // this case, just use the URI as passed in since it wasn't canonicalized in the first
402             // place.
403             uncanonicalUri = uri;
404         }
405 
406         for (int i = 0; i < uris.length && i < RINGTONES.length; i++) {
407             if (uris[i] == null) {
408                 continue;
409             }
410             Uri mappedUri = cr.uncanonicalize(Uri.parse(uris[i]));
411             if (mappedUri == null) {
412                 continue;
413             }
414             if (mappedUri.equals(uncanonicalUri)) {
415                 return get(RINGTONES[i]);
416             }
417         }
418         return null;
419     }
420 
421     /**
422      * Start composing a haptic effect.
423      *
424      * @see VibrationEffect.Composition
425      */
426     @NonNull
startComposition()427     public static Composition startComposition() {
428         return new Composition();
429     }
430 
431     /**
432      * Start building a waveform vibration.
433      *
434      * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing
435      * control over vibration amplitude and frequency via smooth transitions between values.
436      *
437      * <p>The waveform will start the first transition from the vibrator off state, with the
438      * resonant frequency by default. To provide an initial state, use
439      * {@link #startWaveform(VibrationEffect.VibrationParameter)}.
440      *
441      * @see VibrationEffect.WaveformBuilder
442      * @hide
443      */
444     @TestApi
445     @NonNull
startWaveform()446     public static WaveformBuilder startWaveform() {
447         return new WaveformBuilder();
448     }
449 
450     /**
451      * Start building a waveform vibration with an initial state specified by a
452      * {@link VibrationEffect.VibrationParameter}.
453      *
454      * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing
455      * control over vibration amplitude and frequency via smooth transitions between values.
456      *
457      * @param initialParameter The initial {@link VibrationEffect.VibrationParameter} value to be
458      *                         applied at the beginning of the vibration.
459      * @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters.
460      *
461      * @see VibrationEffect.WaveformBuilder
462      * @hide
463      */
464     @TestApi
465     @NonNull
startWaveform(@onNull VibrationParameter initialParameter)466     public static WaveformBuilder startWaveform(@NonNull VibrationParameter initialParameter) {
467         WaveformBuilder builder = startWaveform();
468         builder.addTransition(Duration.ZERO, initialParameter);
469         return builder;
470     }
471 
472     /**
473      * Start building a waveform vibration with an initial state specified by two
474      * {@link VibrationEffect.VibrationParameter VibrationParameters}.
475      *
476      * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing
477      * control over vibration amplitude and frequency via smooth transitions between values.
478      *
479      * @param initialParameter1 The initial {@link VibrationEffect.VibrationParameter} value to be
480      *                          applied at the beginning of the vibration.
481      * @param initialParameter2 The initial {@link VibrationEffect.VibrationParameter} value to be
482      *                          applied at the beginning of the vibration, must be a different type
483      *                          of parameter than the one specified by the first argument.
484      * @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters.
485      *
486      * @see VibrationEffect.WaveformBuilder
487      * @hide
488      */
489     @TestApi
490     @NonNull
startWaveform(@onNull VibrationParameter initialParameter1, @NonNull VibrationParameter initialParameter2)491     public static WaveformBuilder startWaveform(@NonNull VibrationParameter initialParameter1,
492             @NonNull VibrationParameter initialParameter2) {
493         WaveformBuilder builder = startWaveform();
494         builder.addTransition(Duration.ZERO, initialParameter1, initialParameter2);
495         return builder;
496     }
497 
498     @Override
describeContents()499     public int describeContents() {
500         return 0;
501     }
502 
503     /** @hide */
validate()504     public abstract void validate();
505 
506     /**
507      * Gets the estimated duration of the vibration in milliseconds.
508      *
509      * <p>For effects without a defined end (e.g. a Waveform with a non-negative repeat index), this
510      * returns Long.MAX_VALUE. For effects with an unknown duration (e.g. Prebaked effects where
511      * the length is device and potentially run-time dependent), this returns -1.
512      *
513      * @hide
514      */
515     @TestApi
getDuration()516     public abstract long getDuration();
517 
518     /**
519      * Checks if a given {@link Vibrator} can play this effect as intended.
520      *
521      * <p>See @link Vibrator#areVibrationFeaturesSupported(VibrationEffect)} for more information
522      * about what counts as supported by a vibrator, and what counts as not.
523      *
524      * @hide
525      */
areVibrationFeaturesSupported(@onNull Vibrator vibrator)526     public abstract boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator);
527 
528     /**
529      * Returns true if this effect could represent a touch haptic feedback.
530      *
531      * <p>It is strongly recommended that an instance of {@link VibrationAttributes} is specified
532      * for each vibration, with the correct usage. When a vibration is played with usage UNKNOWN,
533      * then this method will be used to classify the most common use case and make sure they are
534      * covered by the user settings for "Touch feedback".
535      *
536      * @hide
537      */
isHapticFeedbackCandidate()538     public boolean isHapticFeedbackCandidate() {
539         return false;
540     }
541 
542     /**
543      * Resolve default values into integer amplitude numbers.
544      *
545      * @param defaultAmplitude the default amplitude to apply, must be between 0 and
546      *                         MAX_AMPLITUDE
547      * @return this if amplitude value is already set, or a copy of this effect with given default
548      *         amplitude otherwise
549      *
550      * @hide
551      */
resolve(int defaultAmplitude)552     public abstract <T extends VibrationEffect> T resolve(int defaultAmplitude);
553 
554     /**
555      * Scale the vibration effect intensity with the given constraints.
556      *
557      * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
558      *                    scale down the intensity, values larger than 1 will scale up
559      * @return this if there is no scaling to be done, or a copy of this effect with scaled
560      *         vibration intensity otherwise
561      *
562      * @hide
563      */
scale(float scaleFactor)564     public abstract <T extends VibrationEffect> T scale(float scaleFactor);
565 
566     /**
567      * Applies given effect strength to prebaked effects represented by one of
568      * VibrationEffect.EFFECT_*.
569      *
570      * @param effectStrength new effect strength to be applied, one of
571      *                       VibrationEffect.EFFECT_STRENGTH_*.
572      * @return this if there is no change to this effect, or a copy of this effect with applied
573      * effect strength otherwise.
574      * @hide
575      */
applyEffectStrength(int effectStrength)576     public <T extends VibrationEffect> T applyEffectStrength(int effectStrength) {
577         return (T) this;
578     }
579 
580     /**
581      * Scale given vibration intensity by the given factor.
582      *
583      * @param intensity   relative intensity of the effect, must be between 0 and 1
584      * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
585      *                    scale down the intensity, values larger than 1 will scale up
586      * @hide
587      */
scale(float intensity, float scaleFactor)588     public static float scale(float intensity, float scaleFactor) {
589         // Applying gamma correction to the scale factor, which is the same as encoding the input
590         // value, scaling it, then decoding the scaled value.
591         float scale = MathUtils.pow(scaleFactor, 1f / SCALE_GAMMA);
592 
593         if (scaleFactor <= 1) {
594             // Scale down is simply a gamma corrected application of scaleFactor to the intensity.
595             // Scale up requires a different curve to ensure the intensity will not become > 1.
596             return intensity * scale;
597         }
598 
599         // Apply the scale factor a few more times to make the ramp curve closer to the raw scale.
600         float extraScale = MathUtils.pow(scaleFactor, 4f - scaleFactor);
601         float x = intensity * scale * extraScale;
602         float maxX = scale * extraScale; // scaled x for intensity == 1
603 
604         float expX = MathUtils.exp(x);
605         float expMaxX = MathUtils.exp(maxX);
606 
607         // Using f = tanh as the scale up function so the max value will converge.
608         // a = 1/f(maxX), used to scale f so that a*f(maxX) = 1 (the value will converge to 1).
609         float a = (expMaxX + 1f) / (expMaxX - 1f);
610         float fx = (expX - 1f) / (expX + 1f);
611 
612         return MathUtils.constrain(a * fx, 0f, 1f);
613     }
614 
615     /** @hide */
effectIdToString(int effectId)616     public static String effectIdToString(int effectId) {
617         switch (effectId) {
618             case EFFECT_CLICK:
619                 return "CLICK";
620             case EFFECT_TICK:
621                 return "TICK";
622             case EFFECT_HEAVY_CLICK:
623                 return "HEAVY_CLICK";
624             case EFFECT_DOUBLE_CLICK:
625                 return "DOUBLE_CLICK";
626             case EFFECT_POP:
627                 return "POP";
628             case EFFECT_THUD:
629                 return "THUD";
630             case EFFECT_TEXTURE_TICK:
631                 return "TEXTURE_TICK";
632             default:
633                 return Integer.toString(effectId);
634         }
635     }
636 
637     /** @hide */
effectStrengthToString(int effectStrength)638     public static String effectStrengthToString(int effectStrength) {
639         switch (effectStrength) {
640             case EFFECT_STRENGTH_LIGHT:
641                 return "LIGHT";
642             case EFFECT_STRENGTH_MEDIUM:
643                 return "MEDIUM";
644             case EFFECT_STRENGTH_STRONG:
645                 return "STRONG";
646             default:
647                 return Integer.toString(effectStrength);
648         }
649     }
650 
651     /**
652      * Implementation of {@link VibrationEffect} described by a composition of one or more
653      * {@link VibrationEffectSegment}, with an optional index to represent repeating effects.
654      *
655      * @hide
656      */
657     @TestApi
658     public static final class Composed extends VibrationEffect {
659         private final ArrayList<VibrationEffectSegment> mSegments;
660         private final int mRepeatIndex;
661 
Composed(@onNull Parcel in)662         Composed(@NonNull Parcel in) {
663             this(in.readArrayList(VibrationEffectSegment.class.getClassLoader(), android.os.vibrator.VibrationEffectSegment.class), in.readInt());
664         }
665 
Composed(@onNull VibrationEffectSegment segment)666         Composed(@NonNull VibrationEffectSegment segment) {
667             this(Arrays.asList(segment), /* repeatIndex= */ -1);
668         }
669 
670         /** @hide */
Composed(@onNull List<? extends VibrationEffectSegment> segments, int repeatIndex)671         public Composed(@NonNull List<? extends VibrationEffectSegment> segments, int repeatIndex) {
672             super();
673             mSegments = new ArrayList<>(segments);
674             mRepeatIndex = repeatIndex;
675         }
676 
677         @NonNull
getSegments()678         public List<VibrationEffectSegment> getSegments() {
679             return mSegments;
680         }
681 
getRepeatIndex()682         public int getRepeatIndex() {
683             return mRepeatIndex;
684         }
685 
686          /** @hide */
687         @Override
688         @Nullable
computeCreateWaveformOffOnTimingsOrNull()689         public long[] computeCreateWaveformOffOnTimingsOrNull() {
690             if (getRepeatIndex() >= 0) {
691                 // Repeating effects cannot be fully represented as a long[] legacy pattern.
692                 return null;
693             }
694 
695             List<VibrationEffectSegment> segments = getSegments();
696 
697             // The maximum possible size of the final pattern is 1 plus the number of segments in
698             // the original effect. This is because we will add an empty "off" segment at the
699             // start of the pattern if the first segment of the original effect is an "on" segment.
700             // (because the legacy patterns start with an "off" pattern). Other than this one case,
701             // we will add the durations of back-to-back segments of similar amplitudes (amplitudes
702             // that are all "on" or "off") and create a pattern entry for the total duration, which
703             // will not take more number pattern entries than the number of segments processed.
704             long[] patternBuffer = new long[segments.size() + 1];
705             int patternIndex = 0;
706 
707             for (int i = 0; i < segments.size(); i++) {
708                 StepSegment stepSegment =
709                         castToValidStepSegmentForOffOnTimingsOrNull(segments.get(i));
710                 if (stepSegment == null) {
711                     // This means that there is 1 or more segments of this effect that is/are not a
712                     // possible component of a legacy vibration pattern. Thus, the VibrationEffect
713                     // does not have any equivalent legacy vibration pattern.
714                     return null;
715                 }
716 
717                 boolean isSegmentOff = stepSegment.getAmplitude() == 0;
718                 // Even pattern indices are "off", and odd pattern indices are "on"
719                 boolean isCurrentPatternIndexOff = (patternIndex % 2) == 0;
720                 if (isSegmentOff != isCurrentPatternIndexOff) {
721                     // Move the pattern index one step ahead, so that the current segment's
722                     // "off"/"on" property matches that of the index's
723                     ++patternIndex;
724                 }
725                 patternBuffer[patternIndex] += stepSegment.getDuration();
726             }
727 
728             return Arrays.copyOf(patternBuffer, patternIndex + 1);
729         }
730 
731         /** @hide */
732         @Override
validate()733         public void validate() {
734             int segmentCount = mSegments.size();
735             boolean hasNonZeroDuration = false;
736             for (int i = 0; i < segmentCount; i++) {
737                 VibrationEffectSegment segment = mSegments.get(i);
738                 segment.validate();
739                 // A segment with unknown duration = -1 still counts as a non-zero duration.
740                 hasNonZeroDuration |= segment.getDuration() != 0;
741             }
742             if (!hasNonZeroDuration) {
743                 throw new IllegalArgumentException("at least one timing must be non-zero"
744                         + " (segments=" + mSegments + ")");
745             }
746             if (mRepeatIndex != -1) {
747                 Preconditions.checkArgumentInRange(mRepeatIndex, 0, segmentCount - 1,
748                         "repeat index must be within the bounds of the segments (segments.length="
749                                 + segmentCount + ", index=" + mRepeatIndex + ")");
750             }
751         }
752 
753         @Override
getDuration()754         public long getDuration() {
755             if (mRepeatIndex >= 0) {
756                 return Long.MAX_VALUE;
757             }
758             int segmentCount = mSegments.size();
759             long totalDuration = 0;
760             for (int i = 0; i < segmentCount; i++) {
761                 long segmentDuration = mSegments.get(i).getDuration();
762                 if (segmentDuration < 0) {
763                     return segmentDuration;
764                 }
765                 totalDuration += segmentDuration;
766             }
767             return totalDuration;
768         }
769 
770         /** @hide */
771         @Override
areVibrationFeaturesSupported(@onNull Vibrator vibrator)772         public boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator) {
773             for (VibrationEffectSegment segment : mSegments) {
774                 if (!segment.areVibrationFeaturesSupported(vibrator)) {
775                     return false;
776                 }
777             }
778             return true;
779         }
780 
781         /** @hide */
782         @Override
isHapticFeedbackCandidate()783         public boolean isHapticFeedbackCandidate() {
784             long totalDuration = getDuration();
785             if (totalDuration > MAX_HAPTIC_FEEDBACK_DURATION) {
786                 // Vibration duration is known and is longer than the max duration used to classify
787                 // haptic feedbacks (or repeating indefinitely with duration == Long.MAX_VALUE).
788                 return false;
789             }
790             int segmentCount = mSegments.size();
791             if (segmentCount > MAX_HAPTIC_FEEDBACK_COMPOSITION_SIZE) {
792                 // Vibration has some prebaked or primitive constants, it should be limited to the
793                 // max composition size used to classify haptic feedbacks.
794                 return false;
795             }
796             totalDuration = 0;
797             for (int i = 0; i < segmentCount; i++) {
798                 if (!mSegments.get(i).isHapticFeedbackCandidate()) {
799                     // There is at least one segment that is not a candidate for a haptic feedback.
800                     return false;
801                 }
802                 long segmentDuration = mSegments.get(i).getDuration();
803                 if (segmentDuration > 0) {
804                     totalDuration += segmentDuration;
805                 }
806             }
807             // Vibration might still have some ramp or step segments, check the known duration.
808             return totalDuration <= MAX_HAPTIC_FEEDBACK_DURATION;
809         }
810 
811         /** @hide */
812         @NonNull
813         @Override
resolve(int defaultAmplitude)814         public Composed resolve(int defaultAmplitude) {
815             int segmentCount = mSegments.size();
816             ArrayList<VibrationEffectSegment> resolvedSegments = new ArrayList<>(segmentCount);
817             for (int i = 0; i < segmentCount; i++) {
818                 resolvedSegments.add(mSegments.get(i).resolve(defaultAmplitude));
819             }
820             if (resolvedSegments.equals(mSegments)) {
821                 return this;
822             }
823             Composed resolved = new Composed(resolvedSegments, mRepeatIndex);
824             resolved.validate();
825             return resolved;
826         }
827 
828         /** @hide */
829         @NonNull
830         @Override
scale(float scaleFactor)831         public Composed scale(float scaleFactor) {
832             int segmentCount = mSegments.size();
833             ArrayList<VibrationEffectSegment> scaledSegments = new ArrayList<>(segmentCount);
834             for (int i = 0; i < segmentCount; i++) {
835                 scaledSegments.add(mSegments.get(i).scale(scaleFactor));
836             }
837             if (scaledSegments.equals(mSegments)) {
838                 return this;
839             }
840             Composed scaled = new Composed(scaledSegments, mRepeatIndex);
841             scaled.validate();
842             return scaled;
843         }
844 
845         /** @hide */
846         @NonNull
847         @Override
applyEffectStrength(int effectStrength)848         public Composed applyEffectStrength(int effectStrength) {
849             int segmentCount = mSegments.size();
850             ArrayList<VibrationEffectSegment> scaledSegments = new ArrayList<>(segmentCount);
851             for (int i = 0; i < segmentCount; i++) {
852                 scaledSegments.add(mSegments.get(i).applyEffectStrength(effectStrength));
853             }
854             if (scaledSegments.equals(mSegments)) {
855                 return this;
856             }
857             Composed scaled = new Composed(scaledSegments, mRepeatIndex);
858             scaled.validate();
859             return scaled;
860         }
861 
862         @Override
equals(@ullable Object o)863         public boolean equals(@Nullable Object o) {
864             if (!(o instanceof Composed)) {
865                 return false;
866             }
867             Composed other = (Composed) o;
868             return mSegments.equals(other.mSegments) && mRepeatIndex == other.mRepeatIndex;
869         }
870 
871         @Override
hashCode()872         public int hashCode() {
873             return Objects.hash(mSegments, mRepeatIndex);
874         }
875 
876         @Override
toString()877         public String toString() {
878             return "Composed{segments=" + mSegments
879                     + ", repeat=" + mRepeatIndex
880                     + "}";
881         }
882 
883         @Override
describeContents()884         public int describeContents() {
885             return 0;
886         }
887 
888         @Override
writeToParcel(@onNull Parcel out, int flags)889         public void writeToParcel(@NonNull Parcel out, int flags) {
890             out.writeList(mSegments);
891             out.writeInt(mRepeatIndex);
892         }
893 
894         @NonNull
895         public static final Creator<Composed> CREATOR =
896                 new Creator<Composed>() {
897                     @Override
898                     public Composed createFromParcel(Parcel in) {
899                         return new Composed(in);
900                     }
901 
902                     @Override
903                     public Composed[] newArray(int size) {
904                         return new Composed[size];
905                     }
906                 };
907 
908         /**
909          * Casts a provided {@link VibrationEffectSegment} to a {@link StepSegment} and returns it,
910          * only if it can possibly be a segment for an effect created via
911          * {@link #createWaveform(int[], int)}. Otherwise, returns {@code null}.
912          */
913         @Nullable
castToValidStepSegmentForOffOnTimingsOrNull( VibrationEffectSegment segment)914         private static StepSegment castToValidStepSegmentForOffOnTimingsOrNull(
915                 VibrationEffectSegment segment) {
916             if (!(segment instanceof StepSegment)) {
917                 return null;
918             }
919 
920             StepSegment stepSegment = (StepSegment) segment;
921             if (stepSegment.getFrequencyHz() != 0) {
922                 return null;
923             }
924 
925             float amplitude = stepSegment.getAmplitude();
926             if (amplitude != 0 && amplitude != DEFAULT_AMPLITUDE) {
927                 return null;
928             }
929 
930             return stepSegment;
931         }
932     }
933 
934     /**
935      * A composition of haptic elements that are combined to be playable as a single
936      * {@link VibrationEffect}.
937      *
938      * <p>The haptic primitives are available as {@code Composition.PRIMITIVE_*} constants and
939      * can be added to a composition to create a custom vibration effect. Here is an example of an
940      * effect that grows in intensity and then dies off, with a longer rising portion for emphasis
941      * and an extra tick 100ms after:
942      *
943      * <pre>
944      * {@code VibrationEffect effect = VibrationEffect.startComposition()
945      *     .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE, 0.5f)
946      *     .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, 0.5f)
947      *     .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1.0f, 100)
948      *     .compose();}</pre>
949      *
950      * <p>When choosing to play a composed effect, you should check that individual components are
951      * supported by the device by using {@link Vibrator#arePrimitivesSupported}.
952      *
953      * @see VibrationEffect#startComposition()
954      */
955     public static final class Composition {
956         /** @hide */
957         @IntDef(prefix = { "PRIMITIVE_" }, value = {
958                 PRIMITIVE_CLICK,
959                 PRIMITIVE_THUD,
960                 PRIMITIVE_SPIN,
961                 PRIMITIVE_QUICK_RISE,
962                 PRIMITIVE_SLOW_RISE,
963                 PRIMITIVE_QUICK_FALL,
964                 PRIMITIVE_TICK,
965                 PRIMITIVE_LOW_TICK,
966         })
967         @Retention(RetentionPolicy.SOURCE)
968         public @interface PrimitiveType {
969         }
970 
971         /**
972          * Exception thrown when adding an element to a {@link Composition} that already ends in an
973          * indefinitely repeating effect.
974          * @hide
975          */
976         @TestApi
977         public static final class UnreachableAfterRepeatingIndefinitelyException
978                 extends IllegalStateException {
UnreachableAfterRepeatingIndefinitelyException()979             UnreachableAfterRepeatingIndefinitelyException() {
980                 super("Compositions ending in an indefinitely repeating effect can't be extended");
981             }
982         }
983 
984         /**
985          * No haptic effect. Used to generate extended delays between primitives.
986          *
987          * @hide
988          */
989         public static final int PRIMITIVE_NOOP = 0;
990         /**
991          * This effect should produce a sharp, crisp click sensation.
992          */
993         public static final int PRIMITIVE_CLICK = 1;
994         /**
995          * A haptic effect that simulates downwards movement with gravity. Often
996          * followed by extra energy of hitting and reverberation to augment
997          * physicality.
998          */
999         public static final int PRIMITIVE_THUD = 2;
1000         /**
1001          * A haptic effect that simulates spinning momentum.
1002          */
1003         public static final int PRIMITIVE_SPIN = 3;
1004         /**
1005          * A haptic effect that simulates quick upward movement against gravity.
1006          */
1007         public static final int PRIMITIVE_QUICK_RISE = 4;
1008         /**
1009          * A haptic effect that simulates slow upward movement against gravity.
1010          */
1011         public static final int PRIMITIVE_SLOW_RISE = 5;
1012         /**
1013          * A haptic effect that simulates quick downwards movement with gravity.
1014          */
1015         public static final int PRIMITIVE_QUICK_FALL = 6;
1016         /**
1017          * This very short effect should produce a light crisp sensation intended
1018          * to be used repetitively for dynamic feedback.
1019          */
1020         // Internally this maps to the HAL constant CompositePrimitive::LIGHT_TICK
1021         public static final int PRIMITIVE_TICK = 7;
1022         /**
1023          * This very short low frequency effect should produce a light crisp sensation
1024          * intended to be used repetitively for dynamic feedback.
1025          */
1026         // Internally this maps to the HAL constant CompositePrimitive::LOW_TICK
1027         public static final int PRIMITIVE_LOW_TICK = 8;
1028 
1029 
1030         private final ArrayList<VibrationEffectSegment> mSegments = new ArrayList<>();
1031         private int mRepeatIndex = -1;
1032 
Composition()1033         Composition() {}
1034 
1035         /**
1036          * Adds a time duration to the current composition, during which the vibrator will be
1037          * turned off.
1038          *
1039          * @param duration The length of time the vibrator should be off. Value must be non-negative
1040          *                 and will be truncated to milliseconds.
1041          * @return This {@link Composition} object to enable adding multiple elements in one chain.
1042          *
1043          * @throws UnreachableAfterRepeatingIndefinitelyException if the composition is currently
1044          * ending with a repeating effect.
1045          * @hide
1046          */
1047         @TestApi
1048         @NonNull
addOffDuration(@onNull Duration duration)1049         public Composition addOffDuration(@NonNull Duration duration) {
1050             int durationMs = (int) duration.toMillis();
1051             Preconditions.checkArgumentNonnegative(durationMs, "Off period must be non-negative");
1052             if (durationMs > 0) {
1053                 // Created a segment sustaining the zero amplitude to represent the delay.
1054                 addSegment(new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0,
1055                         (int) duration.toMillis()));
1056             }
1057             return this;
1058         }
1059 
1060         /**
1061          * Add a haptic effect to the end of the current composition.
1062          *
1063          * <p>If this effect is repeating (e.g. created by {@link VibrationEffect#createWaveform}
1064          * with a non-negative repeat index, or created by another composition that has effects
1065          * repeating indefinitely), then no more effects or primitives will be accepted by this
1066          * composition after this method. Such effects should be cancelled via
1067          * {@link Vibrator#cancel()}.
1068          *
1069          * @param effect The effect to add to the end of this composition.
1070          * @return This {@link Composition} object to enable adding multiple elements in one chain.
1071          *
1072          * @throws UnreachableAfterRepeatingIndefinitelyException if the composition is currently
1073          * ending with a repeating effect.
1074          * @hide
1075          */
1076         @TestApi
1077         @NonNull
addEffect(@onNull VibrationEffect effect)1078         public Composition addEffect(@NonNull VibrationEffect effect) {
1079             return addSegments(effect);
1080         }
1081 
1082         /**
1083          * Add a haptic effect to the end of the current composition and play it on repeat,
1084          * indefinitely.
1085          *
1086          * <p>The entire effect will be played on repeat, indefinitely, after all other elements
1087          * already added to this composition are played. No more effects or primitives will be
1088          * accepted by this composition after this method. Such effects should be cancelled via
1089          * {@link Vibrator#cancel()}.
1090          *
1091          * @param effect The effect to add to the end of this composition, must be finite.
1092          * @return This {@link Composition} object to enable adding multiple elements in one chain,
1093          * although only {@link #compose()} can follow this call.
1094          *
1095          * @throws IllegalArgumentException if the given effect is already repeating indefinitely.
1096          * @throws UnreachableAfterRepeatingIndefinitelyException if the composition is currently
1097          * ending with a repeating effect.
1098          * @hide
1099          */
1100         @TestApi
1101         @NonNull
repeatEffectIndefinitely(@onNull VibrationEffect effect)1102         public Composition repeatEffectIndefinitely(@NonNull VibrationEffect effect) {
1103             Preconditions.checkArgument(effect.getDuration() < Long.MAX_VALUE,
1104                     "Can't repeat an indefinitely repeating effect. Consider addEffect instead.");
1105             int previousSegmentCount = mSegments.size();
1106             addSegments(effect);
1107             // Set repeat after segments were added, since addSegments checks this index.
1108             mRepeatIndex = previousSegmentCount;
1109             return this;
1110         }
1111 
1112         /**
1113          * Add a haptic primitive to the end of the current composition.
1114          *
1115          * <p>Similar to {@link #addPrimitive(int, float, int)}, but with no delay and a
1116          * default scale applied.
1117          *
1118          * @param primitiveId The primitive to add
1119          * @return This {@link Composition} object to enable adding multiple elements in one chain.
1120          */
1121         @NonNull
1122         public Composition addPrimitive(@PrimitiveType int primitiveId) {
1123             return addPrimitive(primitiveId, /*scale*/ 1.0f, /*delay*/ 0);
1124         }
1125 
1126         /**
1127          * Add a haptic primitive to the end of the current composition.
1128          *
1129          * <p>Similar to {@link #addPrimitive(int, float, int)}, but with no delay.
1130          *
1131          * @param primitiveId The primitive to add
1132          * @param scale The scale to apply to the intensity of the primitive.
1133          * @return This {@link Composition} object to enable adding multiple elements in one chain.
1134          */
1135         @NonNull
1136         public Composition addPrimitive(@PrimitiveType int primitiveId,
1137                 @FloatRange(from = 0f, to = 1f) float scale) {
1138             return addPrimitive(primitiveId, scale, /*delay*/ 0);
1139         }
1140 
1141         /**
1142          * Add a haptic primitive to the end of the current composition.
1143          *
1144          * @param primitiveId The primitive to add
1145          * @param scale The scale to apply to the intensity of the primitive.
1146          * @param delay The amount of time in milliseconds to wait before playing this primitive,
1147          *              starting at the time the previous element in this composition is finished.
1148          * @return This {@link Composition} object to enable adding multiple elements in one chain.
1149          */
1150         @NonNull
1151         public Composition addPrimitive(@PrimitiveType int primitiveId,
1152                 @FloatRange(from = 0f, to = 1f) float scale, @IntRange(from = 0) int delay) {
1153             PrimitiveSegment primitive = new PrimitiveSegment(primitiveId, scale,
1154                     delay);
1155             primitive.validate();
1156             return addSegment(primitive);
1157         }
1158 
1159         private Composition addSegment(VibrationEffectSegment segment) {
1160             if (mRepeatIndex >= 0) {
1161                 throw new UnreachableAfterRepeatingIndefinitelyException();
1162             }
1163             mSegments.add(segment);
1164             return this;
1165         }
1166 
addSegments(VibrationEffect effect)1167         private Composition addSegments(VibrationEffect effect) {
1168             if (mRepeatIndex >= 0) {
1169                 throw new UnreachableAfterRepeatingIndefinitelyException();
1170             }
1171             Composed composed = (Composed) effect;
1172             if (composed.getRepeatIndex() >= 0) {
1173                 // Start repeating from the index relative to the composed waveform.
1174                 mRepeatIndex = mSegments.size() + composed.getRepeatIndex();
1175             }
1176             mSegments.addAll(composed.getSegments());
1177             return this;
1178         }
1179 
1180         /**
1181          * Compose all of the added primitives together into a single {@link VibrationEffect}.
1182          *
1183          * <p>The {@link Composition} object is still valid after this call, so you can continue
1184          * adding more primitives to it and generating more {@link VibrationEffect}s by calling this
1185          * method again.
1186          *
1187          * @return The {@link VibrationEffect} resulting from the composition of the primitives.
1188          */
1189         @NonNull
compose()1190         public VibrationEffect compose() {
1191             if (mSegments.isEmpty()) {
1192                 throw new IllegalStateException(
1193                         "Composition must have at least one element to compose.");
1194             }
1195             VibrationEffect effect = new Composed(mSegments, mRepeatIndex);
1196             effect.validate();
1197             return effect;
1198         }
1199 
1200         /**
1201          * Convert the primitive ID to a human readable string for debugging.
1202          * @param id The ID to convert
1203          * @return The ID in a human readable format.
1204          * @hide
1205          */
primitiveToString(@rimitiveType int id)1206         public static String primitiveToString(@PrimitiveType int id) {
1207             switch (id) {
1208                 case PRIMITIVE_NOOP:
1209                     return "PRIMITIVE_NOOP";
1210                 case PRIMITIVE_CLICK:
1211                     return "PRIMITIVE_CLICK";
1212                 case PRIMITIVE_THUD:
1213                     return "PRIMITIVE_THUD";
1214                 case PRIMITIVE_SPIN:
1215                     return "PRIMITIVE_SPIN";
1216                 case PRIMITIVE_QUICK_RISE:
1217                     return "PRIMITIVE_QUICK_RISE";
1218                 case PRIMITIVE_SLOW_RISE:
1219                     return "PRIMITIVE_SLOW_RISE";
1220                 case PRIMITIVE_QUICK_FALL:
1221                     return "PRIMITIVE_QUICK_FALL";
1222                 case PRIMITIVE_TICK:
1223                     return "PRIMITIVE_TICK";
1224                 case PRIMITIVE_LOW_TICK:
1225                     return "PRIMITIVE_LOW_TICK";
1226                 default:
1227                     return Integer.toString(id);
1228             }
1229         }
1230     }
1231 
1232     /**
1233      * A builder for waveform haptic effects.
1234      *
1235      * <p>Waveform vibrations constitute of one or more timed transitions to new sets of vibration
1236      * parameters. These parameters can be the vibration amplitude, frequency, or both.
1237      *
1238      * <p>The following example ramps a vibrator turned off to full amplitude at 120Hz, over 100ms
1239      * starting at 60Hz, then holds that state for 200ms and ramps back down again over 100ms:
1240      *
1241      * <pre>
1242      * {@code import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
1243      * import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
1244      *
1245      * VibrationEffect effect = VibrationEffect.startWaveform(targetFrequency(60))
1246      *     .addTransition(Duration.ofMillis(100), targetAmplitude(1), targetFrequency(120))
1247      *     .addSustain(Duration.ofMillis(200))
1248      *     .addTransition(Duration.ofMillis(100), targetAmplitude(0), targetFrequency(60))
1249      *     .build();}</pre>
1250      *
1251      * <p>The initial state of the waveform can be set via
1252      * {@link VibrationEffect#startWaveform(VibrationParameter)} or
1253      * {@link VibrationEffect#startWaveform(VibrationParameter, VibrationParameter)}. If the initial
1254      * parameters are not set then the {@link WaveformBuilder} will start with the vibrator off,
1255      * represented by zero amplitude, at the vibrator's resonant frequency.
1256      *
1257      * <p>Repeating waveforms can be created by building the repeating block separately and adding
1258      * it to the end of a composition with
1259      * {@link Composition#repeatEffectIndefinitely(VibrationEffect)}:
1260      *
1261      * <p>Note that physical vibration actuators have different reaction times for changing
1262      * amplitude and frequency. Durations specified here represent a timeline for the target
1263      * parameters, and quality of effects may be improved if the durations allow time for a
1264      * transition to be smoothly applied.
1265      *
1266      * <p>The following example illustrates both an initial state and a repeating section, using
1267      * a {@link VibrationEffect.Composition}. The resulting effect will have a tick followed by a
1268      * repeated beating effect with a rise that stretches out and a sharp finish.
1269      *
1270      * <pre>
1271      * {@code VibrationEffect patternToRepeat = VibrationEffect.startWaveform(targetAmplitude(0.2f))
1272      *     .addSustain(Duration.ofMillis(10))
1273      *     .addTransition(Duration.ofMillis(20), targetAmplitude(0.4f))
1274      *     .addSustain(Duration.ofMillis(30))
1275      *     .addTransition(Duration.ofMillis(40), targetAmplitude(0.8f))
1276      *     .addSustain(Duration.ofMillis(50))
1277      *     .addTransition(Duration.ofMillis(60), targetAmplitude(0.2f))
1278      *     .build();
1279      *
1280      * VibrationEffect effect = VibrationEffect.startComposition()
1281      *     .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
1282      *     .addOffDuration(Duration.ofMillis(20))
1283      *     .repeatEffectIndefinitely(patternToRepeat)
1284      *     .compose();}</pre>
1285      *
1286      * <p>The amplitude step waveforms that can be created via
1287      * {@link VibrationEffect#createWaveform(long[], int[], int)} can also be created with
1288      * {@link WaveformBuilder} by adding zero duration transitions:
1289      *
1290      * <pre>
1291      * {@code // These two effects are the same
1292      * VibrationEffect waveform = VibrationEffect.createWaveform(
1293      *     new long[] { 10, 20, 30 },  // timings in milliseconds
1294      *     new int[] { 51, 102, 204 }, // amplitudes in [0,255]
1295      *     -1);                        // repeat index
1296      *
1297      * VibrationEffect sameWaveform = VibrationEffect.startWaveform(targetAmplitude(0.2f))
1298      *     .addSustain(Duration.ofMillis(10))
1299      *     .addTransition(Duration.ZERO, targetAmplitude(0.4f))
1300      *     .addSustain(Duration.ofMillis(20))
1301      *     .addTransition(Duration.ZERO, targetAmplitude(0.8f))
1302      *     .addSustain(Duration.ofMillis(30))
1303      *     .build();}</pre>
1304      *
1305      * @see VibrationEffect#startWaveform
1306      * @hide
1307      */
1308     @TestApi
1309     public static final class WaveformBuilder {
1310         // Epsilon used for float comparison of amplitude and frequency values on transitions.
1311         private static final float EPSILON = 1e-5f;
1312 
1313         private ArrayList<VibrationEffectSegment> mSegments = new ArrayList<>();
1314         private float mLastAmplitude = 0f;
1315         private float mLastFrequencyHz = 0f;
1316 
WaveformBuilder()1317         WaveformBuilder() {}
1318 
1319         /**
1320          * Add a transition to new vibration parameter value to the end of this waveform.
1321          *
1322          * <p>The duration represents how long the vibrator should take to smoothly transition to
1323          * the new vibration parameter. If the duration is zero then the vibrator will jump to the
1324          * new value as fast as possible.
1325          *
1326          * <p>Vibration parameter values will be truncated to conform to the device capabilities
1327          * according to the {@link android.os.vibrator.VibratorFrequencyProfile}.
1328          *
1329          * @param duration        The length of time this transition should take. Value must be
1330          *                        non-negative and will be truncated to milliseconds.
1331          * @param targetParameter The new target {@link VibrationParameter} value to be reached
1332          *                        after the given duration.
1333          * @return This {@link WaveformBuilder} object to enable adding multiple transitions in
1334          * chain.
1335          * @hide
1336          */
1337         @TestApi
1338         @SuppressWarnings("MissingGetterMatchingBuilder") // No getters to segments once created.
1339         @NonNull
addTransition(@onNull Duration duration, @NonNull VibrationParameter targetParameter)1340         public WaveformBuilder addTransition(@NonNull Duration duration,
1341                 @NonNull VibrationParameter targetParameter) {
1342             Preconditions.checkNotNull(duration, "Duration is null");
1343             checkVibrationParameter(targetParameter, "targetParameter");
1344             float amplitude = extractTargetAmplitude(targetParameter, /* target2= */ null);
1345             float frequencyHz = extractTargetFrequency(targetParameter, /* target2= */ null);
1346             addTransitionSegment(duration, amplitude, frequencyHz);
1347             return this;
1348         }
1349 
1350         /**
1351          * Add a transition to new vibration parameters to the end of this waveform.
1352          *
1353          * <p>The duration represents how long the vibrator should take to smoothly transition to
1354          * the new vibration parameters. If the duration is zero then the vibrator will jump to the
1355          * new values as fast as possible.
1356          *
1357          * <p>Vibration parameters values will be truncated to conform to the device capabilities
1358          * according to the {@link android.os.vibrator.VibratorFrequencyProfile}.
1359          *
1360          * @param duration         The length of time this transition should take. Value must be
1361          *                         non-negative and will be truncated to milliseconds.
1362          * @param targetParameter1 The first target {@link VibrationParameter} value to be reached
1363          *                         after the given duration.
1364          * @param targetParameter2 The second target {@link VibrationParameter} value to be reached
1365          *                         after the given duration, must be a different type of parameter
1366          *                         than the one specified by the first argument.
1367          * @return This {@link WaveformBuilder} object to enable adding multiple transitions in
1368          * chain.
1369          * @hide
1370          */
1371         @TestApi
1372         @SuppressWarnings("MissingGetterMatchingBuilder") // No getters to segments once created.
1373         @NonNull
addTransition(@onNull Duration duration, @NonNull VibrationParameter targetParameter1, @NonNull VibrationParameter targetParameter2)1374         public WaveformBuilder addTransition(@NonNull Duration duration,
1375                 @NonNull VibrationParameter targetParameter1,
1376                 @NonNull VibrationParameter targetParameter2) {
1377             Preconditions.checkNotNull(duration, "Duration is null");
1378             checkVibrationParameter(targetParameter1, "targetParameter1");
1379             checkVibrationParameter(targetParameter2, "targetParameter2");
1380             Preconditions.checkArgument(
1381                     !Objects.equals(targetParameter1.getClass(), targetParameter2.getClass()),
1382                     "Parameter arguments must specify different parameter types");
1383             float amplitude = extractTargetAmplitude(targetParameter1, targetParameter2);
1384             float frequencyHz = extractTargetFrequency(targetParameter1, targetParameter2);
1385             addTransitionSegment(duration, amplitude, frequencyHz);
1386             return this;
1387         }
1388 
1389         /**
1390          * Add a duration to sustain the last vibration parameters of this waveform.
1391          *
1392          * <p>The duration represents how long the vibrator should sustain the last set of
1393          * parameters provided to this builder.
1394          *
1395          * @param duration   The length of time the last values should be sustained by the vibrator.
1396          *                   Value must be >= 1ms.
1397          * @return This {@link WaveformBuilder} object to enable adding multiple transitions in
1398          * chain.
1399          * @hide
1400          */
1401         @TestApi
1402         @SuppressWarnings("MissingGetterMatchingBuilder") // No getters to segments once created.
1403         @NonNull
addSustain(@onNull Duration duration)1404         public WaveformBuilder addSustain(@NonNull Duration duration) {
1405             int durationMs = (int) duration.toMillis();
1406             Preconditions.checkArgument(durationMs >= 1, "Sustain duration must be >= 1ms");
1407             mSegments.add(new StepSegment(mLastAmplitude, mLastFrequencyHz, durationMs));
1408             return this;
1409         }
1410 
1411         /**
1412          * Build the waveform as a single {@link VibrationEffect}.
1413          *
1414          * <p>The {@link WaveformBuilder} object is still valid after this call, so you can
1415          * continue adding more primitives to it and generating more {@link VibrationEffect}s by
1416          * calling this method again.
1417          *
1418          * @return The {@link VibrationEffect} resulting from the list of transitions.
1419          * @hide
1420          */
1421         @TestApi
1422         @NonNull
build()1423         public VibrationEffect build() {
1424             if (mSegments.isEmpty()) {
1425                 throw new IllegalStateException(
1426                         "WaveformBuilder must have at least one transition to build.");
1427             }
1428             VibrationEffect effect = new Composed(mSegments, /* repeatIndex= */ -1);
1429             effect.validate();
1430             return effect;
1431         }
1432 
checkVibrationParameter(@onNull VibrationParameter vibrationParameter, String paramName)1433         private void checkVibrationParameter(@NonNull VibrationParameter vibrationParameter,
1434                 String paramName) {
1435             Preconditions.checkNotNull(vibrationParameter, "%s is null", paramName);
1436             Preconditions.checkArgument(
1437                     (vibrationParameter instanceof AmplitudeVibrationParameter)
1438                             || (vibrationParameter instanceof FrequencyVibrationParameter),
1439                     "%s is a unknown parameter", paramName);
1440         }
1441 
extractTargetAmplitude(@ullable VibrationParameter target1, @Nullable VibrationParameter target2)1442         private float extractTargetAmplitude(@Nullable VibrationParameter target1,
1443                 @Nullable VibrationParameter target2) {
1444             if (target2 instanceof AmplitudeVibrationParameter) {
1445                 return ((AmplitudeVibrationParameter) target2).amplitude;
1446             }
1447             if (target1 instanceof AmplitudeVibrationParameter) {
1448                 return ((AmplitudeVibrationParameter) target1).amplitude;
1449             }
1450             return mLastAmplitude;
1451         }
1452 
extractTargetFrequency(@ullable VibrationParameter target1, @Nullable VibrationParameter target2)1453         private float extractTargetFrequency(@Nullable VibrationParameter target1,
1454                 @Nullable VibrationParameter target2) {
1455             if (target2 instanceof FrequencyVibrationParameter) {
1456                 return ((FrequencyVibrationParameter) target2).frequencyHz;
1457             }
1458             if (target1 instanceof FrequencyVibrationParameter) {
1459                 return ((FrequencyVibrationParameter) target1).frequencyHz;
1460             }
1461             return mLastFrequencyHz;
1462         }
1463 
addTransitionSegment(Duration duration, float targetAmplitude, float targetFrequency)1464         private void addTransitionSegment(Duration duration, float targetAmplitude,
1465                 float targetFrequency) {
1466             Preconditions.checkNotNull(duration, "Duration is null");
1467             Preconditions.checkArgument(!duration.isNegative(),
1468                     "Transition duration must be non-negative");
1469             int durationMs = (int) duration.toMillis();
1470 
1471             // Ignore transitions with zero duration, but keep values for next additions.
1472             if (durationMs > 0) {
1473                 if ((Math.abs(mLastAmplitude - targetAmplitude) < EPSILON)
1474                         && (Math.abs(mLastFrequencyHz - targetFrequency) < EPSILON)) {
1475                     // No value is changing, this can be best represented by a step segment.
1476                     mSegments.add(new StepSegment(targetAmplitude, targetFrequency, durationMs));
1477                 } else {
1478                     mSegments.add(new RampSegment(mLastAmplitude, targetAmplitude,
1479                             mLastFrequencyHz, targetFrequency, durationMs));
1480                 }
1481             }
1482 
1483             mLastAmplitude = targetAmplitude;
1484             mLastFrequencyHz = targetFrequency;
1485         }
1486     }
1487 
1488     /**
1489      * A representation of a single vibration parameter.
1490      *
1491      * <p>This is to describe a waveform haptic effect, which consists of one or more timed
1492      * transitions to a new set of {@link VibrationParameter}s.
1493      *
1494      * <p>Examples of concrete parameters are the vibration amplitude or frequency.
1495      *
1496      * @see VibrationEffect.WaveformBuilder
1497      * @hide
1498      */
1499     @TestApi
1500     @SuppressWarnings("UserHandleName") // This is not a regular set of parameters, no *Params.
1501     public static class VibrationParameter {
VibrationParameter()1502         VibrationParameter() {
1503         }
1504 
1505         /**
1506          * The target vibration amplitude.
1507          *
1508          * @param amplitude The amplitude value, between 0 and 1, inclusive, where 0 represents the
1509          *                  vibrator turned off and 1 represents the maximum amplitude the vibrator
1510          *                  can reach across all supported frequencies.
1511          * @return The {@link VibrationParameter} instance that represents given amplitude.
1512          * @hide
1513          */
1514         @TestApi
1515         @NonNull
targetAmplitude( @loatRangefrom = 0, to = 1) float amplitude)1516         public static VibrationParameter targetAmplitude(
1517                 @FloatRange(from = 0, to = 1) float amplitude) {
1518             return new AmplitudeVibrationParameter(amplitude);
1519         }
1520 
1521         /**
1522          * The target vibration frequency.
1523          *
1524          * @param frequencyHz The frequency value, in hertz.
1525          * @return The {@link VibrationParameter} instance that represents given frequency.
1526          * @hide
1527          */
1528         @TestApi
1529         @NonNull
targetFrequency(@loatRangefrom = 1) float frequencyHz)1530         public static VibrationParameter targetFrequency(@FloatRange(from = 1) float frequencyHz) {
1531             return new FrequencyVibrationParameter(frequencyHz);
1532         }
1533     }
1534 
1535     /** The vibration amplitude, represented by a value in [0,1]. */
1536     private static final class AmplitudeVibrationParameter extends VibrationParameter {
1537         public final float amplitude;
1538 
AmplitudeVibrationParameter(float amplitude)1539         AmplitudeVibrationParameter(float amplitude) {
1540             Preconditions.checkArgument((amplitude >= 0) && (amplitude <= 1),
1541                     "Amplitude must be within [0,1]");
1542             this.amplitude = amplitude;
1543         }
1544     }
1545 
1546     /** The vibration frequency, in hertz, or zero to represent undefined frequency. */
1547     private static final class FrequencyVibrationParameter extends VibrationParameter {
1548         public final float frequencyHz;
1549 
FrequencyVibrationParameter(float frequencyHz)1550         FrequencyVibrationParameter(float frequencyHz) {
1551             Preconditions.checkArgument(frequencyHz >= 1, "Frequency must be >= 1");
1552             Preconditions.checkArgument(Float.isFinite(frequencyHz), "Frequency must be finite");
1553             this.frequencyHz = frequencyHz;
1554         }
1555     }
1556 
1557     @NonNull
1558     public static final Parcelable.Creator<VibrationEffect> CREATOR =
1559             new Parcelable.Creator<VibrationEffect>() {
1560                 @Override
1561                 public VibrationEffect createFromParcel(Parcel in) {
1562                     return new Composed(in);
1563                 }
1564                 @Override
1565                 public VibrationEffect[] newArray(int size) {
1566                     return new VibrationEffect[size];
1567                 }
1568             };
1569 }
1570