1 /* 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 17 package android.os; 18 19 import android.annotation.FloatRange; 20 import android.annotation.IntDef; 21 import android.annotation.IntRange; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.TestApi; 25 import android.compat.annotation.UnsupportedAppUsage; 26 import android.content.ContentResolver; 27 import android.content.Context; 28 import android.hardware.vibrator.V1_0.EffectStrength; 29 import android.hardware.vibrator.V1_3.Effect; 30 import android.net.Uri; 31 import android.util.MathUtils; 32 33 import com.android.internal.util.Preconditions; 34 35 import java.lang.annotation.Retention; 36 import java.lang.annotation.RetentionPolicy; 37 import java.util.ArrayList; 38 import java.util.Arrays; 39 import java.util.List; 40 import java.util.Objects; 41 42 /** 43 * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}. 44 * 45 * These effects may be any number of things, from single shot vibrations to complex waveforms. 46 */ 47 public abstract class VibrationEffect implements Parcelable { 48 private static final int PARCEL_TOKEN_ONE_SHOT = 1; 49 private static final int PARCEL_TOKEN_WAVEFORM = 2; 50 private static final int PARCEL_TOKEN_EFFECT = 3; 51 private static final int PARCEL_TOKEN_COMPOSITION = 4; 52 53 54 /** 55 * The default vibration strength of the device. 56 */ 57 public static final int DEFAULT_AMPLITUDE = -1; 58 59 /** 60 * The maximum amplitude value 61 * @hide 62 */ 63 public static final int MAX_AMPLITUDE = 255; 64 65 /** 66 * A click effect. Use this effect as a baseline, as it's the most common type of click effect. 67 */ 68 public static final int EFFECT_CLICK = Effect.CLICK; 69 70 /** 71 * A double click effect. 72 */ 73 public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK; 74 75 /** 76 * A tick effect. This effect is less strong compared to {@link #EFFECT_CLICK}. 77 */ 78 public static final int EFFECT_TICK = Effect.TICK; 79 80 /** 81 * A thud effect. 82 * @see #get(int) 83 * @hide 84 */ 85 @UnsupportedAppUsage 86 @TestApi 87 public static final int EFFECT_THUD = Effect.THUD; 88 89 /** 90 * A pop effect. 91 * @see #get(int) 92 * @hide 93 */ 94 @UnsupportedAppUsage 95 @TestApi 96 public static final int EFFECT_POP = Effect.POP; 97 98 /** 99 * A heavy click effect. This effect is stronger than {@link #EFFECT_CLICK}. 100 */ 101 public static final int EFFECT_HEAVY_CLICK = Effect.HEAVY_CLICK; 102 103 /** 104 * A texture effect meant to replicate soft ticks. 105 * 106 * Unlike normal effects, texture effects are meant to be called repeatedly, generally in 107 * response to some motion, in order to replicate the feeling of some texture underneath the 108 * user's fingers. 109 * 110 * @see #get(int) 111 * @hide 112 */ 113 @TestApi 114 public static final int EFFECT_TEXTURE_TICK = Effect.TEXTURE_TICK; 115 116 /** {@hide} */ 117 @TestApi 118 public static final int EFFECT_STRENGTH_LIGHT = EffectStrength.LIGHT; 119 120 /** {@hide} */ 121 @TestApi 122 public static final int EFFECT_STRENGTH_MEDIUM = EffectStrength.MEDIUM; 123 124 /** {@hide} */ 125 @TestApi 126 public static final int EFFECT_STRENGTH_STRONG = EffectStrength.STRONG; 127 128 /** 129 * Ringtone patterns. They may correspond with the device's ringtone audio, or may just be a 130 * pattern that can be played as a ringtone with any audio, depending on the device. 131 * 132 * @see #get(Uri, Context) 133 * @hide 134 */ 135 @UnsupportedAppUsage 136 @TestApi 137 public static final int[] RINGTONES = { 138 Effect.RINGTONE_1, 139 Effect.RINGTONE_2, 140 Effect.RINGTONE_3, 141 Effect.RINGTONE_4, 142 Effect.RINGTONE_5, 143 Effect.RINGTONE_6, 144 Effect.RINGTONE_7, 145 Effect.RINGTONE_8, 146 Effect.RINGTONE_9, 147 Effect.RINGTONE_10, 148 Effect.RINGTONE_11, 149 Effect.RINGTONE_12, 150 Effect.RINGTONE_13, 151 Effect.RINGTONE_14, 152 Effect.RINGTONE_15 153 }; 154 155 /** @hide */ 156 @IntDef(prefix = { "EFFECT_" }, value = { 157 EFFECT_TICK, 158 EFFECT_CLICK, 159 EFFECT_HEAVY_CLICK, 160 EFFECT_DOUBLE_CLICK, 161 }) 162 @Retention(RetentionPolicy.SOURCE) 163 public @interface EffectType {} 164 165 /** @hide to prevent subclassing from outside of the framework */ VibrationEffect()166 public VibrationEffect() { } 167 168 /** 169 * Create a one shot vibration. 170 * 171 * One shot vibrations will vibrate constantly for the specified period of time at the 172 * specified amplitude, and then stop. 173 * 174 * @param milliseconds The number of milliseconds to vibrate. This must be a positive number. 175 * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or 176 * {@link #DEFAULT_AMPLITUDE}. 177 * 178 * @return The desired effect. 179 */ createOneShot(long milliseconds, int amplitude)180 public static VibrationEffect createOneShot(long milliseconds, int amplitude) { 181 VibrationEffect effect = new OneShot(milliseconds, amplitude); 182 effect.validate(); 183 return effect; 184 } 185 186 /** 187 * Create a waveform vibration. 188 * 189 * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For 190 * each pair, the value in the amplitude array determines the strength of the vibration and the 191 * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no 192 * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored. 193 * <p> 194 * The amplitude array of the generated waveform will be the same size as the given 195 * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE}, 196 * starting with 0. Therefore the first timing value will be the period to wait before turning 197 * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE} 198 * strength, etc. 199 * </p><p> 200 * To cause the pattern to repeat, pass the index into the timings array at which to start the 201 * repetition, or -1 to disable repeating. 202 * </p> 203 * 204 * @param timings The pattern of alternating on-off timings, starting with off. Timing values 205 * of 0 will cause the timing / amplitude pair to be ignored. 206 * @param repeat The index into the timings array at which to repeat, or -1 if you you don't 207 * want to repeat. 208 * 209 * @return The desired effect. 210 */ createWaveform(long[] timings, int repeat)211 public static VibrationEffect createWaveform(long[] timings, int repeat) { 212 int[] amplitudes = new int[timings.length]; 213 for (int i = 0; i < (timings.length / 2); i++) { 214 amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE; 215 } 216 return createWaveform(timings, amplitudes, repeat); 217 } 218 219 /** 220 * Create a waveform vibration. 221 * 222 * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For 223 * each pair, the value in the amplitude array determines the strength of the vibration and the 224 * value in the timing array determines how long it vibrates for, in milliseconds. Amplitude 225 * values must be between 0 and 255, and an amplitude of 0 implies no vibration (i.e. off). Any 226 * pairs with a timing value of 0 will be ignored. 227 * </p><p> 228 * To cause the pattern to repeat, pass the index into the timings array at which to start the 229 * repetition, or -1 to disable repeating. 230 * </p> 231 * 232 * @param timings The timing values, in milliseconds, of the timing / amplitude pairs. Timing 233 * values of 0 will cause the pair to be ignored. 234 * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values 235 * must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An 236 * amplitude value of 0 implies the motor is off. 237 * @param repeat The index into the timings array at which to repeat, or -1 if you you don't 238 * want to repeat. 239 * 240 * @return The desired effect. 241 */ createWaveform(long[] timings, int[] amplitudes, int repeat)242 public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) { 243 VibrationEffect effect = new Waveform(timings, amplitudes, repeat); 244 effect.validate(); 245 return effect; 246 } 247 248 /** 249 * Create a predefined vibration effect. 250 * 251 * Predefined effects are a set of common vibration effects that should be identical, regardless 252 * of the app they come from, in order to provide a cohesive experience for users across 253 * the entire device. They also may be custom tailored to the device hardware in order to 254 * provide a better experience than you could otherwise build using the generic building 255 * blocks. 256 * 257 * This will fallback to a generic pattern if one exists and there does not exist a 258 * hardware-specific implementation of the effect. 259 * 260 * @param effectId The ID of the effect to perform: 261 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK} 262 * 263 * @return The desired effect. 264 */ 265 @NonNull createPredefined(@ffectType int effectId)266 public static VibrationEffect createPredefined(@EffectType int effectId) { 267 return get(effectId, true); 268 } 269 270 /** 271 * Get a predefined vibration effect. 272 * 273 * Predefined effects are a set of common vibration effects that should be identical, regardless 274 * of the app they come from, in order to provide a cohesive experience for users across 275 * the entire device. They also may be custom tailored to the device hardware in order to 276 * provide a better experience than you could otherwise build using the generic building 277 * blocks. 278 * 279 * This will fallback to a generic pattern if one exists and there does not exist a 280 * hardware-specific implementation of the effect. 281 * 282 * @param effectId The ID of the effect to perform: 283 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK} 284 * 285 * @return The desired effect. 286 * @hide 287 */ 288 @TestApi get(int effectId)289 public static VibrationEffect get(int effectId) { 290 return get(effectId, true); 291 } 292 293 /** 294 * Get a predefined vibration effect. 295 * 296 * Predefined effects are a set of common vibration effects that should be identical, regardless 297 * of the app they come from, in order to provide a cohesive experience for users across 298 * the entire device. They also may be custom tailored to the device hardware in order to 299 * provide a better experience than you could otherwise build using the generic building 300 * blocks. 301 * 302 * Some effects you may only want to play if there's a hardware specific implementation because 303 * they may, for example, be too disruptive to the user without tuning. The {@code fallback} 304 * parameter allows you to decide whether you want to fallback to the generic implementation or 305 * only play if there's a tuned, hardware specific one available. 306 * 307 * @param effectId The ID of the effect to perform: 308 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK} 309 * @param fallback Whether to fallback to a generic pattern if a hardware specific 310 * implementation doesn't exist. 311 * 312 * @return The desired effect. 313 * @hide 314 */ 315 @TestApi get(int effectId, boolean fallback)316 public static VibrationEffect get(int effectId, boolean fallback) { 317 VibrationEffect effect = new Prebaked(effectId, fallback); 318 effect.validate(); 319 return effect; 320 } 321 322 /** 323 * Get a predefined vibration effect associated with a given URI. 324 * 325 * Predefined effects are a set of common vibration effects that should be identical, regardless 326 * of the app they come from, in order to provide a cohesive experience for users across 327 * the entire device. They also may be custom tailored to the device hardware in order to 328 * provide a better experience than you could otherwise build using the generic building 329 * blocks. 330 * 331 * @param uri The URI associated with the haptic effect. 332 * @param context The context used to get the URI to haptic effect association. 333 * 334 * @return The desired effect, or {@code null} if there's no associated effect. 335 * 336 * @hide 337 */ 338 @TestApi 339 @Nullable get(Uri uri, Context context)340 public static VibrationEffect get(Uri uri, Context context) { 341 String[] uris = context.getResources().getStringArray( 342 com.android.internal.R.array.config_ringtoneEffectUris); 343 344 // Skip doing any IPC if we don't have any effects configured. 345 if (uris.length == 0) { 346 return null; 347 } 348 349 final ContentResolver cr = context.getContentResolver(); 350 Uri uncanonicalUri = cr.uncanonicalize(uri); 351 if (uncanonicalUri == null) { 352 // If we already had an uncanonical URI, it's possible we'll get null back here. In 353 // this case, just use the URI as passed in since it wasn't canonicalized in the first 354 // place. 355 uncanonicalUri = uri; 356 } 357 358 for (int i = 0; i < uris.length && i < RINGTONES.length; i++) { 359 if (uris[i] == null) { 360 continue; 361 } 362 Uri mappedUri = cr.uncanonicalize(Uri.parse(uris[i])); 363 if (mappedUri == null) { 364 continue; 365 } 366 if (mappedUri.equals(uncanonicalUri)) { 367 return get(RINGTONES[i]); 368 } 369 } 370 return null; 371 } 372 373 /** 374 * Start composing a haptic effect. 375 * 376 * @see VibrationEffect.Composition 377 */ 378 @NonNull startComposition()379 public static VibrationEffect.Composition startComposition() { 380 return new VibrationEffect.Composition(); 381 } 382 383 @Override describeContents()384 public int describeContents() { 385 return 0; 386 } 387 388 /** @hide */ validate()389 public abstract void validate(); 390 391 /** 392 * Gets the estimated duration of the vibration in milliseconds. 393 * 394 * For effects without a defined end (e.g. a Waveform with a non-negative repeat index), this 395 * returns Long.MAX_VALUE. For effects with an unknown duration (e.g. Prebaked effects where 396 * the length is device and potentially run-time dependent), this returns -1. 397 * 398 * @hide 399 */ 400 @TestApi getDuration()401 public abstract long getDuration(); 402 403 /** 404 * Scale the amplitude with the given constraints. 405 * 406 * This assumes that the previous value was in the range [0, MAX_AMPLITUDE] 407 * @hide 408 */ 409 @TestApi scale(int amplitude, float gamma, int maxAmplitude)410 protected static int scale(int amplitude, float gamma, int maxAmplitude) { 411 float val = MathUtils.pow(amplitude / (float) MAX_AMPLITUDE, gamma); 412 return (int) (val * maxAmplitude); 413 } 414 415 /** @hide */ 416 @TestApi 417 public static class OneShot extends VibrationEffect implements Parcelable { 418 private final long mDuration; 419 private final int mAmplitude; 420 OneShot(Parcel in)421 public OneShot(Parcel in) { 422 mDuration = in.readLong(); 423 mAmplitude = in.readInt(); 424 } 425 OneShot(long milliseconds, int amplitude)426 public OneShot(long milliseconds, int amplitude) { 427 mDuration = milliseconds; 428 mAmplitude = amplitude; 429 } 430 431 @Override getDuration()432 public long getDuration() { 433 return mDuration; 434 } 435 getAmplitude()436 public int getAmplitude() { 437 return mAmplitude; 438 } 439 440 /** 441 * Scale the amplitude of this effect. 442 * 443 * @param gamma the gamma adjustment to apply 444 * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and 445 * MAX_AMPLITUDE 446 * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE 447 * 448 * @return A {@link OneShot} effect with the same timing but scaled amplitude. 449 */ scale(float gamma, int maxAmplitude)450 public OneShot scale(float gamma, int maxAmplitude) { 451 if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) { 452 throw new IllegalArgumentException( 453 "Amplitude is negative or greater than MAX_AMPLITUDE"); 454 } 455 int newAmplitude = scale(mAmplitude, gamma, maxAmplitude); 456 return new OneShot(mDuration, newAmplitude); 457 } 458 459 /** 460 * Resolve default values into integer amplitude numbers. 461 * 462 * @param defaultAmplitude the default amplitude to apply, must be between 0 and 463 * MAX_AMPLITUDE 464 * @return A {@link OneShot} effect with same physical meaning but explicitly set amplitude 465 * 466 * @hide 467 */ resolve(int defaultAmplitude)468 public OneShot resolve(int defaultAmplitude) { 469 if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) { 470 throw new IllegalArgumentException( 471 "Amplitude is negative or greater than MAX_AMPLITUDE"); 472 } 473 if (mAmplitude == DEFAULT_AMPLITUDE) { 474 return new OneShot(mDuration, defaultAmplitude); 475 } 476 return this; 477 } 478 479 @Override validate()480 public void validate() { 481 if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) { 482 throw new IllegalArgumentException( 483 "amplitude must either be DEFAULT_AMPLITUDE, " 484 + "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")"); 485 } 486 if (mDuration <= 0) { 487 throw new IllegalArgumentException( 488 "duration must be positive (duration=" + mDuration + ")"); 489 } 490 } 491 492 @Override equals(Object o)493 public boolean equals(Object o) { 494 if (!(o instanceof VibrationEffect.OneShot)) { 495 return false; 496 } 497 VibrationEffect.OneShot other = (VibrationEffect.OneShot) o; 498 return other.mDuration == mDuration && other.mAmplitude == mAmplitude; 499 } 500 501 @Override hashCode()502 public int hashCode() { 503 int result = 17; 504 result += 37 * (int) mDuration; 505 result += 37 * mAmplitude; 506 return result; 507 } 508 509 @Override toString()510 public String toString() { 511 return "OneShot{mDuration=" + mDuration + ", mAmplitude=" + mAmplitude + "}"; 512 } 513 514 @Override writeToParcel(Parcel out, int flags)515 public void writeToParcel(Parcel out, int flags) { 516 out.writeInt(PARCEL_TOKEN_ONE_SHOT); 517 out.writeLong(mDuration); 518 out.writeInt(mAmplitude); 519 } 520 521 @UnsupportedAppUsage 522 public static final @android.annotation.NonNull Parcelable.Creator<OneShot> CREATOR = 523 new Parcelable.Creator<OneShot>() { 524 @Override 525 public OneShot createFromParcel(Parcel in) { 526 // Skip the type token 527 in.readInt(); 528 return new OneShot(in); 529 } 530 @Override 531 public OneShot[] newArray(int size) { 532 return new OneShot[size]; 533 } 534 }; 535 } 536 537 /** @hide */ 538 @TestApi 539 public static class Waveform extends VibrationEffect implements Parcelable { 540 private final long[] mTimings; 541 private final int[] mAmplitudes; 542 private final int mRepeat; 543 Waveform(Parcel in)544 public Waveform(Parcel in) { 545 this(in.createLongArray(), in.createIntArray(), in.readInt()); 546 } 547 Waveform(long[] timings, int[] amplitudes, int repeat)548 public Waveform(long[] timings, int[] amplitudes, int repeat) { 549 mTimings = new long[timings.length]; 550 System.arraycopy(timings, 0, mTimings, 0, timings.length); 551 mAmplitudes = new int[amplitudes.length]; 552 System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length); 553 mRepeat = repeat; 554 } 555 getTimings()556 public long[] getTimings() { 557 return mTimings; 558 } 559 getAmplitudes()560 public int[] getAmplitudes() { 561 return mAmplitudes; 562 } 563 getRepeatIndex()564 public int getRepeatIndex() { 565 return mRepeat; 566 } 567 568 @Override getDuration()569 public long getDuration() { 570 if (mRepeat >= 0) { 571 return Long.MAX_VALUE; 572 } 573 long duration = 0; 574 for (long d : mTimings) { 575 duration += d; 576 } 577 return duration; 578 } 579 580 /** 581 * Scale the Waveform with the given gamma and new max amplitude. 582 * 583 * @param gamma the gamma adjustment to apply 584 * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and 585 * MAX_AMPLITUDE 586 * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE 587 * 588 * @return A {@link Waveform} effect with the same timings and repeat index 589 * but scaled amplitude. 590 */ scale(float gamma, int maxAmplitude)591 public Waveform scale(float gamma, int maxAmplitude) { 592 if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) { 593 throw new IllegalArgumentException( 594 "Amplitude is negative or greater than MAX_AMPLITUDE"); 595 } 596 if (gamma == 1.0f && maxAmplitude == MAX_AMPLITUDE) { 597 // Just return a copy of the original if there's no scaling to be done. 598 return new Waveform(mTimings, mAmplitudes, mRepeat); 599 } 600 601 int[] scaledAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length); 602 for (int i = 0; i < scaledAmplitudes.length; i++) { 603 scaledAmplitudes[i] = scale(scaledAmplitudes[i], gamma, maxAmplitude); 604 } 605 return new Waveform(mTimings, scaledAmplitudes, mRepeat); 606 } 607 608 /** 609 * Resolve default values into integer amplitude numbers. 610 * 611 * @param defaultAmplitude the default amplitude to apply, must be between 0 and 612 * MAX_AMPLITUDE 613 * @return A {@link Waveform} effect with same physical meaning but explicitly set 614 * amplitude 615 * 616 * @hide 617 */ resolve(int defaultAmplitude)618 public Waveform resolve(int defaultAmplitude) { 619 if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) { 620 throw new IllegalArgumentException( 621 "Amplitude is negative or greater than MAX_AMPLITUDE"); 622 } 623 int[] resolvedAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length); 624 for (int i = 0; i < resolvedAmplitudes.length; i++) { 625 if (resolvedAmplitudes[i] == DEFAULT_AMPLITUDE) { 626 resolvedAmplitudes[i] = defaultAmplitude; 627 } 628 } 629 return new Waveform(mTimings, resolvedAmplitudes, mRepeat); 630 } 631 632 @Override validate()633 public void validate() { 634 if (mTimings.length != mAmplitudes.length) { 635 throw new IllegalArgumentException( 636 "timing and amplitude arrays must be of equal length" 637 + " (timings.length=" + mTimings.length 638 + ", amplitudes.length=" + mAmplitudes.length + ")"); 639 } 640 if (!hasNonZeroEntry(mTimings)) { 641 throw new IllegalArgumentException("at least one timing must be non-zero" 642 + " (timings=" + Arrays.toString(mTimings) + ")"); 643 } 644 for (long timing : mTimings) { 645 if (timing < 0) { 646 throw new IllegalArgumentException("timings must all be >= 0" 647 + " (timings=" + Arrays.toString(mTimings) + ")"); 648 } 649 } 650 for (int amplitude : mAmplitudes) { 651 if (amplitude < -1 || amplitude > 255) { 652 throw new IllegalArgumentException( 653 "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255" 654 + " (amplitudes=" + Arrays.toString(mAmplitudes) + ")"); 655 } 656 } 657 if (mRepeat < -1 || mRepeat >= mTimings.length) { 658 throw new IllegalArgumentException( 659 "repeat index must be within the bounds of the timings array" 660 + " (timings.length=" + mTimings.length + ", index=" + mRepeat + ")"); 661 } 662 } 663 664 @Override equals(Object o)665 public boolean equals(Object o) { 666 if (!(o instanceof VibrationEffect.Waveform)) { 667 return false; 668 } 669 VibrationEffect.Waveform other = (VibrationEffect.Waveform) o; 670 return Arrays.equals(mTimings, other.mTimings) 671 && Arrays.equals(mAmplitudes, other.mAmplitudes) 672 && mRepeat == other.mRepeat; 673 } 674 675 @Override hashCode()676 public int hashCode() { 677 int result = 17; 678 result += 37 * Arrays.hashCode(mTimings); 679 result += 37 * Arrays.hashCode(mAmplitudes); 680 result += 37 * mRepeat; 681 return result; 682 } 683 684 @Override toString()685 public String toString() { 686 return "Waveform{mTimings=" + Arrays.toString(mTimings) 687 + ", mAmplitudes=" + Arrays.toString(mAmplitudes) 688 + ", mRepeat=" + mRepeat 689 + "}"; 690 } 691 692 @Override writeToParcel(Parcel out, int flags)693 public void writeToParcel(Parcel out, int flags) { 694 out.writeInt(PARCEL_TOKEN_WAVEFORM); 695 out.writeLongArray(mTimings); 696 out.writeIntArray(mAmplitudes); 697 out.writeInt(mRepeat); 698 } 699 hasNonZeroEntry(long[] vals)700 private static boolean hasNonZeroEntry(long[] vals) { 701 for (long val : vals) { 702 if (val != 0) { 703 return true; 704 } 705 } 706 return false; 707 } 708 709 710 public static final @android.annotation.NonNull Parcelable.Creator<Waveform> CREATOR = 711 new Parcelable.Creator<Waveform>() { 712 @Override 713 public Waveform createFromParcel(Parcel in) { 714 // Skip the type token 715 in.readInt(); 716 return new Waveform(in); 717 } 718 @Override 719 public Waveform[] newArray(int size) { 720 return new Waveform[size]; 721 } 722 }; 723 } 724 725 /** @hide */ 726 @TestApi 727 public static class Prebaked extends VibrationEffect implements Parcelable { 728 private final int mEffectId; 729 private final boolean mFallback; 730 731 private int mEffectStrength; 732 Prebaked(Parcel in)733 public Prebaked(Parcel in) { 734 this(in.readInt(), in.readByte() != 0); 735 mEffectStrength = in.readInt(); 736 } 737 Prebaked(int effectId, boolean fallback)738 public Prebaked(int effectId, boolean fallback) { 739 mEffectId = effectId; 740 mFallback = fallback; 741 mEffectStrength = EffectStrength.MEDIUM; 742 } 743 getId()744 public int getId() { 745 return mEffectId; 746 } 747 748 /** 749 * Whether the effect should fall back to a generic pattern if there's no hardware specific 750 * implementation of it. 751 */ shouldFallback()752 public boolean shouldFallback() { 753 return mFallback; 754 } 755 756 @Override getDuration()757 public long getDuration() { 758 return -1; 759 } 760 761 /** 762 * Set the effect strength of the prebaked effect. 763 */ setEffectStrength(int strength)764 public void setEffectStrength(int strength) { 765 if (!isValidEffectStrength(strength)) { 766 throw new IllegalArgumentException("Invalid effect strength: " + strength); 767 } 768 mEffectStrength = strength; 769 } 770 771 /** 772 * Set the effect strength. 773 */ getEffectStrength()774 public int getEffectStrength() { 775 return mEffectStrength; 776 } 777 isValidEffectStrength(int strength)778 private static boolean isValidEffectStrength(int strength) { 779 switch (strength) { 780 case EffectStrength.LIGHT: 781 case EffectStrength.MEDIUM: 782 case EffectStrength.STRONG: 783 return true; 784 default: 785 return false; 786 } 787 } 788 789 @Override validate()790 public void validate() { 791 switch (mEffectId) { 792 case EFFECT_CLICK: 793 case EFFECT_DOUBLE_CLICK: 794 case EFFECT_TICK: 795 case EFFECT_TEXTURE_TICK: 796 case EFFECT_THUD: 797 case EFFECT_POP: 798 case EFFECT_HEAVY_CLICK: 799 break; 800 default: 801 if (mEffectId < RINGTONES[0] || mEffectId > RINGTONES[RINGTONES.length - 1]) { 802 throw new IllegalArgumentException( 803 "Unknown prebaked effect type (value=" + mEffectId + ")"); 804 } 805 } 806 if (!isValidEffectStrength(mEffectStrength)) { 807 throw new IllegalArgumentException( 808 "Unknown prebaked effect strength (value=" + mEffectStrength + ")"); 809 } 810 } 811 812 @Override equals(Object o)813 public boolean equals(Object o) { 814 if (!(o instanceof VibrationEffect.Prebaked)) { 815 return false; 816 } 817 VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o; 818 return mEffectId == other.mEffectId 819 && mFallback == other.mFallback 820 && mEffectStrength == other.mEffectStrength; 821 } 822 823 @Override hashCode()824 public int hashCode() { 825 int result = 17; 826 result += 37 * mEffectId; 827 result += 37 * mEffectStrength; 828 return result; 829 } 830 831 @Override toString()832 public String toString() { 833 return "Prebaked{mEffectId=" + mEffectId 834 + ", mEffectStrength=" + mEffectStrength 835 + ", mFallback=" + mFallback 836 + "}"; 837 } 838 839 840 @Override writeToParcel(Parcel out, int flags)841 public void writeToParcel(Parcel out, int flags) { 842 out.writeInt(PARCEL_TOKEN_EFFECT); 843 out.writeInt(mEffectId); 844 out.writeByte((byte) (mFallback ? 1 : 0)); 845 out.writeInt(mEffectStrength); 846 } 847 848 public static final @NonNull Parcelable.Creator<Prebaked> CREATOR = 849 new Parcelable.Creator<Prebaked>() { 850 @Override 851 public Prebaked createFromParcel(Parcel in) { 852 // Skip the type token 853 in.readInt(); 854 return new Prebaked(in); 855 } 856 @Override 857 public Prebaked[] newArray(int size) { 858 return new Prebaked[size]; 859 } 860 }; 861 } 862 863 /** @hide */ 864 public static final class Composed extends VibrationEffect implements Parcelable { 865 private final ArrayList<Composition.PrimitiveEffect> mPrimitiveEffects; 866 867 /** 868 * @hide 869 */ 870 @SuppressWarnings("unchecked") Composed(@onNull Parcel in)871 public Composed(@NonNull Parcel in) { 872 this(in.readArrayList(Composed.class.getClassLoader())); 873 } 874 875 /** 876 * @hide 877 */ Composed(List<Composition.PrimitiveEffect> effects)878 public Composed(List<Composition.PrimitiveEffect> effects) { 879 mPrimitiveEffects = new ArrayList<>(Objects.requireNonNull(effects)); 880 } 881 882 /** 883 * @hide 884 */ 885 @NonNull getPrimitiveEffects()886 public List<Composition.PrimitiveEffect> getPrimitiveEffects() { 887 return mPrimitiveEffects; 888 } 889 890 @Override getDuration()891 public long getDuration() { 892 return -1; 893 } 894 895 /** 896 * Scale all primitives of this effect. 897 * 898 * @param gamma the gamma adjustment to apply 899 * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and 900 * MAX_AMPLITUDE 901 * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE 902 * 903 * @return A {@link Composed} effect with same but scaled primitives. 904 */ scale(float gamma, int maxAmplitude)905 public Composed scale(float gamma, int maxAmplitude) { 906 if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) { 907 throw new IllegalArgumentException( 908 "Amplitude is negative or greater than MAX_AMPLITUDE"); 909 } 910 if (gamma == 1.0f && maxAmplitude == MAX_AMPLITUDE) { 911 // Just return a copy of the original if there's no scaling to be done. 912 return new Composed(mPrimitiveEffects); 913 } 914 List<Composition.PrimitiveEffect> scaledPrimitives = new ArrayList<>(); 915 for (Composition.PrimitiveEffect primitive : mPrimitiveEffects) { 916 float adjustedScale = MathUtils.pow(primitive.scale, gamma); 917 float newScale = adjustedScale * maxAmplitude / (float) MAX_AMPLITUDE; 918 scaledPrimitives.add(new Composition.PrimitiveEffect( 919 primitive.id, newScale, primitive.delay)); 920 } 921 return new Composed(scaledPrimitives); 922 } 923 924 /** 925 * @hide 926 */ 927 @Override validate()928 public void validate() { 929 for (Composition.PrimitiveEffect effect : mPrimitiveEffects) { 930 Composition.checkPrimitive(effect.id); 931 Preconditions.checkArgumentInRange( 932 effect.scale, 0.0f, 1.0f, "scale"); 933 } 934 } 935 936 @Override writeToParcel(@onNull Parcel out, int flags)937 public void writeToParcel(@NonNull Parcel out, int flags) { 938 out.writeInt(PARCEL_TOKEN_COMPOSITION); 939 out.writeList(mPrimitiveEffects); 940 } 941 942 @Override describeContents()943 public int describeContents() { 944 return 0; 945 } 946 947 @Override equals(Object o)948 public boolean equals(Object o) { 949 if (this == o) return true; 950 if (o == null || getClass() != o.getClass()) return false; 951 Composed composed = (Composed) o; 952 return mPrimitiveEffects.equals(composed.mPrimitiveEffects); 953 } 954 955 @Override hashCode()956 public int hashCode() { 957 return Objects.hash(mPrimitiveEffects); 958 } 959 960 @Override toString()961 public String toString() { 962 return "Composed{mPrimitiveEffects=" + mPrimitiveEffects + '}'; 963 } 964 965 public static final @NonNull Parcelable.Creator<Composed> CREATOR = 966 new Parcelable.Creator<Composed>() { 967 @Override 968 public Composed createFromParcel(@NonNull Parcel in) { 969 // Skip the type token 970 in.readInt(); 971 return new Composed(in); 972 } 973 974 @Override 975 @NonNull 976 public Composed[] newArray(int size) { 977 return new Composed[size]; 978 } 979 }; 980 } 981 982 /** 983 * A composition of haptic primitives that, when combined, create a single haptic effect. 984 * 985 * @see VibrationEffect#startComposition() 986 */ 987 public static final class Composition { 988 /** @hide */ 989 @IntDef(prefix = { "PRIMITIVE_" }, value = { 990 PRIMITIVE_CLICK, 991 PRIMITIVE_THUD, 992 PRIMITIVE_SPIN, 993 PRIMITIVE_QUICK_RISE, 994 PRIMITIVE_SLOW_RISE, 995 PRIMITIVE_QUICK_FALL, 996 PRIMITIVE_TICK, 997 }) 998 @Retention(RetentionPolicy.SOURCE) 999 public @interface Primitive {} 1000 1001 /** 1002 * No haptic effect. Used to generate extended delays between primitives. 1003 * @hide 1004 */ 1005 public static final int PRIMITIVE_NOOP = 0; 1006 /** 1007 * This effect should produce a sharp, crisp click sensation. 1008 */ 1009 public static final int PRIMITIVE_CLICK = 1; 1010 /** 1011 * A haptic effect that simulates downwards movement with gravity. Often 1012 * followed by extra energy of hitting and reverberation to augment 1013 * physicality. 1014 * 1015 * @hide Not confident enough to expose publicly yet 1016 */ 1017 public static final int PRIMITIVE_THUD = 2; 1018 /** 1019 * A haptic effect that simulates spinning momentum. 1020 * 1021 * @hide Not confident enough to expose publicly yet 1022 */ 1023 public static final int PRIMITIVE_SPIN = 3; 1024 /** 1025 * A haptic effect that simulates quick upward movement against gravity. 1026 */ 1027 public static final int PRIMITIVE_QUICK_RISE = 4; 1028 /** 1029 * A haptic effect that simulates slow upward movement against gravity. 1030 */ 1031 public static final int PRIMITIVE_SLOW_RISE = 5; 1032 /** 1033 * A haptic effect that simulates quick downwards movement with gravity. 1034 */ 1035 public static final int PRIMITIVE_QUICK_FALL = 6; 1036 /** 1037 * This very short effect should produce a light crisp sensation intended 1038 * to be used repetitively for dynamic feedback. 1039 */ 1040 // Internally this maps to the HAL constant CompositePrimitive::LIGHT_TICK 1041 public static final int PRIMITIVE_TICK = 7; 1042 1043 1044 private ArrayList<PrimitiveEffect> mEffects = new ArrayList<>(); 1045 Composition()1046 Composition() { } 1047 1048 /** 1049 * Add a haptic primitive to the end of the current composition. 1050 * 1051 * Similar to {@link #addPrimitive(int, float, int)}, but with no delay and a 1052 * default scale applied. 1053 * 1054 * @param primitiveId The primitive to add 1055 * 1056 * @return The {@link Composition} object to enable adding multiple primitives in one chain. 1057 */ 1058 @NonNull addPrimitive(@rimitive int primitiveId)1059 public Composition addPrimitive(@Primitive int primitiveId) { 1060 addPrimitive(primitiveId, /*scale*/ 1.0f, /*delay*/ 0); 1061 return this; 1062 } 1063 1064 /** 1065 * Add a haptic primitive to the end of the current composition. 1066 * 1067 * Similar to {@link #addPrimitive(int, float, int)}, but with no delay. 1068 * 1069 * @param primitiveId The primitive to add 1070 * @param scale The scale to apply to the intensity of the primitive. 1071 * 1072 * @return The {@link Composition} object to enable adding multiple primitives in one chain. 1073 */ 1074 @NonNull addPrimitive(@rimitive int primitiveId, @FloatRange(from = 0f, to = 1f) float scale)1075 public Composition addPrimitive(@Primitive int primitiveId, 1076 @FloatRange(from = 0f, to = 1f) float scale) { 1077 addPrimitive(primitiveId, scale, /*delay*/ 0); 1078 return this; 1079 } 1080 1081 /** 1082 * Add a haptic primitive to the end of the current composition. 1083 * 1084 * @param primitiveId The primitive to add 1085 * @param scale The scale to apply to the intensity of the primitive. 1086 * @param delay The amount of time, in milliseconds, to wait between playing the prior 1087 * primitive and this one 1088 * @return The {@link Composition} object to enable adding multiple primitives in one chain. 1089 */ 1090 @NonNull addPrimitive(@rimitive int primitiveId, @FloatRange(from = 0f, to = 1f) float scale, @IntRange(from = 0) int delay)1091 public Composition addPrimitive(@Primitive int primitiveId, 1092 @FloatRange(from = 0f, to = 1f) float scale, @IntRange(from = 0) int delay) { 1093 mEffects.add(new PrimitiveEffect(checkPrimitive(primitiveId), scale, delay)); 1094 return this; 1095 } 1096 1097 /** 1098 * Compose all of the added primitives together into a single {@link VibrationEffect}. 1099 * 1100 * The {@link Composition} object is still valid after this call, so you can continue adding 1101 * more primitives to it and generating more {@link VibrationEffect}s by calling this method 1102 * again. 1103 * 1104 * @return The {@link VibrationEffect} resulting from the composition of the primitives. 1105 */ 1106 @NonNull compose()1107 public VibrationEffect compose() { 1108 if (mEffects.isEmpty()) { 1109 throw new IllegalStateException( 1110 "Composition must have at least one element to compose."); 1111 } 1112 return new VibrationEffect.Composed(mEffects); 1113 } 1114 1115 /** 1116 * @throws IllegalArgumentException throws if the primitive ID is not within the valid range 1117 * @hide 1118 * 1119 */ checkPrimitive(int primitiveId)1120 static int checkPrimitive(int primitiveId) { 1121 Preconditions.checkArgumentInRange(primitiveId, PRIMITIVE_NOOP, PRIMITIVE_TICK, 1122 "primitiveId"); 1123 return primitiveId; 1124 } 1125 1126 /** 1127 * Convert the primitive ID to a human readable string for debugging 1128 * @param id The ID to convert 1129 * @return The ID in a human readable format. 1130 * @hide 1131 */ primitiveToString(@rimitive int id)1132 public static String primitiveToString(@Primitive int id) { 1133 switch (id) { 1134 case PRIMITIVE_NOOP: 1135 return "PRIMITIVE_NOOP"; 1136 case PRIMITIVE_CLICK: 1137 return "PRIMITIVE_CLICK"; 1138 case PRIMITIVE_THUD: 1139 return "PRIMITIVE_THUD"; 1140 case PRIMITIVE_SPIN: 1141 return "PRIMITIVE_SPIN"; 1142 case PRIMITIVE_QUICK_RISE: 1143 return "PRIMITIVE_QUICK_RISE"; 1144 case PRIMITIVE_SLOW_RISE: 1145 return "PRIMITIVE_SLOW_RISE"; 1146 case PRIMITIVE_QUICK_FALL: 1147 return "PRIMITIVE_QUICK_FALL"; 1148 case PRIMITIVE_TICK: 1149 return "PRIMITIVE_TICK"; 1150 1151 default: 1152 return Integer.toString(id); 1153 1154 } 1155 } 1156 1157 1158 /** 1159 * @hide 1160 */ 1161 public static class PrimitiveEffect implements Parcelable { 1162 public int id; 1163 public float scale; 1164 public int delay; 1165 PrimitiveEffect(int id, float scale, int delay)1166 PrimitiveEffect(int id, float scale, int delay) { 1167 this.id = id; 1168 this.scale = scale; 1169 this.delay = delay; 1170 } 1171 1172 @Override writeToParcel(Parcel dest, int flags)1173 public void writeToParcel(Parcel dest, int flags) { 1174 dest.writeInt(id); 1175 dest.writeFloat(scale); 1176 dest.writeInt(delay); 1177 } 1178 1179 @Override describeContents()1180 public int describeContents() { 1181 return 0; 1182 } 1183 1184 @Override toString()1185 public String toString() { 1186 return "PrimitiveEffect{" 1187 + "id=" + primitiveToString(id) 1188 + ", scale=" + scale 1189 + ", delay=" + delay 1190 + '}'; 1191 } 1192 1193 @Override equals(Object o)1194 public boolean equals(Object o) { 1195 if (this == o) return true; 1196 if (o == null || getClass() != o.getClass()) return false; 1197 PrimitiveEffect that = (PrimitiveEffect) o; 1198 return id == that.id 1199 && Float.compare(that.scale, scale) == 0 1200 && delay == that.delay; 1201 } 1202 1203 @Override hashCode()1204 public int hashCode() { 1205 return Objects.hash(id, scale, delay); 1206 } 1207 1208 1209 public static final @NonNull Parcelable.Creator<PrimitiveEffect> CREATOR = 1210 new Parcelable.Creator<PrimitiveEffect>() { 1211 @Override 1212 public PrimitiveEffect createFromParcel(Parcel in) { 1213 return new PrimitiveEffect(in.readInt(), in.readFloat(), in.readInt()); 1214 } 1215 @Override 1216 public PrimitiveEffect[] newArray(int size) { 1217 return new PrimitiveEffect[size]; 1218 } 1219 }; 1220 } 1221 } 1222 1223 public static final @NonNull Parcelable.Creator<VibrationEffect> CREATOR = 1224 new Parcelable.Creator<VibrationEffect>() { 1225 @Override 1226 public VibrationEffect createFromParcel(Parcel in) { 1227 int token = in.readInt(); 1228 if (token == PARCEL_TOKEN_ONE_SHOT) { 1229 return new OneShot(in); 1230 } else if (token == PARCEL_TOKEN_WAVEFORM) { 1231 return new Waveform(in); 1232 } else if (token == PARCEL_TOKEN_EFFECT) { 1233 return new Prebaked(in); 1234 } else if (token == PARCEL_TOKEN_COMPOSITION) { 1235 return new Composed(in); 1236 } else { 1237 throw new IllegalStateException( 1238 "Unexpected vibration event type token in parcel."); 1239 } 1240 } 1241 @Override 1242 public VibrationEffect[] newArray(int size) { 1243 return new VibrationEffect[size]; 1244 } 1245 }; 1246 } 1247