blob: 75b4724c7d26aad8cfcf88252ed6a32f73551b93 [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;
Artur Satayevafdb23a2019-12-10 17:47:53 +000023import android.compat.annotation.UnsupportedAppUsage;
Michael Wright32fa5932018-05-18 18:07:09 -070024import android.content.ContentResolver;
Michael Wrightf268bf52018-02-07 23:23:34 +000025import android.content.Context;
26import android.hardware.vibrator.V1_0.EffectStrength;
Michael Wright950bd772019-03-21 21:26:05 +000027import android.hardware.vibrator.V1_3.Effect;
Michael Wrightf268bf52018-02-07 23:23:34 +000028import android.net.Uri;
Michael Wright35a0c672018-01-24 00:32:53 +000029import android.util.MathUtils;
Michael Wright71216972017-01-31 18:33:54 +000030
Alexey Kuzmin3a8a39f2019-01-18 16:56:23 +000031import java.lang.annotation.Retention;
32import java.lang.annotation.RetentionPolicy;
Michael Wright71216972017-01-31 18:33:54 +000033import java.util.Arrays;
34
35/**
36 * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
37 *
38 * These effects may be any number of things, from single shot vibrations to complex waveforms.
39 */
40public abstract class VibrationEffect implements Parcelable {
41 private static final int PARCEL_TOKEN_ONE_SHOT = 1;
42 private static final int PARCEL_TOKEN_WAVEFORM = 2;
43 private static final int PARCEL_TOKEN_EFFECT = 3;
44
45 /**
46 * The default vibration strength of the device.
47 */
48 public static final int DEFAULT_AMPLITUDE = -1;
49
50 /**
Michael Wright35a0c672018-01-24 00:32:53 +000051 * The maximum amplitude value
52 * @hide
53 */
54 public static final int MAX_AMPLITUDE = 255;
55
56 /**
Kevin Hufnagle86b17d22019-08-29 04:29:08 +000057 * 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 +000058 *
59 * @see #get(int)
Michael Wright71216972017-01-31 18:33:54 +000060 */
Michael Wrightf268bf52018-02-07 23:23:34 +000061 public static final int EFFECT_CLICK = Effect.CLICK;
Michael Wright71216972017-01-31 18:33:54 +000062
63 /**
64 * A double click effect.
65 *
66 * @see #get(int)
Michael Wright71216972017-01-31 18:33:54 +000067 */
Michael Wrightf268bf52018-02-07 23:23:34 +000068 public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK;
Michael Wright57d94d92017-05-31 14:44:45 +010069
70 /**
Kevin Hufnagle86b17d22019-08-29 04:29:08 +000071 * A tick effect. This effect is less strong compared to {@link #EFFECT_CLICK}.
Michael Wright57d94d92017-05-31 14:44:45 +010072 * @see #get(int)
Michael Wright57d94d92017-05-31 14:44:45 +010073 */
Michael Wrightf268bf52018-02-07 23:23:34 +000074 public static final int EFFECT_TICK = Effect.TICK;
75
76 /**
77 * A thud effect.
78 * @see #get(int)
79 * @hide
80 */
Artur Satayevf0b7d0b2019-11-04 11:16:45 +000081 @UnsupportedAppUsage
Jeff Sharkeyc6091162018-06-29 17:15:40 -060082 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +000083 public static final int EFFECT_THUD = Effect.THUD;
84
85 /**
86 * A pop effect.
87 * @see #get(int)
88 * @hide
89 */
Artur Satayevf0b7d0b2019-11-04 11:16:45 +000090 @UnsupportedAppUsage
Jeff Sharkeyc6091162018-06-29 17:15:40 -060091 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +000092 public static final int EFFECT_POP = Effect.POP;
93
94 /**
Kevin Hufnagle86b17d22019-08-29 04:29:08 +000095 * A heavy click effect. This effect is stronger than {@link #EFFECT_CLICK}.
Michael Wrightf268bf52018-02-07 23:23:34 +000096 * @see #get(int)
Michael Wrightf268bf52018-02-07 23:23:34 +000097 */
98 public static final int EFFECT_HEAVY_CLICK = Effect.HEAVY_CLICK;
99
Michael Wright950bd772019-03-21 21:26:05 +0000100 /**
101 * A texture effect meant to replicate soft ticks.
102 *
103 * Unlike normal effects, texture effects are meant to be called repeatedly, generally in
104 * response to some motion, in order to replicate the feeling of some texture underneath the
105 * user's fingers.
106 *
107 * @see #get(int)
108 * @hide
109 */
Alexey Kuzmina2112a32019-04-04 18:39:38 +0100110 @TestApi
Michael Wright950bd772019-03-21 21:26:05 +0000111 public static final int EFFECT_TEXTURE_TICK = Effect.TEXTURE_TICK;
112
Jeff Sharkeyc6091162018-06-29 17:15:40 -0600113 /** {@hide} */
114 @TestApi
115 public static final int EFFECT_STRENGTH_LIGHT = EffectStrength.LIGHT;
116
117 /** {@hide} */
118 @TestApi
119 public static final int EFFECT_STRENGTH_MEDIUM = EffectStrength.MEDIUM;
120
121 /** {@hide} */
122 @TestApi
123 public static final int EFFECT_STRENGTH_STRONG = EffectStrength.STRONG;
Michael Wrightf268bf52018-02-07 23:23:34 +0000124
125 /**
126 * Ringtone patterns. They may correspond with the device's ringtone audio, or may just be a
127 * pattern that can be played as a ringtone with any audio, depending on the device.
128 *
129 * @see #get(Uri, Context)
130 * @hide
131 */
Artur Satayevf0b7d0b2019-11-04 11:16:45 +0000132 @UnsupportedAppUsage
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000133 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +0000134 public static final int[] RINGTONES = {
135 Effect.RINGTONE_1,
136 Effect.RINGTONE_2,
137 Effect.RINGTONE_3,
138 Effect.RINGTONE_4,
139 Effect.RINGTONE_5,
140 Effect.RINGTONE_6,
141 Effect.RINGTONE_7,
142 Effect.RINGTONE_8,
143 Effect.RINGTONE_9,
144 Effect.RINGTONE_10,
145 Effect.RINGTONE_11,
146 Effect.RINGTONE_12,
147 Effect.RINGTONE_13,
148 Effect.RINGTONE_14,
149 Effect.RINGTONE_15
150 };
Michael Wright71216972017-01-31 18:33:54 +0000151
Alexey Kuzmin3a8a39f2019-01-18 16:56:23 +0000152 /** @hide */
153 @IntDef(prefix = { "EFFECT_" }, value = {
154 EFFECT_TICK,
155 EFFECT_CLICK,
156 EFFECT_HEAVY_CLICK,
157 EFFECT_DOUBLE_CLICK,
158 })
159 @Retention(RetentionPolicy.SOURCE)
160 public @interface EffectType {}
161
Michael Wright71216972017-01-31 18:33:54 +0000162 /** @hide to prevent subclassing from outside of the framework */
163 public VibrationEffect() { }
164
165 /**
166 * Create a one shot vibration.
167 *
168 * One shot vibrations will vibrate constantly for the specified period of time at the
169 * specified amplitude, and then stop.
170 *
171 * @param milliseconds The number of milliseconds to vibrate. This must be a positive number.
172 * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or
173 * {@link #DEFAULT_AMPLITUDE}.
174 *
175 * @return The desired effect.
176 */
177 public static VibrationEffect createOneShot(long milliseconds, int amplitude) {
178 VibrationEffect effect = new OneShot(milliseconds, amplitude);
179 effect.validate();
180 return effect;
181 }
182
183 /**
184 * Create a waveform vibration.
185 *
186 * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
187 * each pair, the value in the amplitude array determines the strength of the vibration and the
188 * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
189 * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
190 * <p>
191 * The amplitude array of the generated waveform will be the same size as the given
192 * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE},
193 * starting with 0. Therefore the first timing value will be the period to wait before turning
194 * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE}
195 * strength, etc.
196 * </p><p>
197 * To cause the pattern to repeat, pass the index into the timings array at which to start the
198 * repetition, or -1 to disable repeating.
199 * </p>
200 *
201 * @param timings The pattern of alternating on-off timings, starting with off. Timing values
202 * of 0 will cause the timing / amplitude pair to be ignored.
203 * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
204 * want to repeat.
205 *
206 * @return The desired effect.
207 */
208 public static VibrationEffect createWaveform(long[] timings, int repeat) {
209 int[] amplitudes = new int[timings.length];
210 for (int i = 0; i < (timings.length / 2); i++) {
211 amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE;
212 }
213 return createWaveform(timings, amplitudes, repeat);
214 }
215
216 /**
217 * Create a waveform vibration.
218 *
219 * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
220 * each pair, the value in the amplitude array determines the strength of the vibration and the
221 * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
222 * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
223 * </p><p>
224 * To cause the pattern to repeat, pass the index into the timings array at which to start the
225 * repetition, or -1 to disable repeating.
226 * </p>
227 *
228 * @param timings The timing values of the timing / amplitude pairs. Timing values of 0
229 * will cause the pair to be ignored.
230 * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values
231 * must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An
232 * amplitude value of 0 implies the motor is off.
233 * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
234 * want to repeat.
235 *
236 * @return The desired effect.
237 */
238 public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
239 VibrationEffect effect = new Waveform(timings, amplitudes, repeat);
240 effect.validate();
241 return effect;
242 }
243
244 /**
Alexey Kuzmin3a8a39f2019-01-18 16:56:23 +0000245 * Create a predefined vibration effect.
246 *
247 * Predefined effects are a set of common vibration effects that should be identical, regardless
248 * of the app they come from, in order to provide a cohesive experience for users across
249 * the entire device. They also may be custom tailored to the device hardware in order to
250 * provide a better experience than you could otherwise build using the generic building
251 * blocks.
252 *
253 * This will fallback to a generic pattern if one exists and there does not exist a
254 * hardware-specific implementation of the effect.
255 *
256 * @param effectId The ID of the effect to perform:
257 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
258 *
259 * @return The desired effect.
260 */
Arthur Hungcfdc9752019-03-04 19:25:36 +0800261 @NonNull
Arthur Hung00657b82019-02-26 11:56:40 +0800262 public static VibrationEffect createPredefined(@EffectType int effectId) {
Alexey Kuzmin3a8a39f2019-01-18 16:56:23 +0000263 return get(effectId, true);
264 }
265
266 /**
Michael Wright71216972017-01-31 18:33:54 +0000267 * Get a predefined vibration effect.
268 *
269 * Predefined effects are a set of common vibration effects that should be identical, regardless
270 * of the app they come from, in order to provide a cohesive experience for users across
271 * the entire device. They also may be custom tailored to the device hardware in order to
272 * provide a better experience than you could otherwise build using the generic building
273 * blocks.
274 *
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100275 * This will fallback to a generic pattern if one exists and there does not exist a
276 * hardware-specific implementation of the effect.
277 *
Michael Wright71216972017-01-31 18:33:54 +0000278 * @param effectId The ID of the effect to perform:
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100279 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
Michael Wright71216972017-01-31 18:33:54 +0000280 *
281 * @return The desired effect.
282 * @hide
283 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000284 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000285 public static VibrationEffect get(int effectId) {
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100286 return get(effectId, true);
287 }
288
289 /**
290 * Get a predefined vibration effect.
291 *
292 * Predefined effects are a set of common vibration effects that should be identical, regardless
293 * of the app they come from, in order to provide a cohesive experience for users across
294 * the entire device. They also may be custom tailored to the device hardware in order to
295 * provide a better experience than you could otherwise build using the generic building
296 * blocks.
297 *
298 * Some effects you may only want to play if there's a hardware specific implementation because
299 * they may, for example, be too disruptive to the user without tuning. The {@code fallback}
300 * parameter allows you to decide whether you want to fallback to the generic implementation or
301 * only play if there's a tuned, hardware specific one available.
302 *
303 * @param effectId The ID of the effect to perform:
304 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
305 * @param fallback Whether to fallback to a generic pattern if a hardware specific
306 * implementation doesn't exist.
307 *
308 * @return The desired effect.
309 * @hide
310 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000311 @TestApi
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100312 public static VibrationEffect get(int effectId, boolean fallback) {
313 VibrationEffect effect = new Prebaked(effectId, fallback);
Michael Wright71216972017-01-31 18:33:54 +0000314 effect.validate();
315 return effect;
316 }
317
Michael Wrightf268bf52018-02-07 23:23:34 +0000318 /**
319 * Get a predefined vibration effect associated with a given URI.
320 *
321 * Predefined effects are a set of common vibration effects that should be identical, regardless
322 * of the app they come from, in order to provide a cohesive experience for users across
323 * the entire device. They also may be custom tailored to the device hardware in order to
324 * provide a better experience than you could otherwise build using the generic building
325 * blocks.
326 *
327 * @param uri The URI associated with the haptic effect.
328 * @param context The context used to get the URI to haptic effect association.
329 *
330 * @return The desired effect, or {@code null} if there's no associated effect.
331 *
332 * @hide
333 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000334 @TestApi
Michael Wrightf268bf52018-02-07 23:23:34 +0000335 @Nullable
336 public static VibrationEffect get(Uri uri, Context context) {
Michael Wright8b1f3c92019-06-04 15:26:25 +0100337 final ContentResolver cr = context.getContentResolver();
338 Uri uncanonicalUri = cr.uncanonicalize(uri);
339 if (uncanonicalUri == null) {
340 // If we already had an uncanonical URI, it's possible we'll get null back here. In
341 // this case, just use the URI as passed in since it wasn't canonicalized in the first
342 // place.
343 uncanonicalUri = uri;
344 }
Michael Wrightf268bf52018-02-07 23:23:34 +0000345 String[] uris = context.getResources().getStringArray(
346 com.android.internal.R.array.config_ringtoneEffectUris);
347 for (int i = 0; i < uris.length && i < RINGTONES.length; i++) {
348 if (uris[i] == null) {
349 continue;
350 }
Michael Wright32fa5932018-05-18 18:07:09 -0700351 Uri mappedUri = cr.uncanonicalize(Uri.parse(uris[i]));
352 if (mappedUri == null) {
353 continue;
354 }
Michael Wright8b1f3c92019-06-04 15:26:25 +0100355 if (mappedUri.equals(uncanonicalUri)) {
Michael Wrightf268bf52018-02-07 23:23:34 +0000356 return get(RINGTONES[i]);
357 }
358 }
359 return null;
360 }
361
Michael Wright71216972017-01-31 18:33:54 +0000362 @Override
363 public int describeContents() {
364 return 0;
365 }
366
367 /** @hide */
368 public abstract void validate();
369
Michael Wright35a0c672018-01-24 00:32:53 +0000370 /**
371 * Gets the estimated duration of the vibration in milliseconds.
372 *
373 * For effects without a defined end (e.g. a Waveform with a non-negative repeat index), this
374 * returns Long.MAX_VALUE. For effects with an unknown duration (e.g. Prebaked effects where
375 * the length is device and potentially run-time dependent), this returns -1.
376 *
377 * @hide
378 */
Jeff Sharkeyc6091162018-06-29 17:15:40 -0600379 @TestApi
Michael Wright35a0c672018-01-24 00:32:53 +0000380 public abstract long getDuration();
381
382 /**
383 * Scale the amplitude with the given constraints.
384 *
385 * This assumes that the previous value was in the range [0, MAX_AMPLITUDE]
386 * @hide
387 */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000388 @TestApi
Michael Wright35a0c672018-01-24 00:32:53 +0000389 protected static int scale(int amplitude, float gamma, int maxAmplitude) {
390 float val = MathUtils.pow(amplitude / (float) MAX_AMPLITUDE, gamma);
391 return (int) (val * maxAmplitude);
392 }
393
Michael Wright71216972017-01-31 18:33:54 +0000394 /** @hide */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000395 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000396 public static class OneShot extends VibrationEffect implements Parcelable {
Michael Wright35a0c672018-01-24 00:32:53 +0000397 private final long mDuration;
398 private final int mAmplitude;
Michael Wright71216972017-01-31 18:33:54 +0000399
400 public OneShot(Parcel in) {
Michael Wright35a0c672018-01-24 00:32:53 +0000401 mDuration = in.readLong();
402 mAmplitude = in.readInt();
Michael Wright71216972017-01-31 18:33:54 +0000403 }
404
405 public OneShot(long milliseconds, int amplitude) {
Michael Wright35a0c672018-01-24 00:32:53 +0000406 mDuration = milliseconds;
Michael Wright71216972017-01-31 18:33:54 +0000407 mAmplitude = amplitude;
408 }
409
Michael Wright35a0c672018-01-24 00:32:53 +0000410 @Override
411 public long getDuration() {
412 return mDuration;
Michael Wright71216972017-01-31 18:33:54 +0000413 }
414
415 public int getAmplitude() {
416 return mAmplitude;
417 }
418
Michael Wright35a0c672018-01-24 00:32:53 +0000419 /**
420 * Scale the amplitude of this effect.
421 *
422 * @param gamma the gamma adjustment to apply
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000423 * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and
424 * MAX_AMPLITUDE
425 * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE
Michael Wright35a0c672018-01-24 00:32:53 +0000426 *
427 * @return A {@link OneShot} effect with the same timing but scaled amplitude.
428 */
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000429 public OneShot scale(float gamma, int maxAmplitude) {
430 if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) {
431 throw new IllegalArgumentException(
432 "Amplitude is negative or greater than MAX_AMPLITUDE");
433 }
Michael Wright35a0c672018-01-24 00:32:53 +0000434 int newAmplitude = scale(mAmplitude, gamma, maxAmplitude);
435 return new OneShot(mDuration, newAmplitude);
436 }
437
Alexey Kuzmin55bdc592018-04-24 12:58:13 +0100438 /**
439 * Resolve default values into integer amplitude numbers.
440 *
441 * @param defaultAmplitude the default amplitude to apply, must be between 0 and
442 * MAX_AMPLITUDE
443 * @return A {@link OneShot} effect with same physical meaning but explicitly set amplitude
444 *
445 * @hide
446 */
447 public OneShot resolve(int defaultAmplitude) {
448 if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
449 throw new IllegalArgumentException(
450 "Amplitude is negative or greater than MAX_AMPLITUDE");
451 }
452 if (mAmplitude == DEFAULT_AMPLITUDE) {
453 return new OneShot(mDuration, defaultAmplitude);
454 }
455 return this;
456 }
457
Michael Wright71216972017-01-31 18:33:54 +0000458 @Override
459 public void validate() {
460 if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) {
461 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000462 "amplitude must either be DEFAULT_AMPLITUDE, "
463 + "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")");
Michael Wright71216972017-01-31 18:33:54 +0000464 }
Michael Wright35a0c672018-01-24 00:32:53 +0000465 if (mDuration <= 0) {
Michael Wright0ef6edd2017-04-05 20:57:39 +0100466 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000467 "duration must be positive (duration=" + mDuration + ")");
Michael Wright71216972017-01-31 18:33:54 +0000468 }
469 }
470
471 @Override
472 public boolean equals(Object o) {
473 if (!(o instanceof VibrationEffect.OneShot)) {
474 return false;
475 }
476 VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
Michael Wright35a0c672018-01-24 00:32:53 +0000477 return other.mDuration == mDuration && other.mAmplitude == mAmplitude;
Michael Wright71216972017-01-31 18:33:54 +0000478 }
479
480 @Override
481 public int hashCode() {
482 int result = 17;
Michael Wright35a0c672018-01-24 00:32:53 +0000483 result += 37 * (int) mDuration;
484 result += 37 * mAmplitude;
Michael Wright71216972017-01-31 18:33:54 +0000485 return result;
486 }
487
488 @Override
489 public String toString() {
Michael Wright35a0c672018-01-24 00:32:53 +0000490 return "OneShot{mDuration=" + mDuration + ", mAmplitude=" + mAmplitude + "}";
Michael Wright71216972017-01-31 18:33:54 +0000491 }
492
493 @Override
494 public void writeToParcel(Parcel out, int flags) {
495 out.writeInt(PARCEL_TOKEN_ONE_SHOT);
Michael Wright35a0c672018-01-24 00:32:53 +0000496 out.writeLong(mDuration);
Michael Wright71216972017-01-31 18:33:54 +0000497 out.writeInt(mAmplitude);
498 }
499
Artur Satayevf0b7d0b2019-11-04 11:16:45 +0000500 @UnsupportedAppUsage
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700501 public static final @android.annotation.NonNull Parcelable.Creator<OneShot> CREATOR =
Michael Wright71216972017-01-31 18:33:54 +0000502 new Parcelable.Creator<OneShot>() {
503 @Override
504 public OneShot createFromParcel(Parcel in) {
505 // Skip the type token
506 in.readInt();
507 return new OneShot(in);
508 }
509 @Override
510 public OneShot[] newArray(int size) {
511 return new OneShot[size];
512 }
513 };
514 }
515
516 /** @hide */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000517 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000518 public static class Waveform extends VibrationEffect implements Parcelable {
Michael Wright35a0c672018-01-24 00:32:53 +0000519 private final long[] mTimings;
520 private final int[] mAmplitudes;
521 private final int mRepeat;
Michael Wright71216972017-01-31 18:33:54 +0000522
523 public Waveform(Parcel in) {
524 this(in.createLongArray(), in.createIntArray(), in.readInt());
525 }
526
527 public Waveform(long[] timings, int[] amplitudes, int repeat) {
528 mTimings = new long[timings.length];
529 System.arraycopy(timings, 0, mTimings, 0, timings.length);
530 mAmplitudes = new int[amplitudes.length];
531 System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length);
532 mRepeat = repeat;
533 }
534
535 public long[] getTimings() {
536 return mTimings;
537 }
538
539 public int[] getAmplitudes() {
540 return mAmplitudes;
541 }
542
543 public int getRepeatIndex() {
544 return mRepeat;
545 }
546
547 @Override
Michael Wright35a0c672018-01-24 00:32:53 +0000548 public long getDuration() {
549 if (mRepeat >= 0) {
550 return Long.MAX_VALUE;
551 }
552 long duration = 0;
553 for (long d : mTimings) {
554 duration += d;
555 }
556 return duration;
557 }
558
559 /**
560 * Scale the Waveform with the given gamma and new max amplitude.
561 *
562 * @param gamma the gamma adjustment to apply
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000563 * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and
564 * MAX_AMPLITUDE
565 * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE
Michael Wright35a0c672018-01-24 00:32:53 +0000566 *
567 * @return A {@link Waveform} effect with the same timings and repeat index
568 * but scaled amplitude.
569 */
Alexey Kuzminfebc5d52018-03-02 18:54:06 +0000570 public Waveform scale(float gamma, int maxAmplitude) {
571 if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) {
572 throw new IllegalArgumentException(
573 "Amplitude is negative or greater than MAX_AMPLITUDE");
574 }
Michael Wright35a0c672018-01-24 00:32:53 +0000575 if (gamma == 1.0f && maxAmplitude == MAX_AMPLITUDE) {
576 // Just return a copy of the original if there's no scaling to be done.
577 return new Waveform(mTimings, mAmplitudes, mRepeat);
578 }
579
580 int[] scaledAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
581 for (int i = 0; i < scaledAmplitudes.length; i++) {
582 scaledAmplitudes[i] = scale(scaledAmplitudes[i], gamma, maxAmplitude);
583 }
584 return new Waveform(mTimings, scaledAmplitudes, mRepeat);
585 }
586
Alexey Kuzmin55bdc592018-04-24 12:58:13 +0100587 /**
588 * Resolve default values into integer amplitude numbers.
589 *
590 * @param defaultAmplitude the default amplitude to apply, must be between 0 and
591 * MAX_AMPLITUDE
592 * @return A {@link Waveform} effect with same physical meaning but explicitly set
593 * amplitude
594 *
595 * @hide
596 */
597 public Waveform resolve(int defaultAmplitude) {
598 if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
599 throw new IllegalArgumentException(
600 "Amplitude is negative or greater than MAX_AMPLITUDE");
601 }
602 int[] resolvedAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
603 for (int i = 0; i < resolvedAmplitudes.length; i++) {
604 if (resolvedAmplitudes[i] == DEFAULT_AMPLITUDE) {
605 resolvedAmplitudes[i] = defaultAmplitude;
606 }
607 }
608 return new Waveform(mTimings, resolvedAmplitudes, mRepeat);
609 }
610
Michael Wright35a0c672018-01-24 00:32:53 +0000611 @Override
Michael Wright71216972017-01-31 18:33:54 +0000612 public void validate() {
613 if (mTimings.length != mAmplitudes.length) {
614 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000615 "timing and amplitude arrays must be of equal length"
616 + " (timings.length=" + mTimings.length
617 + ", amplitudes.length=" + mAmplitudes.length + ")");
Michael Wright71216972017-01-31 18:33:54 +0000618 }
619 if (!hasNonZeroEntry(mTimings)) {
Michael Wright35a0c672018-01-24 00:32:53 +0000620 throw new IllegalArgumentException("at least one timing must be non-zero"
621 + " (timings=" + Arrays.toString(mTimings) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000622 }
623 for (long timing : mTimings) {
624 if (timing < 0) {
Michael Wright35a0c672018-01-24 00:32:53 +0000625 throw new IllegalArgumentException("timings must all be >= 0"
626 + " (timings=" + Arrays.toString(mTimings) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000627 }
628 }
629 for (int amplitude : mAmplitudes) {
630 if (amplitude < -1 || amplitude > 255) {
631 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000632 "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255"
633 + " (amplitudes=" + Arrays.toString(mAmplitudes) + ")");
Michael Wright71216972017-01-31 18:33:54 +0000634 }
635 }
636 if (mRepeat < -1 || mRepeat >= mTimings.length) {
Michael Wright0ef6edd2017-04-05 20:57:39 +0100637 throw new IllegalArgumentException(
Michael Wright35a0c672018-01-24 00:32:53 +0000638 "repeat index must be within the bounds of the timings array"
639 + " (timings.length=" + mTimings.length + ", index=" + mRepeat + ")");
Michael Wright71216972017-01-31 18:33:54 +0000640 }
641 }
642
643 @Override
644 public boolean equals(Object o) {
645 if (!(o instanceof VibrationEffect.Waveform)) {
646 return false;
647 }
648 VibrationEffect.Waveform other = (VibrationEffect.Waveform) o;
Michael Wright35a0c672018-01-24 00:32:53 +0000649 return Arrays.equals(mTimings, other.mTimings)
650 && Arrays.equals(mAmplitudes, other.mAmplitudes)
651 && mRepeat == other.mRepeat;
Michael Wright71216972017-01-31 18:33:54 +0000652 }
653
654 @Override
655 public int hashCode() {
656 int result = 17;
Michael Wright35a0c672018-01-24 00:32:53 +0000657 result += 37 * Arrays.hashCode(mTimings);
658 result += 37 * Arrays.hashCode(mAmplitudes);
659 result += 37 * mRepeat;
Michael Wright71216972017-01-31 18:33:54 +0000660 return result;
661 }
662
663 @Override
664 public String toString() {
Michael Wright35a0c672018-01-24 00:32:53 +0000665 return "Waveform{mTimings=" + Arrays.toString(mTimings)
666 + ", mAmplitudes=" + Arrays.toString(mAmplitudes)
667 + ", mRepeat=" + mRepeat
668 + "}";
Michael Wright71216972017-01-31 18:33:54 +0000669 }
670
671 @Override
672 public void writeToParcel(Parcel out, int flags) {
673 out.writeInt(PARCEL_TOKEN_WAVEFORM);
674 out.writeLongArray(mTimings);
675 out.writeIntArray(mAmplitudes);
676 out.writeInt(mRepeat);
677 }
678
679 private static boolean hasNonZeroEntry(long[] vals) {
680 for (long val : vals) {
681 if (val != 0) {
682 return true;
683 }
684 }
685 return false;
686 }
687
688
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700689 public static final @android.annotation.NonNull Parcelable.Creator<Waveform> CREATOR =
Michael Wright71216972017-01-31 18:33:54 +0000690 new Parcelable.Creator<Waveform>() {
691 @Override
692 public Waveform createFromParcel(Parcel in) {
693 // Skip the type token
694 in.readInt();
695 return new Waveform(in);
696 }
697 @Override
698 public Waveform[] newArray(int size) {
699 return new Waveform[size];
700 }
701 };
702 }
703
704 /** @hide */
Alexey Kuzmin505872d2018-03-19 19:27:46 +0000705 @TestApi
Michael Wright71216972017-01-31 18:33:54 +0000706 public static class Prebaked extends VibrationEffect implements Parcelable {
Michael Wright35a0c672018-01-24 00:32:53 +0000707 private final int mEffectId;
708 private final boolean mFallback;
709
710 private int mEffectStrength;
Michael Wright71216972017-01-31 18:33:54 +0000711
712 public Prebaked(Parcel in) {
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100713 this(in.readInt(), in.readByte() != 0);
Michael Wright35a0c672018-01-24 00:32:53 +0000714 mEffectStrength = in.readInt();
Michael Wright71216972017-01-31 18:33:54 +0000715 }
716
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100717 public Prebaked(int effectId, boolean fallback) {
Michael Wright71216972017-01-31 18:33:54 +0000718 mEffectId = effectId;
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100719 mFallback = fallback;
Michael Wright35a0c672018-01-24 00:32:53 +0000720 mEffectStrength = EffectStrength.MEDIUM;
Michael Wright71216972017-01-31 18:33:54 +0000721 }
722
723 public int getId() {
724 return mEffectId;
725 }
726
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100727 /**
728 * Whether the effect should fall back to a generic pattern if there's no hardware specific
729 * implementation of it.
730 */
731 public boolean shouldFallback() {
732 return mFallback;
733 }
734
Michael Wright71216972017-01-31 18:33:54 +0000735 @Override
Michael Wright35a0c672018-01-24 00:32:53 +0000736 public long getDuration() {
737 return -1;
738 }
739
740 /**
741 * Set the effect strength of the prebaked effect.
742 */
743 public void setEffectStrength(int strength) {
744 if (!isValidEffectStrength(strength)) {
745 throw new IllegalArgumentException("Invalid effect strength: " + strength);
746 }
747 mEffectStrength = strength;
748 }
749
750 /**
751 * Set the effect strength.
752 */
753 public int getEffectStrength() {
754 return mEffectStrength;
755 }
756
757 private static boolean isValidEffectStrength(int strength) {
758 switch (strength) {
759 case EffectStrength.LIGHT:
760 case EffectStrength.MEDIUM:
761 case EffectStrength.STRONG:
762 return true;
763 default:
764 return false;
765 }
766 }
767
768 @Override
Michael Wright71216972017-01-31 18:33:54 +0000769 public void validate() {
Michael Wright57d94d92017-05-31 14:44:45 +0100770 switch (mEffectId) {
771 case EFFECT_CLICK:
772 case EFFECT_DOUBLE_CLICK:
773 case EFFECT_TICK:
Michael Wright950bd772019-03-21 21:26:05 +0000774 case EFFECT_TEXTURE_TICK:
Michael Wrightf268bf52018-02-07 23:23:34 +0000775 case EFFECT_THUD:
776 case EFFECT_POP:
777 case EFFECT_HEAVY_CLICK:
Michael Wright57d94d92017-05-31 14:44:45 +0100778 break;
779 default:
Michael Wrightf268bf52018-02-07 23:23:34 +0000780 if (mEffectId < RINGTONES[0] || mEffectId > RINGTONES[RINGTONES.length - 1]) {
781 throw new IllegalArgumentException(
782 "Unknown prebaked effect type (value=" + mEffectId + ")");
783 }
Michael Wright71216972017-01-31 18:33:54 +0000784 }
Michael Wright35a0c672018-01-24 00:32:53 +0000785 if (!isValidEffectStrength(mEffectStrength)) {
786 throw new IllegalArgumentException(
787 "Unknown prebaked effect strength (value=" + mEffectStrength + ")");
788 }
Michael Wright71216972017-01-31 18:33:54 +0000789 }
790
791 @Override
792 public boolean equals(Object o) {
793 if (!(o instanceof VibrationEffect.Prebaked)) {
794 return false;
795 }
796 VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
Michael Wright35a0c672018-01-24 00:32:53 +0000797 return mEffectId == other.mEffectId
798 && mFallback == other.mFallback
799 && mEffectStrength == other.mEffectStrength;
Michael Wright71216972017-01-31 18:33:54 +0000800 }
801
802 @Override
803 public int hashCode() {
Michael Wright35a0c672018-01-24 00:32:53 +0000804 int result = 17;
805 result += 37 * mEffectId;
806 result += 37 * mEffectStrength;
807 return result;
Michael Wright71216972017-01-31 18:33:54 +0000808 }
809
810 @Override
811 public String toString() {
Michael Wright35a0c672018-01-24 00:32:53 +0000812 return "Prebaked{mEffectId=" + mEffectId
813 + ", mEffectStrength=" + mEffectStrength
814 + ", mFallback=" + mFallback
815 + "}";
Michael Wright71216972017-01-31 18:33:54 +0000816 }
817
818
819 @Override
820 public void writeToParcel(Parcel out, int flags) {
821 out.writeInt(PARCEL_TOKEN_EFFECT);
822 out.writeInt(mEffectId);
Michael Wrightdc2b3be2017-08-02 20:44:45 +0100823 out.writeByte((byte) (mFallback ? 1 : 0));
Michael Wright35a0c672018-01-24 00:32:53 +0000824 out.writeInt(mEffectStrength);
Michael Wright71216972017-01-31 18:33:54 +0000825 }
826
Michael Wright950bd772019-03-21 21:26:05 +0000827 public static final @NonNull Parcelable.Creator<Prebaked> CREATOR =
Michael Wright71216972017-01-31 18:33:54 +0000828 new Parcelable.Creator<Prebaked>() {
829 @Override
830 public Prebaked createFromParcel(Parcel in) {
831 // Skip the type token
832 in.readInt();
833 return new Prebaked(in);
834 }
835 @Override
836 public Prebaked[] newArray(int size) {
837 return new Prebaked[size];
838 }
839 };
840 }
841
Michael Wright950bd772019-03-21 21:26:05 +0000842 public static final @NonNull Parcelable.Creator<VibrationEffect> CREATOR =
Michael Wright71216972017-01-31 18:33:54 +0000843 new Parcelable.Creator<VibrationEffect>() {
844 @Override
845 public VibrationEffect createFromParcel(Parcel in) {
846 int token = in.readInt();
847 if (token == PARCEL_TOKEN_ONE_SHOT) {
848 return new OneShot(in);
849 } else if (token == PARCEL_TOKEN_WAVEFORM) {
850 return new Waveform(in);
851 } else if (token == PARCEL_TOKEN_EFFECT) {
852 return new Prebaked(in);
853 } else {
854 throw new IllegalStateException(
855 "Unexpected vibration event type token in parcel.");
856 }
857 }
858 @Override
859 public VibrationEffect[] newArray(int size) {
860 return new VibrationEffect[size];
861 }
862 };
863}