blob: 64d36a95102336cff2a692a0e24739414eee2fcd [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
Artur Satayev5a525852019-10-31 15:15:50 +000030import dalvik.annotation.compat.UnsupportedAppUsage;
31
Alexey Kuzmin3a8a39f2019-01-18 16:56:23 +000032import java.lang.annotation.Retention;
33import java.lang.annotation.RetentionPolicy;
Michael Wright71216972017-01-31 18:33:54 +000034import java.util.Arrays;
35
36/**
37 * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
38 *
39 * These effects may be any number of things, from single shot vibrations to complex waveforms.
40 */
41public abstract class VibrationEffect implements Parcelable {
42 private static final int PARCEL_TOKEN_ONE_SHOT = 1;
43 private static final int PARCEL_TOKEN_WAVEFORM = 2;
44 private static final int PARCEL_TOKEN_EFFECT = 3;
45
46 /**
47 * The default vibration strength of the device.
48 */
49 public static final int DEFAULT_AMPLITUDE = -1;
50
51 /**
Michael Wright35a0c672018-01-24 00:32:53 +000052 * The maximum amplitude value
53 * @hide
54 */
55 public static final int MAX_AMPLITUDE = 255;
56
57 /**
Michael Wright71216972017-01-31 18:33:54 +000058 * A click effect.
59 *
60 * @see #get(int)
Michael Wright71216972017-01-31 18:33:54 +000061 */
Michael Wrightf268bf52018-02-07 23:23:34 +000062 public static final int EFFECT_CLICK = Effect.CLICK;
Michael Wright71216972017-01-31 18:33:54 +000063
64 /**
65 * A double click effect.
66 *
67 * @see #get(int)
Michael Wright71216972017-01-31 18:33:54 +000068 */
Michael Wrightf268bf52018-02-07 23:23:34 +000069 public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK;
Michael Wright57d94d92017-05-31 14:44:45 +010070
71 /**
72 * A tick effect.
73 * @see #get(int)
Michael Wright57d94d92017-05-31 14:44:45 +010074 */
Michael Wrightf268bf52018-02-07 23:23:34 +000075 public static final int EFFECT_TICK = Effect.TICK;
76
77 /**
78 * A thud effect.
79 * @see #get(int)
80 * @hide
81 */
Artur Satayev5a525852019-10-31 15:15:50 +000082 @UnsupportedAppUsage
Jeff Sharkeyc6091162018-06-29 17:15:40 -060083 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +000084 public static final int EFFECT_THUD = Effect.THUD;
85
86 /**
87 * A pop effect.
88 * @see #get(int)
89 * @hide
90 */
Artur Satayev5a525852019-10-31 15:15:50 +000091 @UnsupportedAppUsage
Jeff Sharkeyc6091162018-06-29 17:15:40 -060092 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +000093 public static final int EFFECT_POP = Effect.POP;
94
95 /**
96 * A heavy click effect.
97 * @see #get(int)
Michael Wrightf268bf52018-02-07 23:23:34 +000098 */
99 public static final int EFFECT_HEAVY_CLICK = Effect.HEAVY_CLICK;
100
Michael Wright950bd772019-03-21 21:26:05 +0000101 /**
102 * A texture effect meant to replicate soft ticks.
103 *
104 * Unlike normal effects, texture effects are meant to be called repeatedly, generally in
105 * response to some motion, in order to replicate the feeling of some texture underneath the
106 * user's fingers.
107 *
108 * @see #get(int)
109 * @hide
110 */
Alexey Kuzmina2112a32019-04-04 18:39:38 +0100111 @TestApi
Michael Wright950bd772019-03-21 21:26:05 +0000112 public static final int EFFECT_TEXTURE_TICK = Effect.TEXTURE_TICK;
113
Jeff Sharkeyc6091162018-06-29 17:15:40 -0600114 /** {@hide} */
115 @TestApi
116 public static final int EFFECT_STRENGTH_LIGHT = EffectStrength.LIGHT;
117
118 /** {@hide} */
119 @TestApi
120 public static final int EFFECT_STRENGTH_MEDIUM = EffectStrength.MEDIUM;
121
122 /** {@hide} */
123 @TestApi
124 public static final int EFFECT_STRENGTH_STRONG = EffectStrength.STRONG;
Michael Wrightf268bf52018-02-07 23:23:34 +0000125
126 /**
127 * Ringtone patterns. They may correspond with the device's ringtone audio, or may just be a
128 * pattern that can be played as a ringtone with any audio, depending on the device.
129 *
130 * @see #get(Uri, Context)
131 * @hide
132 */
Artur Satayev5a525852019-10-31 15:15:50 +0000133 @UnsupportedAppUsage
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000134 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +0000135 public static final int[] RINGTONES = {
136 Effect.RINGTONE_1,
137 Effect.RINGTONE_2,
138 Effect.RINGTONE_3,
139 Effect.RINGTONE_4,
140 Effect.RINGTONE_5,
141 Effect.RINGTONE_6,
142 Effect.RINGTONE_7,
143 Effect.RINGTONE_8,
144 Effect.RINGTONE_9,
145 Effect.RINGTONE_10,
146 Effect.RINGTONE_11,
147 Effect.RINGTONE_12,
148 Effect.RINGTONE_13,
149 Effect.RINGTONE_14,
150 Effect.RINGTONE_15
151 };
Michael Wright71216972017-01-31 18:33:54 +0000152
Alexey Kuzmin3a8a39f2019-01-18 16:56:23 +0000153 /** @hide */
154 @IntDef(prefix = { "EFFECT_" }, value = {
155 EFFECT_TICK,
156 EFFECT_CLICK,
157 EFFECT_HEAVY_CLICK,
158 EFFECT_DOUBLE_CLICK,
159 })
160 @Retention(RetentionPolicy.SOURCE)
161 public @interface EffectType {}
162
Michael Wright71216972017-01-31 18:33:54 +0000163 /** @hide to prevent subclassing from outside of the framework */
164 public VibrationEffect() { }
165
166 /**
167 * Create a one shot vibration.
168 *
169 * One shot vibrations will vibrate constantly for the specified period of time at the
170 * specified amplitude, and then stop.
171 *
172 * @param milliseconds The number of milliseconds to vibrate. This must be a positive number.
173 * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or
174 * {@link #DEFAULT_AMPLITUDE}.
175 *
176 * @return The desired effect.
177 */
178 public static VibrationEffect createOneShot(long milliseconds, int amplitude) {
179 VibrationEffect effect = new OneShot(milliseconds, amplitude);
180 effect.validate();
181 return effect;
182 }
183
184 /**
185 * Create a waveform vibration.
186 *
187 * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
188 * each pair, the value in the amplitude array determines the strength of the vibration and the
189 * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
190 * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
191 * <p>
192 * The amplitude array of the generated waveform will be the same size as the given
193 * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE},
194 * starting with 0. Therefore the first timing value will be the period to wait before turning
195 * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE}
196 * strength, etc.
197 * </p><p>
198 * To cause the pattern to repeat, pass the index into the timings array at which to start the
199 * repetition, or -1 to disable repeating.
200 * </p>
201 *
202 * @param timings The pattern of alternating on-off timings, starting with off. Timing values
203 * of 0 will cause the timing / amplitude pair to be ignored.
204 * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
205 * want to repeat.
206 *
207 * @return The desired effect.
208 */
209 public static VibrationEffect createWaveform(long[] timings, int repeat) {
210 int[] amplitudes = new int[timings.length];
211 for (int i = 0; i < (timings.length / 2); i++) {
212 amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE;
213 }
214 return createWaveform(timings, amplitudes, repeat);
215 }
216
217 /**
218 * Create a waveform vibration.
219 *
220 * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
221 * each pair, the value in the amplitude array determines the strength of the vibration and the
222 * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
223 * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
224 * </p><p>
225 * To cause the pattern to repeat, pass the index into the timings array at which to start the
226 * repetition, or -1 to disable repeating.
227 * </p>
228 *
229 * @param timings The timing values of the timing / amplitude pairs. Timing values of 0
230 * will cause the pair to be ignored.
231 * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values
232 * must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An
233 * amplitude value of 0 implies the motor is off.
234 * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
235 * want to repeat.
236 *
237 * @return The desired effect.
238 */
239 public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
240 VibrationEffect effect = new Waveform(timings, amplitudes, repeat);
241 effect.validate();
242 return effect;
243 }
244
245 /**
Alexey Kuzmin3a8a39f2019-01-18 16:56:23 +0000246 * Create a predefined vibration effect.
247 *
248 * Predefined effects are a set of common vibration effects that should be identical, regardless
249 * of the app they come from, in order to provide a cohesive experience for users across
250 * the entire device. They also may be custom tailored to the device hardware in order to
251 * provide a better experience than you could otherwise build using the generic building
252 * blocks.
253 *
254 * This will fallback to a generic pattern if one exists and there does not exist a
255 * hardware-specific implementation of the effect.
256 *
257 * @param effectId The ID of the effect to perform:
258 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
259 *
260 * @return The desired effect.
261 */
Arthur Hungcfdc9752019-03-04 19:25:36 +0800262 @NonNull
Arthur Hung00657b82019-02-26 11:56:40 +0800263 public static VibrationEffect createPredefined(@EffectType int effectId) {
Alexey Kuzmin3a8a39f2019-01-18 16:56:23 +0000264 return get(effectId, true);
265 }
266
267 /**
Michael Wright71216972017-01-31 18:33:54 +0000268 * Get a predefined vibration effect.
269 *
270 * Predefined effects are a set of common vibration effects that should be identical, regardless
271 * of the app they come from, in order to provide a cohesive experience for users across
272 * the entire device. They also may be custom tailored to the device hardware in order to
273 * provide a better experience than you could otherwise build using the generic building
274 * blocks.
275 *
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100276 * This will fallback to a generic pattern if one exists and there does not exist a
277 * hardware-specific implementation of the effect.
278 *
Michael Wright71216972017-01-31 18:33:54 +0000279 * @param effectId The ID of the effect to perform:
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100280 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
Michael Wright71216972017-01-31 18:33:54 +0000281 *
282 * @return The desired effect.
283 * @hide
284 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000285 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000286 public static VibrationEffect get(int effectId) {
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100287 return get(effectId, true);
288 }
289
290 /**
291 * Get a predefined vibration effect.
292 *
293 * Predefined effects are a set of common vibration effects that should be identical, regardless
294 * of the app they come from, in order to provide a cohesive experience for users across
295 * the entire device. They also may be custom tailored to the device hardware in order to
296 * provide a better experience than you could otherwise build using the generic building
297 * blocks.
298 *
299 * Some effects you may only want to play if there's a hardware specific implementation because
300 * they may, for example, be too disruptive to the user without tuning. The {@code fallback}
301 * parameter allows you to decide whether you want to fallback to the generic implementation or
302 * only play if there's a tuned, hardware specific one available.
303 *
304 * @param effectId The ID of the effect to perform:
305 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
306 * @param fallback Whether to fallback to a generic pattern if a hardware specific
307 * implementation doesn't exist.
308 *
309 * @return The desired effect.
310 * @hide
311 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000312 @TestApi
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100313 public static VibrationEffect get(int effectId, boolean fallback) {
314 VibrationEffect effect = new Prebaked(effectId, fallback);
Michael Wright71216972017-01-31 18:33:54 +0000315 effect.validate();
316 return effect;
317 }
318
Michael Wrightf268bf52018-02-07 23:23:34 +0000319 /**
320 * Get a predefined vibration effect associated with a given URI.
321 *
322 * Predefined effects are a set of common vibration effects that should be identical, regardless
323 * of the app they come from, in order to provide a cohesive experience for users across
324 * the entire device. They also may be custom tailored to the device hardware in order to
325 * provide a better experience than you could otherwise build using the generic building
326 * blocks.
327 *
328 * @param uri The URI associated with the haptic effect.
329 * @param context The context used to get the URI to haptic effect association.
330 *
331 * @return The desired effect, or {@code null} if there's no associated effect.
332 *
333 * @hide
334 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000335 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +0000336 @Nullable
337 public static VibrationEffect get(Uri uri, Context context) {
Michael Wright8b1f3c92019-06-04 15:26:25 +0100338 final ContentResolver cr = context.getContentResolver();
339 Uri uncanonicalUri = cr.uncanonicalize(uri);
340 if (uncanonicalUri == null) {
341 // If we already had an uncanonical URI, it's possible we'll get null back here. In
342 // this case, just use the URI as passed in since it wasn't canonicalized in the first
343 // place.
344 uncanonicalUri = uri;
345 }
Michael Wrightf268bf52018-02-07 23:23:34 +0000346 String[] uris = context.getResources().getStringArray(
347 com.android.internal.R.array.config_ringtoneEffectUris);
348 for (int i = 0; i < uris.length && i < RINGTONES.length; i++) {
349 if (uris[i] == null) {
350 continue;
351 }
Michael Wright32fa5932018-05-18 18:07:09 -0700352 Uri mappedUri = cr.uncanonicalize(Uri.parse(uris[i]));
353 if (mappedUri == null) {
354 continue;
355 }
Michael Wright8b1f3c92019-06-04 15:26:25 +0100356 if (mappedUri.equals(uncanonicalUri)) {
Michael Wrightf268bf52018-02-07 23:23:34 +0000357 return get(RINGTONES[i]);
358 }
359 }
360 return null;
361 }
362
Michael Wright71216972017-01-31 18:33:54 +0000363 @Override
364 public int describeContents() {
365 return 0;
366 }
367
368 /** @hide */
369 public abstract void validate();
370
Michael Wright35a0c672018-01-24 00:32:53 +0000371 /**
372 * Gets the estimated duration of the vibration in milliseconds.
373 *
374 * For effects without a defined end (e.g. a Waveform with a non-negative repeat index), this
375 * returns Long.MAX_VALUE. For effects with an unknown duration (e.g. Prebaked effects where
376 * the length is device and potentially run-time dependent), this returns -1.
377 *
378 * @hide
379 */
Jeff Sharkeyc6091162018-06-29 17:15:40 -0600380 @TestApi
Michael Wright35a0c672018-01-24 00:32:53 +0000381 public abstract long getDuration();
382
383 /**
384 * Scale the amplitude with the given constraints.
385 *
386 * This assumes that the previous value was in the range [0, MAX_AMPLITUDE]
387 * @hide
388 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000389 @TestApi
Michael Wright35a0c672018-01-24 00:32:53 +0000390 protected static int scale(int amplitude, float gamma, int maxAmplitude) {
391 float val = MathUtils.pow(amplitude / (float) MAX_AMPLITUDE, gamma);
392 return (int) (val * maxAmplitude);
393 }
394
Michael Wright71216972017-01-31 18:33:54 +0000395 /** @hide */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000396 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000397 public static class OneShot extends VibrationEffect implements Parcelable {
Michael Wright35a0c672018-01-24 00:32:53 +0000398 private final long mDuration;
399 private final int mAmplitude;
Michael Wright71216972017-01-31 18:33:54 +0000400
401 public OneShot(Parcel in) {
Michael Wright35a0c672018-01-24 00:32:53 +0000402 mDuration = in.readLong();
403 mAmplitude = in.readInt();
Michael Wright71216972017-01-31 18:33:54 +0000404 }
405
406 public OneShot(long milliseconds, int amplitude) {
Michael Wright35a0c672018-01-24 00:32:53 +0000407 mDuration = milliseconds;
Michael Wright71216972017-01-31 18:33:54 +0000408 mAmplitude = amplitude;
409 }
410
Michael Wright35a0c672018-01-24 00:32:53 +0000411 @Override
412 public long getDuration() {
413 return mDuration;
Michael Wright71216972017-01-31 18:33:54 +0000414 }
415
416 public int getAmplitude() {
417 return mAmplitude;
418 }
419
Michael Wright35a0c672018-01-24 00:32:53 +0000420 /**
421 * Scale the amplitude of this effect.
422 *
423 * @param gamma the gamma adjustment to apply
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000424 * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and
425 * MAX_AMPLITUDE
426 * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE
Michael Wright35a0c672018-01-24 00:32:53 +0000427 *
428 * @return A {@link OneShot} effect with the same timing but scaled amplitude.
429 */
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000430 public OneShot scale(float gamma, int maxAmplitude) {
431 if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) {
432 throw new IllegalArgumentException(
433 "Amplitude is negative or greater than MAX_AMPLITUDE");
434 }
Michael Wright35a0c672018-01-24 00:32:53 +0000435 int newAmplitude = scale(mAmplitude, gamma, maxAmplitude);
436 return new OneShot(mDuration, newAmplitude);
437 }
438
Alexey Kuzmin55bdc592018-04-24 12:58:13 +0100439 /**
440 * Resolve default values into integer amplitude numbers.
441 *
442 * @param defaultAmplitude the default amplitude to apply, must be between 0 and
443 * MAX_AMPLITUDE
444 * @return A {@link OneShot} effect with same physical meaning but explicitly set amplitude
445 *
446 * @hide
447 */
448 public OneShot resolve(int defaultAmplitude) {
449 if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
450 throw new IllegalArgumentException(
451 "Amplitude is negative or greater than MAX_AMPLITUDE");
452 }
453 if (mAmplitude == DEFAULT_AMPLITUDE) {
454 return new OneShot(mDuration, defaultAmplitude);
455 }
456 return this;
457 }
458
Michael Wright71216972017-01-31 18:33:54 +0000459 @Override
460 public void validate() {
461 if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) {
462 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000463 "amplitude must either be DEFAULT_AMPLITUDE, "
464 + "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")");
Michael Wright71216972017-01-31 18:33:54 +0000465 }
Michael Wright35a0c672018-01-24 00:32:53 +0000466 if (mDuration <= 0) {
Michael Wright0ef6edd2017-04-05 20:57:39 +0100467 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000468 "duration must be positive (duration=" + mDuration + ")");
Michael Wright71216972017-01-31 18:33:54 +0000469 }
470 }
471
472 @Override
473 public boolean equals(Object o) {
474 if (!(o instanceof VibrationEffect.OneShot)) {
475 return false;
476 }
477 VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
Michael Wright35a0c672018-01-24 00:32:53 +0000478 return other.mDuration == mDuration && other.mAmplitude == mAmplitude;
Michael Wright71216972017-01-31 18:33:54 +0000479 }
480
481 @Override
482 public int hashCode() {
483 int result = 17;
Michael Wright35a0c672018-01-24 00:32:53 +0000484 result += 37 * (int) mDuration;
485 result += 37 * mAmplitude;
Michael Wright71216972017-01-31 18:33:54 +0000486 return result;
487 }
488
489 @Override
490 public String toString() {
Michael Wright35a0c672018-01-24 00:32:53 +0000491 return "OneShot{mDuration=" + mDuration + ", mAmplitude=" + mAmplitude + "}";
Michael Wright71216972017-01-31 18:33:54 +0000492 }
493
494 @Override
495 public void writeToParcel(Parcel out, int flags) {
496 out.writeInt(PARCEL_TOKEN_ONE_SHOT);
Michael Wright35a0c672018-01-24 00:32:53 +0000497 out.writeLong(mDuration);
Michael Wright71216972017-01-31 18:33:54 +0000498 out.writeInt(mAmplitude);
499 }
500
Artur Satayev5a525852019-10-31 15:15:50 +0000501 @UnsupportedAppUsage
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700502 public static final @android.annotation.NonNull Parcelable.Creator<OneShot> CREATOR =
Michael Wright71216972017-01-31 18:33:54 +0000503 new Parcelable.Creator<OneShot>() {
504 @Override
505 public OneShot createFromParcel(Parcel in) {
506 // Skip the type token
507 in.readInt();
508 return new OneShot(in);
509 }
510 @Override
511 public OneShot[] newArray(int size) {
512 return new OneShot[size];
513 }
514 };
515 }
516
517 /** @hide */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000518 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000519 public static class Waveform extends VibrationEffect implements Parcelable {
Michael Wright35a0c672018-01-24 00:32:53 +0000520 private final long[] mTimings;
521 private final int[] mAmplitudes;
522 private final int mRepeat;
Michael Wright71216972017-01-31 18:33:54 +0000523
524 public Waveform(Parcel in) {
525 this(in.createLongArray(), in.createIntArray(), in.readInt());
526 }
527
528 public Waveform(long[] timings, int[] amplitudes, int repeat) {
529 mTimings = new long[timings.length];
530 System.arraycopy(timings, 0, mTimings, 0, timings.length);
531 mAmplitudes = new int[amplitudes.length];
532 System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length);
533 mRepeat = repeat;
534 }
535
536 public long[] getTimings() {
537 return mTimings;
538 }
539
540 public int[] getAmplitudes() {
541 return mAmplitudes;
542 }
543
544 public int getRepeatIndex() {
545 return mRepeat;
546 }
547
548 @Override
Michael Wright35a0c672018-01-24 00:32:53 +0000549 public long getDuration() {
550 if (mRepeat >= 0) {
551 return Long.MAX_VALUE;
552 }
553 long duration = 0;
554 for (long d : mTimings) {
555 duration += d;
556 }
557 return duration;
558 }
559
560 /**
561 * Scale the Waveform with the given gamma and new max amplitude.
562 *
563 * @param gamma the gamma adjustment to apply
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000564 * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and
565 * MAX_AMPLITUDE
566 * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE
Michael Wright35a0c672018-01-24 00:32:53 +0000567 *
568 * @return A {@link Waveform} effect with the same timings and repeat index
569 * but scaled amplitude.
570 */
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000571 public Waveform scale(float gamma, int maxAmplitude) {
572 if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) {
573 throw new IllegalArgumentException(
574 "Amplitude is negative or greater than MAX_AMPLITUDE");
575 }
Michael Wright35a0c672018-01-24 00:32:53 +0000576 if (gamma == 1.0f && maxAmplitude == MAX_AMPLITUDE) {
577 // Just return a copy of the original if there's no scaling to be done.
578 return new Waveform(mTimings, mAmplitudes, mRepeat);
579 }
580
581 int[] scaledAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
582 for (int i = 0; i < scaledAmplitudes.length; i++) {
583 scaledAmplitudes[i] = scale(scaledAmplitudes[i], gamma, maxAmplitude);
584 }
585 return new Waveform(mTimings, scaledAmplitudes, mRepeat);
586 }
587
Alexey Kuzmin55bdc592018-04-24 12:58:13 +0100588 /**
589 * Resolve default values into integer amplitude numbers.
590 *
591 * @param defaultAmplitude the default amplitude to apply, must be between 0 and
592 * MAX_AMPLITUDE
593 * @return A {@link Waveform} effect with same physical meaning but explicitly set
594 * amplitude
595 *
596 * @hide
597 */
598 public Waveform resolve(int defaultAmplitude) {
599 if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
600 throw new IllegalArgumentException(
601 "Amplitude is negative or greater than MAX_AMPLITUDE");
602 }
603 int[] resolvedAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
604 for (int i = 0; i < resolvedAmplitudes.length; i++) {
605 if (resolvedAmplitudes[i] == DEFAULT_AMPLITUDE) {
606 resolvedAmplitudes[i] = defaultAmplitude;
607 }
608 }
609 return new Waveform(mTimings, resolvedAmplitudes, mRepeat);
610 }
611
Michael Wright35a0c672018-01-24 00:32:53 +0000612 @Override
Michael Wright71216972017-01-31 18:33:54 +0000613 public void validate() {
614 if (mTimings.length != mAmplitudes.length) {
615 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000616 "timing and amplitude arrays must be of equal length"
617 + " (timings.length=" + mTimings.length
618 + ", amplitudes.length=" + mAmplitudes.length + ")");
Michael Wright71216972017-01-31 18:33:54 +0000619 }
620 if (!hasNonZeroEntry(mTimings)) {
Michael Wright35a0c672018-01-24 00:32:53 +0000621 throw new IllegalArgumentException("at least one timing must be non-zero"
622 + " (timings=" + Arrays.toString(mTimings) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000623 }
624 for (long timing : mTimings) {
625 if (timing < 0) {
Michael Wright35a0c672018-01-24 00:32:53 +0000626 throw new IllegalArgumentException("timings must all be >= 0"
627 + " (timings=" + Arrays.toString(mTimings) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000628 }
629 }
630 for (int amplitude : mAmplitudes) {
631 if (amplitude < -1 || amplitude > 255) {
632 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000633 "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255"
634 + " (amplitudes=" + Arrays.toString(mAmplitudes) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000635 }
636 }
637 if (mRepeat < -1 || mRepeat >= mTimings.length) {
Michael Wright0ef6edd2017-04-05 20:57:39 +0100638 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000639 "repeat index must be within the bounds of the timings array"
640 + " (timings.length=" + mTimings.length + ", index=" + mRepeat + ")");
Michael Wright71216972017-01-31 18:33:54 +0000641 }
642 }
643
644 @Override
645 public boolean equals(Object o) {
646 if (!(o instanceof VibrationEffect.Waveform)) {
647 return false;
648 }
649 VibrationEffect.Waveform other = (VibrationEffect.Waveform) o;
Michael Wright35a0c672018-01-24 00:32:53 +0000650 return Arrays.equals(mTimings, other.mTimings)
651 && Arrays.equals(mAmplitudes, other.mAmplitudes)
652 && mRepeat == other.mRepeat;
Michael Wright71216972017-01-31 18:33:54 +0000653 }
654
655 @Override
656 public int hashCode() {
657 int result = 17;
Michael Wright35a0c672018-01-24 00:32:53 +0000658 result += 37 * Arrays.hashCode(mTimings);
659 result += 37 * Arrays.hashCode(mAmplitudes);
660 result += 37 * mRepeat;
Michael Wright71216972017-01-31 18:33:54 +0000661 return result;
662 }
663
664 @Override
665 public String toString() {
Michael Wright35a0c672018-01-24 00:32:53 +0000666 return "Waveform{mTimings=" + Arrays.toString(mTimings)
667 + ", mAmplitudes=" + Arrays.toString(mAmplitudes)
668 + ", mRepeat=" + mRepeat
669 + "}";
Michael Wright71216972017-01-31 18:33:54 +0000670 }
671
672 @Override
673 public void writeToParcel(Parcel out, int flags) {
674 out.writeInt(PARCEL_TOKEN_WAVEFORM);
675 out.writeLongArray(mTimings);
676 out.writeIntArray(mAmplitudes);
677 out.writeInt(mRepeat);
678 }
679
680 private static boolean hasNonZeroEntry(long[] vals) {
681 for (long val : vals) {
682 if (val != 0) {
683 return true;
684 }
685 }
686 return false;
687 }
688
689
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700690 public static final @android.annotation.NonNull Parcelable.Creator<Waveform> CREATOR =
Michael Wright71216972017-01-31 18:33:54 +0000691 new Parcelable.Creator<Waveform>() {
692 @Override
693 public Waveform createFromParcel(Parcel in) {
694 // Skip the type token
695 in.readInt();
696 return new Waveform(in);
697 }
698 @Override
699 public Waveform[] newArray(int size) {
700 return new Waveform[size];
701 }
702 };
703 }
704
705 /** @hide */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000706 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000707 public static class Prebaked extends VibrationEffect implements Parcelable {
Michael Wright35a0c672018-01-24 00:32:53 +0000708 private final int mEffectId;
709 private final boolean mFallback;
710
711 private int mEffectStrength;
Michael Wright71216972017-01-31 18:33:54 +0000712
713 public Prebaked(Parcel in) {
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100714 this(in.readInt(), in.readByte() != 0);
Michael Wright35a0c672018-01-24 00:32:53 +0000715 mEffectStrength = in.readInt();
Michael Wright71216972017-01-31 18:33:54 +0000716 }
717
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100718 public Prebaked(int effectId, boolean fallback) {
Michael Wright71216972017-01-31 18:33:54 +0000719 mEffectId = effectId;
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100720 mFallback = fallback;
Michael Wright35a0c672018-01-24 00:32:53 +0000721 mEffectStrength = EffectStrength.MEDIUM;
Michael Wright71216972017-01-31 18:33:54 +0000722 }
723
724 public int getId() {
725 return mEffectId;
726 }
727
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100728 /**
729 * Whether the effect should fall back to a generic pattern if there's no hardware specific
730 * implementation of it.
731 */
732 public boolean shouldFallback() {
733 return mFallback;
734 }
735
Michael Wright71216972017-01-31 18:33:54 +0000736 @Override
Michael Wright35a0c672018-01-24 00:32:53 +0000737 public long getDuration() {
738 return -1;
739 }
740
741 /**
742 * Set the effect strength of the prebaked effect.
743 */
744 public void setEffectStrength(int strength) {
745 if (!isValidEffectStrength(strength)) {
746 throw new IllegalArgumentException("Invalid effect strength: " + strength);
747 }
748 mEffectStrength = strength;
749 }
750
751 /**
752 * Set the effect strength.
753 */
754 public int getEffectStrength() {
755 return mEffectStrength;
756 }
757
758 private static boolean isValidEffectStrength(int strength) {
759 switch (strength) {
760 case EffectStrength.LIGHT:
761 case EffectStrength.MEDIUM:
762 case EffectStrength.STRONG:
763 return true;
764 default:
765 return false;
766 }
767 }
768
769 @Override
Michael Wright71216972017-01-31 18:33:54 +0000770 public void validate() {
Michael Wright57d94d92017-05-31 14:44:45 +0100771 switch (mEffectId) {
772 case EFFECT_CLICK:
773 case EFFECT_DOUBLE_CLICK:
774 case EFFECT_TICK:
Michael Wright950bd772019-03-21 21:26:05 +0000775 case EFFECT_TEXTURE_TICK:
Michael Wrightf268bf52018-02-07 23:23:34 +0000776 case EFFECT_THUD:
777 case EFFECT_POP:
778 case EFFECT_HEAVY_CLICK:
Michael Wright57d94d92017-05-31 14:44:45 +0100779 break;
780 default:
Michael Wrightf268bf52018-02-07 23:23:34 +0000781 if (mEffectId < RINGTONES[0] || mEffectId > RINGTONES[RINGTONES.length - 1]) {
782 throw new IllegalArgumentException(
783 "Unknown prebaked effect type (value=" + mEffectId + ")");
784 }
Michael Wright71216972017-01-31 18:33:54 +0000785 }
Michael Wright35a0c672018-01-24 00:32:53 +0000786 if (!isValidEffectStrength(mEffectStrength)) {
787 throw new IllegalArgumentException(
788 "Unknown prebaked effect strength (value=" + mEffectStrength + ")");
789 }
Michael Wright71216972017-01-31 18:33:54 +0000790 }
791
792 @Override
793 public boolean equals(Object o) {
794 if (!(o instanceof VibrationEffect.Prebaked)) {
795 return false;
796 }
797 VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
Michael Wright35a0c672018-01-24 00:32:53 +0000798 return mEffectId == other.mEffectId
799 && mFallback == other.mFallback
800 && mEffectStrength == other.mEffectStrength;
Michael Wright71216972017-01-31 18:33:54 +0000801 }
802
803 @Override
804 public int hashCode() {
Michael Wright35a0c672018-01-24 00:32:53 +0000805 int result = 17;
806 result += 37 * mEffectId;
807 result += 37 * mEffectStrength;
808 return result;
Michael Wright71216972017-01-31 18:33:54 +0000809 }
810
811 @Override
812 public String toString() {
Michael Wright35a0c672018-01-24 00:32:53 +0000813 return "Prebaked{mEffectId=" + mEffectId
814 + ", mEffectStrength=" + mEffectStrength
815 + ", mFallback=" + mFallback
816 + "}";
Michael Wright71216972017-01-31 18:33:54 +0000817 }
818
819
820 @Override
821 public void writeToParcel(Parcel out, int flags) {
822 out.writeInt(PARCEL_TOKEN_EFFECT);
823 out.writeInt(mEffectId);
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100824 out.writeByte((byte) (mFallback ? 1 : 0));
Michael Wright35a0c672018-01-24 00:32:53 +0000825 out.writeInt(mEffectStrength);
Michael Wright71216972017-01-31 18:33:54 +0000826 }
827
Michael Wright950bd772019-03-21 21:26:05 +0000828 public static final @NonNull Parcelable.Creator<Prebaked> CREATOR =
Michael Wright71216972017-01-31 18:33:54 +0000829 new Parcelable.Creator<Prebaked>() {
830 @Override
831 public Prebaked createFromParcel(Parcel in) {
832 // Skip the type token
833 in.readInt();
834 return new Prebaked(in);
835 }
836 @Override
837 public Prebaked[] newArray(int size) {
838 return new Prebaked[size];
839 }
840 };
841 }
842
Michael Wright950bd772019-03-21 21:26:05 +0000843 public static final @NonNull Parcelable.Creator<VibrationEffect> CREATOR =
Michael Wright71216972017-01-31 18:33:54 +0000844 new Parcelable.Creator<VibrationEffect>() {
845 @Override
846 public VibrationEffect createFromParcel(Parcel in) {
847 int token = in.readInt();
848 if (token == PARCEL_TOKEN_ONE_SHOT) {
849 return new OneShot(in);
850 } else if (token == PARCEL_TOKEN_WAVEFORM) {
851 return new Waveform(in);
852 } else if (token == PARCEL_TOKEN_EFFECT) {
853 return new Prebaked(in);
854 } else {
855 throw new IllegalStateException(
856 "Unexpected vibration event type token in parcel.");
857 }
858 }
859 @Override
860 public VibrationEffect[] newArray(int size) {
861 return new VibrationEffect[size];
862 }
863 };
864}