blob: eceaa31b9cf87f95a575988b9eeff81cfd37ed70 [file] [log] [blame]
Michael Wright71216972017-01-31 18:33:54 +00001/*
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
17package android.os;
18
19import android.hardware.vibrator.V1_0.Constants.Effect;
20
21import 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 */
28public 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.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.DOUBLE_CLICK;
53
54 /** @hide to prevent subclassing from outside of the framework */
55 public VibrationEffect() { }
56
57 /**
58 * Create a one shot vibration.
59 *
60 * One shot vibrations will vibrate constantly for the specified period of time at the
61 * specified amplitude, and then stop.
62 *
63 * @param milliseconds The number of milliseconds to vibrate. This must be a positive number.
64 * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or
65 * {@link #DEFAULT_AMPLITUDE}.
66 *
67 * @return The desired effect.
68 */
69 public static VibrationEffect createOneShot(long milliseconds, int amplitude) {
70 VibrationEffect effect = new OneShot(milliseconds, amplitude);
71 effect.validate();
72 return effect;
73 }
74
75 /**
76 * Create a waveform vibration.
77 *
78 * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
79 * each pair, the value in the amplitude array determines the strength of the vibration and the
80 * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
81 * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
82 * <p>
83 * The amplitude array of the generated waveform will be the same size as the given
84 * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE},
85 * starting with 0. Therefore the first timing value will be the period to wait before turning
86 * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE}
87 * strength, etc.
88 * </p><p>
89 * To cause the pattern to repeat, pass the index into the timings array at which to start the
90 * repetition, or -1 to disable repeating.
91 * </p>
92 *
93 * @param timings The pattern of alternating on-off timings, starting with off. Timing values
94 * of 0 will cause the timing / amplitude pair to be ignored.
95 * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
96 * want to repeat.
97 *
98 * @return The desired effect.
99 */
100 public static VibrationEffect createWaveform(long[] timings, int repeat) {
101 int[] amplitudes = new int[timings.length];
102 for (int i = 0; i < (timings.length / 2); i++) {
103 amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE;
104 }
105 return createWaveform(timings, amplitudes, repeat);
106 }
107
108 /**
109 * Create a waveform vibration.
110 *
111 * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
112 * each pair, the value in the amplitude array determines the strength of the vibration and the
113 * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
114 * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
115 * </p><p>
116 * To cause the pattern to repeat, pass the index into the timings array at which to start the
117 * repetition, or -1 to disable repeating.
118 * </p>
119 *
120 * @param timings The timing values of the timing / amplitude pairs. Timing values of 0
121 * will cause the pair to be ignored.
122 * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values
123 * must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An
124 * amplitude value of 0 implies the motor is off.
125 * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
126 * want to repeat.
127 *
128 * @return The desired effect.
129 */
130 public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
131 VibrationEffect effect = new Waveform(timings, amplitudes, repeat);
132 effect.validate();
133 return effect;
134 }
135
136 /**
137 * Get a predefined vibration effect.
138 *
139 * Predefined effects are a set of common vibration effects that should be identical, regardless
140 * of the app they come from, in order to provide a cohesive experience for users across
141 * the entire device. They also may be custom tailored to the device hardware in order to
142 * provide a better experience than you could otherwise build using the generic building
143 * blocks.
144 *
145 * @param effectId The ID of the effect to perform:
146 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}.
147 *
148 * @return The desired effect.
149 * @hide
150 */
151 public static VibrationEffect get(int effectId) {
152 VibrationEffect effect = new Prebaked(effectId);
153 effect.validate();
154 return effect;
155 }
156
157 @Override
158 public int describeContents() {
159 return 0;
160 }
161
162 /** @hide */
163 public abstract void validate();
164
165 /** @hide */
166 public static class OneShot extends VibrationEffect implements Parcelable {
167 private long mTiming;
168 private int mAmplitude;
169
170 public OneShot(Parcel in) {
171 this(in.readLong(), in.readInt());
172 }
173
174 public OneShot(long milliseconds, int amplitude) {
175 mTiming = milliseconds;
176 mAmplitude = amplitude;
177 }
178
179 public long getTiming() {
180 return mTiming;
181 }
182
183 public int getAmplitude() {
184 return mAmplitude;
185 }
186
187 @Override
188 public void validate() {
189 if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) {
190 throw new IllegalArgumentException(
191 "amplitude must either be DEFAULT_AMPLITUDE, " +
192 "or between 1 and 255 inclusive");
193 }
194 if (mTiming <= 0) {
195 throw new IllegalArgumentException("timing must be positive");
196 }
197 }
198
199 @Override
200 public boolean equals(Object o) {
201 if (!(o instanceof VibrationEffect.OneShot)) {
202 return false;
203 }
204 VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
205 return other.mTiming == mTiming && other.mAmplitude == mAmplitude;
206 }
207
208 @Override
209 public int hashCode() {
210 int result = 17;
211 result = 37 * (int) mTiming;
212 result = 37 * mAmplitude;
213 return result;
214 }
215
216 @Override
217 public String toString() {
218 return "OneShot{mTiming=" + mTiming +", mAmplitude=" + mAmplitude + "}";
219 }
220
221 @Override
222 public void writeToParcel(Parcel out, int flags) {
223 out.writeInt(PARCEL_TOKEN_ONE_SHOT);
224 out.writeLong(mTiming);
225 out.writeInt(mAmplitude);
226 }
227
228 public static final Parcelable.Creator<OneShot> CREATOR =
229 new Parcelable.Creator<OneShot>() {
230 @Override
231 public OneShot createFromParcel(Parcel in) {
232 // Skip the type token
233 in.readInt();
234 return new OneShot(in);
235 }
236 @Override
237 public OneShot[] newArray(int size) {
238 return new OneShot[size];
239 }
240 };
241 }
242
243 /** @hide */
244 public static class Waveform extends VibrationEffect implements Parcelable {
245 private long[] mTimings;
246 private int[] mAmplitudes;
247 private int mRepeat;
248
249 public Waveform(Parcel in) {
250 this(in.createLongArray(), in.createIntArray(), in.readInt());
251 }
252
253 public Waveform(long[] timings, int[] amplitudes, int repeat) {
254 mTimings = new long[timings.length];
255 System.arraycopy(timings, 0, mTimings, 0, timings.length);
256 mAmplitudes = new int[amplitudes.length];
257 System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length);
258 mRepeat = repeat;
259 }
260
261 public long[] getTimings() {
262 return mTimings;
263 }
264
265 public int[] getAmplitudes() {
266 return mAmplitudes;
267 }
268
269 public int getRepeatIndex() {
270 return mRepeat;
271 }
272
273 @Override
274 public void validate() {
275 if (mTimings.length != mAmplitudes.length) {
276 throw new IllegalArgumentException(
277 "timing and amplitude arrays must be of equal length");
278 }
279 if (!hasNonZeroEntry(mTimings)) {
280 throw new IllegalArgumentException("at least one timing must be non-zero");
281 }
282 for (long timing : mTimings) {
283 if (timing < 0) {
284 throw new IllegalArgumentException("timings must all be >= 0");
285 }
286 }
287 for (int amplitude : mAmplitudes) {
288 if (amplitude < -1 || amplitude > 255) {
289 throw new IllegalArgumentException(
290 "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255");
291 }
292 }
293 if (mRepeat < -1 || mRepeat >= mTimings.length) {
294 throw new IllegalArgumentException("repeat index must be >= -1");
295 }
296 }
297
298 @Override
299 public boolean equals(Object o) {
300 if (!(o instanceof VibrationEffect.Waveform)) {
301 return false;
302 }
303 VibrationEffect.Waveform other = (VibrationEffect.Waveform) o;
304 return Arrays.equals(mTimings, other.mTimings) &&
305 Arrays.equals(mAmplitudes, other.mAmplitudes) &&
306 mRepeat == other.mRepeat;
307 }
308
309 @Override
310 public int hashCode() {
311 int result = 17;
312 result = 37 * Arrays.hashCode(mTimings);
313 result = 37 * Arrays.hashCode(mAmplitudes);
314 result = 37 * mRepeat;
315 return result;
316 }
317
318 @Override
319 public String toString() {
320 return "Waveform{mTimings=" + Arrays.toString(mTimings) +
321 ", mAmplitudes=" + Arrays.toString(mAmplitudes) +
322 ", mRepeat=" + mRepeat +
323 "}";
324 }
325
326 @Override
327 public void writeToParcel(Parcel out, int flags) {
328 out.writeInt(PARCEL_TOKEN_WAVEFORM);
329 out.writeLongArray(mTimings);
330 out.writeIntArray(mAmplitudes);
331 out.writeInt(mRepeat);
332 }
333
334 private static boolean hasNonZeroEntry(long[] vals) {
335 for (long val : vals) {
336 if (val != 0) {
337 return true;
338 }
339 }
340 return false;
341 }
342
343
344 public static final Parcelable.Creator<Waveform> CREATOR =
345 new Parcelable.Creator<Waveform>() {
346 @Override
347 public Waveform createFromParcel(Parcel in) {
348 // Skip the type token
349 in.readInt();
350 return new Waveform(in);
351 }
352 @Override
353 public Waveform[] newArray(int size) {
354 return new Waveform[size];
355 }
356 };
357 }
358
359 /** @hide */
360 public static class Prebaked extends VibrationEffect implements Parcelable {
361 private int mEffectId;
362
363 public Prebaked(Parcel in) {
364 this(in.readInt());
365 }
366
367 public Prebaked(int effectId) {
368 mEffectId = effectId;
369 }
370
371 public int getId() {
372 return mEffectId;
373 }
374
375 @Override
376 public void validate() {
377 if (mEffectId != EFFECT_CLICK) {
378 throw new IllegalArgumentException("Unknown prebaked effect type");
379 }
380 }
381
382 @Override
383 public boolean equals(Object o) {
384 if (!(o instanceof VibrationEffect.Prebaked)) {
385 return false;
386 }
387 VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
388 return mEffectId == other.mEffectId;
389 }
390
391 @Override
392 public int hashCode() {
393 return mEffectId;
394 }
395
396 @Override
397 public String toString() {
398 return "Prebaked{mEffectId=" + mEffectId + "}";
399 }
400
401
402 @Override
403 public void writeToParcel(Parcel out, int flags) {
404 out.writeInt(PARCEL_TOKEN_EFFECT);
405 out.writeInt(mEffectId);
406 }
407
408 public static final Parcelable.Creator<Prebaked> CREATOR =
409 new Parcelable.Creator<Prebaked>() {
410 @Override
411 public Prebaked createFromParcel(Parcel in) {
412 // Skip the type token
413 in.readInt();
414 return new Prebaked(in);
415 }
416 @Override
417 public Prebaked[] newArray(int size) {
418 return new Prebaked[size];
419 }
420 };
421 }
422
423 public static final Parcelable.Creator<VibrationEffect> CREATOR =
424 new Parcelable.Creator<VibrationEffect>() {
425 @Override
426 public VibrationEffect createFromParcel(Parcel in) {
427 int token = in.readInt();
428 if (token == PARCEL_TOKEN_ONE_SHOT) {
429 return new OneShot(in);
430 } else if (token == PARCEL_TOKEN_WAVEFORM) {
431 return new Waveform(in);
432 } else if (token == PARCEL_TOKEN_EFFECT) {
433 return new Prebaked(in);
434 } else {
435 throw new IllegalStateException(
436 "Unexpected vibration event type token in parcel.");
437 }
438 }
439 @Override
440 public VibrationEffect[] newArray(int size) {
441 return new VibrationEffect[size];
442 }
443 };
444}