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