• 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.hardware.vibrator.V1_1.Constants.Effect_1_1;
20 
21 import java.util.Arrays;
22 
23 /**
24  * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
25  *
26  * These effects may be any number of things, from single shot vibrations to complex waveforms.
27  */
28 public abstract class VibrationEffect implements Parcelable {
29     private static final int PARCEL_TOKEN_ONE_SHOT = 1;
30     private static final int PARCEL_TOKEN_WAVEFORM = 2;
31     private static final int PARCEL_TOKEN_EFFECT = 3;
32 
33     /**
34      * The default vibration strength of the device.
35      */
36     public static final int DEFAULT_AMPLITUDE = -1;
37 
38     /**
39      * A click effect.
40      *
41      * @see #get(int)
42      * @hide
43      */
44     public static final int EFFECT_CLICK = Effect_1_1.CLICK;
45 
46     /**
47      * A double click effect.
48      *
49      * @see #get(int)
50      * @hide
51      */
52     public static final int EFFECT_DOUBLE_CLICK = Effect_1_1.DOUBLE_CLICK;
53 
54     /**
55      * A tick effect.
56      * @see #get(int)
57      * @hide
58      */
59     public static final int EFFECT_TICK = Effect_1_1.TICK;
60 
61     /** @hide to prevent subclassing from outside of the framework */
VibrationEffect()62     public VibrationEffect() { }
63 
64     /**
65      * Create a one shot vibration.
66      *
67      * One shot vibrations will vibrate constantly for the specified period of time at the
68      * specified amplitude, and then stop.
69      *
70      * @param milliseconds The number of milliseconds to vibrate. This must be a positive number.
71      * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or
72      * {@link #DEFAULT_AMPLITUDE}.
73      *
74      * @return The desired effect.
75      */
createOneShot(long milliseconds, int amplitude)76     public static VibrationEffect createOneShot(long milliseconds, int amplitude) {
77         VibrationEffect effect = new OneShot(milliseconds, amplitude);
78         effect.validate();
79         return effect;
80     }
81 
82     /**
83      * Create a waveform vibration.
84      *
85      * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
86      * each pair, the value in the amplitude array determines the strength of the vibration and the
87      * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
88      * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
89      * <p>
90      * The amplitude array of the generated waveform will be the same size as the given
91      * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE},
92      * starting with 0. Therefore the first timing value will be the period to wait before turning
93      * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE}
94      * strength, etc.
95      * </p><p>
96      * To cause the pattern to repeat, pass the index into the timings array at which to start the
97      * repetition, or -1 to disable repeating.
98      * </p>
99      *
100      * @param timings The pattern of alternating on-off timings, starting with off. Timing values
101      *                of 0 will cause the timing / amplitude pair to be ignored.
102      * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
103      *               want to repeat.
104      *
105      * @return The desired effect.
106      */
createWaveform(long[] timings, int repeat)107     public static VibrationEffect createWaveform(long[] timings, int repeat) {
108         int[] amplitudes = new int[timings.length];
109         for (int i = 0; i < (timings.length / 2); i++) {
110             amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE;
111         }
112         return createWaveform(timings, amplitudes, repeat);
113     }
114 
115     /**
116      * Create a waveform vibration.
117      *
118      * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
119      * each pair, the value in the amplitude array determines the strength of the vibration and the
120      * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
121      * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
122      * </p><p>
123      * To cause the pattern to repeat, pass the index into the timings array at which to start the
124      * repetition, or -1 to disable repeating.
125      * </p>
126      *
127      * @param timings The timing values of the timing / amplitude pairs. Timing values of 0
128      *                will cause the pair to be ignored.
129      * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values
130      *                   must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An
131      *                   amplitude value of 0 implies the motor is off.
132      * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
133      *               want to repeat.
134      *
135      * @return The desired effect.
136      */
createWaveform(long[] timings, int[] amplitudes, int repeat)137     public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
138         VibrationEffect effect = new Waveform(timings, amplitudes, repeat);
139         effect.validate();
140         return effect;
141     }
142 
143     /**
144      * Get a predefined vibration effect.
145      *
146      * Predefined effects are a set of common vibration effects that should be identical, regardless
147      * of the app they come from, in order to provide a cohesive experience for users across
148      * the entire device. They also may be custom tailored to the device hardware in order to
149      * provide a better experience than you could otherwise build using the generic building
150      * blocks.
151      *
152      * This will fallback to a generic pattern if one exists and there does not exist a
153      * hardware-specific implementation of the effect.
154      *
155      * @param effectId The ID of the effect to perform:
156      *                 {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
157      *
158      * @return The desired effect.
159      * @hide
160      */
get(int effectId)161     public static VibrationEffect get(int effectId) {
162         return get(effectId, true);
163     }
164 
165     /**
166      * Get a predefined vibration effect.
167      *
168      * Predefined effects are a set of common vibration effects that should be identical, regardless
169      * of the app they come from, in order to provide a cohesive experience for users across
170      * the entire device. They also may be custom tailored to the device hardware in order to
171      * provide a better experience than you could otherwise build using the generic building
172      * blocks.
173      *
174      * Some effects you may only want to play if there's a hardware specific implementation because
175      * they may, for example, be too disruptive to the user without tuning. The {@code fallback}
176      * parameter allows you to decide whether you want to fallback to the generic implementation or
177      * only play if there's a tuned, hardware specific one available.
178      *
179      * @param effectId The ID of the effect to perform:
180      *                 {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
181      * @param fallback Whether to fallback to a generic pattern if a hardware specific
182      *                 implementation doesn't exist.
183      *
184      * @return The desired effect.
185      * @hide
186      */
get(int effectId, boolean fallback)187     public static VibrationEffect get(int effectId, boolean fallback) {
188         VibrationEffect effect = new Prebaked(effectId, fallback);
189         effect.validate();
190         return effect;
191     }
192 
193     @Override
describeContents()194     public int describeContents() {
195         return 0;
196     }
197 
198     /** @hide */
validate()199     public abstract void validate();
200 
201     /** @hide */
202     public static class OneShot extends VibrationEffect implements Parcelable {
203         private long mTiming;
204         private int mAmplitude;
205 
OneShot(Parcel in)206         public OneShot(Parcel in) {
207             this(in.readLong(), in.readInt());
208         }
209 
OneShot(long milliseconds, int amplitude)210         public OneShot(long milliseconds, int amplitude) {
211             mTiming = milliseconds;
212             mAmplitude = amplitude;
213         }
214 
getTiming()215         public long getTiming() {
216             return mTiming;
217         }
218 
getAmplitude()219         public int getAmplitude() {
220             return mAmplitude;
221         }
222 
223         @Override
validate()224         public void validate() {
225             if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) {
226                 throw new IllegalArgumentException(
227                         "amplitude must either be DEFAULT_AMPLITUDE, " +
228                         "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")");
229             }
230             if (mTiming <= 0) {
231                 throw new IllegalArgumentException(
232                         "timing must be positive (timing=" + mTiming + ")");
233             }
234         }
235 
236         @Override
equals(Object o)237         public boolean equals(Object o) {
238             if (!(o instanceof VibrationEffect.OneShot)) {
239                 return false;
240             }
241             VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
242             return other.mTiming == mTiming && other.mAmplitude == mAmplitude;
243         }
244 
245         @Override
hashCode()246         public int hashCode() {
247             int result = 17;
248             result = 37 * (int) mTiming;
249             result = 37 * mAmplitude;
250             return result;
251         }
252 
253         @Override
toString()254         public String toString() {
255             return "OneShot{mTiming=" + mTiming +", mAmplitude=" + mAmplitude + "}";
256         }
257 
258         @Override
writeToParcel(Parcel out, int flags)259         public void writeToParcel(Parcel out, int flags) {
260             out.writeInt(PARCEL_TOKEN_ONE_SHOT);
261             out.writeLong(mTiming);
262             out.writeInt(mAmplitude);
263         }
264 
265         public static final Parcelable.Creator<OneShot> CREATOR =
266             new Parcelable.Creator<OneShot>() {
267                 @Override
268                 public OneShot createFromParcel(Parcel in) {
269                     // Skip the type token
270                     in.readInt();
271                     return new OneShot(in);
272                 }
273                 @Override
274                 public OneShot[] newArray(int size) {
275                     return new OneShot[size];
276                 }
277             };
278     }
279 
280     /** @hide */
281     public static class Waveform extends VibrationEffect implements Parcelable {
282         private long[] mTimings;
283         private int[] mAmplitudes;
284         private int mRepeat;
285 
Waveform(Parcel in)286         public Waveform(Parcel in) {
287             this(in.createLongArray(), in.createIntArray(), in.readInt());
288         }
289 
Waveform(long[] timings, int[] amplitudes, int repeat)290         public Waveform(long[] timings, int[] amplitudes, int repeat) {
291             mTimings = new long[timings.length];
292             System.arraycopy(timings, 0, mTimings, 0, timings.length);
293             mAmplitudes = new int[amplitudes.length];
294             System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length);
295             mRepeat = repeat;
296         }
297 
getTimings()298         public long[] getTimings() {
299             return mTimings;
300         }
301 
getAmplitudes()302         public int[] getAmplitudes() {
303             return mAmplitudes;
304         }
305 
getRepeatIndex()306         public int getRepeatIndex() {
307             return mRepeat;
308         }
309 
310         @Override
validate()311         public void validate() {
312             if (mTimings.length != mAmplitudes.length) {
313                 throw new IllegalArgumentException(
314                         "timing and amplitude arrays must be of equal length" +
315                         " (timings.length=" + mTimings.length +
316                         ", amplitudes.length=" + mAmplitudes.length + ")");
317             }
318             if (!hasNonZeroEntry(mTimings)) {
319                 throw new IllegalArgumentException("at least one timing must be non-zero" +
320                         " (timings=" + Arrays.toString(mTimings) + ")");
321             }
322             for (long timing : mTimings) {
323                 if (timing < 0) {
324                     throw new IllegalArgumentException("timings must all be >= 0" +
325                             " (timings=" + Arrays.toString(mTimings) + ")");
326                 }
327             }
328             for (int amplitude : mAmplitudes) {
329                 if (amplitude < -1 || amplitude > 255) {
330                     throw new IllegalArgumentException(
331                             "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255" +
332                             " (amplitudes=" + Arrays.toString(mAmplitudes) + ")");
333                 }
334             }
335             if (mRepeat < -1 || mRepeat >= mTimings.length) {
336                 throw new IllegalArgumentException(
337                         "repeat index must be within the bounds of the timings array" +
338                         " (timings.length=" + mTimings.length + ", index=" + mRepeat +")");
339             }
340         }
341 
342         @Override
equals(Object o)343         public boolean equals(Object o) {
344             if (!(o instanceof VibrationEffect.Waveform)) {
345                 return false;
346             }
347             VibrationEffect.Waveform other = (VibrationEffect.Waveform) o;
348             return Arrays.equals(mTimings, other.mTimings) &&
349                 Arrays.equals(mAmplitudes, other.mAmplitudes) &&
350                 mRepeat == other.mRepeat;
351         }
352 
353         @Override
hashCode()354         public int hashCode() {
355             int result = 17;
356             result = 37 * Arrays.hashCode(mTimings);
357             result = 37 * Arrays.hashCode(mAmplitudes);
358             result = 37 * mRepeat;
359             return result;
360         }
361 
362         @Override
toString()363         public String toString() {
364             return "Waveform{mTimings=" + Arrays.toString(mTimings) +
365                 ", mAmplitudes=" + Arrays.toString(mAmplitudes) +
366                 ", mRepeat=" + mRepeat +
367                 "}";
368         }
369 
370         @Override
writeToParcel(Parcel out, int flags)371         public void writeToParcel(Parcel out, int flags) {
372             out.writeInt(PARCEL_TOKEN_WAVEFORM);
373             out.writeLongArray(mTimings);
374             out.writeIntArray(mAmplitudes);
375             out.writeInt(mRepeat);
376         }
377 
hasNonZeroEntry(long[] vals)378         private static boolean hasNonZeroEntry(long[] vals) {
379             for (long val : vals) {
380                 if (val != 0) {
381                     return true;
382                 }
383             }
384             return false;
385         }
386 
387 
388         public static final Parcelable.Creator<Waveform> CREATOR =
389             new Parcelable.Creator<Waveform>() {
390                 @Override
391                 public Waveform createFromParcel(Parcel in) {
392                     // Skip the type token
393                     in.readInt();
394                     return new Waveform(in);
395                 }
396                 @Override
397                 public Waveform[] newArray(int size) {
398                     return new Waveform[size];
399                 }
400             };
401     }
402 
403     /** @hide */
404     public static class Prebaked extends VibrationEffect implements Parcelable {
405         private int mEffectId;
406         private boolean mFallback;
407 
Prebaked(Parcel in)408         public Prebaked(Parcel in) {
409             this(in.readInt(), in.readByte() != 0);
410         }
411 
Prebaked(int effectId, boolean fallback)412         public Prebaked(int effectId, boolean fallback) {
413             mEffectId = effectId;
414             mFallback = fallback;
415         }
416 
getId()417         public int getId() {
418             return mEffectId;
419         }
420 
421         /**
422          * Whether the effect should fall back to a generic pattern if there's no hardware specific
423          * implementation of it.
424          */
shouldFallback()425         public boolean shouldFallback() {
426             return mFallback;
427         }
428 
429         @Override
validate()430         public void validate() {
431             switch (mEffectId) {
432                 case EFFECT_CLICK:
433                 case EFFECT_DOUBLE_CLICK:
434                 case EFFECT_TICK:
435                     break;
436                 default:
437                     throw new IllegalArgumentException(
438                             "Unknown prebaked effect type (value=" + mEffectId + ")");
439             }
440         }
441 
442         @Override
equals(Object o)443         public boolean equals(Object o) {
444             if (!(o instanceof VibrationEffect.Prebaked)) {
445                 return false;
446             }
447             VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
448             return mEffectId == other.mEffectId && mFallback == other.mFallback;
449         }
450 
451         @Override
hashCode()452         public int hashCode() {
453             return mEffectId;
454         }
455 
456         @Override
toString()457         public String toString() {
458             return "Prebaked{mEffectId=" + mEffectId + ", mFallback=" + mFallback + "}";
459         }
460 
461 
462         @Override
writeToParcel(Parcel out, int flags)463         public void writeToParcel(Parcel out, int flags) {
464             out.writeInt(PARCEL_TOKEN_EFFECT);
465             out.writeInt(mEffectId);
466             out.writeByte((byte) (mFallback ? 1 : 0));
467         }
468 
469         public static final Parcelable.Creator<Prebaked> CREATOR =
470             new Parcelable.Creator<Prebaked>() {
471                 @Override
472                 public Prebaked createFromParcel(Parcel in) {
473                     // Skip the type token
474                     in.readInt();
475                     return new Prebaked(in);
476                 }
477                 @Override
478                 public Prebaked[] newArray(int size) {
479                     return new Prebaked[size];
480                 }
481             };
482     }
483 
484     public static final Parcelable.Creator<VibrationEffect> CREATOR =
485             new Parcelable.Creator<VibrationEffect>() {
486                 @Override
487                 public VibrationEffect createFromParcel(Parcel in) {
488                     int token = in.readInt();
489                     if (token == PARCEL_TOKEN_ONE_SHOT) {
490                         return new OneShot(in);
491                     } else if (token == PARCEL_TOKEN_WAVEFORM) {
492                         return new Waveform(in);
493                     } else if (token == PARCEL_TOKEN_EFFECT) {
494                         return new Prebaked(in);
495                     } else {
496                         throw new IllegalStateException(
497                                 "Unexpected vibration event type token in parcel.");
498                     }
499                 }
500                 @Override
501                 public VibrationEffect[] newArray(int size) {
502                     return new VibrationEffect[size];
503                 }
504             };
505 }
506