blob: 7958ddd29c70f8f4bf796026671024eec7a74551 [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 */
107 public static final int EFFECT_TEXTURE_TICK = Effect.TEXTURE_TICK;
108
Jeff Sharkeyc6091162018-06-29 17:15:40 -0600109 /** {@hide} */
110 @TestApi
111 public static final int EFFECT_STRENGTH_LIGHT = EffectStrength.LIGHT;
112
113 /** {@hide} */
114 @TestApi
115 public static final int EFFECT_STRENGTH_MEDIUM = EffectStrength.MEDIUM;
116
117 /** {@hide} */
118 @TestApi
119 public static final int EFFECT_STRENGTH_STRONG = EffectStrength.STRONG;
Michael Wrightf268bf52018-02-07 23:23:34 +0000120
121 /**
122 * Ringtone patterns. They may correspond with the device's ringtone audio, or may just be a
123 * pattern that can be played as a ringtone with any audio, depending on the device.
124 *
125 * @see #get(Uri, Context)
126 * @hide
127 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000128 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +0000129 public static final int[] RINGTONES = {
130 Effect.RINGTONE_1,
131 Effect.RINGTONE_2,
132 Effect.RINGTONE_3,
133 Effect.RINGTONE_4,
134 Effect.RINGTONE_5,
135 Effect.RINGTONE_6,
136 Effect.RINGTONE_7,
137 Effect.RINGTONE_8,
138 Effect.RINGTONE_9,
139 Effect.RINGTONE_10,
140 Effect.RINGTONE_11,
141 Effect.RINGTONE_12,
142 Effect.RINGTONE_13,
143 Effect.RINGTONE_14,
144 Effect.RINGTONE_15
145 };
Michael Wright71216972017-01-31 18:33:54 +0000146
Alexey Kuzmin3a8a39f2019-01-18 16:56:23 +0000147 /** @hide */
148 @IntDef(prefix = { "EFFECT_" }, value = {
149 EFFECT_TICK,
150 EFFECT_CLICK,
151 EFFECT_HEAVY_CLICK,
152 EFFECT_DOUBLE_CLICK,
153 })
154 @Retention(RetentionPolicy.SOURCE)
155 public @interface EffectType {}
156
Michael Wright71216972017-01-31 18:33:54 +0000157 /** @hide to prevent subclassing from outside of the framework */
158 public VibrationEffect() { }
159
160 /**
161 * Create a one shot vibration.
162 *
163 * One shot vibrations will vibrate constantly for the specified period of time at the
164 * specified amplitude, and then stop.
165 *
166 * @param milliseconds The number of milliseconds to vibrate. This must be a positive number.
167 * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or
168 * {@link #DEFAULT_AMPLITUDE}.
169 *
170 * @return The desired effect.
171 */
172 public static VibrationEffect createOneShot(long milliseconds, int amplitude) {
173 VibrationEffect effect = new OneShot(milliseconds, amplitude);
174 effect.validate();
175 return effect;
176 }
177
178 /**
179 * Create a waveform vibration.
180 *
181 * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
182 * each pair, the value in the amplitude array determines the strength of the vibration and the
183 * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
184 * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
185 * <p>
186 * The amplitude array of the generated waveform will be the same size as the given
187 * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE},
188 * starting with 0. Therefore the first timing value will be the period to wait before turning
189 * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE}
190 * strength, etc.
191 * </p><p>
192 * To cause the pattern to repeat, pass the index into the timings array at which to start the
193 * repetition, or -1 to disable repeating.
194 * </p>
195 *
196 * @param timings The pattern of alternating on-off timings, starting with off. Timing values
197 * of 0 will cause the timing / amplitude pair to be ignored.
198 * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
199 * want to repeat.
200 *
201 * @return The desired effect.
202 */
203 public static VibrationEffect createWaveform(long[] timings, int repeat) {
204 int[] amplitudes = new int[timings.length];
205 for (int i = 0; i < (timings.length / 2); i++) {
206 amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE;
207 }
208 return createWaveform(timings, amplitudes, repeat);
209 }
210
211 /**
212 * Create a waveform vibration.
213 *
214 * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
215 * each pair, the value in the amplitude array determines the strength of the vibration and the
216 * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
217 * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
218 * </p><p>
219 * To cause the pattern to repeat, pass the index into the timings array at which to start the
220 * repetition, or -1 to disable repeating.
221 * </p>
222 *
223 * @param timings The timing values of the timing / amplitude pairs. Timing values of 0
224 * will cause the pair to be ignored.
225 * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values
226 * must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An
227 * amplitude value of 0 implies the motor is off.
228 * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
229 * want to repeat.
230 *
231 * @return The desired effect.
232 */
233 public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
234 VibrationEffect effect = new Waveform(timings, amplitudes, repeat);
235 effect.validate();
236 return effect;
237 }
238
239 /**
Alexey Kuzmin3a8a39f2019-01-18 16:56:23 +0000240 * Create a predefined vibration effect.
241 *
242 * Predefined effects are a set of common vibration effects that should be identical, regardless
243 * of the app they come from, in order to provide a cohesive experience for users across
244 * the entire device. They also may be custom tailored to the device hardware in order to
245 * provide a better experience than you could otherwise build using the generic building
246 * blocks.
247 *
248 * This will fallback to a generic pattern if one exists and there does not exist a
249 * hardware-specific implementation of the effect.
250 *
251 * @param effectId The ID of the effect to perform:
252 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
253 *
254 * @return The desired effect.
255 */
Arthur Hungcfdc9752019-03-04 19:25:36 +0800256 @NonNull
Arthur Hung00657b82019-02-26 11:56:40 +0800257 public static VibrationEffect createPredefined(@EffectType int effectId) {
Alexey Kuzmin3a8a39f2019-01-18 16:56:23 +0000258 return get(effectId, true);
259 }
260
261 /**
Michael Wright71216972017-01-31 18:33:54 +0000262 * Get a predefined vibration effect.
263 *
264 * Predefined effects are a set of common vibration effects that should be identical, regardless
265 * of the app they come from, in order to provide a cohesive experience for users across
266 * the entire device. They also may be custom tailored to the device hardware in order to
267 * provide a better experience than you could otherwise build using the generic building
268 * blocks.
269 *
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100270 * This will fallback to a generic pattern if one exists and there does not exist a
271 * hardware-specific implementation of the effect.
272 *
Michael Wright71216972017-01-31 18:33:54 +0000273 * @param effectId The ID of the effect to perform:
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100274 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
Michael Wright71216972017-01-31 18:33:54 +0000275 *
276 * @return The desired effect.
277 * @hide
278 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000279 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000280 public static VibrationEffect get(int effectId) {
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100281 return get(effectId, true);
282 }
283
284 /**
285 * Get a predefined vibration effect.
286 *
287 * Predefined effects are a set of common vibration effects that should be identical, regardless
288 * of the app they come from, in order to provide a cohesive experience for users across
289 * the entire device. They also may be custom tailored to the device hardware in order to
290 * provide a better experience than you could otherwise build using the generic building
291 * blocks.
292 *
293 * Some effects you may only want to play if there's a hardware specific implementation because
294 * they may, for example, be too disruptive to the user without tuning. The {@code fallback}
295 * parameter allows you to decide whether you want to fallback to the generic implementation or
296 * only play if there's a tuned, hardware specific one available.
297 *
298 * @param effectId The ID of the effect to perform:
299 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
300 * @param fallback Whether to fallback to a generic pattern if a hardware specific
301 * implementation doesn't exist.
302 *
303 * @return The desired effect.
304 * @hide
305 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000306 @TestApi
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100307 public static VibrationEffect get(int effectId, boolean fallback) {
308 VibrationEffect effect = new Prebaked(effectId, fallback);
Michael Wright71216972017-01-31 18:33:54 +0000309 effect.validate();
310 return effect;
311 }
312
Michael Wrightf268bf52018-02-07 23:23:34 +0000313 /**
314 * Get a predefined vibration effect associated with a given URI.
315 *
316 * Predefined effects are a set of common vibration effects that should be identical, regardless
317 * of the app they come from, in order to provide a cohesive experience for users across
318 * the entire device. They also may be custom tailored to the device hardware in order to
319 * provide a better experience than you could otherwise build using the generic building
320 * blocks.
321 *
322 * @param uri The URI associated with the haptic effect.
323 * @param context The context used to get the URI to haptic effect association.
324 *
325 * @return The desired effect, or {@code null} if there's no associated effect.
326 *
327 * @hide
328 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000329 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +0000330 @Nullable
331 public static VibrationEffect get(Uri uri, Context context) {
332 String[] uris = context.getResources().getStringArray(
333 com.android.internal.R.array.config_ringtoneEffectUris);
334 for (int i = 0; i < uris.length && i < RINGTONES.length; i++) {
335 if (uris[i] == null) {
336 continue;
337 }
Michael Wright32fa5932018-05-18 18:07:09 -0700338 ContentResolver cr = context.getContentResolver();
339 Uri mappedUri = cr.uncanonicalize(Uri.parse(uris[i]));
340 if (mappedUri == null) {
341 continue;
342 }
343 if (mappedUri.equals(uri)) {
Michael Wrightf268bf52018-02-07 23:23:34 +0000344 return get(RINGTONES[i]);
345 }
346 }
347 return null;
348 }
349
Michael Wright71216972017-01-31 18:33:54 +0000350 @Override
351 public int describeContents() {
352 return 0;
353 }
354
355 /** @hide */
356 public abstract void validate();
357
Michael Wright35a0c672018-01-24 00:32:53 +0000358 /**
359 * Gets the estimated duration of the vibration in milliseconds.
360 *
361 * For effects without a defined end (e.g. a Waveform with a non-negative repeat index), this
362 * returns Long.MAX_VALUE. For effects with an unknown duration (e.g. Prebaked effects where
363 * the length is device and potentially run-time dependent), this returns -1.
364 *
365 * @hide
366 */
Jeff Sharkeyc6091162018-06-29 17:15:40 -0600367 @TestApi
Michael Wright35a0c672018-01-24 00:32:53 +0000368 public abstract long getDuration();
369
370 /**
371 * Scale the amplitude with the given constraints.
372 *
373 * This assumes that the previous value was in the range [0, MAX_AMPLITUDE]
374 * @hide
375 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000376 @TestApi
Michael Wright35a0c672018-01-24 00:32:53 +0000377 protected static int scale(int amplitude, float gamma, int maxAmplitude) {
378 float val = MathUtils.pow(amplitude / (float) MAX_AMPLITUDE, gamma);
379 return (int) (val * maxAmplitude);
380 }
381
Michael Wright71216972017-01-31 18:33:54 +0000382 /** @hide */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000383 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000384 public static class OneShot extends VibrationEffect implements Parcelable {
Michael Wright35a0c672018-01-24 00:32:53 +0000385 private final long mDuration;
386 private final int mAmplitude;
Michael Wright71216972017-01-31 18:33:54 +0000387
388 public OneShot(Parcel in) {
Michael Wright35a0c672018-01-24 00:32:53 +0000389 mDuration = in.readLong();
390 mAmplitude = in.readInt();
Michael Wright71216972017-01-31 18:33:54 +0000391 }
392
393 public OneShot(long milliseconds, int amplitude) {
Michael Wright35a0c672018-01-24 00:32:53 +0000394 mDuration = milliseconds;
Michael Wright71216972017-01-31 18:33:54 +0000395 mAmplitude = amplitude;
396 }
397
Michael Wright35a0c672018-01-24 00:32:53 +0000398 @Override
399 public long getDuration() {
400 return mDuration;
Michael Wright71216972017-01-31 18:33:54 +0000401 }
402
403 public int getAmplitude() {
404 return mAmplitude;
405 }
406
Michael Wright35a0c672018-01-24 00:32:53 +0000407 /**
408 * Scale the amplitude of this effect.
409 *
410 * @param gamma the gamma adjustment to apply
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000411 * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and
412 * MAX_AMPLITUDE
413 * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE
Michael Wright35a0c672018-01-24 00:32:53 +0000414 *
415 * @return A {@link OneShot} effect with the same timing but scaled amplitude.
416 */
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000417 public OneShot scale(float gamma, int maxAmplitude) {
418 if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) {
419 throw new IllegalArgumentException(
420 "Amplitude is negative or greater than MAX_AMPLITUDE");
421 }
Michael Wright35a0c672018-01-24 00:32:53 +0000422 int newAmplitude = scale(mAmplitude, gamma, maxAmplitude);
423 return new OneShot(mDuration, newAmplitude);
424 }
425
Alexey Kuzmin55bdc592018-04-24 12:58:13 +0100426 /**
427 * Resolve default values into integer amplitude numbers.
428 *
429 * @param defaultAmplitude the default amplitude to apply, must be between 0 and
430 * MAX_AMPLITUDE
431 * @return A {@link OneShot} effect with same physical meaning but explicitly set amplitude
432 *
433 * @hide
434 */
435 public OneShot resolve(int defaultAmplitude) {
436 if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
437 throw new IllegalArgumentException(
438 "Amplitude is negative or greater than MAX_AMPLITUDE");
439 }
440 if (mAmplitude == DEFAULT_AMPLITUDE) {
441 return new OneShot(mDuration, defaultAmplitude);
442 }
443 return this;
444 }
445
Michael Wright71216972017-01-31 18:33:54 +0000446 @Override
447 public void validate() {
448 if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) {
449 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000450 "amplitude must either be DEFAULT_AMPLITUDE, "
451 + "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")");
Michael Wright71216972017-01-31 18:33:54 +0000452 }
Michael Wright35a0c672018-01-24 00:32:53 +0000453 if (mDuration <= 0) {
Michael Wright0ef6edd2017-04-05 20:57:39 +0100454 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000455 "duration must be positive (duration=" + mDuration + ")");
Michael Wright71216972017-01-31 18:33:54 +0000456 }
457 }
458
459 @Override
460 public boolean equals(Object o) {
461 if (!(o instanceof VibrationEffect.OneShot)) {
462 return false;
463 }
464 VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
Michael Wright35a0c672018-01-24 00:32:53 +0000465 return other.mDuration == mDuration && other.mAmplitude == mAmplitude;
Michael Wright71216972017-01-31 18:33:54 +0000466 }
467
468 @Override
469 public int hashCode() {
470 int result = 17;
Michael Wright35a0c672018-01-24 00:32:53 +0000471 result += 37 * (int) mDuration;
472 result += 37 * mAmplitude;
Michael Wright71216972017-01-31 18:33:54 +0000473 return result;
474 }
475
476 @Override
477 public String toString() {
Michael Wright35a0c672018-01-24 00:32:53 +0000478 return "OneShot{mDuration=" + mDuration + ", mAmplitude=" + mAmplitude + "}";
Michael Wright71216972017-01-31 18:33:54 +0000479 }
480
481 @Override
482 public void writeToParcel(Parcel out, int flags) {
483 out.writeInt(PARCEL_TOKEN_ONE_SHOT);
Michael Wright35a0c672018-01-24 00:32:53 +0000484 out.writeLong(mDuration);
Michael Wright71216972017-01-31 18:33:54 +0000485 out.writeInt(mAmplitude);
486 }
487
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700488 public static final @android.annotation.NonNull Parcelable.Creator<OneShot> CREATOR =
Michael Wright71216972017-01-31 18:33:54 +0000489 new Parcelable.Creator<OneShot>() {
490 @Override
491 public OneShot createFromParcel(Parcel in) {
492 // Skip the type token
493 in.readInt();
494 return new OneShot(in);
495 }
496 @Override
497 public OneShot[] newArray(int size) {
498 return new OneShot[size];
499 }
500 };
501 }
502
503 /** @hide */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000504 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000505 public static class Waveform extends VibrationEffect implements Parcelable {
Michael Wright35a0c672018-01-24 00:32:53 +0000506 private final long[] mTimings;
507 private final int[] mAmplitudes;
508 private final int mRepeat;
Michael Wright71216972017-01-31 18:33:54 +0000509
510 public Waveform(Parcel in) {
511 this(in.createLongArray(), in.createIntArray(), in.readInt());
512 }
513
514 public Waveform(long[] timings, int[] amplitudes, int repeat) {
515 mTimings = new long[timings.length];
516 System.arraycopy(timings, 0, mTimings, 0, timings.length);
517 mAmplitudes = new int[amplitudes.length];
518 System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length);
519 mRepeat = repeat;
520 }
521
522 public long[] getTimings() {
523 return mTimings;
524 }
525
526 public int[] getAmplitudes() {
527 return mAmplitudes;
528 }
529
530 public int getRepeatIndex() {
531 return mRepeat;
532 }
533
534 @Override
Michael Wright35a0c672018-01-24 00:32:53 +0000535 public long getDuration() {
536 if (mRepeat >= 0) {
537 return Long.MAX_VALUE;
538 }
539 long duration = 0;
540 for (long d : mTimings) {
541 duration += d;
542 }
543 return duration;
544 }
545
546 /**
547 * Scale the Waveform with the given gamma and new max amplitude.
548 *
549 * @param gamma the gamma adjustment to apply
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000550 * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and
551 * MAX_AMPLITUDE
552 * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE
Michael Wright35a0c672018-01-24 00:32:53 +0000553 *
554 * @return A {@link Waveform} effect with the same timings and repeat index
555 * but scaled amplitude.
556 */
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000557 public Waveform scale(float gamma, int maxAmplitude) {
558 if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) {
559 throw new IllegalArgumentException(
560 "Amplitude is negative or greater than MAX_AMPLITUDE");
561 }
Michael Wright35a0c672018-01-24 00:32:53 +0000562 if (gamma == 1.0f && maxAmplitude == MAX_AMPLITUDE) {
563 // Just return a copy of the original if there's no scaling to be done.
564 return new Waveform(mTimings, mAmplitudes, mRepeat);
565 }
566
567 int[] scaledAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
568 for (int i = 0; i < scaledAmplitudes.length; i++) {
569 scaledAmplitudes[i] = scale(scaledAmplitudes[i], gamma, maxAmplitude);
570 }
571 return new Waveform(mTimings, scaledAmplitudes, mRepeat);
572 }
573
Alexey Kuzmin55bdc592018-04-24 12:58:13 +0100574 /**
575 * Resolve default values into integer amplitude numbers.
576 *
577 * @param defaultAmplitude the default amplitude to apply, must be between 0 and
578 * MAX_AMPLITUDE
579 * @return A {@link Waveform} effect with same physical meaning but explicitly set
580 * amplitude
581 *
582 * @hide
583 */
584 public Waveform resolve(int defaultAmplitude) {
585 if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
586 throw new IllegalArgumentException(
587 "Amplitude is negative or greater than MAX_AMPLITUDE");
588 }
589 int[] resolvedAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
590 for (int i = 0; i < resolvedAmplitudes.length; i++) {
591 if (resolvedAmplitudes[i] == DEFAULT_AMPLITUDE) {
592 resolvedAmplitudes[i] = defaultAmplitude;
593 }
594 }
595 return new Waveform(mTimings, resolvedAmplitudes, mRepeat);
596 }
597
Michael Wright35a0c672018-01-24 00:32:53 +0000598 @Override
Michael Wright71216972017-01-31 18:33:54 +0000599 public void validate() {
600 if (mTimings.length != mAmplitudes.length) {
601 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000602 "timing and amplitude arrays must be of equal length"
603 + " (timings.length=" + mTimings.length
604 + ", amplitudes.length=" + mAmplitudes.length + ")");
Michael Wright71216972017-01-31 18:33:54 +0000605 }
606 if (!hasNonZeroEntry(mTimings)) {
Michael Wright35a0c672018-01-24 00:32:53 +0000607 throw new IllegalArgumentException("at least one timing must be non-zero"
608 + " (timings=" + Arrays.toString(mTimings) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000609 }
610 for (long timing : mTimings) {
611 if (timing < 0) {
Michael Wright35a0c672018-01-24 00:32:53 +0000612 throw new IllegalArgumentException("timings must all be >= 0"
613 + " (timings=" + Arrays.toString(mTimings) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000614 }
615 }
616 for (int amplitude : mAmplitudes) {
617 if (amplitude < -1 || amplitude > 255) {
618 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000619 "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255"
620 + " (amplitudes=" + Arrays.toString(mAmplitudes) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000621 }
622 }
623 if (mRepeat < -1 || mRepeat >= mTimings.length) {
Michael Wright0ef6edd2017-04-05 20:57:39 +0100624 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000625 "repeat index must be within the bounds of the timings array"
626 + " (timings.length=" + mTimings.length + ", index=" + mRepeat + ")");
Michael Wright71216972017-01-31 18:33:54 +0000627 }
628 }
629
630 @Override
631 public boolean equals(Object o) {
632 if (!(o instanceof VibrationEffect.Waveform)) {
633 return false;
634 }
635 VibrationEffect.Waveform other = (VibrationEffect.Waveform) o;
Michael Wright35a0c672018-01-24 00:32:53 +0000636 return Arrays.equals(mTimings, other.mTimings)
637 && Arrays.equals(mAmplitudes, other.mAmplitudes)
638 && mRepeat == other.mRepeat;
Michael Wright71216972017-01-31 18:33:54 +0000639 }
640
641 @Override
642 public int hashCode() {
643 int result = 17;
Michael Wright35a0c672018-01-24 00:32:53 +0000644 result += 37 * Arrays.hashCode(mTimings);
645 result += 37 * Arrays.hashCode(mAmplitudes);
646 result += 37 * mRepeat;
Michael Wright71216972017-01-31 18:33:54 +0000647 return result;
648 }
649
650 @Override
651 public String toString() {
Michael Wright35a0c672018-01-24 00:32:53 +0000652 return "Waveform{mTimings=" + Arrays.toString(mTimings)
653 + ", mAmplitudes=" + Arrays.toString(mAmplitudes)
654 + ", mRepeat=" + mRepeat
655 + "}";
Michael Wright71216972017-01-31 18:33:54 +0000656 }
657
658 @Override
659 public void writeToParcel(Parcel out, int flags) {
660 out.writeInt(PARCEL_TOKEN_WAVEFORM);
661 out.writeLongArray(mTimings);
662 out.writeIntArray(mAmplitudes);
663 out.writeInt(mRepeat);
664 }
665
666 private static boolean hasNonZeroEntry(long[] vals) {
667 for (long val : vals) {
668 if (val != 0) {
669 return true;
670 }
671 }
672 return false;
673 }
674
675
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700676 public static final @android.annotation.NonNull Parcelable.Creator<Waveform> CREATOR =
Michael Wright71216972017-01-31 18:33:54 +0000677 new Parcelable.Creator<Waveform>() {
678 @Override
679 public Waveform createFromParcel(Parcel in) {
680 // Skip the type token
681 in.readInt();
682 return new Waveform(in);
683 }
684 @Override
685 public Waveform[] newArray(int size) {
686 return new Waveform[size];
687 }
688 };
689 }
690
691 /** @hide */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000692 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000693 public static class Prebaked extends VibrationEffect implements Parcelable {
Michael Wright35a0c672018-01-24 00:32:53 +0000694 private final int mEffectId;
695 private final boolean mFallback;
696
697 private int mEffectStrength;
Michael Wright71216972017-01-31 18:33:54 +0000698
699 public Prebaked(Parcel in) {
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100700 this(in.readInt(), in.readByte() != 0);
Michael Wright35a0c672018-01-24 00:32:53 +0000701 mEffectStrength = in.readInt();
Michael Wright71216972017-01-31 18:33:54 +0000702 }
703
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100704 public Prebaked(int effectId, boolean fallback) {
Michael Wright71216972017-01-31 18:33:54 +0000705 mEffectId = effectId;
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100706 mFallback = fallback;
Michael Wright35a0c672018-01-24 00:32:53 +0000707 mEffectStrength = EffectStrength.MEDIUM;
Michael Wright71216972017-01-31 18:33:54 +0000708 }
709
710 public int getId() {
711 return mEffectId;
712 }
713
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100714 /**
715 * Whether the effect should fall back to a generic pattern if there's no hardware specific
716 * implementation of it.
717 */
718 public boolean shouldFallback() {
719 return mFallback;
720 }
721
Michael Wright71216972017-01-31 18:33:54 +0000722 @Override
Michael Wright35a0c672018-01-24 00:32:53 +0000723 public long getDuration() {
724 return -1;
725 }
726
727 /**
728 * Set the effect strength of the prebaked effect.
729 */
730 public void setEffectStrength(int strength) {
731 if (!isValidEffectStrength(strength)) {
732 throw new IllegalArgumentException("Invalid effect strength: " + strength);
733 }
734 mEffectStrength = strength;
735 }
736
737 /**
738 * Set the effect strength.
739 */
740 public int getEffectStrength() {
741 return mEffectStrength;
742 }
743
744 private static boolean isValidEffectStrength(int strength) {
745 switch (strength) {
746 case EffectStrength.LIGHT:
747 case EffectStrength.MEDIUM:
748 case EffectStrength.STRONG:
749 return true;
750 default:
751 return false;
752 }
753 }
754
755 @Override
Michael Wright71216972017-01-31 18:33:54 +0000756 public void validate() {
Michael Wright57d94d92017-05-31 14:44:45 +0100757 switch (mEffectId) {
758 case EFFECT_CLICK:
759 case EFFECT_DOUBLE_CLICK:
760 case EFFECT_TICK:
Michael Wright950bd772019-03-21 21:26:05 +0000761 case EFFECT_TEXTURE_TICK:
Michael Wrightf268bf52018-02-07 23:23:34 +0000762 case EFFECT_THUD:
763 case EFFECT_POP:
764 case EFFECT_HEAVY_CLICK:
Michael Wright57d94d92017-05-31 14:44:45 +0100765 break;
766 default:
Michael Wrightf268bf52018-02-07 23:23:34 +0000767 if (mEffectId < RINGTONES[0] || mEffectId > RINGTONES[RINGTONES.length - 1]) {
768 throw new IllegalArgumentException(
769 "Unknown prebaked effect type (value=" + mEffectId + ")");
770 }
Michael Wright71216972017-01-31 18:33:54 +0000771 }
Michael Wright35a0c672018-01-24 00:32:53 +0000772 if (!isValidEffectStrength(mEffectStrength)) {
773 throw new IllegalArgumentException(
774 "Unknown prebaked effect strength (value=" + mEffectStrength + ")");
775 }
Michael Wright71216972017-01-31 18:33:54 +0000776 }
777
778 @Override
779 public boolean equals(Object o) {
780 if (!(o instanceof VibrationEffect.Prebaked)) {
781 return false;
782 }
783 VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
Michael Wright35a0c672018-01-24 00:32:53 +0000784 return mEffectId == other.mEffectId
785 && mFallback == other.mFallback
786 && mEffectStrength == other.mEffectStrength;
Michael Wright71216972017-01-31 18:33:54 +0000787 }
788
789 @Override
790 public int hashCode() {
Michael Wright35a0c672018-01-24 00:32:53 +0000791 int result = 17;
792 result += 37 * mEffectId;
793 result += 37 * mEffectStrength;
794 return result;
Michael Wright71216972017-01-31 18:33:54 +0000795 }
796
797 @Override
798 public String toString() {
Michael Wright35a0c672018-01-24 00:32:53 +0000799 return "Prebaked{mEffectId=" + mEffectId
800 + ", mEffectStrength=" + mEffectStrength
801 + ", mFallback=" + mFallback
802 + "}";
Michael Wright71216972017-01-31 18:33:54 +0000803 }
804
805
806 @Override
807 public void writeToParcel(Parcel out, int flags) {
808 out.writeInt(PARCEL_TOKEN_EFFECT);
809 out.writeInt(mEffectId);
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100810 out.writeByte((byte) (mFallback ? 1 : 0));
Michael Wright35a0c672018-01-24 00:32:53 +0000811 out.writeInt(mEffectStrength);
Michael Wright71216972017-01-31 18:33:54 +0000812 }
813
Michael Wright950bd772019-03-21 21:26:05 +0000814 public static final @NonNull Parcelable.Creator<Prebaked> CREATOR =
Michael Wright71216972017-01-31 18:33:54 +0000815 new Parcelable.Creator<Prebaked>() {
816 @Override
817 public Prebaked createFromParcel(Parcel in) {
818 // Skip the type token
819 in.readInt();
820 return new Prebaked(in);
821 }
822 @Override
823 public Prebaked[] newArray(int size) {
824 return new Prebaked[size];
825 }
826 };
827 }
828
Michael Wright950bd772019-03-21 21:26:05 +0000829 public static final @NonNull Parcelable.Creator<VibrationEffect> CREATOR =
Michael Wright71216972017-01-31 18:33:54 +0000830 new Parcelable.Creator<VibrationEffect>() {
831 @Override
832 public VibrationEffect createFromParcel(Parcel in) {
833 int token = in.readInt();
834 if (token == PARCEL_TOKEN_ONE_SHOT) {
835 return new OneShot(in);
836 } else if (token == PARCEL_TOKEN_WAVEFORM) {
837 return new Waveform(in);
838 } else if (token == PARCEL_TOKEN_EFFECT) {
839 return new Prebaked(in);
840 } else {
841 throw new IllegalStateException(
842 "Unexpected vibration event type token in parcel.");
843 }
844 }
845 @Override
846 public VibrationEffect[] newArray(int size) {
847 return new VibrationEffect[size];
848 }
849 };
850}