blob: c74cbff567c515d33f0b9b21a21e1685ef959ffa [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;
26import android.hardware.vibrator.V1_2.Effect;
27import 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
Jeff Sharkeyc6091162018-06-29 17:15:40 -060097 /** {@hide} */
98 @TestApi
99 public static final int EFFECT_STRENGTH_LIGHT = EffectStrength.LIGHT;
100
101 /** {@hide} */
102 @TestApi
103 public static final int EFFECT_STRENGTH_MEDIUM = EffectStrength.MEDIUM;
104
105 /** {@hide} */
106 @TestApi
107 public static final int EFFECT_STRENGTH_STRONG = EffectStrength.STRONG;
Michael Wrightf268bf52018-02-07 23:23:34 +0000108
109 /**
110 * Ringtone patterns. They may correspond with the device's ringtone audio, or may just be a
111 * pattern that can be played as a ringtone with any audio, depending on the device.
112 *
113 * @see #get(Uri, Context)
114 * @hide
115 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000116 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +0000117 public static final int[] RINGTONES = {
118 Effect.RINGTONE_1,
119 Effect.RINGTONE_2,
120 Effect.RINGTONE_3,
121 Effect.RINGTONE_4,
122 Effect.RINGTONE_5,
123 Effect.RINGTONE_6,
124 Effect.RINGTONE_7,
125 Effect.RINGTONE_8,
126 Effect.RINGTONE_9,
127 Effect.RINGTONE_10,
128 Effect.RINGTONE_11,
129 Effect.RINGTONE_12,
130 Effect.RINGTONE_13,
131 Effect.RINGTONE_14,
132 Effect.RINGTONE_15
133 };
Michael Wright71216972017-01-31 18:33:54 +0000134
Alexey Kuzmin3a8a39f2019-01-18 16:56:23 +0000135 /** @hide */
136 @IntDef(prefix = { "EFFECT_" }, value = {
137 EFFECT_TICK,
138 EFFECT_CLICK,
139 EFFECT_HEAVY_CLICK,
140 EFFECT_DOUBLE_CLICK,
141 })
142 @Retention(RetentionPolicy.SOURCE)
143 public @interface EffectType {}
144
Michael Wright71216972017-01-31 18:33:54 +0000145 /** @hide to prevent subclassing from outside of the framework */
146 public VibrationEffect() { }
147
148 /**
149 * Create a one shot vibration.
150 *
151 * One shot vibrations will vibrate constantly for the specified period of time at the
152 * specified amplitude, and then stop.
153 *
154 * @param milliseconds The number of milliseconds to vibrate. This must be a positive number.
155 * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or
156 * {@link #DEFAULT_AMPLITUDE}.
157 *
158 * @return The desired effect.
159 */
160 public static VibrationEffect createOneShot(long milliseconds, int amplitude) {
161 VibrationEffect effect = new OneShot(milliseconds, amplitude);
162 effect.validate();
163 return effect;
164 }
165
166 /**
167 * Create a waveform vibration.
168 *
169 * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
170 * each pair, the value in the amplitude array determines the strength of the vibration and the
171 * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
172 * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
173 * <p>
174 * The amplitude array of the generated waveform will be the same size as the given
175 * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE},
176 * starting with 0. Therefore the first timing value will be the period to wait before turning
177 * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE}
178 * strength, etc.
179 * </p><p>
180 * To cause the pattern to repeat, pass the index into the timings array at which to start the
181 * repetition, or -1 to disable repeating.
182 * </p>
183 *
184 * @param timings The pattern of alternating on-off timings, starting with off. Timing values
185 * of 0 will cause the timing / amplitude pair to be ignored.
186 * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
187 * want to repeat.
188 *
189 * @return The desired effect.
190 */
191 public static VibrationEffect createWaveform(long[] timings, int repeat) {
192 int[] amplitudes = new int[timings.length];
193 for (int i = 0; i < (timings.length / 2); i++) {
194 amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE;
195 }
196 return createWaveform(timings, amplitudes, repeat);
197 }
198
199 /**
200 * Create a waveform vibration.
201 *
202 * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
203 * each pair, the value in the amplitude array determines the strength of the vibration and the
204 * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
205 * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
206 * </p><p>
207 * To cause the pattern to repeat, pass the index into the timings array at which to start the
208 * repetition, or -1 to disable repeating.
209 * </p>
210 *
211 * @param timings The timing values of the timing / amplitude pairs. Timing values of 0
212 * will cause the pair to be ignored.
213 * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values
214 * must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An
215 * amplitude value of 0 implies the motor is off.
216 * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
217 * want to repeat.
218 *
219 * @return The desired effect.
220 */
221 public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
222 VibrationEffect effect = new Waveform(timings, amplitudes, repeat);
223 effect.validate();
224 return effect;
225 }
226
227 /**
Alexey Kuzmin3a8a39f2019-01-18 16:56:23 +0000228 * Create a predefined vibration effect.
229 *
230 * Predefined effects are a set of common vibration effects that should be identical, regardless
231 * of the app they come from, in order to provide a cohesive experience for users across
232 * the entire device. They also may be custom tailored to the device hardware in order to
233 * provide a better experience than you could otherwise build using the generic building
234 * blocks.
235 *
236 * This will fallback to a generic pattern if one exists and there does not exist a
237 * hardware-specific implementation of the effect.
238 *
239 * @param effectId The ID of the effect to perform:
240 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
241 *
242 * @return The desired effect.
243 */
Arthur Hungcfdc9752019-03-04 19:25:36 +0800244 @NonNull
Arthur Hung00657b82019-02-26 11:56:40 +0800245 public static VibrationEffect createPredefined(@EffectType int effectId) {
Alexey Kuzmin3a8a39f2019-01-18 16:56:23 +0000246 return get(effectId, true);
247 }
248
249 /**
Michael Wright71216972017-01-31 18:33:54 +0000250 * Get a predefined vibration effect.
251 *
252 * Predefined effects are a set of common vibration effects that should be identical, regardless
253 * of the app they come from, in order to provide a cohesive experience for users across
254 * the entire device. They also may be custom tailored to the device hardware in order to
255 * provide a better experience than you could otherwise build using the generic building
256 * blocks.
257 *
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100258 * This will fallback to a generic pattern if one exists and there does not exist a
259 * hardware-specific implementation of the effect.
260 *
Michael Wright71216972017-01-31 18:33:54 +0000261 * @param effectId The ID of the effect to perform:
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100262 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
Michael Wright71216972017-01-31 18:33:54 +0000263 *
264 * @return The desired effect.
265 * @hide
266 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000267 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000268 public static VibrationEffect get(int effectId) {
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100269 return get(effectId, true);
270 }
271
272 /**
273 * Get a predefined vibration effect.
274 *
275 * Predefined effects are a set of common vibration effects that should be identical, regardless
276 * of the app they come from, in order to provide a cohesive experience for users across
277 * the entire device. They also may be custom tailored to the device hardware in order to
278 * provide a better experience than you could otherwise build using the generic building
279 * blocks.
280 *
281 * Some effects you may only want to play if there's a hardware specific implementation because
282 * they may, for example, be too disruptive to the user without tuning. The {@code fallback}
283 * parameter allows you to decide whether you want to fallback to the generic implementation or
284 * only play if there's a tuned, hardware specific one available.
285 *
286 * @param effectId The ID of the effect to perform:
287 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
288 * @param fallback Whether to fallback to a generic pattern if a hardware specific
289 * implementation doesn't exist.
290 *
291 * @return The desired effect.
292 * @hide
293 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000294 @TestApi
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100295 public static VibrationEffect get(int effectId, boolean fallback) {
296 VibrationEffect effect = new Prebaked(effectId, fallback);
Michael Wright71216972017-01-31 18:33:54 +0000297 effect.validate();
298 return effect;
299 }
300
Michael Wrightf268bf52018-02-07 23:23:34 +0000301 /**
302 * Get a predefined vibration effect associated with a given URI.
303 *
304 * Predefined effects are a set of common vibration effects that should be identical, regardless
305 * of the app they come from, in order to provide a cohesive experience for users across
306 * the entire device. They also may be custom tailored to the device hardware in order to
307 * provide a better experience than you could otherwise build using the generic building
308 * blocks.
309 *
310 * @param uri The URI associated with the haptic effect.
311 * @param context The context used to get the URI to haptic effect association.
312 *
313 * @return The desired effect, or {@code null} if there's no associated effect.
314 *
315 * @hide
316 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000317 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +0000318 @Nullable
319 public static VibrationEffect get(Uri uri, Context context) {
320 String[] uris = context.getResources().getStringArray(
321 com.android.internal.R.array.config_ringtoneEffectUris);
322 for (int i = 0; i < uris.length && i < RINGTONES.length; i++) {
323 if (uris[i] == null) {
324 continue;
325 }
Michael Wright32fa5932018-05-18 18:07:09 -0700326 ContentResolver cr = context.getContentResolver();
327 Uri mappedUri = cr.uncanonicalize(Uri.parse(uris[i]));
328 if (mappedUri == null) {
329 continue;
330 }
331 if (mappedUri.equals(uri)) {
Michael Wrightf268bf52018-02-07 23:23:34 +0000332 return get(RINGTONES[i]);
333 }
334 }
335 return null;
336 }
337
Michael Wright71216972017-01-31 18:33:54 +0000338 @Override
339 public int describeContents() {
340 return 0;
341 }
342
343 /** @hide */
344 public abstract void validate();
345
Michael Wright35a0c672018-01-24 00:32:53 +0000346 /**
347 * Gets the estimated duration of the vibration in milliseconds.
348 *
349 * For effects without a defined end (e.g. a Waveform with a non-negative repeat index), this
350 * returns Long.MAX_VALUE. For effects with an unknown duration (e.g. Prebaked effects where
351 * the length is device and potentially run-time dependent), this returns -1.
352 *
353 * @hide
354 */
Jeff Sharkeyc6091162018-06-29 17:15:40 -0600355 @TestApi
Michael Wright35a0c672018-01-24 00:32:53 +0000356 public abstract long getDuration();
357
358 /**
359 * Scale the amplitude with the given constraints.
360 *
361 * This assumes that the previous value was in the range [0, MAX_AMPLITUDE]
362 * @hide
363 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000364 @TestApi
Michael Wright35a0c672018-01-24 00:32:53 +0000365 protected static int scale(int amplitude, float gamma, int maxAmplitude) {
366 float val = MathUtils.pow(amplitude / (float) MAX_AMPLITUDE, gamma);
367 return (int) (val * maxAmplitude);
368 }
369
Michael Wright71216972017-01-31 18:33:54 +0000370 /** @hide */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000371 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000372 public static class OneShot extends VibrationEffect implements Parcelable {
Michael Wright35a0c672018-01-24 00:32:53 +0000373 private final long mDuration;
374 private final int mAmplitude;
Michael Wright71216972017-01-31 18:33:54 +0000375
376 public OneShot(Parcel in) {
Michael Wright35a0c672018-01-24 00:32:53 +0000377 mDuration = in.readLong();
378 mAmplitude = in.readInt();
Michael Wright71216972017-01-31 18:33:54 +0000379 }
380
381 public OneShot(long milliseconds, int amplitude) {
Michael Wright35a0c672018-01-24 00:32:53 +0000382 mDuration = milliseconds;
Michael Wright71216972017-01-31 18:33:54 +0000383 mAmplitude = amplitude;
384 }
385
Michael Wright35a0c672018-01-24 00:32:53 +0000386 @Override
387 public long getDuration() {
388 return mDuration;
Michael Wright71216972017-01-31 18:33:54 +0000389 }
390
391 public int getAmplitude() {
392 return mAmplitude;
393 }
394
Michael Wright35a0c672018-01-24 00:32:53 +0000395 /**
396 * Scale the amplitude of this effect.
397 *
398 * @param gamma the gamma adjustment to apply
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000399 * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and
400 * MAX_AMPLITUDE
401 * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE
Michael Wright35a0c672018-01-24 00:32:53 +0000402 *
403 * @return A {@link OneShot} effect with the same timing but scaled amplitude.
404 */
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000405 public OneShot scale(float gamma, int maxAmplitude) {
406 if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) {
407 throw new IllegalArgumentException(
408 "Amplitude is negative or greater than MAX_AMPLITUDE");
409 }
Michael Wright35a0c672018-01-24 00:32:53 +0000410 int newAmplitude = scale(mAmplitude, gamma, maxAmplitude);
411 return new OneShot(mDuration, newAmplitude);
412 }
413
Alexey Kuzmin55bdc592018-04-24 12:58:13 +0100414 /**
415 * Resolve default values into integer amplitude numbers.
416 *
417 * @param defaultAmplitude the default amplitude to apply, must be between 0 and
418 * MAX_AMPLITUDE
419 * @return A {@link OneShot} effect with same physical meaning but explicitly set amplitude
420 *
421 * @hide
422 */
423 public OneShot resolve(int defaultAmplitude) {
424 if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
425 throw new IllegalArgumentException(
426 "Amplitude is negative or greater than MAX_AMPLITUDE");
427 }
428 if (mAmplitude == DEFAULT_AMPLITUDE) {
429 return new OneShot(mDuration, defaultAmplitude);
430 }
431 return this;
432 }
433
Michael Wright71216972017-01-31 18:33:54 +0000434 @Override
435 public void validate() {
436 if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) {
437 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000438 "amplitude must either be DEFAULT_AMPLITUDE, "
439 + "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")");
Michael Wright71216972017-01-31 18:33:54 +0000440 }
Michael Wright35a0c672018-01-24 00:32:53 +0000441 if (mDuration <= 0) {
Michael Wright0ef6edd2017-04-05 20:57:39 +0100442 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000443 "duration must be positive (duration=" + mDuration + ")");
Michael Wright71216972017-01-31 18:33:54 +0000444 }
445 }
446
447 @Override
448 public boolean equals(Object o) {
449 if (!(o instanceof VibrationEffect.OneShot)) {
450 return false;
451 }
452 VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
Michael Wright35a0c672018-01-24 00:32:53 +0000453 return other.mDuration == mDuration && other.mAmplitude == mAmplitude;
Michael Wright71216972017-01-31 18:33:54 +0000454 }
455
456 @Override
457 public int hashCode() {
458 int result = 17;
Michael Wright35a0c672018-01-24 00:32:53 +0000459 result += 37 * (int) mDuration;
460 result += 37 * mAmplitude;
Michael Wright71216972017-01-31 18:33:54 +0000461 return result;
462 }
463
464 @Override
465 public String toString() {
Michael Wright35a0c672018-01-24 00:32:53 +0000466 return "OneShot{mDuration=" + mDuration + ", mAmplitude=" + mAmplitude + "}";
Michael Wright71216972017-01-31 18:33:54 +0000467 }
468
469 @Override
470 public void writeToParcel(Parcel out, int flags) {
471 out.writeInt(PARCEL_TOKEN_ONE_SHOT);
Michael Wright35a0c672018-01-24 00:32:53 +0000472 out.writeLong(mDuration);
Michael Wright71216972017-01-31 18:33:54 +0000473 out.writeInt(mAmplitude);
474 }
475
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700476 public static final @android.annotation.NonNull Parcelable.Creator<OneShot> CREATOR =
Michael Wright71216972017-01-31 18:33:54 +0000477 new Parcelable.Creator<OneShot>() {
478 @Override
479 public OneShot createFromParcel(Parcel in) {
480 // Skip the type token
481 in.readInt();
482 return new OneShot(in);
483 }
484 @Override
485 public OneShot[] newArray(int size) {
486 return new OneShot[size];
487 }
488 };
489 }
490
491 /** @hide */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000492 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000493 public static class Waveform extends VibrationEffect implements Parcelable {
Michael Wright35a0c672018-01-24 00:32:53 +0000494 private final long[] mTimings;
495 private final int[] mAmplitudes;
496 private final int mRepeat;
Michael Wright71216972017-01-31 18:33:54 +0000497
498 public Waveform(Parcel in) {
499 this(in.createLongArray(), in.createIntArray(), in.readInt());
500 }
501
502 public Waveform(long[] timings, int[] amplitudes, int repeat) {
503 mTimings = new long[timings.length];
504 System.arraycopy(timings, 0, mTimings, 0, timings.length);
505 mAmplitudes = new int[amplitudes.length];
506 System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length);
507 mRepeat = repeat;
508 }
509
510 public long[] getTimings() {
511 return mTimings;
512 }
513
514 public int[] getAmplitudes() {
515 return mAmplitudes;
516 }
517
518 public int getRepeatIndex() {
519 return mRepeat;
520 }
521
522 @Override
Michael Wright35a0c672018-01-24 00:32:53 +0000523 public long getDuration() {
524 if (mRepeat >= 0) {
525 return Long.MAX_VALUE;
526 }
527 long duration = 0;
528 for (long d : mTimings) {
529 duration += d;
530 }
531 return duration;
532 }
533
534 /**
535 * Scale the Waveform with the given gamma and new max amplitude.
536 *
537 * @param gamma the gamma adjustment to apply
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000538 * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and
539 * MAX_AMPLITUDE
540 * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE
Michael Wright35a0c672018-01-24 00:32:53 +0000541 *
542 * @return A {@link Waveform} effect with the same timings and repeat index
543 * but scaled amplitude.
544 */
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000545 public Waveform scale(float gamma, int maxAmplitude) {
546 if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) {
547 throw new IllegalArgumentException(
548 "Amplitude is negative or greater than MAX_AMPLITUDE");
549 }
Michael Wright35a0c672018-01-24 00:32:53 +0000550 if (gamma == 1.0f && maxAmplitude == MAX_AMPLITUDE) {
551 // Just return a copy of the original if there's no scaling to be done.
552 return new Waveform(mTimings, mAmplitudes, mRepeat);
553 }
554
555 int[] scaledAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
556 for (int i = 0; i < scaledAmplitudes.length; i++) {
557 scaledAmplitudes[i] = scale(scaledAmplitudes[i], gamma, maxAmplitude);
558 }
559 return new Waveform(mTimings, scaledAmplitudes, mRepeat);
560 }
561
Alexey Kuzmin55bdc592018-04-24 12:58:13 +0100562 /**
563 * Resolve default values into integer amplitude numbers.
564 *
565 * @param defaultAmplitude the default amplitude to apply, must be between 0 and
566 * MAX_AMPLITUDE
567 * @return A {@link Waveform} effect with same physical meaning but explicitly set
568 * amplitude
569 *
570 * @hide
571 */
572 public Waveform resolve(int defaultAmplitude) {
573 if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
574 throw new IllegalArgumentException(
575 "Amplitude is negative or greater than MAX_AMPLITUDE");
576 }
577 int[] resolvedAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
578 for (int i = 0; i < resolvedAmplitudes.length; i++) {
579 if (resolvedAmplitudes[i] == DEFAULT_AMPLITUDE) {
580 resolvedAmplitudes[i] = defaultAmplitude;
581 }
582 }
583 return new Waveform(mTimings, resolvedAmplitudes, mRepeat);
584 }
585
Michael Wright35a0c672018-01-24 00:32:53 +0000586 @Override
Michael Wright71216972017-01-31 18:33:54 +0000587 public void validate() {
588 if (mTimings.length != mAmplitudes.length) {
589 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000590 "timing and amplitude arrays must be of equal length"
591 + " (timings.length=" + mTimings.length
592 + ", amplitudes.length=" + mAmplitudes.length + ")");
Michael Wright71216972017-01-31 18:33:54 +0000593 }
594 if (!hasNonZeroEntry(mTimings)) {
Michael Wright35a0c672018-01-24 00:32:53 +0000595 throw new IllegalArgumentException("at least one timing must be non-zero"
596 + " (timings=" + Arrays.toString(mTimings) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000597 }
598 for (long timing : mTimings) {
599 if (timing < 0) {
Michael Wright35a0c672018-01-24 00:32:53 +0000600 throw new IllegalArgumentException("timings must all be >= 0"
601 + " (timings=" + Arrays.toString(mTimings) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000602 }
603 }
604 for (int amplitude : mAmplitudes) {
605 if (amplitude < -1 || amplitude > 255) {
606 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000607 "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255"
608 + " (amplitudes=" + Arrays.toString(mAmplitudes) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000609 }
610 }
611 if (mRepeat < -1 || mRepeat >= mTimings.length) {
Michael Wright0ef6edd2017-04-05 20:57:39 +0100612 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000613 "repeat index must be within the bounds of the timings array"
614 + " (timings.length=" + mTimings.length + ", index=" + mRepeat + ")");
Michael Wright71216972017-01-31 18:33:54 +0000615 }
616 }
617
618 @Override
619 public boolean equals(Object o) {
620 if (!(o instanceof VibrationEffect.Waveform)) {
621 return false;
622 }
623 VibrationEffect.Waveform other = (VibrationEffect.Waveform) o;
Michael Wright35a0c672018-01-24 00:32:53 +0000624 return Arrays.equals(mTimings, other.mTimings)
625 && Arrays.equals(mAmplitudes, other.mAmplitudes)
626 && mRepeat == other.mRepeat;
Michael Wright71216972017-01-31 18:33:54 +0000627 }
628
629 @Override
630 public int hashCode() {
631 int result = 17;
Michael Wright35a0c672018-01-24 00:32:53 +0000632 result += 37 * Arrays.hashCode(mTimings);
633 result += 37 * Arrays.hashCode(mAmplitudes);
634 result += 37 * mRepeat;
Michael Wright71216972017-01-31 18:33:54 +0000635 return result;
636 }
637
638 @Override
639 public String toString() {
Michael Wright35a0c672018-01-24 00:32:53 +0000640 return "Waveform{mTimings=" + Arrays.toString(mTimings)
641 + ", mAmplitudes=" + Arrays.toString(mAmplitudes)
642 + ", mRepeat=" + mRepeat
643 + "}";
Michael Wright71216972017-01-31 18:33:54 +0000644 }
645
646 @Override
647 public void writeToParcel(Parcel out, int flags) {
648 out.writeInt(PARCEL_TOKEN_WAVEFORM);
649 out.writeLongArray(mTimings);
650 out.writeIntArray(mAmplitudes);
651 out.writeInt(mRepeat);
652 }
653
654 private static boolean hasNonZeroEntry(long[] vals) {
655 for (long val : vals) {
656 if (val != 0) {
657 return true;
658 }
659 }
660 return false;
661 }
662
663
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700664 public static final @android.annotation.NonNull Parcelable.Creator<Waveform> CREATOR =
Michael Wright71216972017-01-31 18:33:54 +0000665 new Parcelable.Creator<Waveform>() {
666 @Override
667 public Waveform createFromParcel(Parcel in) {
668 // Skip the type token
669 in.readInt();
670 return new Waveform(in);
671 }
672 @Override
673 public Waveform[] newArray(int size) {
674 return new Waveform[size];
675 }
676 };
677 }
678
679 /** @hide */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000680 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000681 public static class Prebaked extends VibrationEffect implements Parcelable {
Michael Wright35a0c672018-01-24 00:32:53 +0000682 private final int mEffectId;
683 private final boolean mFallback;
684
685 private int mEffectStrength;
Michael Wright71216972017-01-31 18:33:54 +0000686
687 public Prebaked(Parcel in) {
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100688 this(in.readInt(), in.readByte() != 0);
Michael Wright35a0c672018-01-24 00:32:53 +0000689 mEffectStrength = in.readInt();
Michael Wright71216972017-01-31 18:33:54 +0000690 }
691
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100692 public Prebaked(int effectId, boolean fallback) {
Michael Wright71216972017-01-31 18:33:54 +0000693 mEffectId = effectId;
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100694 mFallback = fallback;
Michael Wright35a0c672018-01-24 00:32:53 +0000695 mEffectStrength = EffectStrength.MEDIUM;
Michael Wright71216972017-01-31 18:33:54 +0000696 }
697
698 public int getId() {
699 return mEffectId;
700 }
701
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100702 /**
703 * Whether the effect should fall back to a generic pattern if there's no hardware specific
704 * implementation of it.
705 */
706 public boolean shouldFallback() {
707 return mFallback;
708 }
709
Michael Wright71216972017-01-31 18:33:54 +0000710 @Override
Michael Wright35a0c672018-01-24 00:32:53 +0000711 public long getDuration() {
712 return -1;
713 }
714
715 /**
716 * Set the effect strength of the prebaked effect.
717 */
718 public void setEffectStrength(int strength) {
719 if (!isValidEffectStrength(strength)) {
720 throw new IllegalArgumentException("Invalid effect strength: " + strength);
721 }
722 mEffectStrength = strength;
723 }
724
725 /**
726 * Set the effect strength.
727 */
728 public int getEffectStrength() {
729 return mEffectStrength;
730 }
731
732 private static boolean isValidEffectStrength(int strength) {
733 switch (strength) {
734 case EffectStrength.LIGHT:
735 case EffectStrength.MEDIUM:
736 case EffectStrength.STRONG:
737 return true;
738 default:
739 return false;
740 }
741 }
742
743 @Override
Michael Wright71216972017-01-31 18:33:54 +0000744 public void validate() {
Michael Wright57d94d92017-05-31 14:44:45 +0100745 switch (mEffectId) {
746 case EFFECT_CLICK:
747 case EFFECT_DOUBLE_CLICK:
748 case EFFECT_TICK:
Michael Wrightf268bf52018-02-07 23:23:34 +0000749 case EFFECT_THUD:
750 case EFFECT_POP:
751 case EFFECT_HEAVY_CLICK:
Michael Wright57d94d92017-05-31 14:44:45 +0100752 break;
753 default:
Michael Wrightf268bf52018-02-07 23:23:34 +0000754 if (mEffectId < RINGTONES[0] || mEffectId > RINGTONES[RINGTONES.length - 1]) {
755 throw new IllegalArgumentException(
756 "Unknown prebaked effect type (value=" + mEffectId + ")");
757 }
Michael Wright71216972017-01-31 18:33:54 +0000758 }
Michael Wright35a0c672018-01-24 00:32:53 +0000759 if (!isValidEffectStrength(mEffectStrength)) {
760 throw new IllegalArgumentException(
761 "Unknown prebaked effect strength (value=" + mEffectStrength + ")");
762 }
Michael Wright71216972017-01-31 18:33:54 +0000763 }
764
765 @Override
766 public boolean equals(Object o) {
767 if (!(o instanceof VibrationEffect.Prebaked)) {
768 return false;
769 }
770 VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
Michael Wright35a0c672018-01-24 00:32:53 +0000771 return mEffectId == other.mEffectId
772 && mFallback == other.mFallback
773 && mEffectStrength == other.mEffectStrength;
Michael Wright71216972017-01-31 18:33:54 +0000774 }
775
776 @Override
777 public int hashCode() {
Michael Wright35a0c672018-01-24 00:32:53 +0000778 int result = 17;
779 result += 37 * mEffectId;
780 result += 37 * mEffectStrength;
781 return result;
Michael Wright71216972017-01-31 18:33:54 +0000782 }
783
784 @Override
785 public String toString() {
Michael Wright35a0c672018-01-24 00:32:53 +0000786 return "Prebaked{mEffectId=" + mEffectId
787 + ", mEffectStrength=" + mEffectStrength
788 + ", mFallback=" + mFallback
789 + "}";
Michael Wright71216972017-01-31 18:33:54 +0000790 }
791
792
793 @Override
794 public void writeToParcel(Parcel out, int flags) {
795 out.writeInt(PARCEL_TOKEN_EFFECT);
796 out.writeInt(mEffectId);
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100797 out.writeByte((byte) (mFallback ? 1 : 0));
Michael Wright35a0c672018-01-24 00:32:53 +0000798 out.writeInt(mEffectStrength);
Michael Wright71216972017-01-31 18:33:54 +0000799 }
800
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700801 public static final @android.annotation.NonNull Parcelable.Creator<Prebaked> CREATOR =
Michael Wright71216972017-01-31 18:33:54 +0000802 new Parcelable.Creator<Prebaked>() {
803 @Override
804 public Prebaked createFromParcel(Parcel in) {
805 // Skip the type token
806 in.readInt();
807 return new Prebaked(in);
808 }
809 @Override
810 public Prebaked[] newArray(int size) {
811 return new Prebaked[size];
812 }
813 };
814 }
815
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700816 public static final @android.annotation.NonNull Parcelable.Creator<VibrationEffect> CREATOR =
Michael Wright71216972017-01-31 18:33:54 +0000817 new Parcelable.Creator<VibrationEffect>() {
818 @Override
819 public VibrationEffect createFromParcel(Parcel in) {
820 int token = in.readInt();
821 if (token == PARCEL_TOKEN_ONE_SHOT) {
822 return new OneShot(in);
823 } else if (token == PARCEL_TOKEN_WAVEFORM) {
824 return new Waveform(in);
825 } else if (token == PARCEL_TOKEN_EFFECT) {
826 return new Prebaked(in);
827 } else {
828 throw new IllegalStateException(
829 "Unexpected vibration event type token in parcel.");
830 }
831 }
832 @Override
833 public VibrationEffect[] newArray(int size) {
834 return new VibrationEffect[size];
835 }
836 };
837}