blob: 91c69fb07f773508249acea9e14879856bde5266 [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
Michael Wrightf268bf52018-02-07 23:23:34 +000019import android.annotation.Nullable;
Alexey Kuzmin505872d2018-03-19 19:27:46 +000020import android.annotation.TestApi;
Michael Wright32fa5932018-05-18 18:07:09 -070021import android.content.ContentResolver;
Michael Wrightf268bf52018-02-07 23:23:34 +000022import android.content.Context;
23import android.hardware.vibrator.V1_0.EffectStrength;
24import android.hardware.vibrator.V1_2.Effect;
25import android.net.Uri;
Michael Wright35a0c672018-01-24 00:32:53 +000026import android.util.MathUtils;
Michael Wright71216972017-01-31 18:33:54 +000027
28import java.util.Arrays;
29
30/**
31 * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
32 *
33 * These effects may be any number of things, from single shot vibrations to complex waveforms.
34 */
35public abstract class VibrationEffect implements Parcelable {
36 private static final int PARCEL_TOKEN_ONE_SHOT = 1;
37 private static final int PARCEL_TOKEN_WAVEFORM = 2;
38 private static final int PARCEL_TOKEN_EFFECT = 3;
39
40 /**
41 * The default vibration strength of the device.
42 */
43 public static final int DEFAULT_AMPLITUDE = -1;
44
45 /**
Michael Wright35a0c672018-01-24 00:32:53 +000046 * The maximum amplitude value
47 * @hide
48 */
49 public static final int MAX_AMPLITUDE = 255;
50
51 /**
Michael Wright71216972017-01-31 18:33:54 +000052 * A click effect.
53 *
54 * @see #get(int)
55 * @hide
56 */
Michael Wrightf268bf52018-02-07 23:23:34 +000057 public static final int EFFECT_CLICK = Effect.CLICK;
Michael Wright71216972017-01-31 18:33:54 +000058
59 /**
60 * A double click effect.
61 *
62 * @see #get(int)
63 * @hide
64 */
Michael Wrightf268bf52018-02-07 23:23:34 +000065 public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK;
Michael Wright57d94d92017-05-31 14:44:45 +010066
67 /**
68 * A tick effect.
69 * @see #get(int)
70 * @hide
71 */
Michael Wrightf268bf52018-02-07 23:23:34 +000072 public static final int EFFECT_TICK = Effect.TICK;
73
74 /**
75 * A thud effect.
76 * @see #get(int)
77 * @hide
78 */
79 public static final int EFFECT_THUD = Effect.THUD;
80
81 /**
82 * A pop effect.
83 * @see #get(int)
84 * @hide
85 */
86 public static final int EFFECT_POP = Effect.POP;
87
88 /**
89 * A heavy click effect.
90 * @see #get(int)
91 * @hide
92 */
93 public static final int EFFECT_HEAVY_CLICK = Effect.HEAVY_CLICK;
94
95
96 /**
97 * Ringtone patterns. They may correspond with the device's ringtone audio, or may just be a
98 * pattern that can be played as a ringtone with any audio, depending on the device.
99 *
100 * @see #get(Uri, Context)
101 * @hide
102 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000103 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +0000104 public static final int[] RINGTONES = {
105 Effect.RINGTONE_1,
106 Effect.RINGTONE_2,
107 Effect.RINGTONE_3,
108 Effect.RINGTONE_4,
109 Effect.RINGTONE_5,
110 Effect.RINGTONE_6,
111 Effect.RINGTONE_7,
112 Effect.RINGTONE_8,
113 Effect.RINGTONE_9,
114 Effect.RINGTONE_10,
115 Effect.RINGTONE_11,
116 Effect.RINGTONE_12,
117 Effect.RINGTONE_13,
118 Effect.RINGTONE_14,
119 Effect.RINGTONE_15
120 };
Michael Wright71216972017-01-31 18:33:54 +0000121
122 /** @hide to prevent subclassing from outside of the framework */
123 public VibrationEffect() { }
124
125 /**
126 * Create a one shot vibration.
127 *
128 * One shot vibrations will vibrate constantly for the specified period of time at the
129 * specified amplitude, and then stop.
130 *
131 * @param milliseconds The number of milliseconds to vibrate. This must be a positive number.
132 * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or
133 * {@link #DEFAULT_AMPLITUDE}.
134 *
135 * @return The desired effect.
136 */
137 public static VibrationEffect createOneShot(long milliseconds, int amplitude) {
138 VibrationEffect effect = new OneShot(milliseconds, amplitude);
139 effect.validate();
140 return effect;
141 }
142
143 /**
144 * Create a waveform vibration.
145 *
146 * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
147 * each pair, the value in the amplitude array determines the strength of the vibration and the
148 * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
149 * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
150 * <p>
151 * The amplitude array of the generated waveform will be the same size as the given
152 * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE},
153 * starting with 0. Therefore the first timing value will be the period to wait before turning
154 * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE}
155 * strength, etc.
156 * </p><p>
157 * To cause the pattern to repeat, pass the index into the timings array at which to start the
158 * repetition, or -1 to disable repeating.
159 * </p>
160 *
161 * @param timings The pattern of alternating on-off timings, starting with off. Timing values
162 * of 0 will cause the timing / amplitude pair to be ignored.
163 * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
164 * want to repeat.
165 *
166 * @return The desired effect.
167 */
168 public static VibrationEffect createWaveform(long[] timings, int repeat) {
169 int[] amplitudes = new int[timings.length];
170 for (int i = 0; i < (timings.length / 2); i++) {
171 amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE;
172 }
173 return createWaveform(timings, amplitudes, repeat);
174 }
175
176 /**
177 * Create a waveform vibration.
178 *
179 * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
180 * each pair, the value in the amplitude array determines the strength of the vibration and the
181 * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
182 * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
183 * </p><p>
184 * To cause the pattern to repeat, pass the index into the timings array at which to start the
185 * repetition, or -1 to disable repeating.
186 * </p>
187 *
188 * @param timings The timing values of the timing / amplitude pairs. Timing values of 0
189 * will cause the pair to be ignored.
190 * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values
191 * must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An
192 * amplitude value of 0 implies the motor is off.
193 * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
194 * want to repeat.
195 *
196 * @return The desired effect.
197 */
198 public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
199 VibrationEffect effect = new Waveform(timings, amplitudes, repeat);
200 effect.validate();
201 return effect;
202 }
203
204 /**
205 * Get a predefined vibration effect.
206 *
207 * Predefined effects are a set of common vibration effects that should be identical, regardless
208 * of the app they come from, in order to provide a cohesive experience for users across
209 * the entire device. They also may be custom tailored to the device hardware in order to
210 * provide a better experience than you could otherwise build using the generic building
211 * blocks.
212 *
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100213 * This will fallback to a generic pattern if one exists and there does not exist a
214 * hardware-specific implementation of the effect.
215 *
Michael Wright71216972017-01-31 18:33:54 +0000216 * @param effectId The ID of the effect to perform:
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100217 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
Michael Wright71216972017-01-31 18:33:54 +0000218 *
219 * @return The desired effect.
220 * @hide
221 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000222 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000223 public static VibrationEffect get(int effectId) {
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100224 return get(effectId, true);
225 }
226
227 /**
228 * Get a predefined vibration effect.
229 *
230 * Predefined effects are a set of common vibration effects that should be identical, regardless
231 * of the app they come from, in order to provide a cohesive experience for users across
232 * the entire device. They also may be custom tailored to the device hardware in order to
233 * provide a better experience than you could otherwise build using the generic building
234 * blocks.
235 *
236 * Some effects you may only want to play if there's a hardware specific implementation because
237 * they may, for example, be too disruptive to the user without tuning. The {@code fallback}
238 * parameter allows you to decide whether you want to fallback to the generic implementation or
239 * only play if there's a tuned, hardware specific one available.
240 *
241 * @param effectId The ID of the effect to perform:
242 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
243 * @param fallback Whether to fallback to a generic pattern if a hardware specific
244 * implementation doesn't exist.
245 *
246 * @return The desired effect.
247 * @hide
248 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000249 @TestApi
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100250 public static VibrationEffect get(int effectId, boolean fallback) {
251 VibrationEffect effect = new Prebaked(effectId, fallback);
Michael Wright71216972017-01-31 18:33:54 +0000252 effect.validate();
253 return effect;
254 }
255
Michael Wrightf268bf52018-02-07 23:23:34 +0000256 /**
257 * Get a predefined vibration effect associated with a given URI.
258 *
259 * Predefined effects are a set of common vibration effects that should be identical, regardless
260 * of the app they come from, in order to provide a cohesive experience for users across
261 * the entire device. They also may be custom tailored to the device hardware in order to
262 * provide a better experience than you could otherwise build using the generic building
263 * blocks.
264 *
265 * @param uri The URI associated with the haptic effect.
266 * @param context The context used to get the URI to haptic effect association.
267 *
268 * @return The desired effect, or {@code null} if there's no associated effect.
269 *
270 * @hide
271 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000272 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +0000273 @Nullable
274 public static VibrationEffect get(Uri uri, Context context) {
275 String[] uris = context.getResources().getStringArray(
276 com.android.internal.R.array.config_ringtoneEffectUris);
277 for (int i = 0; i < uris.length && i < RINGTONES.length; i++) {
278 if (uris[i] == null) {
279 continue;
280 }
Michael Wright32fa5932018-05-18 18:07:09 -0700281 ContentResolver cr = context.getContentResolver();
282 Uri mappedUri = cr.uncanonicalize(Uri.parse(uris[i]));
283 if (mappedUri == null) {
284 continue;
285 }
286 if (mappedUri.equals(uri)) {
Michael Wrightf268bf52018-02-07 23:23:34 +0000287 return get(RINGTONES[i]);
288 }
289 }
290 return null;
291 }
292
Michael Wright71216972017-01-31 18:33:54 +0000293 @Override
294 public int describeContents() {
295 return 0;
296 }
297
298 /** @hide */
299 public abstract void validate();
300
Michael Wright35a0c672018-01-24 00:32:53 +0000301 /**
302 * Gets the estimated duration of the vibration in milliseconds.
303 *
304 * For effects without a defined end (e.g. a Waveform with a non-negative repeat index), this
305 * returns Long.MAX_VALUE. For effects with an unknown duration (e.g. Prebaked effects where
306 * the length is device and potentially run-time dependent), this returns -1.
307 *
308 * @hide
309 */
310 public abstract long getDuration();
311
312 /**
313 * Scale the amplitude with the given constraints.
314 *
315 * This assumes that the previous value was in the range [0, MAX_AMPLITUDE]
316 * @hide
317 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000318 @TestApi
Michael Wright35a0c672018-01-24 00:32:53 +0000319 protected static int scale(int amplitude, float gamma, int maxAmplitude) {
320 float val = MathUtils.pow(amplitude / (float) MAX_AMPLITUDE, gamma);
321 return (int) (val * maxAmplitude);
322 }
323
Michael Wright71216972017-01-31 18:33:54 +0000324 /** @hide */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000325 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000326 public static class OneShot extends VibrationEffect implements Parcelable {
Michael Wright35a0c672018-01-24 00:32:53 +0000327 private final long mDuration;
328 private final int mAmplitude;
Michael Wright71216972017-01-31 18:33:54 +0000329
330 public OneShot(Parcel in) {
Michael Wright35a0c672018-01-24 00:32:53 +0000331 mDuration = in.readLong();
332 mAmplitude = in.readInt();
Michael Wright71216972017-01-31 18:33:54 +0000333 }
334
335 public OneShot(long milliseconds, int amplitude) {
Michael Wright35a0c672018-01-24 00:32:53 +0000336 mDuration = milliseconds;
Michael Wright71216972017-01-31 18:33:54 +0000337 mAmplitude = amplitude;
338 }
339
Michael Wright35a0c672018-01-24 00:32:53 +0000340 @Override
341 public long getDuration() {
342 return mDuration;
Michael Wright71216972017-01-31 18:33:54 +0000343 }
344
345 public int getAmplitude() {
346 return mAmplitude;
347 }
348
Michael Wright35a0c672018-01-24 00:32:53 +0000349 /**
350 * Scale the amplitude of this effect.
351 *
352 * @param gamma the gamma adjustment to apply
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000353 * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and
354 * MAX_AMPLITUDE
355 * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE
Michael Wright35a0c672018-01-24 00:32:53 +0000356 *
357 * @return A {@link OneShot} effect with the same timing but scaled amplitude.
358 */
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000359 public OneShot scale(float gamma, int maxAmplitude) {
360 if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) {
361 throw new IllegalArgumentException(
362 "Amplitude is negative or greater than MAX_AMPLITUDE");
363 }
Michael Wright35a0c672018-01-24 00:32:53 +0000364 int newAmplitude = scale(mAmplitude, gamma, maxAmplitude);
365 return new OneShot(mDuration, newAmplitude);
366 }
367
Alexey Kuzmin55bdc592018-04-24 12:58:13 +0100368 /**
369 * Resolve default values into integer amplitude numbers.
370 *
371 * @param defaultAmplitude the default amplitude to apply, must be between 0 and
372 * MAX_AMPLITUDE
373 * @return A {@link OneShot} effect with same physical meaning but explicitly set amplitude
374 *
375 * @hide
376 */
377 public OneShot resolve(int defaultAmplitude) {
378 if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
379 throw new IllegalArgumentException(
380 "Amplitude is negative or greater than MAX_AMPLITUDE");
381 }
382 if (mAmplitude == DEFAULT_AMPLITUDE) {
383 return new OneShot(mDuration, defaultAmplitude);
384 }
385 return this;
386 }
387
Michael Wright71216972017-01-31 18:33:54 +0000388 @Override
389 public void validate() {
390 if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) {
391 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000392 "amplitude must either be DEFAULT_AMPLITUDE, "
393 + "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")");
Michael Wright71216972017-01-31 18:33:54 +0000394 }
Michael Wright35a0c672018-01-24 00:32:53 +0000395 if (mDuration <= 0) {
Michael Wright0ef6edd2017-04-05 20:57:39 +0100396 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000397 "duration must be positive (duration=" + mDuration + ")");
Michael Wright71216972017-01-31 18:33:54 +0000398 }
399 }
400
401 @Override
402 public boolean equals(Object o) {
403 if (!(o instanceof VibrationEffect.OneShot)) {
404 return false;
405 }
406 VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
Michael Wright35a0c672018-01-24 00:32:53 +0000407 return other.mDuration == mDuration && other.mAmplitude == mAmplitude;
Michael Wright71216972017-01-31 18:33:54 +0000408 }
409
410 @Override
411 public int hashCode() {
412 int result = 17;
Michael Wright35a0c672018-01-24 00:32:53 +0000413 result += 37 * (int) mDuration;
414 result += 37 * mAmplitude;
Michael Wright71216972017-01-31 18:33:54 +0000415 return result;
416 }
417
418 @Override
419 public String toString() {
Michael Wright35a0c672018-01-24 00:32:53 +0000420 return "OneShot{mDuration=" + mDuration + ", mAmplitude=" + mAmplitude + "}";
Michael Wright71216972017-01-31 18:33:54 +0000421 }
422
423 @Override
424 public void writeToParcel(Parcel out, int flags) {
425 out.writeInt(PARCEL_TOKEN_ONE_SHOT);
Michael Wright35a0c672018-01-24 00:32:53 +0000426 out.writeLong(mDuration);
Michael Wright71216972017-01-31 18:33:54 +0000427 out.writeInt(mAmplitude);
428 }
429
430 public static final Parcelable.Creator<OneShot> CREATOR =
431 new Parcelable.Creator<OneShot>() {
432 @Override
433 public OneShot createFromParcel(Parcel in) {
434 // Skip the type token
435 in.readInt();
436 return new OneShot(in);
437 }
438 @Override
439 public OneShot[] newArray(int size) {
440 return new OneShot[size];
441 }
442 };
443 }
444
445 /** @hide */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000446 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000447 public static class Waveform extends VibrationEffect implements Parcelable {
Michael Wright35a0c672018-01-24 00:32:53 +0000448 private final long[] mTimings;
449 private final int[] mAmplitudes;
450 private final int mRepeat;
Michael Wright71216972017-01-31 18:33:54 +0000451
452 public Waveform(Parcel in) {
453 this(in.createLongArray(), in.createIntArray(), in.readInt());
454 }
455
456 public Waveform(long[] timings, int[] amplitudes, int repeat) {
457 mTimings = new long[timings.length];
458 System.arraycopy(timings, 0, mTimings, 0, timings.length);
459 mAmplitudes = new int[amplitudes.length];
460 System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length);
461 mRepeat = repeat;
462 }
463
464 public long[] getTimings() {
465 return mTimings;
466 }
467
468 public int[] getAmplitudes() {
469 return mAmplitudes;
470 }
471
472 public int getRepeatIndex() {
473 return mRepeat;
474 }
475
476 @Override
Michael Wright35a0c672018-01-24 00:32:53 +0000477 public long getDuration() {
478 if (mRepeat >= 0) {
479 return Long.MAX_VALUE;
480 }
481 long duration = 0;
482 for (long d : mTimings) {
483 duration += d;
484 }
485 return duration;
486 }
487
488 /**
489 * Scale the Waveform with the given gamma and new max amplitude.
490 *
491 * @param gamma the gamma adjustment to apply
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000492 * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and
493 * MAX_AMPLITUDE
494 * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE
Michael Wright35a0c672018-01-24 00:32:53 +0000495 *
496 * @return A {@link Waveform} effect with the same timings and repeat index
497 * but scaled amplitude.
498 */
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000499 public Waveform scale(float gamma, int maxAmplitude) {
500 if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) {
501 throw new IllegalArgumentException(
502 "Amplitude is negative or greater than MAX_AMPLITUDE");
503 }
Michael Wright35a0c672018-01-24 00:32:53 +0000504 if (gamma == 1.0f && maxAmplitude == MAX_AMPLITUDE) {
505 // Just return a copy of the original if there's no scaling to be done.
506 return new Waveform(mTimings, mAmplitudes, mRepeat);
507 }
508
509 int[] scaledAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
510 for (int i = 0; i < scaledAmplitudes.length; i++) {
511 scaledAmplitudes[i] = scale(scaledAmplitudes[i], gamma, maxAmplitude);
512 }
513 return new Waveform(mTimings, scaledAmplitudes, mRepeat);
514 }
515
Alexey Kuzmin55bdc592018-04-24 12:58:13 +0100516 /**
517 * Resolve default values into integer amplitude numbers.
518 *
519 * @param defaultAmplitude the default amplitude to apply, must be between 0 and
520 * MAX_AMPLITUDE
521 * @return A {@link Waveform} effect with same physical meaning but explicitly set
522 * amplitude
523 *
524 * @hide
525 */
526 public Waveform resolve(int defaultAmplitude) {
527 if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
528 throw new IllegalArgumentException(
529 "Amplitude is negative or greater than MAX_AMPLITUDE");
530 }
531 int[] resolvedAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
532 for (int i = 0; i < resolvedAmplitudes.length; i++) {
533 if (resolvedAmplitudes[i] == DEFAULT_AMPLITUDE) {
534 resolvedAmplitudes[i] = defaultAmplitude;
535 }
536 }
537 return new Waveform(mTimings, resolvedAmplitudes, mRepeat);
538 }
539
Michael Wright35a0c672018-01-24 00:32:53 +0000540 @Override
Michael Wright71216972017-01-31 18:33:54 +0000541 public void validate() {
542 if (mTimings.length != mAmplitudes.length) {
543 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000544 "timing and amplitude arrays must be of equal length"
545 + " (timings.length=" + mTimings.length
546 + ", amplitudes.length=" + mAmplitudes.length + ")");
Michael Wright71216972017-01-31 18:33:54 +0000547 }
548 if (!hasNonZeroEntry(mTimings)) {
Michael Wright35a0c672018-01-24 00:32:53 +0000549 throw new IllegalArgumentException("at least one timing must be non-zero"
550 + " (timings=" + Arrays.toString(mTimings) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000551 }
552 for (long timing : mTimings) {
553 if (timing < 0) {
Michael Wright35a0c672018-01-24 00:32:53 +0000554 throw new IllegalArgumentException("timings must all be >= 0"
555 + " (timings=" + Arrays.toString(mTimings) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000556 }
557 }
558 for (int amplitude : mAmplitudes) {
559 if (amplitude < -1 || amplitude > 255) {
560 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000561 "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255"
562 + " (amplitudes=" + Arrays.toString(mAmplitudes) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000563 }
564 }
565 if (mRepeat < -1 || mRepeat >= mTimings.length) {
Michael Wright0ef6edd2017-04-05 20:57:39 +0100566 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000567 "repeat index must be within the bounds of the timings array"
568 + " (timings.length=" + mTimings.length + ", index=" + mRepeat + ")");
Michael Wright71216972017-01-31 18:33:54 +0000569 }
570 }
571
572 @Override
573 public boolean equals(Object o) {
574 if (!(o instanceof VibrationEffect.Waveform)) {
575 return false;
576 }
577 VibrationEffect.Waveform other = (VibrationEffect.Waveform) o;
Michael Wright35a0c672018-01-24 00:32:53 +0000578 return Arrays.equals(mTimings, other.mTimings)
579 && Arrays.equals(mAmplitudes, other.mAmplitudes)
580 && mRepeat == other.mRepeat;
Michael Wright71216972017-01-31 18:33:54 +0000581 }
582
583 @Override
584 public int hashCode() {
585 int result = 17;
Michael Wright35a0c672018-01-24 00:32:53 +0000586 result += 37 * Arrays.hashCode(mTimings);
587 result += 37 * Arrays.hashCode(mAmplitudes);
588 result += 37 * mRepeat;
Michael Wright71216972017-01-31 18:33:54 +0000589 return result;
590 }
591
592 @Override
593 public String toString() {
Michael Wright35a0c672018-01-24 00:32:53 +0000594 return "Waveform{mTimings=" + Arrays.toString(mTimings)
595 + ", mAmplitudes=" + Arrays.toString(mAmplitudes)
596 + ", mRepeat=" + mRepeat
597 + "}";
Michael Wright71216972017-01-31 18:33:54 +0000598 }
599
600 @Override
601 public void writeToParcel(Parcel out, int flags) {
602 out.writeInt(PARCEL_TOKEN_WAVEFORM);
603 out.writeLongArray(mTimings);
604 out.writeIntArray(mAmplitudes);
605 out.writeInt(mRepeat);
606 }
607
608 private static boolean hasNonZeroEntry(long[] vals) {
609 for (long val : vals) {
610 if (val != 0) {
611 return true;
612 }
613 }
614 return false;
615 }
616
617
618 public static final Parcelable.Creator<Waveform> CREATOR =
619 new Parcelable.Creator<Waveform>() {
620 @Override
621 public Waveform createFromParcel(Parcel in) {
622 // Skip the type token
623 in.readInt();
624 return new Waveform(in);
625 }
626 @Override
627 public Waveform[] newArray(int size) {
628 return new Waveform[size];
629 }
630 };
631 }
632
633 /** @hide */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000634 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000635 public static class Prebaked extends VibrationEffect implements Parcelable {
Michael Wright35a0c672018-01-24 00:32:53 +0000636 private final int mEffectId;
637 private final boolean mFallback;
638
639 private int mEffectStrength;
Michael Wright71216972017-01-31 18:33:54 +0000640
641 public Prebaked(Parcel in) {
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100642 this(in.readInt(), in.readByte() != 0);
Michael Wright35a0c672018-01-24 00:32:53 +0000643 mEffectStrength = in.readInt();
Michael Wright71216972017-01-31 18:33:54 +0000644 }
645
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100646 public Prebaked(int effectId, boolean fallback) {
Michael Wright71216972017-01-31 18:33:54 +0000647 mEffectId = effectId;
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100648 mFallback = fallback;
Michael Wright35a0c672018-01-24 00:32:53 +0000649 mEffectStrength = EffectStrength.MEDIUM;
Michael Wright71216972017-01-31 18:33:54 +0000650 }
651
652 public int getId() {
653 return mEffectId;
654 }
655
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100656 /**
657 * Whether the effect should fall back to a generic pattern if there's no hardware specific
658 * implementation of it.
659 */
660 public boolean shouldFallback() {
661 return mFallback;
662 }
663
Michael Wright71216972017-01-31 18:33:54 +0000664 @Override
Michael Wright35a0c672018-01-24 00:32:53 +0000665 public long getDuration() {
666 return -1;
667 }
668
669 /**
670 * Set the effect strength of the prebaked effect.
671 */
672 public void setEffectStrength(int strength) {
673 if (!isValidEffectStrength(strength)) {
674 throw new IllegalArgumentException("Invalid effect strength: " + strength);
675 }
676 mEffectStrength = strength;
677 }
678
679 /**
680 * Set the effect strength.
681 */
682 public int getEffectStrength() {
683 return mEffectStrength;
684 }
685
686 private static boolean isValidEffectStrength(int strength) {
687 switch (strength) {
688 case EffectStrength.LIGHT:
689 case EffectStrength.MEDIUM:
690 case EffectStrength.STRONG:
691 return true;
692 default:
693 return false;
694 }
695 }
696
697 @Override
Michael Wright71216972017-01-31 18:33:54 +0000698 public void validate() {
Michael Wright57d94d92017-05-31 14:44:45 +0100699 switch (mEffectId) {
700 case EFFECT_CLICK:
701 case EFFECT_DOUBLE_CLICK:
702 case EFFECT_TICK:
Michael Wrightf268bf52018-02-07 23:23:34 +0000703 case EFFECT_THUD:
704 case EFFECT_POP:
705 case EFFECT_HEAVY_CLICK:
Michael Wright57d94d92017-05-31 14:44:45 +0100706 break;
707 default:
Michael Wrightf268bf52018-02-07 23:23:34 +0000708 if (mEffectId < RINGTONES[0] || mEffectId > RINGTONES[RINGTONES.length - 1]) {
709 throw new IllegalArgumentException(
710 "Unknown prebaked effect type (value=" + mEffectId + ")");
711 }
Michael Wright71216972017-01-31 18:33:54 +0000712 }
Michael Wright35a0c672018-01-24 00:32:53 +0000713 if (!isValidEffectStrength(mEffectStrength)) {
714 throw new IllegalArgumentException(
715 "Unknown prebaked effect strength (value=" + mEffectStrength + ")");
716 }
Michael Wright71216972017-01-31 18:33:54 +0000717 }
718
719 @Override
720 public boolean equals(Object o) {
721 if (!(o instanceof VibrationEffect.Prebaked)) {
722 return false;
723 }
724 VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
Michael Wright35a0c672018-01-24 00:32:53 +0000725 return mEffectId == other.mEffectId
726 && mFallback == other.mFallback
727 && mEffectStrength == other.mEffectStrength;
Michael Wright71216972017-01-31 18:33:54 +0000728 }
729
730 @Override
731 public int hashCode() {
Michael Wright35a0c672018-01-24 00:32:53 +0000732 int result = 17;
733 result += 37 * mEffectId;
734 result += 37 * mEffectStrength;
735 return result;
Michael Wright71216972017-01-31 18:33:54 +0000736 }
737
738 @Override
739 public String toString() {
Michael Wright35a0c672018-01-24 00:32:53 +0000740 return "Prebaked{mEffectId=" + mEffectId
741 + ", mEffectStrength=" + mEffectStrength
742 + ", mFallback=" + mFallback
743 + "}";
Michael Wright71216972017-01-31 18:33:54 +0000744 }
745
746
747 @Override
748 public void writeToParcel(Parcel out, int flags) {
749 out.writeInt(PARCEL_TOKEN_EFFECT);
750 out.writeInt(mEffectId);
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100751 out.writeByte((byte) (mFallback ? 1 : 0));
Michael Wright35a0c672018-01-24 00:32:53 +0000752 out.writeInt(mEffectStrength);
Michael Wright71216972017-01-31 18:33:54 +0000753 }
754
755 public static final Parcelable.Creator<Prebaked> CREATOR =
756 new Parcelable.Creator<Prebaked>() {
757 @Override
758 public Prebaked createFromParcel(Parcel in) {
759 // Skip the type token
760 in.readInt();
761 return new Prebaked(in);
762 }
763 @Override
764 public Prebaked[] newArray(int size) {
765 return new Prebaked[size];
766 }
767 };
768 }
769
770 public static final Parcelable.Creator<VibrationEffect> CREATOR =
771 new Parcelable.Creator<VibrationEffect>() {
772 @Override
773 public VibrationEffect createFromParcel(Parcel in) {
774 int token = in.readInt();
775 if (token == PARCEL_TOKEN_ONE_SHOT) {
776 return new OneShot(in);
777 } else if (token == PARCEL_TOKEN_WAVEFORM) {
778 return new Waveform(in);
779 } else if (token == PARCEL_TOKEN_EFFECT) {
780 return new Prebaked(in);
781 } else {
782 throw new IllegalStateException(
783 "Unexpected vibration event type token in parcel.");
784 }
785 }
786 @Override
787 public VibrationEffect[] newArray(int size) {
788 return new VibrationEffect[size];
789 }
790 };
791}