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