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