blob: 471ae30a405835f89f99af3fe029f67049e3f500 [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;
Michael Wrightf268bf52018-02-07 23:23:34 +000020import android.annotation.Nullable;
Alexey Kuzmin505872d2018-03-19 19:27:46 +000021import android.annotation.TestApi;
Michael Wright32fa5932018-05-18 18:07:09 -070022import android.content.ContentResolver;
Michael Wrightf268bf52018-02-07 23:23:34 +000023import android.content.Context;
24import android.hardware.vibrator.V1_0.EffectStrength;
25import android.hardware.vibrator.V1_2.Effect;
26import android.net.Uri;
Michael Wright35a0c672018-01-24 00:32:53 +000027import android.util.MathUtils;
Michael Wright71216972017-01-31 18:33:54 +000028
Alexey Kuzmin3a8a39f2019-01-18 16:56:23 +000029import java.lang.annotation.Retention;
30import java.lang.annotation.RetentionPolicy;
Michael Wright71216972017-01-31 18:33:54 +000031import java.util.Arrays;
32
33/**
34 * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
35 *
36 * These effects may be any number of things, from single shot vibrations to complex waveforms.
37 */
38public abstract class VibrationEffect implements Parcelable {
39 private static final int PARCEL_TOKEN_ONE_SHOT = 1;
40 private static final int PARCEL_TOKEN_WAVEFORM = 2;
41 private static final int PARCEL_TOKEN_EFFECT = 3;
42
43 /**
44 * The default vibration strength of the device.
45 */
46 public static final int DEFAULT_AMPLITUDE = -1;
47
48 /**
Michael Wright35a0c672018-01-24 00:32:53 +000049 * The maximum amplitude value
50 * @hide
51 */
52 public static final int MAX_AMPLITUDE = 255;
53
54 /**
Michael Wright71216972017-01-31 18:33:54 +000055 * A click effect.
56 *
57 * @see #get(int)
Michael Wright71216972017-01-31 18:33:54 +000058 */
Michael Wrightf268bf52018-02-07 23:23:34 +000059 public static final int EFFECT_CLICK = Effect.CLICK;
Michael Wright71216972017-01-31 18:33:54 +000060
61 /**
62 * A double click effect.
63 *
64 * @see #get(int)
Michael Wright71216972017-01-31 18:33:54 +000065 */
Michael Wrightf268bf52018-02-07 23:23:34 +000066 public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK;
Michael Wright57d94d92017-05-31 14:44:45 +010067
68 /**
69 * A tick effect.
70 * @see #get(int)
Michael Wright57d94d92017-05-31 14:44:45 +010071 */
Michael Wrightf268bf52018-02-07 23:23:34 +000072 public static final int EFFECT_TICK = Effect.TICK;
73
74 /**
75 * A thud effect.
76 * @see #get(int)
77 * @hide
78 */
Jeff Sharkeyc6091162018-06-29 17:15:40 -060079 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +000080 public static final int EFFECT_THUD = Effect.THUD;
81
82 /**
83 * A pop effect.
84 * @see #get(int)
85 * @hide
86 */
Jeff Sharkeyc6091162018-06-29 17:15:40 -060087 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +000088 public static final int EFFECT_POP = Effect.POP;
89
90 /**
91 * A heavy click effect.
92 * @see #get(int)
Michael Wrightf268bf52018-02-07 23:23:34 +000093 */
94 public static final int EFFECT_HEAVY_CLICK = Effect.HEAVY_CLICK;
95
Jeff Sharkeyc6091162018-06-29 17:15:40 -060096 /** {@hide} */
97 @TestApi
98 public static final int EFFECT_STRENGTH_LIGHT = EffectStrength.LIGHT;
99
100 /** {@hide} */
101 @TestApi
102 public static final int EFFECT_STRENGTH_MEDIUM = EffectStrength.MEDIUM;
103
104 /** {@hide} */
105 @TestApi
106 public static final int EFFECT_STRENGTH_STRONG = EffectStrength.STRONG;
Michael Wrightf268bf52018-02-07 23:23:34 +0000107
108 /**
109 * Ringtone patterns. They may correspond with the device's ringtone audio, or may just be a
110 * pattern that can be played as a ringtone with any audio, depending on the device.
111 *
112 * @see #get(Uri, Context)
113 * @hide
114 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000115 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +0000116 public static final int[] RINGTONES = {
117 Effect.RINGTONE_1,
118 Effect.RINGTONE_2,
119 Effect.RINGTONE_3,
120 Effect.RINGTONE_4,
121 Effect.RINGTONE_5,
122 Effect.RINGTONE_6,
123 Effect.RINGTONE_7,
124 Effect.RINGTONE_8,
125 Effect.RINGTONE_9,
126 Effect.RINGTONE_10,
127 Effect.RINGTONE_11,
128 Effect.RINGTONE_12,
129 Effect.RINGTONE_13,
130 Effect.RINGTONE_14,
131 Effect.RINGTONE_15
132 };
Michael Wright71216972017-01-31 18:33:54 +0000133
Alexey Kuzmin3a8a39f2019-01-18 16:56:23 +0000134 /** @hide */
135 @IntDef(prefix = { "EFFECT_" }, value = {
136 EFFECT_TICK,
137 EFFECT_CLICK,
138 EFFECT_HEAVY_CLICK,
139 EFFECT_DOUBLE_CLICK,
140 })
141 @Retention(RetentionPolicy.SOURCE)
142 public @interface EffectType {}
143
Michael Wright71216972017-01-31 18:33:54 +0000144 /** @hide to prevent subclassing from outside of the framework */
145 public VibrationEffect() { }
146
147 /**
148 * Create a one shot vibration.
149 *
150 * One shot vibrations will vibrate constantly for the specified period of time at the
151 * specified amplitude, and then stop.
152 *
153 * @param milliseconds The number of milliseconds to vibrate. This must be a positive number.
154 * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or
155 * {@link #DEFAULT_AMPLITUDE}.
156 *
157 * @return The desired effect.
158 */
159 public static VibrationEffect createOneShot(long milliseconds, int amplitude) {
160 VibrationEffect effect = new OneShot(milliseconds, amplitude);
161 effect.validate();
162 return effect;
163 }
164
165 /**
166 * Create a waveform vibration.
167 *
168 * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
169 * each pair, the value in the amplitude array determines the strength of the vibration and the
170 * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
171 * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
172 * <p>
173 * The amplitude array of the generated waveform will be the same size as the given
174 * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE},
175 * starting with 0. Therefore the first timing value will be the period to wait before turning
176 * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE}
177 * strength, etc.
178 * </p><p>
179 * To cause the pattern to repeat, pass the index into the timings array at which to start the
180 * repetition, or -1 to disable repeating.
181 * </p>
182 *
183 * @param timings The pattern of alternating on-off timings, starting with off. Timing values
184 * of 0 will cause the timing / amplitude pair to be ignored.
185 * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
186 * want to repeat.
187 *
188 * @return The desired effect.
189 */
190 public static VibrationEffect createWaveform(long[] timings, int repeat) {
191 int[] amplitudes = new int[timings.length];
192 for (int i = 0; i < (timings.length / 2); i++) {
193 amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE;
194 }
195 return createWaveform(timings, amplitudes, repeat);
196 }
197
198 /**
199 * Create a waveform vibration.
200 *
201 * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
202 * each pair, the value in the amplitude array determines the strength of the vibration and the
203 * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
204 * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
205 * </p><p>
206 * To cause the pattern to repeat, pass the index into the timings array at which to start the
207 * repetition, or -1 to disable repeating.
208 * </p>
209 *
210 * @param timings The timing values of the timing / amplitude pairs. Timing values of 0
211 * will cause the pair to be ignored.
212 * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values
213 * must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An
214 * amplitude value of 0 implies the motor is off.
215 * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
216 * want to repeat.
217 *
218 * @return The desired effect.
219 */
220 public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
221 VibrationEffect effect = new Waveform(timings, amplitudes, repeat);
222 effect.validate();
223 return effect;
224 }
225
226 /**
Alexey Kuzmin3a8a39f2019-01-18 16:56:23 +0000227 * Create a predefined vibration effect.
228 *
229 * Predefined effects are a set of common vibration effects that should be identical, regardless
230 * of the app they come from, in order to provide a cohesive experience for users across
231 * the entire device. They also may be custom tailored to the device hardware in order to
232 * provide a better experience than you could otherwise build using the generic building
233 * blocks.
234 *
235 * This will fallback to a generic pattern if one exists and there does not exist a
236 * hardware-specific implementation of the effect.
237 *
238 * @param effectId The ID of the effect to perform:
239 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
240 *
241 * @return The desired effect.
242 */
Arthur Hung00657b82019-02-26 11:56:40 +0800243 public static VibrationEffect createPredefined(@EffectType int effectId) {
Alexey Kuzmin3a8a39f2019-01-18 16:56:23 +0000244 return get(effectId, true);
245 }
246
247 /**
Michael Wright71216972017-01-31 18:33:54 +0000248 * Get a predefined vibration effect.
249 *
250 * Predefined effects are a set of common vibration effects that should be identical, regardless
251 * of the app they come from, in order to provide a cohesive experience for users across
252 * the entire device. They also may be custom tailored to the device hardware in order to
253 * provide a better experience than you could otherwise build using the generic building
254 * blocks.
255 *
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100256 * This will fallback to a generic pattern if one exists and there does not exist a
257 * hardware-specific implementation of the effect.
258 *
Michael Wright71216972017-01-31 18:33:54 +0000259 * @param effectId The ID of the effect to perform:
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100260 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
Michael Wright71216972017-01-31 18:33:54 +0000261 *
262 * @return The desired effect.
263 * @hide
264 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000265 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000266 public static VibrationEffect get(int effectId) {
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100267 return get(effectId, true);
268 }
269
270 /**
271 * Get a predefined vibration effect.
272 *
273 * Predefined effects are a set of common vibration effects that should be identical, regardless
274 * of the app they come from, in order to provide a cohesive experience for users across
275 * the entire device. They also may be custom tailored to the device hardware in order to
276 * provide a better experience than you could otherwise build using the generic building
277 * blocks.
278 *
279 * Some effects you may only want to play if there's a hardware specific implementation because
280 * they may, for example, be too disruptive to the user without tuning. The {@code fallback}
281 * parameter allows you to decide whether you want to fallback to the generic implementation or
282 * only play if there's a tuned, hardware specific one available.
283 *
284 * @param effectId The ID of the effect to perform:
285 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
286 * @param fallback Whether to fallback to a generic pattern if a hardware specific
287 * implementation doesn't exist.
288 *
289 * @return The desired effect.
290 * @hide
291 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000292 @TestApi
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100293 public static VibrationEffect get(int effectId, boolean fallback) {
294 VibrationEffect effect = new Prebaked(effectId, fallback);
Michael Wright71216972017-01-31 18:33:54 +0000295 effect.validate();
296 return effect;
297 }
298
Michael Wrightf268bf52018-02-07 23:23:34 +0000299 /**
300 * Get a predefined vibration effect associated with a given URI.
301 *
302 * Predefined effects are a set of common vibration effects that should be identical, regardless
303 * of the app they come from, in order to provide a cohesive experience for users across
304 * the entire device. They also may be custom tailored to the device hardware in order to
305 * provide a better experience than you could otherwise build using the generic building
306 * blocks.
307 *
308 * @param uri The URI associated with the haptic effect.
309 * @param context The context used to get the URI to haptic effect association.
310 *
311 * @return The desired effect, or {@code null} if there's no associated effect.
312 *
313 * @hide
314 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000315 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +0000316 @Nullable
317 public static VibrationEffect get(Uri uri, Context context) {
318 String[] uris = context.getResources().getStringArray(
319 com.android.internal.R.array.config_ringtoneEffectUris);
320 for (int i = 0; i < uris.length && i < RINGTONES.length; i++) {
321 if (uris[i] == null) {
322 continue;
323 }
Michael Wright32fa5932018-05-18 18:07:09 -0700324 ContentResolver cr = context.getContentResolver();
325 Uri mappedUri = cr.uncanonicalize(Uri.parse(uris[i]));
326 if (mappedUri == null) {
327 continue;
328 }
329 if (mappedUri.equals(uri)) {
Michael Wrightf268bf52018-02-07 23:23:34 +0000330 return get(RINGTONES[i]);
331 }
332 }
333 return null;
334 }
335
Michael Wright71216972017-01-31 18:33:54 +0000336 @Override
337 public int describeContents() {
338 return 0;
339 }
340
341 /** @hide */
342 public abstract void validate();
343
Michael Wright35a0c672018-01-24 00:32:53 +0000344 /**
345 * Gets the estimated duration of the vibration in milliseconds.
346 *
347 * For effects without a defined end (e.g. a Waveform with a non-negative repeat index), this
348 * returns Long.MAX_VALUE. For effects with an unknown duration (e.g. Prebaked effects where
349 * the length is device and potentially run-time dependent), this returns -1.
350 *
351 * @hide
352 */
Jeff Sharkeyc6091162018-06-29 17:15:40 -0600353 @TestApi
Michael Wright35a0c672018-01-24 00:32:53 +0000354 public abstract long getDuration();
355
356 /**
357 * Scale the amplitude with the given constraints.
358 *
359 * This assumes that the previous value was in the range [0, MAX_AMPLITUDE]
360 * @hide
361 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000362 @TestApi
Michael Wright35a0c672018-01-24 00:32:53 +0000363 protected static int scale(int amplitude, float gamma, int maxAmplitude) {
364 float val = MathUtils.pow(amplitude / (float) MAX_AMPLITUDE, gamma);
365 return (int) (val * maxAmplitude);
366 }
367
Michael Wright71216972017-01-31 18:33:54 +0000368 /** @hide */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000369 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000370 public static class OneShot extends VibrationEffect implements Parcelable {
Michael Wright35a0c672018-01-24 00:32:53 +0000371 private final long mDuration;
372 private final int mAmplitude;
Michael Wright71216972017-01-31 18:33:54 +0000373
374 public OneShot(Parcel in) {
Michael Wright35a0c672018-01-24 00:32:53 +0000375 mDuration = in.readLong();
376 mAmplitude = in.readInt();
Michael Wright71216972017-01-31 18:33:54 +0000377 }
378
379 public OneShot(long milliseconds, int amplitude) {
Michael Wright35a0c672018-01-24 00:32:53 +0000380 mDuration = milliseconds;
Michael Wright71216972017-01-31 18:33:54 +0000381 mAmplitude = amplitude;
382 }
383
Michael Wright35a0c672018-01-24 00:32:53 +0000384 @Override
385 public long getDuration() {
386 return mDuration;
Michael Wright71216972017-01-31 18:33:54 +0000387 }
388
389 public int getAmplitude() {
390 return mAmplitude;
391 }
392
Michael Wright35a0c672018-01-24 00:32:53 +0000393 /**
394 * Scale the amplitude of this effect.
395 *
396 * @param gamma the gamma adjustment to apply
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000397 * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and
398 * MAX_AMPLITUDE
399 * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE
Michael Wright35a0c672018-01-24 00:32:53 +0000400 *
401 * @return A {@link OneShot} effect with the same timing but scaled amplitude.
402 */
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000403 public OneShot scale(float gamma, int maxAmplitude) {
404 if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) {
405 throw new IllegalArgumentException(
406 "Amplitude is negative or greater than MAX_AMPLITUDE");
407 }
Michael Wright35a0c672018-01-24 00:32:53 +0000408 int newAmplitude = scale(mAmplitude, gamma, maxAmplitude);
409 return new OneShot(mDuration, newAmplitude);
410 }
411
Alexey Kuzmin55bdc592018-04-24 12:58:13 +0100412 /**
413 * Resolve default values into integer amplitude numbers.
414 *
415 * @param defaultAmplitude the default amplitude to apply, must be between 0 and
416 * MAX_AMPLITUDE
417 * @return A {@link OneShot} effect with same physical meaning but explicitly set amplitude
418 *
419 * @hide
420 */
421 public OneShot resolve(int defaultAmplitude) {
422 if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
423 throw new IllegalArgumentException(
424 "Amplitude is negative or greater than MAX_AMPLITUDE");
425 }
426 if (mAmplitude == DEFAULT_AMPLITUDE) {
427 return new OneShot(mDuration, defaultAmplitude);
428 }
429 return this;
430 }
431
Michael Wright71216972017-01-31 18:33:54 +0000432 @Override
433 public void validate() {
434 if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) {
435 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000436 "amplitude must either be DEFAULT_AMPLITUDE, "
437 + "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")");
Michael Wright71216972017-01-31 18:33:54 +0000438 }
Michael Wright35a0c672018-01-24 00:32:53 +0000439 if (mDuration <= 0) {
Michael Wright0ef6edd2017-04-05 20:57:39 +0100440 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000441 "duration must be positive (duration=" + mDuration + ")");
Michael Wright71216972017-01-31 18:33:54 +0000442 }
443 }
444
445 @Override
446 public boolean equals(Object o) {
447 if (!(o instanceof VibrationEffect.OneShot)) {
448 return false;
449 }
450 VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
Michael Wright35a0c672018-01-24 00:32:53 +0000451 return other.mDuration == mDuration && other.mAmplitude == mAmplitude;
Michael Wright71216972017-01-31 18:33:54 +0000452 }
453
454 @Override
455 public int hashCode() {
456 int result = 17;
Michael Wright35a0c672018-01-24 00:32:53 +0000457 result += 37 * (int) mDuration;
458 result += 37 * mAmplitude;
Michael Wright71216972017-01-31 18:33:54 +0000459 return result;
460 }
461
462 @Override
463 public String toString() {
Michael Wright35a0c672018-01-24 00:32:53 +0000464 return "OneShot{mDuration=" + mDuration + ", mAmplitude=" + mAmplitude + "}";
Michael Wright71216972017-01-31 18:33:54 +0000465 }
466
467 @Override
468 public void writeToParcel(Parcel out, int flags) {
469 out.writeInt(PARCEL_TOKEN_ONE_SHOT);
Michael Wright35a0c672018-01-24 00:32:53 +0000470 out.writeLong(mDuration);
Michael Wright71216972017-01-31 18:33:54 +0000471 out.writeInt(mAmplitude);
472 }
473
474 public static final Parcelable.Creator<OneShot> CREATOR =
475 new Parcelable.Creator<OneShot>() {
476 @Override
477 public OneShot createFromParcel(Parcel in) {
478 // Skip the type token
479 in.readInt();
480 return new OneShot(in);
481 }
482 @Override
483 public OneShot[] newArray(int size) {
484 return new OneShot[size];
485 }
486 };
487 }
488
489 /** @hide */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000490 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000491 public static class Waveform extends VibrationEffect implements Parcelable {
Michael Wright35a0c672018-01-24 00:32:53 +0000492 private final long[] mTimings;
493 private final int[] mAmplitudes;
494 private final int mRepeat;
Michael Wright71216972017-01-31 18:33:54 +0000495
496 public Waveform(Parcel in) {
497 this(in.createLongArray(), in.createIntArray(), in.readInt());
498 }
499
500 public Waveform(long[] timings, int[] amplitudes, int repeat) {
501 mTimings = new long[timings.length];
502 System.arraycopy(timings, 0, mTimings, 0, timings.length);
503 mAmplitudes = new int[amplitudes.length];
504 System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length);
505 mRepeat = repeat;
506 }
507
508 public long[] getTimings() {
509 return mTimings;
510 }
511
512 public int[] getAmplitudes() {
513 return mAmplitudes;
514 }
515
516 public int getRepeatIndex() {
517 return mRepeat;
518 }
519
520 @Override
Michael Wright35a0c672018-01-24 00:32:53 +0000521 public long getDuration() {
522 if (mRepeat >= 0) {
523 return Long.MAX_VALUE;
524 }
525 long duration = 0;
526 for (long d : mTimings) {
527 duration += d;
528 }
529 return duration;
530 }
531
532 /**
533 * Scale the Waveform with the given gamma and new max amplitude.
534 *
535 * @param gamma the gamma adjustment to apply
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000536 * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and
537 * MAX_AMPLITUDE
538 * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE
Michael Wright35a0c672018-01-24 00:32:53 +0000539 *
540 * @return A {@link Waveform} effect with the same timings and repeat index
541 * but scaled amplitude.
542 */
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000543 public Waveform scale(float gamma, int maxAmplitude) {
544 if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) {
545 throw new IllegalArgumentException(
546 "Amplitude is negative or greater than MAX_AMPLITUDE");
547 }
Michael Wright35a0c672018-01-24 00:32:53 +0000548 if (gamma == 1.0f && maxAmplitude == MAX_AMPLITUDE) {
549 // Just return a copy of the original if there's no scaling to be done.
550 return new Waveform(mTimings, mAmplitudes, mRepeat);
551 }
552
553 int[] scaledAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
554 for (int i = 0; i < scaledAmplitudes.length; i++) {
555 scaledAmplitudes[i] = scale(scaledAmplitudes[i], gamma, maxAmplitude);
556 }
557 return new Waveform(mTimings, scaledAmplitudes, mRepeat);
558 }
559
Alexey Kuzmin55bdc592018-04-24 12:58:13 +0100560 /**
561 * Resolve default values into integer amplitude numbers.
562 *
563 * @param defaultAmplitude the default amplitude to apply, must be between 0 and
564 * MAX_AMPLITUDE
565 * @return A {@link Waveform} effect with same physical meaning but explicitly set
566 * amplitude
567 *
568 * @hide
569 */
570 public Waveform resolve(int defaultAmplitude) {
571 if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
572 throw new IllegalArgumentException(
573 "Amplitude is negative or greater than MAX_AMPLITUDE");
574 }
575 int[] resolvedAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
576 for (int i = 0; i < resolvedAmplitudes.length; i++) {
577 if (resolvedAmplitudes[i] == DEFAULT_AMPLITUDE) {
578 resolvedAmplitudes[i] = defaultAmplitude;
579 }
580 }
581 return new Waveform(mTimings, resolvedAmplitudes, mRepeat);
582 }
583
Michael Wright35a0c672018-01-24 00:32:53 +0000584 @Override
Michael Wright71216972017-01-31 18:33:54 +0000585 public void validate() {
586 if (mTimings.length != mAmplitudes.length) {
587 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000588 "timing and amplitude arrays must be of equal length"
589 + " (timings.length=" + mTimings.length
590 + ", amplitudes.length=" + mAmplitudes.length + ")");
Michael Wright71216972017-01-31 18:33:54 +0000591 }
592 if (!hasNonZeroEntry(mTimings)) {
Michael Wright35a0c672018-01-24 00:32:53 +0000593 throw new IllegalArgumentException("at least one timing must be non-zero"
594 + " (timings=" + Arrays.toString(mTimings) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000595 }
596 for (long timing : mTimings) {
597 if (timing < 0) {
Michael Wright35a0c672018-01-24 00:32:53 +0000598 throw new IllegalArgumentException("timings must all be >= 0"
599 + " (timings=" + Arrays.toString(mTimings) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000600 }
601 }
602 for (int amplitude : mAmplitudes) {
603 if (amplitude < -1 || amplitude > 255) {
604 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000605 "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255"
606 + " (amplitudes=" + Arrays.toString(mAmplitudes) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000607 }
608 }
609 if (mRepeat < -1 || mRepeat >= mTimings.length) {
Michael Wright0ef6edd2017-04-05 20:57:39 +0100610 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000611 "repeat index must be within the bounds of the timings array"
612 + " (timings.length=" + mTimings.length + ", index=" + mRepeat + ")");
Michael Wright71216972017-01-31 18:33:54 +0000613 }
614 }
615
616 @Override
617 public boolean equals(Object o) {
618 if (!(o instanceof VibrationEffect.Waveform)) {
619 return false;
620 }
621 VibrationEffect.Waveform other = (VibrationEffect.Waveform) o;
Michael Wright35a0c672018-01-24 00:32:53 +0000622 return Arrays.equals(mTimings, other.mTimings)
623 && Arrays.equals(mAmplitudes, other.mAmplitudes)
624 && mRepeat == other.mRepeat;
Michael Wright71216972017-01-31 18:33:54 +0000625 }
626
627 @Override
628 public int hashCode() {
629 int result = 17;
Michael Wright35a0c672018-01-24 00:32:53 +0000630 result += 37 * Arrays.hashCode(mTimings);
631 result += 37 * Arrays.hashCode(mAmplitudes);
632 result += 37 * mRepeat;
Michael Wright71216972017-01-31 18:33:54 +0000633 return result;
634 }
635
636 @Override
637 public String toString() {
Michael Wright35a0c672018-01-24 00:32:53 +0000638 return "Waveform{mTimings=" + Arrays.toString(mTimings)
639 + ", mAmplitudes=" + Arrays.toString(mAmplitudes)
640 + ", mRepeat=" + mRepeat
641 + "}";
Michael Wright71216972017-01-31 18:33:54 +0000642 }
643
644 @Override
645 public void writeToParcel(Parcel out, int flags) {
646 out.writeInt(PARCEL_TOKEN_WAVEFORM);
647 out.writeLongArray(mTimings);
648 out.writeIntArray(mAmplitudes);
649 out.writeInt(mRepeat);
650 }
651
652 private static boolean hasNonZeroEntry(long[] vals) {
653 for (long val : vals) {
654 if (val != 0) {
655 return true;
656 }
657 }
658 return false;
659 }
660
661
662 public static final Parcelable.Creator<Waveform> CREATOR =
663 new Parcelable.Creator<Waveform>() {
664 @Override
665 public Waveform createFromParcel(Parcel in) {
666 // Skip the type token
667 in.readInt();
668 return new Waveform(in);
669 }
670 @Override
671 public Waveform[] newArray(int size) {
672 return new Waveform[size];
673 }
674 };
675 }
676
677 /** @hide */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000678 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000679 public static class Prebaked extends VibrationEffect implements Parcelable {
Michael Wright35a0c672018-01-24 00:32:53 +0000680 private final int mEffectId;
681 private final boolean mFallback;
682
683 private int mEffectStrength;
Michael Wright71216972017-01-31 18:33:54 +0000684
685 public Prebaked(Parcel in) {
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100686 this(in.readInt(), in.readByte() != 0);
Michael Wright35a0c672018-01-24 00:32:53 +0000687 mEffectStrength = in.readInt();
Michael Wright71216972017-01-31 18:33:54 +0000688 }
689
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100690 public Prebaked(int effectId, boolean fallback) {
Michael Wright71216972017-01-31 18:33:54 +0000691 mEffectId = effectId;
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100692 mFallback = fallback;
Michael Wright35a0c672018-01-24 00:32:53 +0000693 mEffectStrength = EffectStrength.MEDIUM;
Michael Wright71216972017-01-31 18:33:54 +0000694 }
695
696 public int getId() {
697 return mEffectId;
698 }
699
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100700 /**
701 * Whether the effect should fall back to a generic pattern if there's no hardware specific
702 * implementation of it.
703 */
704 public boolean shouldFallback() {
705 return mFallback;
706 }
707
Michael Wright71216972017-01-31 18:33:54 +0000708 @Override
Michael Wright35a0c672018-01-24 00:32:53 +0000709 public long getDuration() {
710 return -1;
711 }
712
713 /**
714 * Set the effect strength of the prebaked effect.
715 */
716 public void setEffectStrength(int strength) {
717 if (!isValidEffectStrength(strength)) {
718 throw new IllegalArgumentException("Invalid effect strength: " + strength);
719 }
720 mEffectStrength = strength;
721 }
722
723 /**
724 * Set the effect strength.
725 */
726 public int getEffectStrength() {
727 return mEffectStrength;
728 }
729
730 private static boolean isValidEffectStrength(int strength) {
731 switch (strength) {
732 case EffectStrength.LIGHT:
733 case EffectStrength.MEDIUM:
734 case EffectStrength.STRONG:
735 return true;
736 default:
737 return false;
738 }
739 }
740
741 @Override
Michael Wright71216972017-01-31 18:33:54 +0000742 public void validate() {
Michael Wright57d94d92017-05-31 14:44:45 +0100743 switch (mEffectId) {
744 case EFFECT_CLICK:
745 case EFFECT_DOUBLE_CLICK:
746 case EFFECT_TICK:
Michael Wrightf268bf52018-02-07 23:23:34 +0000747 case EFFECT_THUD:
748 case EFFECT_POP:
749 case EFFECT_HEAVY_CLICK:
Michael Wright57d94d92017-05-31 14:44:45 +0100750 break;
751 default:
Michael Wrightf268bf52018-02-07 23:23:34 +0000752 if (mEffectId < RINGTONES[0] || mEffectId > RINGTONES[RINGTONES.length - 1]) {
753 throw new IllegalArgumentException(
754 "Unknown prebaked effect type (value=" + mEffectId + ")");
755 }
Michael Wright71216972017-01-31 18:33:54 +0000756 }
Michael Wright35a0c672018-01-24 00:32:53 +0000757 if (!isValidEffectStrength(mEffectStrength)) {
758 throw new IllegalArgumentException(
759 "Unknown prebaked effect strength (value=" + mEffectStrength + ")");
760 }
Michael Wright71216972017-01-31 18:33:54 +0000761 }
762
763 @Override
764 public boolean equals(Object o) {
765 if (!(o instanceof VibrationEffect.Prebaked)) {
766 return false;
767 }
768 VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
Michael Wright35a0c672018-01-24 00:32:53 +0000769 return mEffectId == other.mEffectId
770 && mFallback == other.mFallback
771 && mEffectStrength == other.mEffectStrength;
Michael Wright71216972017-01-31 18:33:54 +0000772 }
773
774 @Override
775 public int hashCode() {
Michael Wright35a0c672018-01-24 00:32:53 +0000776 int result = 17;
777 result += 37 * mEffectId;
778 result += 37 * mEffectStrength;
779 return result;
Michael Wright71216972017-01-31 18:33:54 +0000780 }
781
782 @Override
783 public String toString() {
Michael Wright35a0c672018-01-24 00:32:53 +0000784 return "Prebaked{mEffectId=" + mEffectId
785 + ", mEffectStrength=" + mEffectStrength
786 + ", mFallback=" + mFallback
787 + "}";
Michael Wright71216972017-01-31 18:33:54 +0000788 }
789
790
791 @Override
792 public void writeToParcel(Parcel out, int flags) {
793 out.writeInt(PARCEL_TOKEN_EFFECT);
794 out.writeInt(mEffectId);
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100795 out.writeByte((byte) (mFallback ? 1 : 0));
Michael Wright35a0c672018-01-24 00:32:53 +0000796 out.writeInt(mEffectStrength);
Michael Wright71216972017-01-31 18:33:54 +0000797 }
798
799 public static final Parcelable.Creator<Prebaked> CREATOR =
800 new Parcelable.Creator<Prebaked>() {
801 @Override
802 public Prebaked createFromParcel(Parcel in) {
803 // Skip the type token
804 in.readInt();
805 return new Prebaked(in);
806 }
807 @Override
808 public Prebaked[] newArray(int size) {
809 return new Prebaked[size];
810 }
811 };
812 }
813
814 public static final Parcelable.Creator<VibrationEffect> CREATOR =
815 new Parcelable.Creator<VibrationEffect>() {
816 @Override
817 public VibrationEffect createFromParcel(Parcel in) {
818 int token = in.readInt();
819 if (token == PARCEL_TOKEN_ONE_SHOT) {
820 return new OneShot(in);
821 } else if (token == PARCEL_TOKEN_WAVEFORM) {
822 return new Waveform(in);
823 } else if (token == PARCEL_TOKEN_EFFECT) {
824 return new Prebaked(in);
825 } else {
826 throw new IllegalStateException(
827 "Unexpected vibration event type token in parcel.");
828 }
829 }
830 @Override
831 public VibrationEffect[] newArray(int size) {
832 return new VibrationEffect[size];
833 }
834 };
835}