• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.TestApi;
22 import android.os.vibrator.Flags;
23 import android.util.SparseArray;
24 
25 import com.android.internal.util.Preconditions;
26 
27 import java.util.ArrayList;
28 import java.util.List;
29 import java.util.Locale;
30 import java.util.Objects;
31 import java.util.StringJoiner;
32 import java.util.function.Function;
33 
34 /**
35  * A CombinedVibration describes a combination of haptic effects to be performed by one or more
36  * {@link Vibrator Vibrators}.
37  *
38  * These effects may be any number of things, from single shot vibrations to complex waveforms.
39  *
40  * @see VibrationEffect
41  */
42 @SuppressWarnings({"ParcelNotFinal", "ParcelCreator"}) // Parcel only extended here.
43 public abstract class CombinedVibration implements Parcelable {
44     private static final int PARCEL_TOKEN_MONO = 1;
45     private static final int PARCEL_TOKEN_STEREO = 2;
46     private static final int PARCEL_TOKEN_SEQUENTIAL = 3;
47 
48     /** Prevent subclassing from outside of the framework. */
CombinedVibration()49     CombinedVibration() {
50     }
51 
52     /**
53      * Create a vibration that plays a single effect in parallel on all vibrators.
54      *
55      * A parallel vibration that takes a single {@link VibrationEffect} to be performed by multiple
56      * vibrators at the same time.
57      *
58      * @param effect The {@link VibrationEffect} to perform.
59      * @return The combined vibration representing the single effect to be played in all vibrators.
60      */
61     @NonNull
createParallel(@onNull VibrationEffect effect)62     public static CombinedVibration createParallel(@NonNull VibrationEffect effect) {
63         CombinedVibration combined = new Mono(effect);
64         combined.validate();
65         return combined;
66     }
67 
68     /**
69      * Start creating a vibration that plays effects in parallel on one or more vibrators.
70      *
71      * A parallel vibration takes one or more {@link VibrationEffect VibrationEffects} associated to
72      * individual vibrators to be performed at the same time.
73      *
74      * @see CombinedVibration.ParallelCombination
75      */
76     @NonNull
startParallel()77     public static ParallelCombination startParallel() {
78         return new ParallelCombination();
79     }
80 
81     /**
82      * Start creating a vibration that plays effects in sequence on one or more vibrators.
83      *
84      * A sequential vibration takes one or more {@link CombinedVibration CombinedVibrations} to be
85      * performed by one or more vibrators in order. Each {@link CombinedVibration} starts only after
86      * the previous one is finished.
87      *
88      * @hide
89      * @see CombinedVibration.SequentialCombination
90      */
91     @TestApi
92     @NonNull
startSequential()93     public static SequentialCombination startSequential() {
94         return new SequentialCombination();
95     }
96 
97     @Override
describeContents()98     public int describeContents() {
99         return 0;
100     }
101 
102     /**
103      * Gets the estimated duration of the combined vibration in milliseconds.
104      *
105      * <p>For parallel combinations this means the maximum duration of any individual {@link
106      * VibrationEffect}. For sequential combinations, this is a sum of each step and delays.
107      *
108      * <p>For combinations of effects without a defined end (e.g. a Waveform with a non-negative
109      * repeat index), this returns Long.MAX_VALUE. For effects with an unknown duration (e.g.
110      * Prebaked effects where the length is device and potentially run-time dependent), this returns
111      * -1.
112      *
113      * @hide
114      */
115     @TestApi
getDuration()116     public abstract long getDuration();
117 
118     /**
119      * Gets the estimated duration of the combined vibration in milliseconds.
120      *
121      * <p>For effects with hardware-dependent constants (e.g. primitive compositions), this returns
122      * the estimated duration based on the {@link VibratorInfo}. For all other effects this will
123      * return the same as {@link #getDuration()}.
124      *
125      * @hide
126      */
getDuration(@ullable SparseArray<VibratorInfo> vibratorInfos)127     public abstract long getDuration(@Nullable SparseArray<VibratorInfo> vibratorInfos);
128 
129     /**
130      * Returns true if this effect could represent a touch haptic feedback.
131      *
132      * <p>It is strongly recommended that an instance of {@link VibrationAttributes} is specified
133      * for each vibration, with the correct usage. When a vibration is played with usage UNKNOWN,
134      * then this method will be used to classify the most common use case and make sure they are
135      * covered by the user settings for "Touch feedback".
136      *
137      * @hide
138      */
isHapticFeedbackCandidate()139     public boolean isHapticFeedbackCandidate() {
140         return false;
141     }
142 
143     /** @hide */
validate()144     public abstract void validate();
145 
146     /**
147      * Applies given effect transformation with a fixed parameter to each effect in this vibration.
148      *
149      * @param transformation The vibration effect transformation to be applied to all effects
150      * @param param          The fixed parameter to be applied in all effect transformations
151      * @return the result of running the given transformation on all effects of this vibration
152      * @hide
153      */
transform( VibrationEffect.Transformation<ParamT> transformation, ParamT param)154     public abstract <ParamT> CombinedVibration transform(
155             VibrationEffect.Transformation<ParamT> transformation, ParamT param);
156 
157     /**
158      * Applies given vibrator adapter to each effect in this combined vibration.
159      *
160      * @param adapter The vibrator adapter to be used on this vibration
161      * @return the result of running the given adapter on all effects of this vibration
162      * @hide
163      */
adapt(VibratorAdapter adapter)164     public abstract CombinedVibration adapt(VibratorAdapter adapter);
165 
166     /** @hide */
hasVibrator(int vibratorId)167     public abstract boolean hasVibrator(int vibratorId);
168 
169     /** @hide */
hasVendorEffects()170     public abstract boolean hasVendorEffects();
171 
172     /**
173      * Returns a compact version of the {@link #toString()} result for debugging purposes.
174      *
175      * @hide
176      */
toDebugString()177     public abstract String toDebugString();
178 
179     /**
180      * Adapts a {@link VibrationEffect} to a specific device vibrator using the ID.
181      *
182      * <p>This can be used for adapting effects to the capabilities of the specific device vibrator
183      * it's been mapped to by the combined vibration.
184      *
185      * @hide
186      */
187     public interface VibratorAdapter {
188 
189         /**
190          * Return the list of vibrator IDs available on the device, to be used by {@link
191          * CombinedVibration} to fan-out individual effects that aren't assigned to a specific
192          * vibrator.
193          */
getAvailableVibratorIds()194         int[] getAvailableVibratorIds();
195 
196         /** Adapts a {@link VibrationEffect} to a given vibrator. */
adaptToVibrator(int vibratorId, @NonNull VibrationEffect effect)197         VibrationEffect adaptToVibrator(int vibratorId, @NonNull VibrationEffect effect);
198     }
199 
200     /**
201      * A combination of haptic effects that should be played in multiple vibrators in parallel.
202      *
203      * @see CombinedVibration#startParallel()
204      */
205     public static final class ParallelCombination {
206 
207         private final SparseArray<VibrationEffect> mEffects = new SparseArray<>();
208 
ParallelCombination()209         ParallelCombination() {
210         }
211 
212         /**
213          * Add or replace a one shot vibration effect to be performed by the specified vibrator.
214          *
215          * @param vibratorId The id of the vibrator that should perform this effect.
216          * @param effect     The effect this vibrator should play.
217          * @return The {@link ParallelCombination} object to enable adding
218          * multiple effects in one chain.
219          * @see VibrationEffect#createOneShot(long, int)
220          */
221         @NonNull
addVibrator(int vibratorId, @NonNull VibrationEffect effect)222         public ParallelCombination addVibrator(int vibratorId, @NonNull VibrationEffect effect) {
223             mEffects.put(vibratorId, effect);
224             return this;
225         }
226 
227         /**
228          * Combine all of the added effects into a {@link CombinedVibration}.
229          *
230          * The {@link ParallelCombination} object is still valid after this
231          * call, so you can continue adding more effects to it and generating more
232          * {@link CombinedVibration}s by calling this method again.
233          *
234          * @return The {@link CombinedVibration} resulting from combining the added effects to
235          * be played in parallel.
236          */
237         @NonNull
combine()238         public CombinedVibration combine() {
239             if (mEffects.size() == 0) {
240                 throw new IllegalStateException(
241                         "Combination must have at least one element to combine.");
242             }
243             CombinedVibration combined = new Stereo(mEffects);
244             combined.validate();
245             return combined;
246         }
247     }
248 
249     /**
250      * A combination of haptic effects that should be played in multiple vibrators in sequence.
251      *
252      * @hide
253      * @see CombinedVibration#startSequential()
254      */
255     @TestApi
256     public static final class SequentialCombination {
257 
258         private final ArrayList<CombinedVibration> mEffects = new ArrayList<>();
259         private final ArrayList<Integer> mDelays = new ArrayList<>();
260 
SequentialCombination()261         SequentialCombination() {
262         }
263 
264         /**
265          * Add a single vibration effect to be performed next.
266          *
267          * Similar to {@link #addNext(int, VibrationEffect, int)}, but with no delay. The effect
268          * will start playing immediately after the previous vibration is finished.
269          *
270          * @param vibratorId The id of the vibrator that should perform this effect.
271          * @param effect     The effect this vibrator should play.
272          * @return The {@link CombinedVibration.SequentialCombination} object to enable adding
273          * multiple effects in one chain.
274          */
275         @NonNull
addNext(int vibratorId, @NonNull VibrationEffect effect)276         public SequentialCombination addNext(int vibratorId, @NonNull VibrationEffect effect) {
277             return addNext(vibratorId, effect, /* delay= */ 0);
278         }
279 
280         /**
281          * Add a single vibration effect to be performed next.
282          *
283          * The delay is applied immediately after the previous vibration is finished. The effect
284          * will start playing after the delay.
285          *
286          * @param vibratorId The id of the vibrator that should perform this effect.
287          * @param effect     The effect this vibrator should play.
288          * @param delay      The amount of time, in milliseconds, to wait between playing the prior
289          *                   vibration and this one, starting at the time the previous vibration in
290          *                   this sequence is finished.
291          * @return The {@link CombinedVibration.SequentialCombination} object to enable adding
292          * multiple effects in one chain.
293          */
294         @NonNull
addNext(int vibratorId, @NonNull VibrationEffect effect, int delay)295         public SequentialCombination addNext(int vibratorId, @NonNull VibrationEffect effect,
296                 int delay) {
297             return addNext(
298                     CombinedVibration.startParallel().addVibrator(vibratorId, effect).combine(),
299                     delay);
300         }
301 
302         /**
303          * Add a combined vibration effect to be performed next.
304          *
305          * Similar to {@link #addNext(CombinedVibration, int)}, but with no delay. The effect will
306          * start playing immediately after the previous vibration is finished.
307          *
308          * @param effect The combined effect to be performed next.
309          * @return The {@link CombinedVibration.SequentialCombination} object to enable adding
310          * multiple effects in one chain.
311          * @see VibrationEffect#createOneShot(long, int)
312          */
313         @NonNull
addNext(@onNull CombinedVibration effect)314         public SequentialCombination addNext(@NonNull CombinedVibration effect) {
315             return addNext(effect, /* delay= */ 0);
316         }
317 
318         /**
319          * Add a combined vibration effect to be performed next.
320          *
321          * The delay is applied immediately after the previous vibration is finished. The vibration
322          * will start playing after the delay.
323          *
324          * @param effect The combined effect to be performed next.
325          * @param delay  The amount of time, in milliseconds, to wait between playing the prior
326          *               vibration and this one, starting at the time the previous vibration in this
327          *               sequence is finished.
328          * @return The {@link CombinedVibration.SequentialCombination} object to enable adding
329          * multiple effects in one chain.
330          */
331         @NonNull
addNext(@onNull CombinedVibration effect, int delay)332         public SequentialCombination addNext(@NonNull CombinedVibration effect, int delay) {
333             if (effect instanceof Sequential) {
334                 Sequential sequentialEffect = (Sequential) effect;
335                 int firstEffectIndex = mDelays.size();
336                 mEffects.addAll(sequentialEffect.getEffects());
337                 mDelays.addAll(sequentialEffect.getDelays());
338                 mDelays.set(firstEffectIndex, delay + mDelays.get(firstEffectIndex));
339             } else {
340                 mEffects.add(effect);
341                 mDelays.add(delay);
342             }
343             return this;
344         }
345 
346         /**
347          * Combine all of the added effects in sequence.
348          *
349          * The {@link CombinedVibration.SequentialCombination} object is still valid after
350          * this call, so you can continue adding more effects to it and generating more {@link
351          * CombinedVibration}s by calling this method again.
352          *
353          * @return The {@link CombinedVibration} resulting from combining the added effects to
354          * be played in sequence.
355          */
356         @NonNull
combine()357         public CombinedVibration combine() {
358             if (mEffects.size() == 0) {
359                 throw new IllegalStateException(
360                         "Combination must have at least one element to combine.");
361             }
362             CombinedVibration combined = new Sequential(mEffects, mDelays);
363             combined.validate();
364             return combined;
365         }
366     }
367 
368     /**
369      * Represents a single {@link VibrationEffect} that should be played in all vibrators at the
370      * same time.
371      *
372      * @hide
373      */
374     @TestApi
375     public static final class Mono extends CombinedVibration {
376         private final VibrationEffect mEffect;
377 
Mono(Parcel in)378         Mono(Parcel in) {
379             mEffect = VibrationEffect.CREATOR.createFromParcel(in);
380         }
381 
Mono(@onNull VibrationEffect effect)382         Mono(@NonNull VibrationEffect effect) {
383             mEffect = effect;
384         }
385 
386         @NonNull
getEffect()387         public VibrationEffect getEffect() {
388             return mEffect;
389         }
390 
391         @Override
getDuration()392         public long getDuration() {
393             return mEffect.getDuration();
394         }
395 
396         /** @hide */
397         @Override
getDuration(@ullable SparseArray<VibratorInfo> vibratorInfos)398         public long getDuration(@Nullable SparseArray<VibratorInfo> vibratorInfos) {
399             if (vibratorInfos == null) {
400                 return getDuration();
401             }
402             long maxDuration = 0;
403             for (int i = 0; i < vibratorInfos.size(); i++) {
404                 long duration = mEffect.getDuration(vibratorInfos.valueAt(i));
405                 if ((duration == Long.MAX_VALUE) || (duration < 0)) {
406                     return duration;
407                 }
408                 maxDuration = Math.max(maxDuration, duration);
409             }
410             return maxDuration;
411         }
412 
413         /** @hide */
414         @Override
isHapticFeedbackCandidate()415         public boolean isHapticFeedbackCandidate() {
416             return mEffect.isHapticFeedbackCandidate();
417         }
418 
419         /** @hide */
420         @Override
validate()421         public void validate() {
422             mEffect.validate();
423         }
424 
425         /** @hide */
426         @Override
transform( VibrationEffect.Transformation<ParamT> transformation, ParamT param)427         public <ParamT> CombinedVibration transform(
428                 VibrationEffect.Transformation<ParamT> transformation, ParamT param) {
429             VibrationEffect newEffect = transformation.transform(mEffect, param);
430             if (mEffect.equals(newEffect)) {
431                 return this;
432             }
433             // Make sure the validate methods are triggered
434             return CombinedVibration.createParallel(newEffect);
435         }
436 
437         /** @hide */
438         @Override
adapt(VibratorAdapter adapter)439         public CombinedVibration adapt(VibratorAdapter adapter) {
440             ParallelCombination combination = CombinedVibration.startParallel();
441             boolean hasSameEffects = true;
442             for (int vibratorId : adapter.getAvailableVibratorIds()) {
443                 VibrationEffect newEffect = adapter.adaptToVibrator(vibratorId, mEffect);
444                 if (newEffect == null) {
445                     // The vibration effect contains unsupported segments and cannot be played.
446                     return null;
447                 }
448                 combination.addVibrator(vibratorId, newEffect);
449                 hasSameEffects &= mEffect.equals(newEffect);
450             }
451             if (hasSameEffects) {
452                 return this;
453             }
454             // Make sure the validate methods are triggered
455             return combination.combine();
456         }
457 
458         /** @hide */
459         @Override
hasVibrator(int vibratorId)460         public boolean hasVibrator(int vibratorId) {
461             return true;
462         }
463 
464         /** @hide */
465         @Override
hasVendorEffects()466         public boolean hasVendorEffects() {
467             if (!Flags.vendorVibrationEffects()) {
468                 return false;
469             }
470             return mEffect instanceof VibrationEffect.VendorEffect;
471         }
472 
473         @Override
equals(Object o)474         public boolean equals(Object o) {
475             if (this == o) {
476                 return true;
477             }
478             if (!(o instanceof Mono)) {
479                 return false;
480             }
481             Mono other = (Mono) o;
482             return mEffect.equals(other.mEffect);
483         }
484 
485         @Override
hashCode()486         public int hashCode() {
487             return Objects.hash(mEffect);
488         }
489 
490         @Override
toString()491         public String toString() {
492             return "Mono{mEffect=" + mEffect + '}';
493         }
494 
495         /** @hide */
496         @Override
toDebugString()497         public String toDebugString() {
498             // Simplify vibration string, use the single effect to represent it.
499             return mEffect.toDebugString();
500         }
501 
502         @Override
describeContents()503         public int describeContents() {
504             return 0;
505         }
506 
507         @Override
writeToParcel(@onNull Parcel out, int flags)508         public void writeToParcel(@NonNull Parcel out, int flags) {
509             out.writeInt(PARCEL_TOKEN_MONO);
510             mEffect.writeToParcel(out, flags);
511         }
512 
513         @NonNull
514         public static final Parcelable.Creator<Mono> CREATOR =
515                 new Parcelable.Creator<Mono>() {
516                     @Override
517                     public Mono createFromParcel(@NonNull Parcel in) {
518                         // Skip the type token
519                         in.readInt();
520                         return new Mono(in);
521                     }
522 
523                     @Override
524                     @NonNull
525                     public Mono[] newArray(int size) {
526                         return new Mono[size];
527                     }
528                 };
529     }
530 
531     /**
532      * Represents a set of {@link VibrationEffect VibrationEffects} associated to individual
533      * vibrators that should be played at the same time.
534      *
535      * @hide
536      */
537     @TestApi
538     public static final class Stereo extends CombinedVibration {
539 
540         /** Mapping vibrator ids to effects. */
541         private final SparseArray<VibrationEffect> mEffects;
542 
Stereo(Parcel in)543         Stereo(Parcel in) {
544             int size = in.readInt();
545             mEffects = new SparseArray<>(size);
546             for (int i = 0; i < size; i++) {
547                 int vibratorId = in.readInt();
548                 mEffects.put(vibratorId, VibrationEffect.CREATOR.createFromParcel(in));
549             }
550         }
551 
Stereo(@onNull SparseArray<VibrationEffect> effects)552         Stereo(@NonNull SparseArray<VibrationEffect> effects) {
553             mEffects = new SparseArray<>(effects.size());
554             for (int i = 0; i < effects.size(); i++) {
555                 mEffects.put(effects.keyAt(i), effects.valueAt(i));
556             }
557         }
558 
559         /** Effects to be performed in parallel, where each key represents the vibrator id. */
560         @NonNull
getEffects()561         public SparseArray<VibrationEffect> getEffects() {
562             return mEffects;
563         }
564 
565         @Override
getDuration()566         public long getDuration() {
567             return getDuration(idx -> mEffects.valueAt(idx).getDuration());
568         }
569 
570         /** @hide */
571         @Override
getDuration(@ullable SparseArray<VibratorInfo> vibratorInfos)572         public long getDuration(@Nullable SparseArray<VibratorInfo> vibratorInfos) {
573             if (vibratorInfos == null) {
574                 return getDuration();
575             }
576             return getDuration(idx -> {
577                 VibrationEffect effect = mEffects.valueAt(idx);
578                 VibratorInfo info = vibratorInfos.get(mEffects.keyAt(idx));
579                 return effect.getDuration(info);
580             });
581         }
582 
getDuration(Function<Integer, Long> durationFn)583         private long getDuration(Function<Integer, Long> durationFn) {
584             long maxDuration = Long.MIN_VALUE;
585             boolean hasUnknownStep = false;
586             for (int i = 0; i < mEffects.size(); i++) {
587                 long duration = durationFn.apply(i);
588                 if (duration == Long.MAX_VALUE) {
589                     // If any duration is repeating, this combination duration is also repeating.
590                     return duration;
591                 }
592                 maxDuration = Math.max(maxDuration, duration);
593                 // If any step is unknown, this combination duration will also be unknown, unless
594                 // any step is repeating. Repeating vibrations take precedence over non-repeating
595                 // ones in the service, so continue looping to check for repeating steps.
596                 hasUnknownStep |= duration < 0;
597             }
598             if (hasUnknownStep) {
599                 // If any step is unknown, this combination duration is also unknown.
600                 return -1;
601             }
602             return maxDuration;
603         }
604 
605         /** @hide */
606         @Override
607         public boolean isHapticFeedbackCandidate() {
608             for (int i = 0; i < mEffects.size(); i++) {
609                 if (!mEffects.valueAt(i).isHapticFeedbackCandidate()) {
610                     return false;
611                 }
612             }
613             return true;
614         }
615 
616         /** @hide */
617         @Override
618         public void validate() {
619             Preconditions.checkArgument(mEffects.size() > 0,
620                     "There should be at least one effect set for a combined effect");
621             for (int i = 0; i < mEffects.size(); i++) {
622                 mEffects.valueAt(i).validate();
623             }
624         }
625 
626         /** @hide */
627         @Override
628         public <ParamT> CombinedVibration transform(
629                 VibrationEffect.Transformation<ParamT> transformation, ParamT param) {
630             ParallelCombination combination = CombinedVibration.startParallel();
631             boolean hasSameEffects = true;
632             for (int i = 0; i < mEffects.size(); i++) {
633                 int vibratorId = mEffects.keyAt(i);
634                 VibrationEffect effect = mEffects.valueAt(i);
635                 VibrationEffect newEffect = transformation.transform(effect, param);
636                 combination.addVibrator(vibratorId, newEffect);
637                 hasSameEffects &= effect.equals(newEffect);
638             }
639             if (hasSameEffects) {
640                 return this;
641             }
642             // Make sure the validate methods are triggered
643             return combination.combine();
644         }
645 
646         /** @hide */
647         @Override
648         public CombinedVibration adapt(VibratorAdapter adapter) {
649             ParallelCombination combination = CombinedVibration.startParallel();
650             boolean hasSameEffects = true;
651             for (int i = 0; i < mEffects.size(); i++) {
652                 int vibratorId = mEffects.keyAt(i);
653                 VibrationEffect effect = mEffects.valueAt(i);
654                 VibrationEffect newEffect = adapter.adaptToVibrator(vibratorId, effect);
655                 if (newEffect == null) {
656                     // The vibration effect contains unsupported segments and cannot be played.
657                     return null;
658                 }
659                 combination.addVibrator(vibratorId, newEffect);
660                 hasSameEffects &= effect.equals(newEffect);
661             }
662             if (hasSameEffects) {
663                 return this;
664             }
665             // Make sure the validate methods are triggered
666             return combination.combine();
667         }
668 
669         /** @hide */
670         @Override
671         public boolean hasVibrator(int vibratorId) {
672             return mEffects.indexOfKey(vibratorId) >= 0;
673         }
674 
675         /** @hide */
676         @Override
677         public boolean hasVendorEffects() {
678             if (!Flags.vendorVibrationEffects()) {
679                 return false;
680             }
681             for (int i = 0; i < mEffects.size(); i++) {
682                 if (mEffects.get(i) instanceof VibrationEffect.VendorEffect) {
683                     return true;
684                 }
685             }
686             return false;
687         }
688 
689         @Override
690         public boolean equals(Object o) {
691             if (this == o) {
692                 return true;
693             }
694             if (!(o instanceof Stereo)) {
695                 return false;
696             }
697             Stereo other = (Stereo) o;
698             if (mEffects.size() != other.mEffects.size()) {
699                 return false;
700             }
701             for (int i = 0; i < mEffects.size(); i++) {
702                 if (!mEffects.valueAt(i).equals(other.mEffects.get(mEffects.keyAt(i)))) {
703                     return false;
704                 }
705             }
706             return true;
707         }
708 
709         @Override
710         public int hashCode() {
711             return mEffects.contentHashCode();
712         }
713 
714         @Override
715         public String toString() {
716             return "Stereo{mEffects=" + mEffects + '}';
717         }
718 
719         /** @hide */
720         @Override
721         public String toDebugString() {
722             StringJoiner sj = new StringJoiner(",", "Stereo{", "}");
723             for (int i = 0; i < mEffects.size(); i++) {
724                 sj.add(String.format(Locale.ROOT, "vibrator(id=%d): %s",
725                         mEffects.keyAt(i), mEffects.valueAt(i).toDebugString()));
726             }
727             return sj.toString();
728         }
729 
730         @Override
731         public int describeContents() {
732             return 0;
733         }
734 
735         @Override
736         public void writeToParcel(@NonNull Parcel out, int flags) {
737             out.writeInt(PARCEL_TOKEN_STEREO);
738             out.writeInt(mEffects.size());
739             for (int i = 0; i < mEffects.size(); i++) {
740                 out.writeInt(mEffects.keyAt(i));
741                 mEffects.valueAt(i).writeToParcel(out, flags);
742             }
743         }
744 
745         @NonNull
746         public static final Parcelable.Creator<Stereo> CREATOR =
747                 new Parcelable.Creator<Stereo>() {
748                     @Override
749                     public Stereo createFromParcel(@NonNull Parcel in) {
750                         // Skip the type token
751                         in.readInt();
752                         return new Stereo(in);
753                     }
754 
755                     @Override
756                     @NonNull
757                     public Stereo[] newArray(int size) {
758                         return new Stereo[size];
759                     }
760                 };
761     }
762 
763     /**
764      * Represents a list of {@link CombinedVibration CombinedVibrations} that should be played in
765      * sequence.
766      *
767      * @hide
768      */
769     @TestApi
770     public static final class Sequential extends CombinedVibration {
771         // If a vibration is playing more than 3 effects, it's probably not haptic feedback
772         private static final long MAX_HAPTIC_FEEDBACK_SEQUENCE_SIZE = 3;
773 
774         private final List<CombinedVibration> mEffects;
775         private final List<Integer> mDelays;
776 
777         Sequential(Parcel in) {
778             int size = in.readInt();
779             mEffects = new ArrayList<>(size);
780             mDelays = new ArrayList<>(size);
781             for (int i = 0; i < size; i++) {
782                 mDelays.add(in.readInt());
783                 mEffects.add(CombinedVibration.CREATOR.createFromParcel(in));
784             }
785         }
786 
787         Sequential(@NonNull List<CombinedVibration> effects,
788                 @NonNull List<Integer> delays) {
789             mEffects = new ArrayList<>(effects);
790             mDelays = new ArrayList<>(delays);
791         }
792 
793         /** Effects to be performed in sequence. */
794         @NonNull
795         public List<CombinedVibration> getEffects() {
796             return mEffects;
797         }
798 
799         /** Delay to be applied before each effect in {@link #getEffects()}. */
800         @NonNull
801         public List<Integer> getDelays() {
802             return mDelays;
803         }
804 
805         @Override
806         public long getDuration() {
807             return getDuration(CombinedVibration::getDuration);
808         }
809 
810         /** @hide */
811         @Override
812         public long getDuration(@Nullable SparseArray<VibratorInfo> vibratorInfos) {
813             return getDuration(effect -> effect.getDuration(vibratorInfos));
814         }
815 
816         private long getDuration(Function<CombinedVibration, Long> durationFn) {
817             boolean hasUnknownStep = false;
818             long durations = 0;
819             final int effectCount = mEffects.size();
820             for (int i = 0; i < effectCount; i++) {
821                 long duration = durationFn.apply(mEffects.get(i));
822                 if (duration == Long.MAX_VALUE) {
823                     // If any duration is repeating, this combination duration is also repeating.
824                     return duration;
825                 }
826                 durations += duration;
827                 // If any step is unknown, this combination duration will also be unknown, unless
828                 // any step is repeating. Repeating vibrations take precedence over non-repeating
829                 // ones in the service, so continue looping to check for repeating steps.
830                 hasUnknownStep |= duration < 0;
831             }
832             if (hasUnknownStep) {
833                 // If any step is unknown, this combination duration is also unknown.
834                 return -1;
835             }
836             long delays = 0;
837             for (int i = 0; i < effectCount; i++) {
838                 delays += mDelays.get(i);
839             }
840             return durations + delays;
841         }
842 
843         /** @hide */
844         @Override
845         public boolean isHapticFeedbackCandidate() {
846             final int effectCount = mEffects.size();
847             if (effectCount > MAX_HAPTIC_FEEDBACK_SEQUENCE_SIZE) {
848                 return false;
849             }
850             for (int i = 0; i < effectCount; i++) {
851                 if (!mEffects.get(i).isHapticFeedbackCandidate()) {
852                     return false;
853                 }
854             }
855             return true;
856         }
857 
858         /** @hide */
859         @Override
860         public void validate() {
861             Preconditions.checkArgument(mEffects.size() > 0,
862                     "There should be at least one effect set for a combined effect");
863             Preconditions.checkArgument(mEffects.size() == mDelays.size(),
864                     "Effect and delays should have equal length");
865             final int effectCount = mEffects.size();
866             for (int i = 0; i < effectCount; i++) {
867                 if (mDelays.get(i) < 0) {
868                     throw new IllegalArgumentException("Delays must all be >= 0"
869                             + " (delays=" + mDelays + ")");
870                 }
871             }
872             for (int i = 0; i < effectCount; i++) {
873                 CombinedVibration effect = mEffects.get(i);
874                 if (effect instanceof Sequential) {
875                     throw new IllegalArgumentException(
876                             "There should be no nested sequential effects in a combined effect");
877                 }
878                 effect.validate();
879             }
880         }
881 
882         /** @hide */
883         @Override
884         public <ParamT> CombinedVibration transform(
885                 VibrationEffect.Transformation<ParamT> transformation, ParamT param) {
886             SequentialCombination combination = CombinedVibration.startSequential();
887             boolean hasSameEffects = true;
888             for (int i = 0; i < mEffects.size(); i++) {
889                 CombinedVibration vibration = mEffects.get(i);
890                 CombinedVibration newVibration = vibration.transform(transformation, param);
891                 combination.addNext(newVibration, mDelays.get(i));
892                 hasSameEffects &= vibration.equals(newVibration);
893             }
894             if (hasSameEffects) {
895                 return this;
896             }
897             // Make sure the validate methods are triggered
898             return combination.combine();
899         }
900 
901         /** @hide */
902         @Override
903         public CombinedVibration adapt(VibratorAdapter adapter) {
904             SequentialCombination combination = CombinedVibration.startSequential();
905             boolean hasSameEffects = true;
906             for (int i = 0; i < mEffects.size(); i++) {
907                 CombinedVibration vibration = mEffects.get(i);
908                 CombinedVibration newVibration = vibration.adapt(adapter);
909                 combination.addNext(newVibration, mDelays.get(i));
910                 hasSameEffects &= vibration.equals(newVibration);
911             }
912             if (hasSameEffects) {
913                 return this;
914             }
915             // Make sure the validate methods are triggered
916             return combination.combine();
917         }
918 
919         /** @hide */
920         @Override
921         public boolean hasVibrator(int vibratorId) {
922             final int effectCount = mEffects.size();
923             for (int i = 0; i < effectCount; i++) {
924                 if (mEffects.get(i).hasVibrator(vibratorId)) {
925                     return true;
926                 }
927             }
928             return false;
929         }
930 
931         /** @hide */
932         @Override
933         public boolean hasVendorEffects() {
934             for (int i = 0; i < mEffects.size(); i++) {
935                 if (mEffects.get(i).hasVendorEffects()) {
936                     return true;
937                 }
938             }
939             return false;
940         }
941 
942         @Override
943         public boolean equals(Object o) {
944             if (this == o) {
945                 return true;
946             }
947             if (!(o instanceof Sequential)) {
948                 return false;
949             }
950             Sequential other = (Sequential) o;
951             return mDelays.equals(other.mDelays) && mEffects.equals(other.mEffects);
952         }
953 
954         @Override
955         public int hashCode() {
956             return Objects.hash(mEffects, mDelays);
957         }
958 
959         @Override
960         public String toString() {
961             return "Sequential{mEffects=" + mEffects + ", mDelays=" + mDelays + '}';
962         }
963 
964         /** @hide */
965         @Override
966         public String toDebugString() {
967             StringJoiner sj = new StringJoiner(",", "Sequential{", "}");
968             for (int i = 0; i < mEffects.size(); i++) {
969                 sj.add(String.format(Locale.ROOT, "delayMs=%d, effect=%s",
970                         mDelays.get(i), mEffects.get(i).toDebugString()));
971             }
972             return sj.toString();
973         }
974 
975         @Override
976         public int describeContents() {
977             return 0;
978         }
979 
980         @Override
981         public void writeToParcel(@NonNull Parcel out, int flags) {
982             out.writeInt(PARCEL_TOKEN_SEQUENTIAL);
983             out.writeInt(mEffects.size());
984             for (int i = 0; i < mEffects.size(); i++) {
985                 out.writeInt(mDelays.get(i));
986                 mEffects.get(i).writeToParcel(out, flags);
987             }
988         }
989 
990         @NonNull
991         public static final Parcelable.Creator<Sequential> CREATOR =
992                 new Parcelable.Creator<Sequential>() {
993                     @Override
994                     public Sequential createFromParcel(@NonNull Parcel in) {
995                         // Skip the type token
996                         in.readInt();
997                         return new Sequential(in);
998                     }
999 
1000                     @Override
1001                     @NonNull
1002                     public Sequential[] newArray(int size) {
1003                         return new Sequential[size];
1004                     }
1005                 };
1006     }
1007 
1008     @NonNull
1009     public static final Parcelable.Creator<CombinedVibration> CREATOR =
1010             new Parcelable.Creator<CombinedVibration>() {
1011                 @Override
1012                 public CombinedVibration createFromParcel(Parcel in) {
1013                     int token = in.readInt();
1014                     if (token == PARCEL_TOKEN_MONO) {
1015                         return new Mono(in);
1016                     } else if (token == PARCEL_TOKEN_STEREO) {
1017                         return new Stereo(in);
1018                     } else if (token == PARCEL_TOKEN_SEQUENTIAL) {
1019                         return new Sequential(in);
1020                     } else {
1021                         throw new IllegalStateException(
1022                                 "Unexpected combined vibration event type token in parcel.");
1023                     }
1024                 }
1025 
1026                 @Override
1027                 public CombinedVibration[] newArray(int size) {
1028                     return new CombinedVibration[size];
1029                 }
1030             };
1031 }
1032