blob: 035061b614f87df0a76892f51d352e1594b22b9f [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
Alexey Kuzmin3a8a39f2019-01-18 16:56:23 +000019import android.annotation.IntDef;
Arthur Hungcfdc9752019-03-04 19:25:36 +080020import android.annotation.NonNull;
Michael Wrightf268bf52018-02-07 23:23:34 +000021import android.annotation.Nullable;
Alexey Kuzmin505872d2018-03-19 19:27:46 +000022import android.annotation.TestApi;
Michael Wright32fa5932018-05-18 18:07:09 -070023import android.content.ContentResolver;
Michael Wrightf268bf52018-02-07 23:23:34 +000024import android.content.Context;
25import android.hardware.vibrator.V1_0.EffectStrength;
Michael Wright950bd772019-03-21 21:26:05 +000026import android.hardware.vibrator.V1_3.Effect;
Michael Wrightf268bf52018-02-07 23:23:34 +000027import android.net.Uri;
Michael Wright35a0c672018-01-24 00:32:53 +000028import android.util.MathUtils;
Michael Wright71216972017-01-31 18:33:54 +000029
Alexey Kuzmin3a8a39f2019-01-18 16:56:23 +000030import java.lang.annotation.Retention;
31import java.lang.annotation.RetentionPolicy;
Michael Wright71216972017-01-31 18:33:54 +000032import java.util.Arrays;
33
34/**
35 * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
36 *
37 * These effects may be any number of things, from single shot vibrations to complex waveforms.
38 */
39public abstract class VibrationEffect implements Parcelable {
40 private static final int PARCEL_TOKEN_ONE_SHOT = 1;
41 private static final int PARCEL_TOKEN_WAVEFORM = 2;
42 private static final int PARCEL_TOKEN_EFFECT = 3;
43
44 /**
45 * The default vibration strength of the device.
46 */
47 public static final int DEFAULT_AMPLITUDE = -1;
48
49 /**
Michael Wright35a0c672018-01-24 00:32:53 +000050 * The maximum amplitude value
51 * @hide
52 */
53 public static final int MAX_AMPLITUDE = 255;
54
55 /**
Michael Wright71216972017-01-31 18:33:54 +000056 * A click effect.
57 *
58 * @see #get(int)
Michael Wright71216972017-01-31 18:33:54 +000059 */
Michael Wrightf268bf52018-02-07 23:23:34 +000060 public static final int EFFECT_CLICK = Effect.CLICK;
Michael Wright71216972017-01-31 18:33:54 +000061
62 /**
63 * A double click effect.
64 *
65 * @see #get(int)
Michael Wright71216972017-01-31 18:33:54 +000066 */
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)
Michael Wright57d94d92017-05-31 14:44:45 +010072 */
Michael Wrightf268bf52018-02-07 23:23:34 +000073 public static final int EFFECT_TICK = Effect.TICK;
74
75 /**
76 * A thud effect.
77 * @see #get(int)
78 * @hide
79 */
Jeff Sharkeyc6091162018-06-29 17:15:40 -060080 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +000081 public static final int EFFECT_THUD = Effect.THUD;
82
83 /**
84 * A pop effect.
85 * @see #get(int)
86 * @hide
87 */
Jeff Sharkeyc6091162018-06-29 17:15:40 -060088 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +000089 public static final int EFFECT_POP = Effect.POP;
90
91 /**
92 * A heavy click effect.
93 * @see #get(int)
Michael Wrightf268bf52018-02-07 23:23:34 +000094 */
95 public static final int EFFECT_HEAVY_CLICK = Effect.HEAVY_CLICK;
96
Michael Wright950bd772019-03-21 21:26:05 +000097 /**
98 * A texture effect meant to replicate soft ticks.
99 *
100 * Unlike normal effects, texture effects are meant to be called repeatedly, generally in
101 * response to some motion, in order to replicate the feeling of some texture underneath the
102 * user's fingers.
103 *
104 * @see #get(int)
105 * @hide
106 */
Alexey Kuzmina2112a32019-04-04 18:39:38 +0100107 @TestApi
Michael Wright950bd772019-03-21 21:26:05 +0000108 public static final int EFFECT_TEXTURE_TICK = Effect.TEXTURE_TICK;
109
Jeff Sharkeyc6091162018-06-29 17:15:40 -0600110 /** {@hide} */
111 @TestApi
112 public static final int EFFECT_STRENGTH_LIGHT = EffectStrength.LIGHT;
113
114 /** {@hide} */
115 @TestApi
116 public static final int EFFECT_STRENGTH_MEDIUM = EffectStrength.MEDIUM;
117
118 /** {@hide} */
119 @TestApi
120 public static final int EFFECT_STRENGTH_STRONG = EffectStrength.STRONG;
Michael Wrightf268bf52018-02-07 23:23:34 +0000121
122 /**
123 * Ringtone patterns. They may correspond with the device's ringtone audio, or may just be a
124 * pattern that can be played as a ringtone with any audio, depending on the device.
125 *
126 * @see #get(Uri, Context)
127 * @hide
128 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000129 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +0000130 public static final int[] RINGTONES = {
131 Effect.RINGTONE_1,
132 Effect.RINGTONE_2,
133 Effect.RINGTONE_3,
134 Effect.RINGTONE_4,
135 Effect.RINGTONE_5,
136 Effect.RINGTONE_6,
137 Effect.RINGTONE_7,
138 Effect.RINGTONE_8,
139 Effect.RINGTONE_9,
140 Effect.RINGTONE_10,
141 Effect.RINGTONE_11,
142 Effect.RINGTONE_12,
143 Effect.RINGTONE_13,
144 Effect.RINGTONE_14,
145 Effect.RINGTONE_15
146 };
Michael Wright71216972017-01-31 18:33:54 +0000147
Alexey Kuzmin3a8a39f2019-01-18 16:56:23 +0000148 /** @hide */
149 @IntDef(prefix = { "EFFECT_" }, value = {
150 EFFECT_TICK,
151 EFFECT_CLICK,
152 EFFECT_HEAVY_CLICK,
153 EFFECT_DOUBLE_CLICK,
154 })
155 @Retention(RetentionPolicy.SOURCE)
156 public @interface EffectType {}
157
Michael Wright71216972017-01-31 18:33:54 +0000158 /** @hide to prevent subclassing from outside of the framework */
159 public VibrationEffect() { }
160
161 /**
162 * Create a one shot vibration.
163 *
164 * One shot vibrations will vibrate constantly for the specified period of time at the
165 * specified amplitude, and then stop.
166 *
167 * @param milliseconds The number of milliseconds to vibrate. This must be a positive number.
168 * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or
169 * {@link #DEFAULT_AMPLITUDE}.
170 *
171 * @return The desired effect.
172 */
173 public static VibrationEffect createOneShot(long milliseconds, int amplitude) {
174 VibrationEffect effect = new OneShot(milliseconds, amplitude);
175 effect.validate();
176 return effect;
177 }
178
179 /**
180 * Create a waveform vibration.
181 *
182 * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
183 * each pair, the value in the amplitude array determines the strength of the vibration and the
184 * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
185 * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
186 * <p>
187 * The amplitude array of the generated waveform will be the same size as the given
188 * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE},
189 * starting with 0. Therefore the first timing value will be the period to wait before turning
190 * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE}
191 * strength, etc.
192 * </p><p>
193 * To cause the pattern to repeat, pass the index into the timings array at which to start the
194 * repetition, or -1 to disable repeating.
195 * </p>
196 *
197 * @param timings The pattern of alternating on-off timings, starting with off. Timing values
198 * of 0 will cause the timing / amplitude pair to be ignored.
199 * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
200 * want to repeat.
201 *
202 * @return The desired effect.
203 */
204 public static VibrationEffect createWaveform(long[] timings, int repeat) {
205 int[] amplitudes = new int[timings.length];
206 for (int i = 0; i < (timings.length / 2); i++) {
207 amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE;
208 }
209 return createWaveform(timings, amplitudes, repeat);
210 }
211
212 /**
213 * Create a waveform vibration.
214 *
215 * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
216 * each pair, the value in the amplitude array determines the strength of the vibration and the
217 * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
218 * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
219 * </p><p>
220 * To cause the pattern to repeat, pass the index into the timings array at which to start the
221 * repetition, or -1 to disable repeating.
222 * </p>
223 *
224 * @param timings The timing values of the timing / amplitude pairs. Timing values of 0
225 * will cause the pair to be ignored.
226 * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values
227 * must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An
228 * amplitude value of 0 implies the motor is off.
229 * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
230 * want to repeat.
231 *
232 * @return The desired effect.
233 */
234 public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
235 VibrationEffect effect = new Waveform(timings, amplitudes, repeat);
236 effect.validate();
237 return effect;
238 }
239
240 /**
Alexey Kuzmin3a8a39f2019-01-18 16:56:23 +0000241 * Create a predefined vibration effect.
242 *
243 * Predefined effects are a set of common vibration effects that should be identical, regardless
244 * of the app they come from, in order to provide a cohesive experience for users across
245 * the entire device. They also may be custom tailored to the device hardware in order to
246 * provide a better experience than you could otherwise build using the generic building
247 * blocks.
248 *
249 * This will fallback to a generic pattern if one exists and there does not exist a
250 * hardware-specific implementation of the effect.
251 *
252 * @param effectId The ID of the effect to perform:
253 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
254 *
255 * @return The desired effect.
256 */
Arthur Hungcfdc9752019-03-04 19:25:36 +0800257 @NonNull
Arthur Hung00657b82019-02-26 11:56:40 +0800258 public static VibrationEffect createPredefined(@EffectType int effectId) {
Alexey Kuzmin3a8a39f2019-01-18 16:56:23 +0000259 return get(effectId, true);
260 }
261
262 /**
Michael Wright71216972017-01-31 18:33:54 +0000263 * Get a predefined vibration effect.
264 *
265 * Predefined effects are a set of common vibration effects that should be identical, regardless
266 * of the app they come from, in order to provide a cohesive experience for users across
267 * the entire device. They also may be custom tailored to the device hardware in order to
268 * provide a better experience than you could otherwise build using the generic building
269 * blocks.
270 *
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100271 * This will fallback to a generic pattern if one exists and there does not exist a
272 * hardware-specific implementation of the effect.
273 *
Michael Wright71216972017-01-31 18:33:54 +0000274 * @param effectId The ID of the effect to perform:
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100275 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
Michael Wright71216972017-01-31 18:33:54 +0000276 *
277 * @return The desired effect.
278 * @hide
279 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000280 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000281 public static VibrationEffect get(int effectId) {
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100282 return get(effectId, true);
283 }
284
285 /**
286 * Get a predefined vibration effect.
287 *
288 * Predefined effects are a set of common vibration effects that should be identical, regardless
289 * of the app they come from, in order to provide a cohesive experience for users across
290 * the entire device. They also may be custom tailored to the device hardware in order to
291 * provide a better experience than you could otherwise build using the generic building
292 * blocks.
293 *
294 * Some effects you may only want to play if there's a hardware specific implementation because
295 * they may, for example, be too disruptive to the user without tuning. The {@code fallback}
296 * parameter allows you to decide whether you want to fallback to the generic implementation or
297 * only play if there's a tuned, hardware specific one available.
298 *
299 * @param effectId The ID of the effect to perform:
300 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
301 * @param fallback Whether to fallback to a generic pattern if a hardware specific
302 * implementation doesn't exist.
303 *
304 * @return The desired effect.
305 * @hide
306 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000307 @TestApi
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100308 public static VibrationEffect get(int effectId, boolean fallback) {
309 VibrationEffect effect = new Prebaked(effectId, fallback);
Michael Wright71216972017-01-31 18:33:54 +0000310 effect.validate();
311 return effect;
312 }
313
Michael Wrightf268bf52018-02-07 23:23:34 +0000314 /**
315 * Get a predefined vibration effect associated with a given URI.
316 *
317 * Predefined effects are a set of common vibration effects that should be identical, regardless
318 * of the app they come from, in order to provide a cohesive experience for users across
319 * the entire device. They also may be custom tailored to the device hardware in order to
320 * provide a better experience than you could otherwise build using the generic building
321 * blocks.
322 *
323 * @param uri The URI associated with the haptic effect.
324 * @param context The context used to get the URI to haptic effect association.
325 *
326 * @return The desired effect, or {@code null} if there's no associated effect.
327 *
328 * @hide
329 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000330 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +0000331 @Nullable
332 public static VibrationEffect get(Uri uri, Context context) {
333 String[] uris = context.getResources().getStringArray(
334 com.android.internal.R.array.config_ringtoneEffectUris);
335 for (int i = 0; i < uris.length && i < RINGTONES.length; i++) {
336 if (uris[i] == null) {
337 continue;
338 }
Michael Wright32fa5932018-05-18 18:07:09 -0700339 ContentResolver cr = context.getContentResolver();
340 Uri mappedUri = cr.uncanonicalize(Uri.parse(uris[i]));
341 if (mappedUri == null) {
342 continue;
343 }
344 if (mappedUri.equals(uri)) {
Michael Wrightf268bf52018-02-07 23:23:34 +0000345 return get(RINGTONES[i]);
346 }
347 }
348 return null;
349 }
350
Michael Wright71216972017-01-31 18:33:54 +0000351 @Override
352 public int describeContents() {
353 return 0;
354 }
355
356 /** @hide */
357 public abstract void validate();
358
Michael Wright35a0c672018-01-24 00:32:53 +0000359 /**
360 * Gets the estimated duration of the vibration in milliseconds.
361 *
362 * For effects without a defined end (e.g. a Waveform with a non-negative repeat index), this
363 * returns Long.MAX_VALUE. For effects with an unknown duration (e.g. Prebaked effects where
364 * the length is device and potentially run-time dependent), this returns -1.
365 *
366 * @hide
367 */
Jeff Sharkeyc6091162018-06-29 17:15:40 -0600368 @TestApi
Michael Wright35a0c672018-01-24 00:32:53 +0000369 public abstract long getDuration();
370
371 /**
372 * Scale the amplitude with the given constraints.
373 *
374 * This assumes that the previous value was in the range [0, MAX_AMPLITUDE]
375 * @hide
376 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000377 @TestApi
Michael Wright35a0c672018-01-24 00:32:53 +0000378 protected static int scale(int amplitude, float gamma, int maxAmplitude) {
379 float val = MathUtils.pow(amplitude / (float) MAX_AMPLITUDE, gamma);
380 return (int) (val * maxAmplitude);
381 }
382
Michael Wright71216972017-01-31 18:33:54 +0000383 /** @hide */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000384 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000385 public static class OneShot extends VibrationEffect implements Parcelable {
Michael Wright35a0c672018-01-24 00:32:53 +0000386 private final long mDuration;
387 private final int mAmplitude;
Michael Wright71216972017-01-31 18:33:54 +0000388
389 public OneShot(Parcel in) {
Michael Wright35a0c672018-01-24 00:32:53 +0000390 mDuration = in.readLong();
391 mAmplitude = in.readInt();
Michael Wright71216972017-01-31 18:33:54 +0000392 }
393
394 public OneShot(long milliseconds, int amplitude) {
Michael Wright35a0c672018-01-24 00:32:53 +0000395 mDuration = milliseconds;
Michael Wright71216972017-01-31 18:33:54 +0000396 mAmplitude = amplitude;
397 }
398
Michael Wright35a0c672018-01-24 00:32:53 +0000399 @Override
400 public long getDuration() {
401 return mDuration;
Michael Wright71216972017-01-31 18:33:54 +0000402 }
403
404 public int getAmplitude() {
405 return mAmplitude;
406 }
407
Michael Wright35a0c672018-01-24 00:32:53 +0000408 /**
409 * Scale the amplitude of this effect.
410 *
411 * @param gamma the gamma adjustment to apply
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000412 * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and
413 * MAX_AMPLITUDE
414 * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE
Michael Wright35a0c672018-01-24 00:32:53 +0000415 *
416 * @return A {@link OneShot} effect with the same timing but scaled amplitude.
417 */
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000418 public OneShot scale(float gamma, int maxAmplitude) {
419 if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) {
420 throw new IllegalArgumentException(
421 "Amplitude is negative or greater than MAX_AMPLITUDE");
422 }
Michael Wright35a0c672018-01-24 00:32:53 +0000423 int newAmplitude = scale(mAmplitude, gamma, maxAmplitude);
424 return new OneShot(mDuration, newAmplitude);
425 }
426
Alexey Kuzmin55bdc592018-04-24 12:58:13 +0100427 /**
428 * Resolve default values into integer amplitude numbers.
429 *
430 * @param defaultAmplitude the default amplitude to apply, must be between 0 and
431 * MAX_AMPLITUDE
432 * @return A {@link OneShot} effect with same physical meaning but explicitly set amplitude
433 *
434 * @hide
435 */
436 public OneShot resolve(int defaultAmplitude) {
437 if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
438 throw new IllegalArgumentException(
439 "Amplitude is negative or greater than MAX_AMPLITUDE");
440 }
441 if (mAmplitude == DEFAULT_AMPLITUDE) {
442 return new OneShot(mDuration, defaultAmplitude);
443 }
444 return this;
445 }
446
Michael Wright71216972017-01-31 18:33:54 +0000447 @Override
448 public void validate() {
449 if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) {
450 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000451 "amplitude must either be DEFAULT_AMPLITUDE, "
452 + "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")");
Michael Wright71216972017-01-31 18:33:54 +0000453 }
Michael Wright35a0c672018-01-24 00:32:53 +0000454 if (mDuration <= 0) {
Michael Wright0ef6edd2017-04-05 20:57:39 +0100455 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000456 "duration must be positive (duration=" + mDuration + ")");
Michael Wright71216972017-01-31 18:33:54 +0000457 }
458 }
459
460 @Override
461 public boolean equals(Object o) {
462 if (!(o instanceof VibrationEffect.OneShot)) {
463 return false;
464 }
465 VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
Michael Wright35a0c672018-01-24 00:32:53 +0000466 return other.mDuration == mDuration && other.mAmplitude == mAmplitude;
Michael Wright71216972017-01-31 18:33:54 +0000467 }
468
469 @Override
470 public int hashCode() {
471 int result = 17;
Michael Wright35a0c672018-01-24 00:32:53 +0000472 result += 37 * (int) mDuration;
473 result += 37 * mAmplitude;
Michael Wright71216972017-01-31 18:33:54 +0000474 return result;
475 }
476
477 @Override
478 public String toString() {
Michael Wright35a0c672018-01-24 00:32:53 +0000479 return "OneShot{mDuration=" + mDuration + ", mAmplitude=" + mAmplitude + "}";
Michael Wright71216972017-01-31 18:33:54 +0000480 }
481
482 @Override
483 public void writeToParcel(Parcel out, int flags) {
484 out.writeInt(PARCEL_TOKEN_ONE_SHOT);
Michael Wright35a0c672018-01-24 00:32:53 +0000485 out.writeLong(mDuration);
Michael Wright71216972017-01-31 18:33:54 +0000486 out.writeInt(mAmplitude);
487 }
488
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700489 public static final @android.annotation.NonNull Parcelable.Creator<OneShot> CREATOR =
Michael Wright71216972017-01-31 18:33:54 +0000490 new Parcelable.Creator<OneShot>() {
491 @Override
492 public OneShot createFromParcel(Parcel in) {
493 // Skip the type token
494 in.readInt();
495 return new OneShot(in);
496 }
497 @Override
498 public OneShot[] newArray(int size) {
499 return new OneShot[size];
500 }
501 };
502 }
503
504 /** @hide */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000505 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000506 public static class Waveform extends VibrationEffect implements Parcelable {
Michael Wright35a0c672018-01-24 00:32:53 +0000507 private final long[] mTimings;
508 private final int[] mAmplitudes;
509 private final int mRepeat;
Michael Wright71216972017-01-31 18:33:54 +0000510
511 public Waveform(Parcel in) {
512 this(in.createLongArray(), in.createIntArray(), in.readInt());
513 }
514
515 public Waveform(long[] timings, int[] amplitudes, int repeat) {
516 mTimings = new long[timings.length];
517 System.arraycopy(timings, 0, mTimings, 0, timings.length);
518 mAmplitudes = new int[amplitudes.length];
519 System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length);
520 mRepeat = repeat;
521 }
522
523 public long[] getTimings() {
524 return mTimings;
525 }
526
527 public int[] getAmplitudes() {
528 return mAmplitudes;
529 }
530
531 public int getRepeatIndex() {
532 return mRepeat;
533 }
534
535 @Override
Michael Wright35a0c672018-01-24 00:32:53 +0000536 public long getDuration() {
537 if (mRepeat >= 0) {
538 return Long.MAX_VALUE;
539 }
540 long duration = 0;
541 for (long d : mTimings) {
542 duration += d;
543 }
544 return duration;
545 }
546
547 /**
548 * Scale the Waveform with the given gamma and new max amplitude.
549 *
550 * @param gamma the gamma adjustment to apply
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000551 * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and
552 * MAX_AMPLITUDE
553 * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE
Michael Wright35a0c672018-01-24 00:32:53 +0000554 *
555 * @return A {@link Waveform} effect with the same timings and repeat index
556 * but scaled amplitude.
557 */
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000558 public Waveform scale(float gamma, int maxAmplitude) {
559 if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) {
560 throw new IllegalArgumentException(
561 "Amplitude is negative or greater than MAX_AMPLITUDE");
562 }
Michael Wright35a0c672018-01-24 00:32:53 +0000563 if (gamma == 1.0f && maxAmplitude == MAX_AMPLITUDE) {
564 // Just return a copy of the original if there's no scaling to be done.
565 return new Waveform(mTimings, mAmplitudes, mRepeat);
566 }
567
568 int[] scaledAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
569 for (int i = 0; i < scaledAmplitudes.length; i++) {
570 scaledAmplitudes[i] = scale(scaledAmplitudes[i], gamma, maxAmplitude);
571 }
572 return new Waveform(mTimings, scaledAmplitudes, mRepeat);
573 }
574
Alexey Kuzmin55bdc592018-04-24 12:58:13 +0100575 /**
576 * Resolve default values into integer amplitude numbers.
577 *
578 * @param defaultAmplitude the default amplitude to apply, must be between 0 and
579 * MAX_AMPLITUDE
580 * @return A {@link Waveform} effect with same physical meaning but explicitly set
581 * amplitude
582 *
583 * @hide
584 */
585 public Waveform resolve(int defaultAmplitude) {
586 if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
587 throw new IllegalArgumentException(
588 "Amplitude is negative or greater than MAX_AMPLITUDE");
589 }
590 int[] resolvedAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
591 for (int i = 0; i < resolvedAmplitudes.length; i++) {
592 if (resolvedAmplitudes[i] == DEFAULT_AMPLITUDE) {
593 resolvedAmplitudes[i] = defaultAmplitude;
594 }
595 }
596 return new Waveform(mTimings, resolvedAmplitudes, mRepeat);
597 }
598
Michael Wright35a0c672018-01-24 00:32:53 +0000599 @Override
Michael Wright71216972017-01-31 18:33:54 +0000600 public void validate() {
601 if (mTimings.length != mAmplitudes.length) {
602 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000603 "timing and amplitude arrays must be of equal length"
604 + " (timings.length=" + mTimings.length
605 + ", amplitudes.length=" + mAmplitudes.length + ")");
Michael Wright71216972017-01-31 18:33:54 +0000606 }
607 if (!hasNonZeroEntry(mTimings)) {
Michael Wright35a0c672018-01-24 00:32:53 +0000608 throw new IllegalArgumentException("at least one timing must be non-zero"
609 + " (timings=" + Arrays.toString(mTimings) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000610 }
611 for (long timing : mTimings) {
612 if (timing < 0) {
Michael Wright35a0c672018-01-24 00:32:53 +0000613 throw new IllegalArgumentException("timings must all be >= 0"
614 + " (timings=" + Arrays.toString(mTimings) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000615 }
616 }
617 for (int amplitude : mAmplitudes) {
618 if (amplitude < -1 || amplitude > 255) {
619 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000620 "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255"
621 + " (amplitudes=" + Arrays.toString(mAmplitudes) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000622 }
623 }
624 if (mRepeat < -1 || mRepeat >= mTimings.length) {
Michael Wright0ef6edd2017-04-05 20:57:39 +0100625 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000626 "repeat index must be within the bounds of the timings array"
627 + " (timings.length=" + mTimings.length + ", index=" + mRepeat + ")");
Michael Wright71216972017-01-31 18:33:54 +0000628 }
629 }
630
631 @Override
632 public boolean equals(Object o) {
633 if (!(o instanceof VibrationEffect.Waveform)) {
634 return false;
635 }
636 VibrationEffect.Waveform other = (VibrationEffect.Waveform) o;
Michael Wright35a0c672018-01-24 00:32:53 +0000637 return Arrays.equals(mTimings, other.mTimings)
638 && Arrays.equals(mAmplitudes, other.mAmplitudes)
639 && mRepeat == other.mRepeat;
Michael Wright71216972017-01-31 18:33:54 +0000640 }
641
642 @Override
643 public int hashCode() {
644 int result = 17;
Michael Wright35a0c672018-01-24 00:32:53 +0000645 result += 37 * Arrays.hashCode(mTimings);
646 result += 37 * Arrays.hashCode(mAmplitudes);
647 result += 37 * mRepeat;
Michael Wright71216972017-01-31 18:33:54 +0000648 return result;
649 }
650
651 @Override
652 public String toString() {
Michael Wright35a0c672018-01-24 00:32:53 +0000653 return "Waveform{mTimings=" + Arrays.toString(mTimings)
654 + ", mAmplitudes=" + Arrays.toString(mAmplitudes)
655 + ", mRepeat=" + mRepeat
656 + "}";
Michael Wright71216972017-01-31 18:33:54 +0000657 }
658
659 @Override
660 public void writeToParcel(Parcel out, int flags) {
661 out.writeInt(PARCEL_TOKEN_WAVEFORM);
662 out.writeLongArray(mTimings);
663 out.writeIntArray(mAmplitudes);
664 out.writeInt(mRepeat);
665 }
666
667 private static boolean hasNonZeroEntry(long[] vals) {
668 for (long val : vals) {
669 if (val != 0) {
670 return true;
671 }
672 }
673 return false;
674 }
675
676
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700677 public static final @android.annotation.NonNull Parcelable.Creator<Waveform> CREATOR =
Michael Wright71216972017-01-31 18:33:54 +0000678 new Parcelable.Creator<Waveform>() {
679 @Override
680 public Waveform createFromParcel(Parcel in) {
681 // Skip the type token
682 in.readInt();
683 return new Waveform(in);
684 }
685 @Override
686 public Waveform[] newArray(int size) {
687 return new Waveform[size];
688 }
689 };
690 }
691
692 /** @hide */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000693 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000694 public static class Prebaked extends VibrationEffect implements Parcelable {
Michael Wright35a0c672018-01-24 00:32:53 +0000695 private final int mEffectId;
696 private final boolean mFallback;
697
698 private int mEffectStrength;
Michael Wright71216972017-01-31 18:33:54 +0000699
700 public Prebaked(Parcel in) {
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100701 this(in.readInt(), in.readByte() != 0);
Michael Wright35a0c672018-01-24 00:32:53 +0000702 mEffectStrength = in.readInt();
Michael Wright71216972017-01-31 18:33:54 +0000703 }
704
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100705 public Prebaked(int effectId, boolean fallback) {
Michael Wright71216972017-01-31 18:33:54 +0000706 mEffectId = effectId;
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100707 mFallback = fallback;
Michael Wright35a0c672018-01-24 00:32:53 +0000708 mEffectStrength = EffectStrength.MEDIUM;
Michael Wright71216972017-01-31 18:33:54 +0000709 }
710
711 public int getId() {
712 return mEffectId;
713 }
714
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100715 /**
716 * Whether the effect should fall back to a generic pattern if there's no hardware specific
717 * implementation of it.
718 */
719 public boolean shouldFallback() {
720 return mFallback;
721 }
722
Michael Wright71216972017-01-31 18:33:54 +0000723 @Override
Michael Wright35a0c672018-01-24 00:32:53 +0000724 public long getDuration() {
725 return -1;
726 }
727
728 /**
729 * Set the effect strength of the prebaked effect.
730 */
731 public void setEffectStrength(int strength) {
732 if (!isValidEffectStrength(strength)) {
733 throw new IllegalArgumentException("Invalid effect strength: " + strength);
734 }
735 mEffectStrength = strength;
736 }
737
738 /**
739 * Set the effect strength.
740 */
741 public int getEffectStrength() {
742 return mEffectStrength;
743 }
744
745 private static boolean isValidEffectStrength(int strength) {
746 switch (strength) {
747 case EffectStrength.LIGHT:
748 case EffectStrength.MEDIUM:
749 case EffectStrength.STRONG:
750 return true;
751 default:
752 return false;
753 }
754 }
755
756 @Override
Michael Wright71216972017-01-31 18:33:54 +0000757 public void validate() {
Michael Wright57d94d92017-05-31 14:44:45 +0100758 switch (mEffectId) {
759 case EFFECT_CLICK:
760 case EFFECT_DOUBLE_CLICK:
761 case EFFECT_TICK:
Michael Wright950bd772019-03-21 21:26:05 +0000762 case EFFECT_TEXTURE_TICK:
Michael Wrightf268bf52018-02-07 23:23:34 +0000763 case EFFECT_THUD:
764 case EFFECT_POP:
765 case EFFECT_HEAVY_CLICK:
Michael Wright57d94d92017-05-31 14:44:45 +0100766 break;
767 default:
Michael Wrightf268bf52018-02-07 23:23:34 +0000768 if (mEffectId < RINGTONES[0] || mEffectId > RINGTONES[RINGTONES.length - 1]) {
769 throw new IllegalArgumentException(
770 "Unknown prebaked effect type (value=" + mEffectId + ")");
771 }
Michael Wright71216972017-01-31 18:33:54 +0000772 }
Michael Wright35a0c672018-01-24 00:32:53 +0000773 if (!isValidEffectStrength(mEffectStrength)) {
774 throw new IllegalArgumentException(
775 "Unknown prebaked effect strength (value=" + mEffectStrength + ")");
776 }
Michael Wright71216972017-01-31 18:33:54 +0000777 }
778
779 @Override
780 public boolean equals(Object o) {
781 if (!(o instanceof VibrationEffect.Prebaked)) {
782 return false;
783 }
784 VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
Michael Wright35a0c672018-01-24 00:32:53 +0000785 return mEffectId == other.mEffectId
786 && mFallback == other.mFallback
787 && mEffectStrength == other.mEffectStrength;
Michael Wright71216972017-01-31 18:33:54 +0000788 }
789
790 @Override
791 public int hashCode() {
Michael Wright35a0c672018-01-24 00:32:53 +0000792 int result = 17;
793 result += 37 * mEffectId;
794 result += 37 * mEffectStrength;
795 return result;
Michael Wright71216972017-01-31 18:33:54 +0000796 }
797
798 @Override
799 public String toString() {
Michael Wright35a0c672018-01-24 00:32:53 +0000800 return "Prebaked{mEffectId=" + mEffectId
801 + ", mEffectStrength=" + mEffectStrength
802 + ", mFallback=" + mFallback
803 + "}";
Michael Wright71216972017-01-31 18:33:54 +0000804 }
805
806
807 @Override
808 public void writeToParcel(Parcel out, int flags) {
809 out.writeInt(PARCEL_TOKEN_EFFECT);
810 out.writeInt(mEffectId);
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100811 out.writeByte((byte) (mFallback ? 1 : 0));
Michael Wright35a0c672018-01-24 00:32:53 +0000812 out.writeInt(mEffectStrength);
Michael Wright71216972017-01-31 18:33:54 +0000813 }
814
Michael Wright950bd772019-03-21 21:26:05 +0000815 public static final @NonNull Parcelable.Creator<Prebaked> CREATOR =
Michael Wright71216972017-01-31 18:33:54 +0000816 new Parcelable.Creator<Prebaked>() {
817 @Override
818 public Prebaked createFromParcel(Parcel in) {
819 // Skip the type token
820 in.readInt();
821 return new Prebaked(in);
822 }
823 @Override
824 public Prebaked[] newArray(int size) {
825 return new Prebaked[size];
826 }
827 };
828 }
829
Michael Wright950bd772019-03-21 21:26:05 +0000830 public static final @NonNull Parcelable.Creator<VibrationEffect> CREATOR =
Michael Wright71216972017-01-31 18:33:54 +0000831 new Parcelable.Creator<VibrationEffect>() {
832 @Override
833 public VibrationEffect createFromParcel(Parcel in) {
834 int token = in.readInt();
835 if (token == PARCEL_TOKEN_ONE_SHOT) {
836 return new OneShot(in);
837 } else if (token == PARCEL_TOKEN_WAVEFORM) {
838 return new Waveform(in);
839 } else if (token == PARCEL_TOKEN_EFFECT) {
840 return new Prebaked(in);
841 } else {
842 throw new IllegalStateException(
843 "Unexpected vibration event type token in parcel.");
844 }
845 }
846 @Override
847 public VibrationEffect[] newArray(int size) {
848 return new VibrationEffect[size];
849 }
850 };
851}