blob: 6aa601a98f3bb71a2e98afc9af9bd7d39adec2b4 [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, " +
Michael Wright0ef6edd2017-04-05 20:57:39 +0100192 "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")");
Michael Wright71216972017-01-31 18:33:54 +0000193 }
194 if (mTiming <= 0) {
Michael Wright0ef6edd2017-04-05 20:57:39 +0100195 throw new IllegalArgumentException(
196 "timing must be positive (timing=" + mTiming + ")");
Michael Wright71216972017-01-31 18:33:54 +0000197 }
198 }
199
200 @Override
201 public boolean equals(Object o) {
202 if (!(o instanceof VibrationEffect.OneShot)) {
203 return false;
204 }
205 VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
206 return other.mTiming == mTiming && other.mAmplitude == mAmplitude;
207 }
208
209 @Override
210 public int hashCode() {
211 int result = 17;
212 result = 37 * (int) mTiming;
213 result = 37 * mAmplitude;
214 return result;
215 }
216
217 @Override
218 public String toString() {
219 return "OneShot{mTiming=" + mTiming +", mAmplitude=" + mAmplitude + "}";
220 }
221
222 @Override
223 public void writeToParcel(Parcel out, int flags) {
224 out.writeInt(PARCEL_TOKEN_ONE_SHOT);
225 out.writeLong(mTiming);
226 out.writeInt(mAmplitude);
227 }
228
229 public static final Parcelable.Creator<OneShot> CREATOR =
230 new Parcelable.Creator<OneShot>() {
231 @Override
232 public OneShot createFromParcel(Parcel in) {
233 // Skip the type token
234 in.readInt();
235 return new OneShot(in);
236 }
237 @Override
238 public OneShot[] newArray(int size) {
239 return new OneShot[size];
240 }
241 };
242 }
243
244 /** @hide */
245 public static class Waveform extends VibrationEffect implements Parcelable {
246 private long[] mTimings;
247 private int[] mAmplitudes;
248 private int mRepeat;
249
250 public Waveform(Parcel in) {
251 this(in.createLongArray(), in.createIntArray(), in.readInt());
252 }
253
254 public Waveform(long[] timings, int[] amplitudes, int repeat) {
255 mTimings = new long[timings.length];
256 System.arraycopy(timings, 0, mTimings, 0, timings.length);
257 mAmplitudes = new int[amplitudes.length];
258 System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length);
259 mRepeat = repeat;
260 }
261
262 public long[] getTimings() {
263 return mTimings;
264 }
265
266 public int[] getAmplitudes() {
267 return mAmplitudes;
268 }
269
270 public int getRepeatIndex() {
271 return mRepeat;
272 }
273
274 @Override
275 public void validate() {
276 if (mTimings.length != mAmplitudes.length) {
277 throw new IllegalArgumentException(
Michael Wright0ef6edd2017-04-05 20:57:39 +0100278 "timing and amplitude arrays must be of equal length" +
279 " (timings.length=" + mTimings.length +
280 ", amplitudes.length=" + mAmplitudes.length + ")");
Michael Wright71216972017-01-31 18:33:54 +0000281 }
282 if (!hasNonZeroEntry(mTimings)) {
Michael Wright0ef6edd2017-04-05 20:57:39 +0100283 throw new IllegalArgumentException("at least one timing must be non-zero" +
284 " (timings=" + Arrays.toString(mTimings) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000285 }
286 for (long timing : mTimings) {
287 if (timing < 0) {
Michael Wright0ef6edd2017-04-05 20:57:39 +0100288 throw new IllegalArgumentException("timings must all be >= 0" +
289 " (timings=" + Arrays.toString(mTimings) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000290 }
291 }
292 for (int amplitude : mAmplitudes) {
293 if (amplitude < -1 || amplitude > 255) {
294 throw new IllegalArgumentException(
Michael Wright0ef6edd2017-04-05 20:57:39 +0100295 "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255" +
296 " (amplitudes=" + Arrays.toString(mAmplitudes) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000297 }
298 }
299 if (mRepeat < -1 || mRepeat >= mTimings.length) {
Michael Wright0ef6edd2017-04-05 20:57:39 +0100300 throw new IllegalArgumentException(
301 "repeat index must be within the bounds of the timings array" +
302 " (timings.length=" + mTimings.length + ", index=" + mRepeat +")");
Michael Wright71216972017-01-31 18:33:54 +0000303 }
304 }
305
306 @Override
307 public boolean equals(Object o) {
308 if (!(o instanceof VibrationEffect.Waveform)) {
309 return false;
310 }
311 VibrationEffect.Waveform other = (VibrationEffect.Waveform) o;
312 return Arrays.equals(mTimings, other.mTimings) &&
313 Arrays.equals(mAmplitudes, other.mAmplitudes) &&
314 mRepeat == other.mRepeat;
315 }
316
317 @Override
318 public int hashCode() {
319 int result = 17;
320 result = 37 * Arrays.hashCode(mTimings);
321 result = 37 * Arrays.hashCode(mAmplitudes);
322 result = 37 * mRepeat;
323 return result;
324 }
325
326 @Override
327 public String toString() {
328 return "Waveform{mTimings=" + Arrays.toString(mTimings) +
329 ", mAmplitudes=" + Arrays.toString(mAmplitudes) +
330 ", mRepeat=" + mRepeat +
331 "}";
332 }
333
334 @Override
335 public void writeToParcel(Parcel out, int flags) {
336 out.writeInt(PARCEL_TOKEN_WAVEFORM);
337 out.writeLongArray(mTimings);
338 out.writeIntArray(mAmplitudes);
339 out.writeInt(mRepeat);
340 }
341
342 private static boolean hasNonZeroEntry(long[] vals) {
343 for (long val : vals) {
344 if (val != 0) {
345 return true;
346 }
347 }
348 return false;
349 }
350
351
352 public static final Parcelable.Creator<Waveform> CREATOR =
353 new Parcelable.Creator<Waveform>() {
354 @Override
355 public Waveform createFromParcel(Parcel in) {
356 // Skip the type token
357 in.readInt();
358 return new Waveform(in);
359 }
360 @Override
361 public Waveform[] newArray(int size) {
362 return new Waveform[size];
363 }
364 };
365 }
366
367 /** @hide */
368 public static class Prebaked extends VibrationEffect implements Parcelable {
369 private int mEffectId;
370
371 public Prebaked(Parcel in) {
372 this(in.readInt());
373 }
374
375 public Prebaked(int effectId) {
376 mEffectId = effectId;
377 }
378
379 public int getId() {
380 return mEffectId;
381 }
382
383 @Override
384 public void validate() {
385 if (mEffectId != EFFECT_CLICK) {
Michael Wright0ef6edd2017-04-05 20:57:39 +0100386 throw new IllegalArgumentException(
387 "Unknown prebaked effect type (value=" + mEffectId + ")");
Michael Wright71216972017-01-31 18:33:54 +0000388 }
389 }
390
391 @Override
392 public boolean equals(Object o) {
393 if (!(o instanceof VibrationEffect.Prebaked)) {
394 return false;
395 }
396 VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
397 return mEffectId == other.mEffectId;
398 }
399
400 @Override
401 public int hashCode() {
402 return mEffectId;
403 }
404
405 @Override
406 public String toString() {
407 return "Prebaked{mEffectId=" + mEffectId + "}";
408 }
409
410
411 @Override
412 public void writeToParcel(Parcel out, int flags) {
413 out.writeInt(PARCEL_TOKEN_EFFECT);
414 out.writeInt(mEffectId);
415 }
416
417 public static final Parcelable.Creator<Prebaked> CREATOR =
418 new Parcelable.Creator<Prebaked>() {
419 @Override
420 public Prebaked createFromParcel(Parcel in) {
421 // Skip the type token
422 in.readInt();
423 return new Prebaked(in);
424 }
425 @Override
426 public Prebaked[] newArray(int size) {
427 return new Prebaked[size];
428 }
429 };
430 }
431
432 public static final Parcelable.Creator<VibrationEffect> CREATOR =
433 new Parcelable.Creator<VibrationEffect>() {
434 @Override
435 public VibrationEffect createFromParcel(Parcel in) {
436 int token = in.readInt();
437 if (token == PARCEL_TOKEN_ONE_SHOT) {
438 return new OneShot(in);
439 } else if (token == PARCEL_TOKEN_WAVEFORM) {
440 return new Waveform(in);
441 } else if (token == PARCEL_TOKEN_EFFECT) {
442 return new Prebaked(in);
443 } else {
444 throw new IllegalStateException(
445 "Unexpected vibration event type token in parcel.");
446 }
447 }
448 @Override
449 public VibrationEffect[] newArray(int size) {
450 return new VibrationEffect[size];
451 }
452 };
453}