blob: 26da0a0aee0745462fce300e849e952b7cfbce1b [file] [log] [blame]
Michael Wright71216972017-01-31 18:33:54 +00001/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.os;
18
Alexey Kuzmin3a8a39f2019-01-18 16:56:23 +000019import android.annotation.IntDef;
Arthur Hungcfdc9752019-03-04 19:25:36 +080020import android.annotation.NonNull;
Michael Wrightf268bf52018-02-07 23:23:34 +000021import android.annotation.Nullable;
Alexey Kuzmin505872d2018-03-19 19:27:46 +000022import android.annotation.TestApi;
Michael Wright32fa5932018-05-18 18:07:09 -070023import android.content.ContentResolver;
Michael Wrightf268bf52018-02-07 23:23:34 +000024import android.content.Context;
25import android.hardware.vibrator.V1_0.EffectStrength;
Michael Wright950bd772019-03-21 21:26:05 +000026import android.hardware.vibrator.V1_3.Effect;
Michael Wrightf268bf52018-02-07 23:23:34 +000027import android.net.Uri;
Michael Wright35a0c672018-01-24 00:32:53 +000028import android.util.MathUtils;
Michael Wright71216972017-01-31 18:33:54 +000029
Alexey Kuzmin3a8a39f2019-01-18 16:56:23 +000030import java.lang.annotation.Retention;
31import java.lang.annotation.RetentionPolicy;
Michael Wright71216972017-01-31 18:33:54 +000032import java.util.Arrays;
33
34/**
35 * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
36 *
37 * These effects may be any number of things, from single shot vibrations to complex waveforms.
38 */
39public abstract class VibrationEffect implements Parcelable {
40 private static final int PARCEL_TOKEN_ONE_SHOT = 1;
41 private static final int PARCEL_TOKEN_WAVEFORM = 2;
42 private static final int PARCEL_TOKEN_EFFECT = 3;
43
44 /**
45 * The default vibration strength of the device.
46 */
47 public static final int DEFAULT_AMPLITUDE = -1;
48
49 /**
Michael Wright35a0c672018-01-24 00:32:53 +000050 * The maximum amplitude value
51 * @hide
52 */
53 public static final int MAX_AMPLITUDE = 255;
54
55 /**
Kevin Hufnagle86b17d22019-08-29 04:29:08 +000056 * A click effect. Use this effect as a baseline, as it's the most common type of click effect.
Michael Wright71216972017-01-31 18:33:54 +000057 *
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 /**
Kevin Hufnagle86b17d22019-08-29 04:29:08 +000070 * A tick effect. This effect is less strong compared to {@link #EFFECT_CLICK}.
Michael Wright57d94d92017-05-31 14:44:45 +010071 * @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 /**
Kevin Hufnagle86b17d22019-08-29 04:29:08 +000092 * A heavy click effect. This effect is stronger than {@link #EFFECT_CLICK}.
Michael Wrightf268bf52018-02-07 23:23:34 +000093 * @see #get(int)
Michael Wrightf268bf52018-02-07 23:23:34 +000094 */
95 public static final int EFFECT_HEAVY_CLICK = Effect.HEAVY_CLICK;
96
Michael Wright950bd772019-03-21 21:26:05 +000097 /**
98 * A texture effect meant to replicate soft ticks.
99 *
100 * Unlike normal effects, texture effects are meant to be called repeatedly, generally in
101 * response to some motion, in order to replicate the feeling of some texture underneath the
102 * user's fingers.
103 *
104 * @see #get(int)
105 * @hide
106 */
Alexey Kuzmina2112a32019-04-04 18:39:38 +0100107 @TestApi
Michael Wright950bd772019-03-21 21:26:05 +0000108 public static final int EFFECT_TEXTURE_TICK = Effect.TEXTURE_TICK;
109
Jeff Sharkeyc6091162018-06-29 17:15:40 -0600110 /** {@hide} */
111 @TestApi
112 public static final int EFFECT_STRENGTH_LIGHT = EffectStrength.LIGHT;
113
114 /** {@hide} */
115 @TestApi
116 public static final int EFFECT_STRENGTH_MEDIUM = EffectStrength.MEDIUM;
117
118 /** {@hide} */
119 @TestApi
120 public static final int EFFECT_STRENGTH_STRONG = EffectStrength.STRONG;
Michael Wrightf268bf52018-02-07 23:23:34 +0000121
122 /**
123 * Ringtone patterns. They may correspond with the device's ringtone audio, or may just be a
124 * pattern that can be played as a ringtone with any audio, depending on the device.
125 *
126 * @see #get(Uri, Context)
127 * @hide
128 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000129 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +0000130 public static final int[] RINGTONES = {
131 Effect.RINGTONE_1,
132 Effect.RINGTONE_2,
133 Effect.RINGTONE_3,
134 Effect.RINGTONE_4,
135 Effect.RINGTONE_5,
136 Effect.RINGTONE_6,
137 Effect.RINGTONE_7,
138 Effect.RINGTONE_8,
139 Effect.RINGTONE_9,
140 Effect.RINGTONE_10,
141 Effect.RINGTONE_11,
142 Effect.RINGTONE_12,
143 Effect.RINGTONE_13,
144 Effect.RINGTONE_14,
145 Effect.RINGTONE_15
146 };
Michael Wright71216972017-01-31 18:33:54 +0000147
Alexey Kuzmin3a8a39f2019-01-18 16:56:23 +0000148 /** @hide */
149 @IntDef(prefix = { "EFFECT_" }, value = {
150 EFFECT_TICK,
151 EFFECT_CLICK,
152 EFFECT_HEAVY_CLICK,
153 EFFECT_DOUBLE_CLICK,
154 })
155 @Retention(RetentionPolicy.SOURCE)
156 public @interface EffectType {}
157
Michael Wright71216972017-01-31 18:33:54 +0000158 /** @hide to prevent subclassing from outside of the framework */
159 public VibrationEffect() { }
160
161 /**
162 * Create a one shot vibration.
163 *
164 * One shot vibrations will vibrate constantly for the specified period of time at the
165 * specified amplitude, and then stop.
166 *
167 * @param milliseconds The number of milliseconds to vibrate. This must be a positive number.
168 * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or
169 * {@link #DEFAULT_AMPLITUDE}.
170 *
171 * @return The desired effect.
172 */
173 public static VibrationEffect createOneShot(long milliseconds, int amplitude) {
174 VibrationEffect effect = new OneShot(milliseconds, amplitude);
175 effect.validate();
176 return effect;
177 }
178
179 /**
180 * Create a waveform vibration.
181 *
182 * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
183 * each pair, the value in the amplitude array determines the strength of the vibration and the
184 * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
185 * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
186 * <p>
187 * The amplitude array of the generated waveform will be the same size as the given
188 * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE},
189 * starting with 0. Therefore the first timing value will be the period to wait before turning
190 * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE}
191 * strength, etc.
192 * </p><p>
193 * To cause the pattern to repeat, pass the index into the timings array at which to start the
194 * repetition, or -1 to disable repeating.
195 * </p>
196 *
197 * @param timings The pattern of alternating on-off timings, starting with off. Timing values
198 * of 0 will cause the timing / amplitude pair to be ignored.
199 * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
200 * want to repeat.
201 *
202 * @return The desired effect.
203 */
204 public static VibrationEffect createWaveform(long[] timings, int repeat) {
205 int[] amplitudes = new int[timings.length];
206 for (int i = 0; i < (timings.length / 2); i++) {
207 amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE;
208 }
209 return createWaveform(timings, amplitudes, repeat);
210 }
211
212 /**
213 * Create a waveform vibration.
214 *
215 * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
216 * each pair, the value in the amplitude array determines the strength of the vibration and the
217 * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
218 * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
219 * </p><p>
220 * To cause the pattern to repeat, pass the index into the timings array at which to start the
221 * repetition, or -1 to disable repeating.
222 * </p>
223 *
224 * @param timings The timing values of the timing / amplitude pairs. Timing values of 0
225 * will cause the pair to be ignored.
226 * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values
227 * must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An
228 * amplitude value of 0 implies the motor is off.
229 * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
230 * want to repeat.
231 *
232 * @return The desired effect.
233 */
234 public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
235 VibrationEffect effect = new Waveform(timings, amplitudes, repeat);
236 effect.validate();
237 return effect;
238 }
239
240 /**
Alexey Kuzmin3a8a39f2019-01-18 16:56:23 +0000241 * Create a predefined vibration effect.
242 *
243 * Predefined effects are a set of common vibration effects that should be identical, regardless
244 * of the app they come from, in order to provide a cohesive experience for users across
245 * the entire device. They also may be custom tailored to the device hardware in order to
246 * provide a better experience than you could otherwise build using the generic building
247 * blocks.
248 *
249 * This will fallback to a generic pattern if one exists and there does not exist a
250 * hardware-specific implementation of the effect.
251 *
252 * @param effectId The ID of the effect to perform:
253 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
254 *
255 * @return The desired effect.
256 */
Arthur Hungcfdc9752019-03-04 19:25:36 +0800257 @NonNull
Arthur Hung00657b82019-02-26 11:56:40 +0800258 public static VibrationEffect createPredefined(@EffectType int effectId) {
Alexey Kuzmin3a8a39f2019-01-18 16:56:23 +0000259 return get(effectId, true);
260 }
261
262 /**
Michael Wright71216972017-01-31 18:33:54 +0000263 * Get a predefined vibration effect.
264 *
265 * Predefined effects are a set of common vibration effects that should be identical, regardless
266 * of the app they come from, in order to provide a cohesive experience for users across
267 * the entire device. They also may be custom tailored to the device hardware in order to
268 * provide a better experience than you could otherwise build using the generic building
269 * blocks.
270 *
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100271 * This will fallback to a generic pattern if one exists and there does not exist a
272 * hardware-specific implementation of the effect.
273 *
Michael Wright71216972017-01-31 18:33:54 +0000274 * @param effectId The ID of the effect to perform:
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100275 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
Michael Wright71216972017-01-31 18:33:54 +0000276 *
277 * @return The desired effect.
278 * @hide
279 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000280 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000281 public static VibrationEffect get(int effectId) {
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100282 return get(effectId, true);
283 }
284
285 /**
286 * Get a predefined vibration effect.
287 *
288 * Predefined effects are a set of common vibration effects that should be identical, regardless
289 * of the app they come from, in order to provide a cohesive experience for users across
290 * the entire device. They also may be custom tailored to the device hardware in order to
291 * provide a better experience than you could otherwise build using the generic building
292 * blocks.
293 *
294 * Some effects you may only want to play if there's a hardware specific implementation because
295 * they may, for example, be too disruptive to the user without tuning. The {@code fallback}
296 * parameter allows you to decide whether you want to fallback to the generic implementation or
297 * only play if there's a tuned, hardware specific one available.
298 *
299 * @param effectId The ID of the effect to perform:
300 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
301 * @param fallback Whether to fallback to a generic pattern if a hardware specific
302 * implementation doesn't exist.
303 *
304 * @return The desired effect.
305 * @hide
306 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000307 @TestApi
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100308 public static VibrationEffect get(int effectId, boolean fallback) {
309 VibrationEffect effect = new Prebaked(effectId, fallback);
Michael Wright71216972017-01-31 18:33:54 +0000310 effect.validate();
311 return effect;
312 }
313
Michael Wrightf268bf52018-02-07 23:23:34 +0000314 /**
315 * Get a predefined vibration effect associated with a given URI.
316 *
317 * Predefined effects are a set of common vibration effects that should be identical, regardless
318 * of the app they come from, in order to provide a cohesive experience for users across
319 * the entire device. They also may be custom tailored to the device hardware in order to
320 * provide a better experience than you could otherwise build using the generic building
321 * blocks.
322 *
323 * @param uri The URI associated with the haptic effect.
324 * @param context The context used to get the URI to haptic effect association.
325 *
326 * @return The desired effect, or {@code null} if there's no associated effect.
327 *
328 * @hide
329 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000330 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +0000331 @Nullable
332 public static VibrationEffect get(Uri uri, Context context) {
Michael Wright8b1f3c92019-06-04 15:26:25 +0100333 final ContentResolver cr = context.getContentResolver();
334 Uri uncanonicalUri = cr.uncanonicalize(uri);
335 if (uncanonicalUri == null) {
336 // If we already had an uncanonical URI, it's possible we'll get null back here. In
337 // this case, just use the URI as passed in since it wasn't canonicalized in the first
338 // place.
339 uncanonicalUri = uri;
340 }
Michael Wrightf268bf52018-02-07 23:23:34 +0000341 String[] uris = context.getResources().getStringArray(
342 com.android.internal.R.array.config_ringtoneEffectUris);
343 for (int i = 0; i < uris.length && i < RINGTONES.length; i++) {
344 if (uris[i] == null) {
345 continue;
346 }
Michael Wright32fa5932018-05-18 18:07:09 -0700347 Uri mappedUri = cr.uncanonicalize(Uri.parse(uris[i]));
348 if (mappedUri == null) {
349 continue;
350 }
Michael Wright8b1f3c92019-06-04 15:26:25 +0100351 if (mappedUri.equals(uncanonicalUri)) {
Michael Wrightf268bf52018-02-07 23:23:34 +0000352 return get(RINGTONES[i]);
353 }
354 }
355 return null;
356 }
357
Michael Wright71216972017-01-31 18:33:54 +0000358 @Override
359 public int describeContents() {
360 return 0;
361 }
362
363 /** @hide */
364 public abstract void validate();
365
Michael Wright35a0c672018-01-24 00:32:53 +0000366 /**
367 * Gets the estimated duration of the vibration in milliseconds.
368 *
369 * For effects without a defined end (e.g. a Waveform with a non-negative repeat index), this
370 * returns Long.MAX_VALUE. For effects with an unknown duration (e.g. Prebaked effects where
371 * the length is device and potentially run-time dependent), this returns -1.
372 *
373 * @hide
374 */
Jeff Sharkeyc6091162018-06-29 17:15:40 -0600375 @TestApi
Michael Wright35a0c672018-01-24 00:32:53 +0000376 public abstract long getDuration();
377
378 /**
379 * Scale the amplitude with the given constraints.
380 *
381 * This assumes that the previous value was in the range [0, MAX_AMPLITUDE]
382 * @hide
383 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000384 @TestApi
Michael Wright35a0c672018-01-24 00:32:53 +0000385 protected static int scale(int amplitude, float gamma, int maxAmplitude) {
386 float val = MathUtils.pow(amplitude / (float) MAX_AMPLITUDE, gamma);
387 return (int) (val * maxAmplitude);
388 }
389
Michael Wright71216972017-01-31 18:33:54 +0000390 /** @hide */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000391 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000392 public static class OneShot extends VibrationEffect implements Parcelable {
Michael Wright35a0c672018-01-24 00:32:53 +0000393 private final long mDuration;
394 private final int mAmplitude;
Michael Wright71216972017-01-31 18:33:54 +0000395
396 public OneShot(Parcel in) {
Michael Wright35a0c672018-01-24 00:32:53 +0000397 mDuration = in.readLong();
398 mAmplitude = in.readInt();
Michael Wright71216972017-01-31 18:33:54 +0000399 }
400
401 public OneShot(long milliseconds, int amplitude) {
Michael Wright35a0c672018-01-24 00:32:53 +0000402 mDuration = milliseconds;
Michael Wright71216972017-01-31 18:33:54 +0000403 mAmplitude = amplitude;
404 }
405
Michael Wright35a0c672018-01-24 00:32:53 +0000406 @Override
407 public long getDuration() {
408 return mDuration;
Michael Wright71216972017-01-31 18:33:54 +0000409 }
410
411 public int getAmplitude() {
412 return mAmplitude;
413 }
414
Michael Wright35a0c672018-01-24 00:32:53 +0000415 /**
416 * Scale the amplitude of this effect.
417 *
418 * @param gamma the gamma adjustment to apply
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000419 * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and
420 * MAX_AMPLITUDE
421 * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE
Michael Wright35a0c672018-01-24 00:32:53 +0000422 *
423 * @return A {@link OneShot} effect with the same timing but scaled amplitude.
424 */
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000425 public OneShot scale(float gamma, int maxAmplitude) {
426 if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) {
427 throw new IllegalArgumentException(
428 "Amplitude is negative or greater than MAX_AMPLITUDE");
429 }
Michael Wright35a0c672018-01-24 00:32:53 +0000430 int newAmplitude = scale(mAmplitude, gamma, maxAmplitude);
431 return new OneShot(mDuration, newAmplitude);
432 }
433
Alexey Kuzmin55bdc592018-04-24 12:58:13 +0100434 /**
435 * Resolve default values into integer amplitude numbers.
436 *
437 * @param defaultAmplitude the default amplitude to apply, must be between 0 and
438 * MAX_AMPLITUDE
439 * @return A {@link OneShot} effect with same physical meaning but explicitly set amplitude
440 *
441 * @hide
442 */
443 public OneShot resolve(int defaultAmplitude) {
444 if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
445 throw new IllegalArgumentException(
446 "Amplitude is negative or greater than MAX_AMPLITUDE");
447 }
448 if (mAmplitude == DEFAULT_AMPLITUDE) {
449 return new OneShot(mDuration, defaultAmplitude);
450 }
451 return this;
452 }
453
Michael Wright71216972017-01-31 18:33:54 +0000454 @Override
455 public void validate() {
456 if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) {
457 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000458 "amplitude must either be DEFAULT_AMPLITUDE, "
459 + "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")");
Michael Wright71216972017-01-31 18:33:54 +0000460 }
Michael Wright35a0c672018-01-24 00:32:53 +0000461 if (mDuration <= 0) {
Michael Wright0ef6edd2017-04-05 20:57:39 +0100462 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000463 "duration must be positive (duration=" + mDuration + ")");
Michael Wright71216972017-01-31 18:33:54 +0000464 }
465 }
466
467 @Override
468 public boolean equals(Object o) {
469 if (!(o instanceof VibrationEffect.OneShot)) {
470 return false;
471 }
472 VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
Michael Wright35a0c672018-01-24 00:32:53 +0000473 return other.mDuration == mDuration && other.mAmplitude == mAmplitude;
Michael Wright71216972017-01-31 18:33:54 +0000474 }
475
476 @Override
477 public int hashCode() {
478 int result = 17;
Michael Wright35a0c672018-01-24 00:32:53 +0000479 result += 37 * (int) mDuration;
480 result += 37 * mAmplitude;
Michael Wright71216972017-01-31 18:33:54 +0000481 return result;
482 }
483
484 @Override
485 public String toString() {
Michael Wright35a0c672018-01-24 00:32:53 +0000486 return "OneShot{mDuration=" + mDuration + ", mAmplitude=" + mAmplitude + "}";
Michael Wright71216972017-01-31 18:33:54 +0000487 }
488
489 @Override
490 public void writeToParcel(Parcel out, int flags) {
491 out.writeInt(PARCEL_TOKEN_ONE_SHOT);
Michael Wright35a0c672018-01-24 00:32:53 +0000492 out.writeLong(mDuration);
Michael Wright71216972017-01-31 18:33:54 +0000493 out.writeInt(mAmplitude);
494 }
495
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700496 public static final @android.annotation.NonNull Parcelable.Creator<OneShot> CREATOR =
Michael Wright71216972017-01-31 18:33:54 +0000497 new Parcelable.Creator<OneShot>() {
498 @Override
499 public OneShot createFromParcel(Parcel in) {
500 // Skip the type token
501 in.readInt();
502 return new OneShot(in);
503 }
504 @Override
505 public OneShot[] newArray(int size) {
506 return new OneShot[size];
507 }
508 };
509 }
510
511 /** @hide */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000512 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000513 public static class Waveform extends VibrationEffect implements Parcelable {
Michael Wright35a0c672018-01-24 00:32:53 +0000514 private final long[] mTimings;
515 private final int[] mAmplitudes;
516 private final int mRepeat;
Michael Wright71216972017-01-31 18:33:54 +0000517
518 public Waveform(Parcel in) {
519 this(in.createLongArray(), in.createIntArray(), in.readInt());
520 }
521
522 public Waveform(long[] timings, int[] amplitudes, int repeat) {
523 mTimings = new long[timings.length];
524 System.arraycopy(timings, 0, mTimings, 0, timings.length);
525 mAmplitudes = new int[amplitudes.length];
526 System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length);
527 mRepeat = repeat;
528 }
529
530 public long[] getTimings() {
531 return mTimings;
532 }
533
534 public int[] getAmplitudes() {
535 return mAmplitudes;
536 }
537
538 public int getRepeatIndex() {
539 return mRepeat;
540 }
541
542 @Override
Michael Wright35a0c672018-01-24 00:32:53 +0000543 public long getDuration() {
544 if (mRepeat >= 0) {
545 return Long.MAX_VALUE;
546 }
547 long duration = 0;
548 for (long d : mTimings) {
549 duration += d;
550 }
551 return duration;
552 }
553
554 /**
555 * Scale the Waveform with the given gamma and new max amplitude.
556 *
557 * @param gamma the gamma adjustment to apply
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000558 * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and
559 * MAX_AMPLITUDE
560 * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE
Michael Wright35a0c672018-01-24 00:32:53 +0000561 *
562 * @return A {@link Waveform} effect with the same timings and repeat index
563 * but scaled amplitude.
564 */
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000565 public Waveform scale(float gamma, int maxAmplitude) {
566 if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) {
567 throw new IllegalArgumentException(
568 "Amplitude is negative or greater than MAX_AMPLITUDE");
569 }
Michael Wright35a0c672018-01-24 00:32:53 +0000570 if (gamma == 1.0f && maxAmplitude == MAX_AMPLITUDE) {
571 // Just return a copy of the original if there's no scaling to be done.
572 return new Waveform(mTimings, mAmplitudes, mRepeat);
573 }
574
575 int[] scaledAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
576 for (int i = 0; i < scaledAmplitudes.length; i++) {
577 scaledAmplitudes[i] = scale(scaledAmplitudes[i], gamma, maxAmplitude);
578 }
579 return new Waveform(mTimings, scaledAmplitudes, mRepeat);
580 }
581
Alexey Kuzmin55bdc592018-04-24 12:58:13 +0100582 /**
583 * Resolve default values into integer amplitude numbers.
584 *
585 * @param defaultAmplitude the default amplitude to apply, must be between 0 and
586 * MAX_AMPLITUDE
587 * @return A {@link Waveform} effect with same physical meaning but explicitly set
588 * amplitude
589 *
590 * @hide
591 */
592 public Waveform resolve(int defaultAmplitude) {
593 if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
594 throw new IllegalArgumentException(
595 "Amplitude is negative or greater than MAX_AMPLITUDE");
596 }
597 int[] resolvedAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
598 for (int i = 0; i < resolvedAmplitudes.length; i++) {
599 if (resolvedAmplitudes[i] == DEFAULT_AMPLITUDE) {
600 resolvedAmplitudes[i] = defaultAmplitude;
601 }
602 }
603 return new Waveform(mTimings, resolvedAmplitudes, mRepeat);
604 }
605
Michael Wright35a0c672018-01-24 00:32:53 +0000606 @Override
Michael Wright71216972017-01-31 18:33:54 +0000607 public void validate() {
608 if (mTimings.length != mAmplitudes.length) {
609 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000610 "timing and amplitude arrays must be of equal length"
611 + " (timings.length=" + mTimings.length
612 + ", amplitudes.length=" + mAmplitudes.length + ")");
Michael Wright71216972017-01-31 18:33:54 +0000613 }
614 if (!hasNonZeroEntry(mTimings)) {
Michael Wright35a0c672018-01-24 00:32:53 +0000615 throw new IllegalArgumentException("at least one timing must be non-zero"
616 + " (timings=" + Arrays.toString(mTimings) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000617 }
618 for (long timing : mTimings) {
619 if (timing < 0) {
Michael Wright35a0c672018-01-24 00:32:53 +0000620 throw new IllegalArgumentException("timings must all be >= 0"
621 + " (timings=" + Arrays.toString(mTimings) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000622 }
623 }
624 for (int amplitude : mAmplitudes) {
625 if (amplitude < -1 || amplitude > 255) {
626 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000627 "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255"
628 + " (amplitudes=" + Arrays.toString(mAmplitudes) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000629 }
630 }
631 if (mRepeat < -1 || mRepeat >= mTimings.length) {
Michael Wright0ef6edd2017-04-05 20:57:39 +0100632 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000633 "repeat index must be within the bounds of the timings array"
634 + " (timings.length=" + mTimings.length + ", index=" + mRepeat + ")");
Michael Wright71216972017-01-31 18:33:54 +0000635 }
636 }
637
638 @Override
639 public boolean equals(Object o) {
640 if (!(o instanceof VibrationEffect.Waveform)) {
641 return false;
642 }
643 VibrationEffect.Waveform other = (VibrationEffect.Waveform) o;
Michael Wright35a0c672018-01-24 00:32:53 +0000644 return Arrays.equals(mTimings, other.mTimings)
645 && Arrays.equals(mAmplitudes, other.mAmplitudes)
646 && mRepeat == other.mRepeat;
Michael Wright71216972017-01-31 18:33:54 +0000647 }
648
649 @Override
650 public int hashCode() {
651 int result = 17;
Michael Wright35a0c672018-01-24 00:32:53 +0000652 result += 37 * Arrays.hashCode(mTimings);
653 result += 37 * Arrays.hashCode(mAmplitudes);
654 result += 37 * mRepeat;
Michael Wright71216972017-01-31 18:33:54 +0000655 return result;
656 }
657
658 @Override
659 public String toString() {
Michael Wright35a0c672018-01-24 00:32:53 +0000660 return "Waveform{mTimings=" + Arrays.toString(mTimings)
661 + ", mAmplitudes=" + Arrays.toString(mAmplitudes)
662 + ", mRepeat=" + mRepeat
663 + "}";
Michael Wright71216972017-01-31 18:33:54 +0000664 }
665
666 @Override
667 public void writeToParcel(Parcel out, int flags) {
668 out.writeInt(PARCEL_TOKEN_WAVEFORM);
669 out.writeLongArray(mTimings);
670 out.writeIntArray(mAmplitudes);
671 out.writeInt(mRepeat);
672 }
673
674 private static boolean hasNonZeroEntry(long[] vals) {
675 for (long val : vals) {
676 if (val != 0) {
677 return true;
678 }
679 }
680 return false;
681 }
682
683
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700684 public static final @android.annotation.NonNull Parcelable.Creator<Waveform> CREATOR =
Michael Wright71216972017-01-31 18:33:54 +0000685 new Parcelable.Creator<Waveform>() {
686 @Override
687 public Waveform createFromParcel(Parcel in) {
688 // Skip the type token
689 in.readInt();
690 return new Waveform(in);
691 }
692 @Override
693 public Waveform[] newArray(int size) {
694 return new Waveform[size];
695 }
696 };
697 }
698
699 /** @hide */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000700 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000701 public static class Prebaked extends VibrationEffect implements Parcelable {
Michael Wright35a0c672018-01-24 00:32:53 +0000702 private final int mEffectId;
703 private final boolean mFallback;
704
705 private int mEffectStrength;
Michael Wright71216972017-01-31 18:33:54 +0000706
707 public Prebaked(Parcel in) {
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100708 this(in.readInt(), in.readByte() != 0);
Michael Wright35a0c672018-01-24 00:32:53 +0000709 mEffectStrength = in.readInt();
Michael Wright71216972017-01-31 18:33:54 +0000710 }
711
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100712 public Prebaked(int effectId, boolean fallback) {
Michael Wright71216972017-01-31 18:33:54 +0000713 mEffectId = effectId;
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100714 mFallback = fallback;
Michael Wright35a0c672018-01-24 00:32:53 +0000715 mEffectStrength = EffectStrength.MEDIUM;
Michael Wright71216972017-01-31 18:33:54 +0000716 }
717
718 public int getId() {
719 return mEffectId;
720 }
721
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100722 /**
723 * Whether the effect should fall back to a generic pattern if there's no hardware specific
724 * implementation of it.
725 */
726 public boolean shouldFallback() {
727 return mFallback;
728 }
729
Michael Wright71216972017-01-31 18:33:54 +0000730 @Override
Michael Wright35a0c672018-01-24 00:32:53 +0000731 public long getDuration() {
732 return -1;
733 }
734
735 /**
736 * Set the effect strength of the prebaked effect.
737 */
738 public void setEffectStrength(int strength) {
739 if (!isValidEffectStrength(strength)) {
740 throw new IllegalArgumentException("Invalid effect strength: " + strength);
741 }
742 mEffectStrength = strength;
743 }
744
745 /**
746 * Set the effect strength.
747 */
748 public int getEffectStrength() {
749 return mEffectStrength;
750 }
751
752 private static boolean isValidEffectStrength(int strength) {
753 switch (strength) {
754 case EffectStrength.LIGHT:
755 case EffectStrength.MEDIUM:
756 case EffectStrength.STRONG:
757 return true;
758 default:
759 return false;
760 }
761 }
762
763 @Override
Michael Wright71216972017-01-31 18:33:54 +0000764 public void validate() {
Michael Wright57d94d92017-05-31 14:44:45 +0100765 switch (mEffectId) {
766 case EFFECT_CLICK:
767 case EFFECT_DOUBLE_CLICK:
768 case EFFECT_TICK:
Michael Wright950bd772019-03-21 21:26:05 +0000769 case EFFECT_TEXTURE_TICK:
Michael Wrightf268bf52018-02-07 23:23:34 +0000770 case EFFECT_THUD:
771 case EFFECT_POP:
772 case EFFECT_HEAVY_CLICK:
Michael Wright57d94d92017-05-31 14:44:45 +0100773 break;
774 default:
Michael Wrightf268bf52018-02-07 23:23:34 +0000775 if (mEffectId < RINGTONES[0] || mEffectId > RINGTONES[RINGTONES.length - 1]) {
776 throw new IllegalArgumentException(
777 "Unknown prebaked effect type (value=" + mEffectId + ")");
778 }
Michael Wright71216972017-01-31 18:33:54 +0000779 }
Michael Wright35a0c672018-01-24 00:32:53 +0000780 if (!isValidEffectStrength(mEffectStrength)) {
781 throw new IllegalArgumentException(
782 "Unknown prebaked effect strength (value=" + mEffectStrength + ")");
783 }
Michael Wright71216972017-01-31 18:33:54 +0000784 }
785
786 @Override
787 public boolean equals(Object o) {
788 if (!(o instanceof VibrationEffect.Prebaked)) {
789 return false;
790 }
791 VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
Michael Wright35a0c672018-01-24 00:32:53 +0000792 return mEffectId == other.mEffectId
793 && mFallback == other.mFallback
794 && mEffectStrength == other.mEffectStrength;
Michael Wright71216972017-01-31 18:33:54 +0000795 }
796
797 @Override
798 public int hashCode() {
Michael Wright35a0c672018-01-24 00:32:53 +0000799 int result = 17;
800 result += 37 * mEffectId;
801 result += 37 * mEffectStrength;
802 return result;
Michael Wright71216972017-01-31 18:33:54 +0000803 }
804
805 @Override
806 public String toString() {
Michael Wright35a0c672018-01-24 00:32:53 +0000807 return "Prebaked{mEffectId=" + mEffectId
808 + ", mEffectStrength=" + mEffectStrength
809 + ", mFallback=" + mFallback
810 + "}";
Michael Wright71216972017-01-31 18:33:54 +0000811 }
812
813
814 @Override
815 public void writeToParcel(Parcel out, int flags) {
816 out.writeInt(PARCEL_TOKEN_EFFECT);
817 out.writeInt(mEffectId);
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100818 out.writeByte((byte) (mFallback ? 1 : 0));
Michael Wright35a0c672018-01-24 00:32:53 +0000819 out.writeInt(mEffectStrength);
Michael Wright71216972017-01-31 18:33:54 +0000820 }
821
Michael Wright950bd772019-03-21 21:26:05 +0000822 public static final @NonNull Parcelable.Creator<Prebaked> CREATOR =
Michael Wright71216972017-01-31 18:33:54 +0000823 new Parcelable.Creator<Prebaked>() {
824 @Override
825 public Prebaked createFromParcel(Parcel in) {
826 // Skip the type token
827 in.readInt();
828 return new Prebaked(in);
829 }
830 @Override
831 public Prebaked[] newArray(int size) {
832 return new Prebaked[size];
833 }
834 };
835 }
836
Michael Wright950bd772019-03-21 21:26:05 +0000837 public static final @NonNull Parcelable.Creator<VibrationEffect> CREATOR =
Michael Wright71216972017-01-31 18:33:54 +0000838 new Parcelable.Creator<VibrationEffect>() {
839 @Override
840 public VibrationEffect createFromParcel(Parcel in) {
841 int token = in.readInt();
842 if (token == PARCEL_TOKEN_ONE_SHOT) {
843 return new OneShot(in);
844 } else if (token == PARCEL_TOKEN_WAVEFORM) {
845 return new Waveform(in);
846 } else if (token == PARCEL_TOKEN_EFFECT) {
847 return new Prebaked(in);
848 } else {
849 throw new IllegalStateException(
850 "Unexpected vibration event type token in parcel.");
851 }
852 }
853 @Override
854 public VibrationEffect[] newArray(int size) {
855 return new VibrationEffect[size];
856 }
857 };
858}