• 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.TestApi;
21 import android.util.SparseArray;
22 
23 import com.android.internal.util.Preconditions;
24 
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.Objects;
28 
29 /**
30  * A CombinedVibration describes a combination of haptic effects to be performed by one or more
31  * {@link Vibrator Vibrators}.
32  *
33  * These effects may be any number of things, from single shot vibrations to complex waveforms.
34  *
35  * @see VibrationEffect
36  */
37 @SuppressWarnings({"ParcelNotFinal", "ParcelCreator"}) // Parcel only extended here.
38 public abstract class CombinedVibration implements Parcelable {
39     private static final int PARCEL_TOKEN_MONO = 1;
40     private static final int PARCEL_TOKEN_STEREO = 2;
41     private static final int PARCEL_TOKEN_SEQUENTIAL = 3;
42 
43     /** Prevent subclassing from outside of the framework. */
CombinedVibration()44     CombinedVibration() {
45     }
46 
47     /**
48      * Create a vibration that plays a single effect in parallel on all vibrators.
49      *
50      * A parallel vibration that takes a single {@link VibrationEffect} to be performed by multiple
51      * vibrators at the same time.
52      *
53      * @param effect The {@link VibrationEffect} to perform.
54      * @return The combined vibration representing the single effect to be played in all vibrators.
55      */
56     @NonNull
createParallel(@onNull VibrationEffect effect)57     public static CombinedVibration createParallel(@NonNull VibrationEffect effect) {
58         CombinedVibration combined = new Mono(effect);
59         combined.validate();
60         return combined;
61     }
62 
63     /**
64      * Start creating a vibration that plays effects in parallel on one or more vibrators.
65      *
66      * A parallel vibration takes one or more {@link VibrationEffect VibrationEffects} associated to
67      * individual vibrators to be performed at the same time.
68      *
69      * @see CombinedVibration.ParallelCombination
70      */
71     @NonNull
startParallel()72     public static ParallelCombination startParallel() {
73         return new ParallelCombination();
74     }
75 
76     /**
77      * Start creating a vibration that plays effects in sequence on one or more vibrators.
78      *
79      * A sequential vibration takes one or more {@link CombinedVibration CombinedVibrations} to be
80      * performed by one or more vibrators in order. Each {@link CombinedVibration} starts only after
81      * the previous one is finished.
82      *
83      * @hide
84      * @see CombinedVibration.SequentialCombination
85      */
86     @TestApi
87     @NonNull
startSequential()88     public static SequentialCombination startSequential() {
89         return new SequentialCombination();
90     }
91 
92     @Override
describeContents()93     public int describeContents() {
94         return 0;
95     }
96 
97     /**
98      * Gets the estimated duration of the combined vibration in milliseconds.
99      *
100      * <p>For parallel combinations this means the maximum duration of any individual {@link
101      * VibrationEffect}. For sequential combinations, this is a sum of each step and delays.
102      *
103      * <p>For combinations of effects without a defined end (e.g. a Waveform with a non-negative
104      * repeat index), this returns Long.MAX_VALUE. For effects with an unknown duration (e.g.
105      * Prebaked effects where the length is device and potentially run-time dependent), this returns
106      * -1.
107      *
108      * @hide
109      */
110     @TestApi
getDuration()111     public abstract long getDuration();
112 
113     /** @hide */
validate()114     public abstract void validate();
115 
116     /** @hide */
hasVibrator(int vibratorId)117     public abstract boolean hasVibrator(int vibratorId);
118 
119     /**
120      * A combination of haptic effects that should be played in multiple vibrators in parallel.
121      *
122      * @see CombinedVibration#startParallel()
123      */
124     public static final class ParallelCombination {
125 
126         private final SparseArray<VibrationEffect> mEffects = new SparseArray<>();
127 
ParallelCombination()128         ParallelCombination() {
129         }
130 
131         /**
132          * Add or replace a one shot vibration effect to be performed by the specified vibrator.
133          *
134          * @param vibratorId The id of the vibrator that should perform this effect.
135          * @param effect     The effect this vibrator should play.
136          * @return The {@link ParallelCombination} object to enable adding
137          * multiple effects in one chain.
138          * @see VibrationEffect#createOneShot(long, int)
139          */
140         @NonNull
addVibrator(int vibratorId, @NonNull VibrationEffect effect)141         public ParallelCombination addVibrator(int vibratorId, @NonNull VibrationEffect effect) {
142             mEffects.put(vibratorId, effect);
143             return this;
144         }
145 
146         /**
147          * Combine all of the added effects into a {@link CombinedVibration}.
148          *
149          * The {@link ParallelCombination} object is still valid after this
150          * call, so you can continue adding more effects to it and generating more
151          * {@link CombinedVibration}s by calling this method again.
152          *
153          * @return The {@link CombinedVibration} resulting from combining the added effects to
154          * be played in parallel.
155          */
156         @NonNull
combine()157         public CombinedVibration combine() {
158             if (mEffects.size() == 0) {
159                 throw new IllegalStateException(
160                         "Combination must have at least one element to combine.");
161             }
162             CombinedVibration combined = new Stereo(mEffects);
163             combined.validate();
164             return combined;
165         }
166     }
167 
168     /**
169      * A combination of haptic effects that should be played in multiple vibrators in sequence.
170      *
171      * @hide
172      * @see CombinedVibration#startSequential()
173      */
174     @TestApi
175     public static final class SequentialCombination {
176 
177         private final ArrayList<CombinedVibration> mEffects = new ArrayList<>();
178         private final ArrayList<Integer> mDelays = new ArrayList<>();
179 
SequentialCombination()180         SequentialCombination() {
181         }
182 
183         /**
184          * Add a single vibration effect to be performed next.
185          *
186          * Similar to {@link #addNext(int, VibrationEffect, int)}, but with no delay. The effect
187          * will start playing immediately after the previous vibration is finished.
188          *
189          * @param vibratorId The id of the vibrator that should perform this effect.
190          * @param effect     The effect this vibrator should play.
191          * @return The {@link CombinedVibration.SequentialCombination} object to enable adding
192          * multiple effects in one chain.
193          */
194         @NonNull
addNext(int vibratorId, @NonNull VibrationEffect effect)195         public SequentialCombination addNext(int vibratorId, @NonNull VibrationEffect effect) {
196             return addNext(vibratorId, effect, /* delay= */ 0);
197         }
198 
199         /**
200          * Add a single vibration effect to be performed next.
201          *
202          * The delay is applied immediately after the previous vibration is finished. The effect
203          * will start playing after the delay.
204          *
205          * @param vibratorId The id of the vibrator that should perform this effect.
206          * @param effect     The effect this vibrator should play.
207          * @param delay      The amount of time, in milliseconds, to wait between playing the prior
208          *                   vibration and this one, starting at the time the previous vibration in
209          *                   this sequence is finished.
210          * @return The {@link CombinedVibration.SequentialCombination} object to enable adding
211          * multiple effects in one chain.
212          */
213         @NonNull
addNext(int vibratorId, @NonNull VibrationEffect effect, int delay)214         public SequentialCombination addNext(int vibratorId, @NonNull VibrationEffect effect,
215                 int delay) {
216             return addNext(
217                     CombinedVibration.startParallel().addVibrator(vibratorId, effect).combine(),
218                     delay);
219         }
220 
221         /**
222          * Add a combined vibration effect to be performed next.
223          *
224          * Similar to {@link #addNext(CombinedVibration, int)}, but with no delay. The effect will
225          * start playing immediately after the previous vibration is finished.
226          *
227          * @param effect The combined effect to be performed next.
228          * @return The {@link CombinedVibration.SequentialCombination} object to enable adding
229          * multiple effects in one chain.
230          * @see VibrationEffect#createOneShot(long, int)
231          */
232         @NonNull
addNext(@onNull CombinedVibration effect)233         public SequentialCombination addNext(@NonNull CombinedVibration effect) {
234             return addNext(effect, /* delay= */ 0);
235         }
236 
237         /**
238          * Add a combined vibration effect to be performed next.
239          *
240          * The delay is applied immediately after the previous vibration is finished. The vibration
241          * will start playing after the delay.
242          *
243          * @param effect The combined effect to be performed next.
244          * @param delay  The amount of time, in milliseconds, to wait between playing the prior
245          *               vibration and this one, starting at the time the previous vibration in this
246          *               sequence is finished.
247          * @return The {@link CombinedVibration.SequentialCombination} object to enable adding
248          * multiple effects in one chain.
249          */
250         @NonNull
addNext(@onNull CombinedVibration effect, int delay)251         public SequentialCombination addNext(@NonNull CombinedVibration effect, int delay) {
252             if (effect instanceof Sequential) {
253                 Sequential sequentialEffect = (Sequential) effect;
254                 int firstEffectIndex = mDelays.size();
255                 mEffects.addAll(sequentialEffect.getEffects());
256                 mDelays.addAll(sequentialEffect.getDelays());
257                 mDelays.set(firstEffectIndex, delay + mDelays.get(firstEffectIndex));
258             } else {
259                 mEffects.add(effect);
260                 mDelays.add(delay);
261             }
262             return this;
263         }
264 
265         /**
266          * Combine all of the added effects in sequence.
267          *
268          * The {@link CombinedVibration.SequentialCombination} object is still valid after
269          * this call, so you can continue adding more effects to it and generating more {@link
270          * CombinedVibration}s by calling this method again.
271          *
272          * @return The {@link CombinedVibration} resulting from combining the added effects to
273          * be played in sequence.
274          */
275         @NonNull
combine()276         public CombinedVibration combine() {
277             if (mEffects.size() == 0) {
278                 throw new IllegalStateException(
279                         "Combination must have at least one element to combine.");
280             }
281             CombinedVibration combined = new Sequential(mEffects, mDelays);
282             combined.validate();
283             return combined;
284         }
285     }
286 
287     /**
288      * Represents a single {@link VibrationEffect} that should be played in all vibrators at the
289      * same time.
290      *
291      * @hide
292      */
293     @TestApi
294     public static final class Mono extends CombinedVibration {
295         private final VibrationEffect mEffect;
296 
Mono(Parcel in)297         Mono(Parcel in) {
298             mEffect = VibrationEffect.CREATOR.createFromParcel(in);
299         }
300 
Mono(@onNull VibrationEffect effect)301         Mono(@NonNull VibrationEffect effect) {
302             mEffect = effect;
303         }
304 
305         @NonNull
getEffect()306         public VibrationEffect getEffect() {
307             return mEffect;
308         }
309 
310         @Override
getDuration()311         public long getDuration() {
312             return mEffect.getDuration();
313         }
314 
315         /** @hide */
316         @Override
validate()317         public void validate() {
318             mEffect.validate();
319         }
320 
321         /** @hide */
322         @Override
hasVibrator(int vibratorId)323         public boolean hasVibrator(int vibratorId) {
324             return true;
325         }
326 
327         @Override
equals(Object o)328         public boolean equals(Object o) {
329             if (!(o instanceof Mono)) {
330                 return false;
331             }
332             Mono other = (Mono) o;
333             return mEffect.equals(other.mEffect);
334         }
335 
336         @Override
hashCode()337         public int hashCode() {
338             return Objects.hash(mEffect);
339         }
340 
341         @Override
toString()342         public String toString() {
343             return "Mono{mEffect=" + mEffect + '}';
344         }
345 
346         @Override
describeContents()347         public int describeContents() {
348             return 0;
349         }
350 
351         @Override
writeToParcel(@onNull Parcel out, int flags)352         public void writeToParcel(@NonNull Parcel out, int flags) {
353             out.writeInt(PARCEL_TOKEN_MONO);
354             mEffect.writeToParcel(out, flags);
355         }
356 
357         @NonNull
358         public static final Parcelable.Creator<Mono> CREATOR =
359                 new Parcelable.Creator<Mono>() {
360                     @Override
361                     public Mono createFromParcel(@NonNull Parcel in) {
362                         // Skip the type token
363                         in.readInt();
364                         return new Mono(in);
365                     }
366 
367                     @Override
368                     @NonNull
369                     public Mono[] newArray(int size) {
370                         return new Mono[size];
371                     }
372                 };
373     }
374 
375     /**
376      * Represents a set of {@link VibrationEffect VibrationEffects} associated to individual
377      * vibrators that should be played at the same time.
378      *
379      * @hide
380      */
381     @TestApi
382     public static final class Stereo extends CombinedVibration {
383 
384         /** Mapping vibrator ids to effects. */
385         private final SparseArray<VibrationEffect> mEffects;
386 
Stereo(Parcel in)387         Stereo(Parcel in) {
388             int size = in.readInt();
389             mEffects = new SparseArray<>(size);
390             for (int i = 0; i < size; i++) {
391                 int vibratorId = in.readInt();
392                 mEffects.put(vibratorId, VibrationEffect.CREATOR.createFromParcel(in));
393             }
394         }
395 
Stereo(@onNull SparseArray<VibrationEffect> effects)396         Stereo(@NonNull SparseArray<VibrationEffect> effects) {
397             mEffects = new SparseArray<>(effects.size());
398             for (int i = 0; i < effects.size(); i++) {
399                 mEffects.put(effects.keyAt(i), effects.valueAt(i));
400             }
401         }
402 
403         /** Effects to be performed in parallel, where each key represents the vibrator id. */
404         @NonNull
getEffects()405         public SparseArray<VibrationEffect> getEffects() {
406             return mEffects;
407         }
408 
409         @Override
getDuration()410         public long getDuration() {
411             long maxDuration = Long.MIN_VALUE;
412             boolean hasUnknownStep = false;
413             for (int i = 0; i < mEffects.size(); i++) {
414                 long duration = mEffects.valueAt(i).getDuration();
415                 if (duration == Long.MAX_VALUE) {
416                     // If any duration is repeating, this combination duration is also repeating.
417                     return duration;
418                 }
419                 maxDuration = Math.max(maxDuration, duration);
420                 // If any step is unknown, this combination duration will also be unknown, unless
421                 // any step is repeating. Repeating vibrations take precedence over non-repeating
422                 // ones in the service, so continue looping to check for repeating steps.
423                 hasUnknownStep |= duration < 0;
424             }
425             if (hasUnknownStep) {
426                 // If any step is unknown, this combination duration is also unknown.
427                 return -1;
428             }
429             return maxDuration;
430         }
431 
432         /** @hide */
433         @Override
434         public void validate() {
435             Preconditions.checkArgument(mEffects.size() > 0,
436                     "There should be at least one effect set for a combined effect");
437             for (int i = 0; i < mEffects.size(); i++) {
438                 mEffects.valueAt(i).validate();
439             }
440         }
441 
442         /** @hide */
443         @Override
hasVibrator(int vibratorId)444         public boolean hasVibrator(int vibratorId) {
445             return mEffects.indexOfKey(vibratorId) >= 0;
446         }
447 
448         @Override
equals(Object o)449         public boolean equals(Object o) {
450             if (!(o instanceof Stereo)) {
451                 return false;
452             }
453             Stereo other = (Stereo) o;
454             if (mEffects.size() != other.mEffects.size()) {
455                 return false;
456             }
457             for (int i = 0; i < mEffects.size(); i++) {
458                 if (!mEffects.valueAt(i).equals(other.mEffects.get(mEffects.keyAt(i)))) {
459                     return false;
460                 }
461             }
462             return true;
463         }
464 
465         @Override
hashCode()466         public int hashCode() {
467             return mEffects.contentHashCode();
468         }
469 
470         @Override
toString()471         public String toString() {
472             return "Stereo{mEffects=" + mEffects + '}';
473         }
474 
475         @Override
describeContents()476         public int describeContents() {
477             return 0;
478         }
479 
480         @Override
writeToParcel(@onNull Parcel out, int flags)481         public void writeToParcel(@NonNull Parcel out, int flags) {
482             out.writeInt(PARCEL_TOKEN_STEREO);
483             out.writeInt(mEffects.size());
484             for (int i = 0; i < mEffects.size(); i++) {
485                 out.writeInt(mEffects.keyAt(i));
486                 mEffects.valueAt(i).writeToParcel(out, flags);
487             }
488         }
489 
490         @NonNull
491         public static final Parcelable.Creator<Stereo> CREATOR =
492                 new Parcelable.Creator<Stereo>() {
493                     @Override
494                     public Stereo createFromParcel(@NonNull Parcel in) {
495                         // Skip the type token
496                         in.readInt();
497                         return new Stereo(in);
498                     }
499 
500                     @Override
501                     @NonNull
502                     public Stereo[] newArray(int size) {
503                         return new Stereo[size];
504                     }
505                 };
506     }
507 
508     /**
509      * Represents a list of {@link CombinedVibration CombinedVibrations} that should be played in
510      * sequence.
511      *
512      * @hide
513      */
514     @TestApi
515     public static final class Sequential extends CombinedVibration {
516         private final List<CombinedVibration> mEffects;
517         private final List<Integer> mDelays;
518 
Sequential(Parcel in)519         Sequential(Parcel in) {
520             int size = in.readInt();
521             mEffects = new ArrayList<>(size);
522             mDelays = new ArrayList<>(size);
523             for (int i = 0; i < size; i++) {
524                 mDelays.add(in.readInt());
525                 mEffects.add(CombinedVibration.CREATOR.createFromParcel(in));
526             }
527         }
528 
Sequential(@onNull List<CombinedVibration> effects, @NonNull List<Integer> delays)529         Sequential(@NonNull List<CombinedVibration> effects,
530                 @NonNull List<Integer> delays) {
531             mEffects = new ArrayList<>(effects);
532             mDelays = new ArrayList<>(delays);
533         }
534 
535         /** Effects to be performed in sequence. */
536         @NonNull
getEffects()537         public List<CombinedVibration> getEffects() {
538             return mEffects;
539         }
540 
541         /** Delay to be applied before each effect in {@link #getEffects()}. */
542         @NonNull
getDelays()543         public List<Integer> getDelays() {
544             return mDelays;
545         }
546 
547         @Override
getDuration()548         public long getDuration() {
549             boolean hasUnknownStep = false;
550             long durations = 0;
551             final int effectCount = mEffects.size();
552             for (int i = 0; i < effectCount; i++) {
553                 CombinedVibration effect = mEffects.get(i);
554                 long duration = effect.getDuration();
555                 if (duration == Long.MAX_VALUE) {
556                     // If any duration is repeating, this combination duration is also repeating.
557                     return duration;
558                 }
559                 durations += duration;
560                 // If any step is unknown, this combination duration will also be unknown, unless
561                 // any step is repeating. Repeating vibrations take precedence over non-repeating
562                 // ones in the service, so continue looping to check for repeating steps.
563                 hasUnknownStep |= duration < 0;
564             }
565             if (hasUnknownStep) {
566                 // If any step is unknown, this combination duration is also unknown.
567                 return -1;
568             }
569             long delays = 0;
570             for (int i = 0; i < effectCount; i++) {
571                 delays += mDelays.get(i);
572             }
573             return durations + delays;
574         }
575 
576         /** @hide */
577         @Override
578         public void validate() {
579             Preconditions.checkArgument(mEffects.size() > 0,
580                     "There should be at least one effect set for a combined effect");
581             Preconditions.checkArgument(mEffects.size() == mDelays.size(),
582                     "Effect and delays should have equal length");
583             final int effectCount = mEffects.size();
584             for (int i = 0; i < effectCount; i++) {
585                 if (mDelays.get(i) < 0) {
586                     throw new IllegalArgumentException("Delays must all be >= 0"
587                             + " (delays=" + mDelays + ")");
588                 }
589             }
590             for (int i = 0; i < effectCount; i++) {
591                 CombinedVibration effect = mEffects.get(i);
592                 if (effect instanceof Sequential) {
593                     throw new IllegalArgumentException(
594                             "There should be no nested sequential effects in a combined effect");
595                 }
596                 effect.validate();
597             }
598         }
599 
600         /** @hide */
601         @Override
602         public boolean hasVibrator(int vibratorId) {
603             final int effectCount = mEffects.size();
604             for (int i = 0; i < effectCount; i++) {
605                 if (mEffects.get(i).hasVibrator(vibratorId)) {
606                     return true;
607                 }
608             }
609             return false;
610         }
611 
612         @Override
613         public boolean equals(Object o) {
614             if (!(o instanceof Sequential)) {
615                 return false;
616             }
617             Sequential other = (Sequential) o;
618             return mDelays.equals(other.mDelays) && mEffects.equals(other.mEffects);
619         }
620 
621         @Override
622         public int hashCode() {
623             return Objects.hash(mEffects, mDelays);
624         }
625 
626         @Override
627         public String toString() {
628             return "Sequential{mEffects=" + mEffects + ", mDelays=" + mDelays + '}';
629         }
630 
631         @Override
632         public int describeContents() {
633             return 0;
634         }
635 
636         @Override
637         public void writeToParcel(@NonNull Parcel out, int flags) {
638             out.writeInt(PARCEL_TOKEN_SEQUENTIAL);
639             out.writeInt(mEffects.size());
640             for (int i = 0; i < mEffects.size(); i++) {
641                 out.writeInt(mDelays.get(i));
642                 mEffects.get(i).writeToParcel(out, flags);
643             }
644         }
645 
646         @NonNull
647         public static final Parcelable.Creator<Sequential> CREATOR =
648                 new Parcelable.Creator<Sequential>() {
649                     @Override
650                     public Sequential createFromParcel(@NonNull Parcel in) {
651                         // Skip the type token
652                         in.readInt();
653                         return new Sequential(in);
654                     }
655 
656                     @Override
657                     @NonNull
658                     public Sequential[] newArray(int size) {
659                         return new Sequential[size];
660                     }
661                 };
662     }
663 
664     @NonNull
665     public static final Parcelable.Creator<CombinedVibration> CREATOR =
666             new Parcelable.Creator<CombinedVibration>() {
667                 @Override
668                 public CombinedVibration createFromParcel(Parcel in) {
669                     int token = in.readInt();
670                     if (token == PARCEL_TOKEN_MONO) {
671                         return new Mono(in);
672                     } else if (token == PARCEL_TOKEN_STEREO) {
673                         return new Stereo(in);
674                     } else if (token == PARCEL_TOKEN_SEQUENTIAL) {
675                         return new Sequential(in);
676                     } else {
677                         throw new IllegalStateException(
678                                 "Unexpected combined vibration event type token in parcel.");
679                     }
680                 }
681 
682                 @Override
683                 public CombinedVibration[] newArray(int size) {
684                     return new CombinedVibration[size];
685                 }
686             };
687 }
688