blob: 01d85c6c6c85bd2de451d573c2fbc7517919c432 [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 */
Jeff Sharkeyc6091162018-06-29 17:15:40 -060057 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +000058 public static final int EFFECT_CLICK = Effect.CLICK;
Michael Wright71216972017-01-31 18:33:54 +000059
60 /**
61 * A double click effect.
62 *
63 * @see #get(int)
64 * @hide
65 */
Jeff Sharkeyc6091162018-06-29 17:15:40 -060066 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +000067 public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK;
Michael Wright57d94d92017-05-31 14:44:45 +010068
69 /**
70 * A tick effect.
71 * @see #get(int)
72 * @hide
73 */
Jeff Sharkeyc6091162018-06-29 17:15:40 -060074 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +000075 public static final int EFFECT_TICK = Effect.TICK;
76
77 /**
78 * A thud effect.
79 * @see #get(int)
80 * @hide
81 */
Jeff Sharkeyc6091162018-06-29 17:15:40 -060082 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +000083 public static final int EFFECT_THUD = Effect.THUD;
84
85 /**
86 * A pop effect.
87 * @see #get(int)
88 * @hide
89 */
Jeff Sharkeyc6091162018-06-29 17:15:40 -060090 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +000091 public static final int EFFECT_POP = Effect.POP;
92
93 /**
94 * A heavy click effect.
95 * @see #get(int)
96 * @hide
97 */
Jeff Sharkeyc6091162018-06-29 17:15:40 -060098 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +000099 public static final int EFFECT_HEAVY_CLICK = Effect.HEAVY_CLICK;
100
Jeff Sharkeyc6091162018-06-29 17:15:40 -0600101 /** {@hide} */
102 @TestApi
103 public static final int EFFECT_STRENGTH_LIGHT = EffectStrength.LIGHT;
104
105 /** {@hide} */
106 @TestApi
107 public static final int EFFECT_STRENGTH_MEDIUM = EffectStrength.MEDIUM;
108
109 /** {@hide} */
110 @TestApi
111 public static final int EFFECT_STRENGTH_STRONG = EffectStrength.STRONG;
Michael Wrightf268bf52018-02-07 23:23:34 +0000112
113 /**
114 * Ringtone patterns. They may correspond with the device's ringtone audio, or may just be a
115 * pattern that can be played as a ringtone with any audio, depending on the device.
116 *
117 * @see #get(Uri, Context)
118 * @hide
119 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000120 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +0000121 public static final int[] RINGTONES = {
122 Effect.RINGTONE_1,
123 Effect.RINGTONE_2,
124 Effect.RINGTONE_3,
125 Effect.RINGTONE_4,
126 Effect.RINGTONE_5,
127 Effect.RINGTONE_6,
128 Effect.RINGTONE_7,
129 Effect.RINGTONE_8,
130 Effect.RINGTONE_9,
131 Effect.RINGTONE_10,
132 Effect.RINGTONE_11,
133 Effect.RINGTONE_12,
134 Effect.RINGTONE_13,
135 Effect.RINGTONE_14,
136 Effect.RINGTONE_15
137 };
Michael Wright71216972017-01-31 18:33:54 +0000138
139 /** @hide to prevent subclassing from outside of the framework */
140 public VibrationEffect() { }
141
142 /**
143 * Create a one shot vibration.
144 *
145 * One shot vibrations will vibrate constantly for the specified period of time at the
146 * specified amplitude, and then stop.
147 *
148 * @param milliseconds The number of milliseconds to vibrate. This must be a positive number.
149 * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or
150 * {@link #DEFAULT_AMPLITUDE}.
151 *
152 * @return The desired effect.
153 */
154 public static VibrationEffect createOneShot(long milliseconds, int amplitude) {
155 VibrationEffect effect = new OneShot(milliseconds, amplitude);
156 effect.validate();
157 return effect;
158 }
159
160 /**
161 * Create a waveform vibration.
162 *
163 * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
164 * each pair, the value in the amplitude array determines the strength of the vibration and the
165 * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
166 * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
167 * <p>
168 * The amplitude array of the generated waveform will be the same size as the given
169 * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE},
170 * starting with 0. Therefore the first timing value will be the period to wait before turning
171 * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE}
172 * strength, etc.
173 * </p><p>
174 * To cause the pattern to repeat, pass the index into the timings array at which to start the
175 * repetition, or -1 to disable repeating.
176 * </p>
177 *
178 * @param timings The pattern of alternating on-off timings, starting with off. Timing values
179 * of 0 will cause the timing / amplitude pair to be ignored.
180 * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
181 * want to repeat.
182 *
183 * @return The desired effect.
184 */
185 public static VibrationEffect createWaveform(long[] timings, int repeat) {
186 int[] amplitudes = new int[timings.length];
187 for (int i = 0; i < (timings.length / 2); i++) {
188 amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE;
189 }
190 return createWaveform(timings, amplitudes, repeat);
191 }
192
193 /**
194 * Create a waveform vibration.
195 *
196 * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
197 * each pair, the value in the amplitude array determines the strength of the vibration and the
198 * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
199 * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
200 * </p><p>
201 * To cause the pattern to repeat, pass the index into the timings array at which to start the
202 * repetition, or -1 to disable repeating.
203 * </p>
204 *
205 * @param timings The timing values of the timing / amplitude pairs. Timing values of 0
206 * will cause the pair to be ignored.
207 * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values
208 * must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An
209 * amplitude value of 0 implies the motor is off.
210 * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
211 * want to repeat.
212 *
213 * @return The desired effect.
214 */
215 public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
216 VibrationEffect effect = new Waveform(timings, amplitudes, repeat);
217 effect.validate();
218 return effect;
219 }
220
221 /**
222 * Get a predefined vibration effect.
223 *
224 * Predefined effects are a set of common vibration effects that should be identical, regardless
225 * of the app they come from, in order to provide a cohesive experience for users across
226 * the entire device. They also may be custom tailored to the device hardware in order to
227 * provide a better experience than you could otherwise build using the generic building
228 * blocks.
229 *
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100230 * This will fallback to a generic pattern if one exists and there does not exist a
231 * hardware-specific implementation of the effect.
232 *
Michael Wright71216972017-01-31 18:33:54 +0000233 * @param effectId The ID of the effect to perform:
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100234 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
Michael Wright71216972017-01-31 18:33:54 +0000235 *
236 * @return The desired effect.
237 * @hide
238 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000239 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000240 public static VibrationEffect get(int effectId) {
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100241 return get(effectId, true);
242 }
243
244 /**
245 * Get a predefined vibration effect.
246 *
247 * Predefined effects are a set of common vibration effects that should be identical, regardless
248 * of the app they come from, in order to provide a cohesive experience for users across
249 * the entire device. They also may be custom tailored to the device hardware in order to
250 * provide a better experience than you could otherwise build using the generic building
251 * blocks.
252 *
253 * Some effects you may only want to play if there's a hardware specific implementation because
254 * they may, for example, be too disruptive to the user without tuning. The {@code fallback}
255 * parameter allows you to decide whether you want to fallback to the generic implementation or
256 * only play if there's a tuned, hardware specific one available.
257 *
258 * @param effectId The ID of the effect to perform:
259 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
260 * @param fallback Whether to fallback to a generic pattern if a hardware specific
261 * implementation doesn't exist.
262 *
263 * @return The desired effect.
264 * @hide
265 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000266 @TestApi
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100267 public static VibrationEffect get(int effectId, boolean fallback) {
268 VibrationEffect effect = new Prebaked(effectId, fallback);
Michael Wright71216972017-01-31 18:33:54 +0000269 effect.validate();
270 return effect;
271 }
272
Michael Wrightf268bf52018-02-07 23:23:34 +0000273 /**
274 * Get a predefined vibration effect associated with a given URI.
275 *
276 * Predefined effects are a set of common vibration effects that should be identical, regardless
277 * of the app they come from, in order to provide a cohesive experience for users across
278 * the entire device. They also may be custom tailored to the device hardware in order to
279 * provide a better experience than you could otherwise build using the generic building
280 * blocks.
281 *
282 * @param uri The URI associated with the haptic effect.
283 * @param context The context used to get the URI to haptic effect association.
284 *
285 * @return The desired effect, or {@code null} if there's no associated effect.
286 *
287 * @hide
288 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000289 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +0000290 @Nullable
291 public static VibrationEffect get(Uri uri, Context context) {
292 String[] uris = context.getResources().getStringArray(
293 com.android.internal.R.array.config_ringtoneEffectUris);
294 for (int i = 0; i < uris.length && i < RINGTONES.length; i++) {
295 if (uris[i] == null) {
296 continue;
297 }
Michael Wright32fa5932018-05-18 18:07:09 -0700298 ContentResolver cr = context.getContentResolver();
299 Uri mappedUri = cr.uncanonicalize(Uri.parse(uris[i]));
300 if (mappedUri == null) {
301 continue;
302 }
303 if (mappedUri.equals(uri)) {
Michael Wrightf268bf52018-02-07 23:23:34 +0000304 return get(RINGTONES[i]);
305 }
306 }
307 return null;
308 }
309
Michael Wright71216972017-01-31 18:33:54 +0000310 @Override
311 public int describeContents() {
312 return 0;
313 }
314
315 /** @hide */
316 public abstract void validate();
317
Michael Wright35a0c672018-01-24 00:32:53 +0000318 /**
319 * Gets the estimated duration of the vibration in milliseconds.
320 *
321 * For effects without a defined end (e.g. a Waveform with a non-negative repeat index), this
322 * returns Long.MAX_VALUE. For effects with an unknown duration (e.g. Prebaked effects where
323 * the length is device and potentially run-time dependent), this returns -1.
324 *
325 * @hide
326 */
Jeff Sharkeyc6091162018-06-29 17:15:40 -0600327 @TestApi
Michael Wright35a0c672018-01-24 00:32:53 +0000328 public abstract long getDuration();
329
330 /**
331 * Scale the amplitude with the given constraints.
332 *
333 * This assumes that the previous value was in the range [0, MAX_AMPLITUDE]
334 * @hide
335 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000336 @TestApi
Michael Wright35a0c672018-01-24 00:32:53 +0000337 protected static int scale(int amplitude, float gamma, int maxAmplitude) {
338 float val = MathUtils.pow(amplitude / (float) MAX_AMPLITUDE, gamma);
339 return (int) (val * maxAmplitude);
340 }
341
Michael Wright71216972017-01-31 18:33:54 +0000342 /** @hide */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000343 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000344 public static class OneShot extends VibrationEffect implements Parcelable {
Michael Wright35a0c672018-01-24 00:32:53 +0000345 private final long mDuration;
346 private final int mAmplitude;
Michael Wright71216972017-01-31 18:33:54 +0000347
348 public OneShot(Parcel in) {
Michael Wright35a0c672018-01-24 00:32:53 +0000349 mDuration = in.readLong();
350 mAmplitude = in.readInt();
Michael Wright71216972017-01-31 18:33:54 +0000351 }
352
353 public OneShot(long milliseconds, int amplitude) {
Michael Wright35a0c672018-01-24 00:32:53 +0000354 mDuration = milliseconds;
Michael Wright71216972017-01-31 18:33:54 +0000355 mAmplitude = amplitude;
356 }
357
Michael Wright35a0c672018-01-24 00:32:53 +0000358 @Override
359 public long getDuration() {
360 return mDuration;
Michael Wright71216972017-01-31 18:33:54 +0000361 }
362
363 public int getAmplitude() {
364 return mAmplitude;
365 }
366
Michael Wright35a0c672018-01-24 00:32:53 +0000367 /**
368 * Scale the amplitude of this effect.
369 *
370 * @param gamma the gamma adjustment to apply
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000371 * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and
372 * MAX_AMPLITUDE
373 * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE
Michael Wright35a0c672018-01-24 00:32:53 +0000374 *
375 * @return A {@link OneShot} effect with the same timing but scaled amplitude.
376 */
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000377 public OneShot scale(float gamma, int maxAmplitude) {
378 if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) {
379 throw new IllegalArgumentException(
380 "Amplitude is negative or greater than MAX_AMPLITUDE");
381 }
Michael Wright35a0c672018-01-24 00:32:53 +0000382 int newAmplitude = scale(mAmplitude, gamma, maxAmplitude);
383 return new OneShot(mDuration, newAmplitude);
384 }
385
Alexey Kuzmin55bdc592018-04-24 12:58:13 +0100386 /**
387 * Resolve default values into integer amplitude numbers.
388 *
389 * @param defaultAmplitude the default amplitude to apply, must be between 0 and
390 * MAX_AMPLITUDE
391 * @return A {@link OneShot} effect with same physical meaning but explicitly set amplitude
392 *
393 * @hide
394 */
395 public OneShot resolve(int defaultAmplitude) {
396 if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
397 throw new IllegalArgumentException(
398 "Amplitude is negative or greater than MAX_AMPLITUDE");
399 }
400 if (mAmplitude == DEFAULT_AMPLITUDE) {
401 return new OneShot(mDuration, defaultAmplitude);
402 }
403 return this;
404 }
405
Michael Wright71216972017-01-31 18:33:54 +0000406 @Override
407 public void validate() {
408 if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) {
409 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000410 "amplitude must either be DEFAULT_AMPLITUDE, "
411 + "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")");
Michael Wright71216972017-01-31 18:33:54 +0000412 }
Michael Wright35a0c672018-01-24 00:32:53 +0000413 if (mDuration <= 0) {
Michael Wright0ef6edd2017-04-05 20:57:39 +0100414 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000415 "duration must be positive (duration=" + mDuration + ")");
Michael Wright71216972017-01-31 18:33:54 +0000416 }
417 }
418
419 @Override
420 public boolean equals(Object o) {
421 if (!(o instanceof VibrationEffect.OneShot)) {
422 return false;
423 }
424 VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
Michael Wright35a0c672018-01-24 00:32:53 +0000425 return other.mDuration == mDuration && other.mAmplitude == mAmplitude;
Michael Wright71216972017-01-31 18:33:54 +0000426 }
427
428 @Override
429 public int hashCode() {
430 int result = 17;
Michael Wright35a0c672018-01-24 00:32:53 +0000431 result += 37 * (int) mDuration;
432 result += 37 * mAmplitude;
Michael Wright71216972017-01-31 18:33:54 +0000433 return result;
434 }
435
436 @Override
437 public String toString() {
Michael Wright35a0c672018-01-24 00:32:53 +0000438 return "OneShot{mDuration=" + mDuration + ", mAmplitude=" + mAmplitude + "}";
Michael Wright71216972017-01-31 18:33:54 +0000439 }
440
441 @Override
442 public void writeToParcel(Parcel out, int flags) {
443 out.writeInt(PARCEL_TOKEN_ONE_SHOT);
Michael Wright35a0c672018-01-24 00:32:53 +0000444 out.writeLong(mDuration);
Michael Wright71216972017-01-31 18:33:54 +0000445 out.writeInt(mAmplitude);
446 }
447
448 public static final Parcelable.Creator<OneShot> CREATOR =
449 new Parcelable.Creator<OneShot>() {
450 @Override
451 public OneShot createFromParcel(Parcel in) {
452 // Skip the type token
453 in.readInt();
454 return new OneShot(in);
455 }
456 @Override
457 public OneShot[] newArray(int size) {
458 return new OneShot[size];
459 }
460 };
461 }
462
463 /** @hide */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000464 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000465 public static class Waveform extends VibrationEffect implements Parcelable {
Michael Wright35a0c672018-01-24 00:32:53 +0000466 private final long[] mTimings;
467 private final int[] mAmplitudes;
468 private final int mRepeat;
Michael Wright71216972017-01-31 18:33:54 +0000469
470 public Waveform(Parcel in) {
471 this(in.createLongArray(), in.createIntArray(), in.readInt());
472 }
473
474 public Waveform(long[] timings, int[] amplitudes, int repeat) {
475 mTimings = new long[timings.length];
476 System.arraycopy(timings, 0, mTimings, 0, timings.length);
477 mAmplitudes = new int[amplitudes.length];
478 System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length);
479 mRepeat = repeat;
480 }
481
482 public long[] getTimings() {
483 return mTimings;
484 }
485
486 public int[] getAmplitudes() {
487 return mAmplitudes;
488 }
489
490 public int getRepeatIndex() {
491 return mRepeat;
492 }
493
494 @Override
Michael Wright35a0c672018-01-24 00:32:53 +0000495 public long getDuration() {
496 if (mRepeat >= 0) {
497 return Long.MAX_VALUE;
498 }
499 long duration = 0;
500 for (long d : mTimings) {
501 duration += d;
502 }
503 return duration;
504 }
505
506 /**
507 * Scale the Waveform with the given gamma and new max amplitude.
508 *
509 * @param gamma the gamma adjustment to apply
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000510 * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and
511 * MAX_AMPLITUDE
512 * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE
Michael Wright35a0c672018-01-24 00:32:53 +0000513 *
514 * @return A {@link Waveform} effect with the same timings and repeat index
515 * but scaled amplitude.
516 */
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000517 public Waveform scale(float gamma, int maxAmplitude) {
518 if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) {
519 throw new IllegalArgumentException(
520 "Amplitude is negative or greater than MAX_AMPLITUDE");
521 }
Michael Wright35a0c672018-01-24 00:32:53 +0000522 if (gamma == 1.0f && maxAmplitude == MAX_AMPLITUDE) {
523 // Just return a copy of the original if there's no scaling to be done.
524 return new Waveform(mTimings, mAmplitudes, mRepeat);
525 }
526
527 int[] scaledAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
528 for (int i = 0; i < scaledAmplitudes.length; i++) {
529 scaledAmplitudes[i] = scale(scaledAmplitudes[i], gamma, maxAmplitude);
530 }
531 return new Waveform(mTimings, scaledAmplitudes, mRepeat);
532 }
533
Alexey Kuzmin55bdc592018-04-24 12:58:13 +0100534 /**
535 * Resolve default values into integer amplitude numbers.
536 *
537 * @param defaultAmplitude the default amplitude to apply, must be between 0 and
538 * MAX_AMPLITUDE
539 * @return A {@link Waveform} effect with same physical meaning but explicitly set
540 * amplitude
541 *
542 * @hide
543 */
544 public Waveform resolve(int defaultAmplitude) {
545 if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
546 throw new IllegalArgumentException(
547 "Amplitude is negative or greater than MAX_AMPLITUDE");
548 }
549 int[] resolvedAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
550 for (int i = 0; i < resolvedAmplitudes.length; i++) {
551 if (resolvedAmplitudes[i] == DEFAULT_AMPLITUDE) {
552 resolvedAmplitudes[i] = defaultAmplitude;
553 }
554 }
555 return new Waveform(mTimings, resolvedAmplitudes, mRepeat);
556 }
557
Michael Wright35a0c672018-01-24 00:32:53 +0000558 @Override
Michael Wright71216972017-01-31 18:33:54 +0000559 public void validate() {
560 if (mTimings.length != mAmplitudes.length) {
561 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000562 "timing and amplitude arrays must be of equal length"
563 + " (timings.length=" + mTimings.length
564 + ", amplitudes.length=" + mAmplitudes.length + ")");
Michael Wright71216972017-01-31 18:33:54 +0000565 }
566 if (!hasNonZeroEntry(mTimings)) {
Michael Wright35a0c672018-01-24 00:32:53 +0000567 throw new IllegalArgumentException("at least one timing must be non-zero"
568 + " (timings=" + Arrays.toString(mTimings) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000569 }
570 for (long timing : mTimings) {
571 if (timing < 0) {
Michael Wright35a0c672018-01-24 00:32:53 +0000572 throw new IllegalArgumentException("timings must all be >= 0"
573 + " (timings=" + Arrays.toString(mTimings) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000574 }
575 }
576 for (int amplitude : mAmplitudes) {
577 if (amplitude < -1 || amplitude > 255) {
578 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000579 "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255"
580 + " (amplitudes=" + Arrays.toString(mAmplitudes) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000581 }
582 }
583 if (mRepeat < -1 || mRepeat >= mTimings.length) {
Michael Wright0ef6edd2017-04-05 20:57:39 +0100584 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000585 "repeat index must be within the bounds of the timings array"
586 + " (timings.length=" + mTimings.length + ", index=" + mRepeat + ")");
Michael Wright71216972017-01-31 18:33:54 +0000587 }
588 }
589
590 @Override
591 public boolean equals(Object o) {
592 if (!(o instanceof VibrationEffect.Waveform)) {
593 return false;
594 }
595 VibrationEffect.Waveform other = (VibrationEffect.Waveform) o;
Michael Wright35a0c672018-01-24 00:32:53 +0000596 return Arrays.equals(mTimings, other.mTimings)
597 && Arrays.equals(mAmplitudes, other.mAmplitudes)
598 && mRepeat == other.mRepeat;
Michael Wright71216972017-01-31 18:33:54 +0000599 }
600
601 @Override
602 public int hashCode() {
603 int result = 17;
Michael Wright35a0c672018-01-24 00:32:53 +0000604 result += 37 * Arrays.hashCode(mTimings);
605 result += 37 * Arrays.hashCode(mAmplitudes);
606 result += 37 * mRepeat;
Michael Wright71216972017-01-31 18:33:54 +0000607 return result;
608 }
609
610 @Override
611 public String toString() {
Michael Wright35a0c672018-01-24 00:32:53 +0000612 return "Waveform{mTimings=" + Arrays.toString(mTimings)
613 + ", mAmplitudes=" + Arrays.toString(mAmplitudes)
614 + ", mRepeat=" + mRepeat
615 + "}";
Michael Wright71216972017-01-31 18:33:54 +0000616 }
617
618 @Override
619 public void writeToParcel(Parcel out, int flags) {
620 out.writeInt(PARCEL_TOKEN_WAVEFORM);
621 out.writeLongArray(mTimings);
622 out.writeIntArray(mAmplitudes);
623 out.writeInt(mRepeat);
624 }
625
626 private static boolean hasNonZeroEntry(long[] vals) {
627 for (long val : vals) {
628 if (val != 0) {
629 return true;
630 }
631 }
632 return false;
633 }
634
635
636 public static final Parcelable.Creator<Waveform> CREATOR =
637 new Parcelable.Creator<Waveform>() {
638 @Override
639 public Waveform createFromParcel(Parcel in) {
640 // Skip the type token
641 in.readInt();
642 return new Waveform(in);
643 }
644 @Override
645 public Waveform[] newArray(int size) {
646 return new Waveform[size];
647 }
648 };
649 }
650
651 /** @hide */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000652 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000653 public static class Prebaked extends VibrationEffect implements Parcelable {
Michael Wright35a0c672018-01-24 00:32:53 +0000654 private final int mEffectId;
655 private final boolean mFallback;
656
657 private int mEffectStrength;
Michael Wright71216972017-01-31 18:33:54 +0000658
659 public Prebaked(Parcel in) {
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100660 this(in.readInt(), in.readByte() != 0);
Michael Wright35a0c672018-01-24 00:32:53 +0000661 mEffectStrength = in.readInt();
Michael Wright71216972017-01-31 18:33:54 +0000662 }
663
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100664 public Prebaked(int effectId, boolean fallback) {
Michael Wright71216972017-01-31 18:33:54 +0000665 mEffectId = effectId;
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100666 mFallback = fallback;
Michael Wright35a0c672018-01-24 00:32:53 +0000667 mEffectStrength = EffectStrength.MEDIUM;
Michael Wright71216972017-01-31 18:33:54 +0000668 }
669
670 public int getId() {
671 return mEffectId;
672 }
673
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100674 /**
675 * Whether the effect should fall back to a generic pattern if there's no hardware specific
676 * implementation of it.
677 */
678 public boolean shouldFallback() {
679 return mFallback;
680 }
681
Michael Wright71216972017-01-31 18:33:54 +0000682 @Override
Michael Wright35a0c672018-01-24 00:32:53 +0000683 public long getDuration() {
684 return -1;
685 }
686
687 /**
688 * Set the effect strength of the prebaked effect.
689 */
690 public void setEffectStrength(int strength) {
691 if (!isValidEffectStrength(strength)) {
692 throw new IllegalArgumentException("Invalid effect strength: " + strength);
693 }
694 mEffectStrength = strength;
695 }
696
697 /**
698 * Set the effect strength.
699 */
700 public int getEffectStrength() {
701 return mEffectStrength;
702 }
703
704 private static boolean isValidEffectStrength(int strength) {
705 switch (strength) {
706 case EffectStrength.LIGHT:
707 case EffectStrength.MEDIUM:
708 case EffectStrength.STRONG:
709 return true;
710 default:
711 return false;
712 }
713 }
714
715 @Override
Michael Wright71216972017-01-31 18:33:54 +0000716 public void validate() {
Michael Wright57d94d92017-05-31 14:44:45 +0100717 switch (mEffectId) {
718 case EFFECT_CLICK:
719 case EFFECT_DOUBLE_CLICK:
720 case EFFECT_TICK:
Michael Wrightf268bf52018-02-07 23:23:34 +0000721 case EFFECT_THUD:
722 case EFFECT_POP:
723 case EFFECT_HEAVY_CLICK:
Michael Wright57d94d92017-05-31 14:44:45 +0100724 break;
725 default:
Michael Wrightf268bf52018-02-07 23:23:34 +0000726 if (mEffectId < RINGTONES[0] || mEffectId > RINGTONES[RINGTONES.length - 1]) {
727 throw new IllegalArgumentException(
728 "Unknown prebaked effect type (value=" + mEffectId + ")");
729 }
Michael Wright71216972017-01-31 18:33:54 +0000730 }
Michael Wright35a0c672018-01-24 00:32:53 +0000731 if (!isValidEffectStrength(mEffectStrength)) {
732 throw new IllegalArgumentException(
733 "Unknown prebaked effect strength (value=" + mEffectStrength + ")");
734 }
Michael Wright71216972017-01-31 18:33:54 +0000735 }
736
737 @Override
738 public boolean equals(Object o) {
739 if (!(o instanceof VibrationEffect.Prebaked)) {
740 return false;
741 }
742 VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
Michael Wright35a0c672018-01-24 00:32:53 +0000743 return mEffectId == other.mEffectId
744 && mFallback == other.mFallback
745 && mEffectStrength == other.mEffectStrength;
Michael Wright71216972017-01-31 18:33:54 +0000746 }
747
748 @Override
749 public int hashCode() {
Michael Wright35a0c672018-01-24 00:32:53 +0000750 int result = 17;
751 result += 37 * mEffectId;
752 result += 37 * mEffectStrength;
753 return result;
Michael Wright71216972017-01-31 18:33:54 +0000754 }
755
756 @Override
757 public String toString() {
Michael Wright35a0c672018-01-24 00:32:53 +0000758 return "Prebaked{mEffectId=" + mEffectId
759 + ", mEffectStrength=" + mEffectStrength
760 + ", mFallback=" + mFallback
761 + "}";
Michael Wright71216972017-01-31 18:33:54 +0000762 }
763
764
765 @Override
766 public void writeToParcel(Parcel out, int flags) {
767 out.writeInt(PARCEL_TOKEN_EFFECT);
768 out.writeInt(mEffectId);
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100769 out.writeByte((byte) (mFallback ? 1 : 0));
Michael Wright35a0c672018-01-24 00:32:53 +0000770 out.writeInt(mEffectStrength);
Michael Wright71216972017-01-31 18:33:54 +0000771 }
772
773 public static final Parcelable.Creator<Prebaked> CREATOR =
774 new Parcelable.Creator<Prebaked>() {
775 @Override
776 public Prebaked createFromParcel(Parcel in) {
777 // Skip the type token
778 in.readInt();
779 return new Prebaked(in);
780 }
781 @Override
782 public Prebaked[] newArray(int size) {
783 return new Prebaked[size];
784 }
785 };
786 }
787
788 public static final Parcelable.Creator<VibrationEffect> CREATOR =
789 new Parcelable.Creator<VibrationEffect>() {
790 @Override
791 public VibrationEffect createFromParcel(Parcel in) {
792 int token = in.readInt();
793 if (token == PARCEL_TOKEN_ONE_SHOT) {
794 return new OneShot(in);
795 } else if (token == PARCEL_TOKEN_WAVEFORM) {
796 return new Waveform(in);
797 } else if (token == PARCEL_TOKEN_EFFECT) {
798 return new Prebaked(in);
799 } else {
800 throw new IllegalStateException(
801 "Unexpected vibration event type token in parcel.");
802 }
803 }
804 @Override
805 public VibrationEffect[] newArray(int size) {
806 return new VibrationEffect[size];
807 }
808 };
809}