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.Nullable; 20 import android.content.ContentResolver; 21 import android.content.Context; 22 import android.hardware.vibrator.V1_0.EffectStrength; 23 import android.hardware.vibrator.V1_2.Effect; 24 import android.net.Uri; 25 import android.util.MathUtils; 26 27 import com.android.internal.annotations.VisibleForTesting; 28 29 import java.util.Arrays; 30 31 /** 32 * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}. 33 * 34 * These effects may be any number of things, from single shot vibrations to complex waveforms. 35 */ 36 public abstract class VibrationEffect implements Parcelable { 37 private static final int PARCEL_TOKEN_ONE_SHOT = 1; 38 private static final int PARCEL_TOKEN_WAVEFORM = 2; 39 private static final int PARCEL_TOKEN_EFFECT = 3; 40 41 /** 42 * The default vibration strength of the device. 43 */ 44 public static final int DEFAULT_AMPLITUDE = -1; 45 46 /** 47 * The maximum amplitude value 48 * @hide 49 */ 50 public static final int MAX_AMPLITUDE = 255; 51 52 /** 53 * A click effect. 54 * 55 * @see #get(int) 56 * @hide 57 */ 58 public static final int EFFECT_CLICK = Effect.CLICK; 59 60 /** 61 * A double click effect. 62 * 63 * @see #get(int) 64 * @hide 65 */ 66 public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK; 67 68 /** 69 * A tick effect. 70 * @see #get(int) 71 * @hide 72 */ 73 public static final int EFFECT_TICK = Effect.TICK; 74 75 /** 76 * A thud effect. 77 * @see #get(int) 78 * @hide 79 */ 80 public static final int EFFECT_THUD = Effect.THUD; 81 82 /** 83 * A pop effect. 84 * @see #get(int) 85 * @hide 86 */ 87 public static final int EFFECT_POP = Effect.POP; 88 89 /** 90 * A heavy click effect. 91 * @see #get(int) 92 * @hide 93 */ 94 public static final int EFFECT_HEAVY_CLICK = Effect.HEAVY_CLICK; 95 96 97 /** 98 * Ringtone patterns. They may correspond with the device's ringtone audio, or may just be a 99 * pattern that can be played as a ringtone with any audio, depending on the device. 100 * 101 * @see #get(Uri, Context) 102 * @hide 103 */ 104 @VisibleForTesting 105 public static final int[] RINGTONES = { 106 Effect.RINGTONE_1, 107 Effect.RINGTONE_2, 108 Effect.RINGTONE_3, 109 Effect.RINGTONE_4, 110 Effect.RINGTONE_5, 111 Effect.RINGTONE_6, 112 Effect.RINGTONE_7, 113 Effect.RINGTONE_8, 114 Effect.RINGTONE_9, 115 Effect.RINGTONE_10, 116 Effect.RINGTONE_11, 117 Effect.RINGTONE_12, 118 Effect.RINGTONE_13, 119 Effect.RINGTONE_14, 120 Effect.RINGTONE_15 121 }; 122 123 /** @hide to prevent subclassing from outside of the framework */ VibrationEffect()124 public VibrationEffect() { } 125 126 /** 127 * Create a one shot vibration. 128 * 129 * One shot vibrations will vibrate constantly for the specified period of time at the 130 * specified amplitude, and then stop. 131 * 132 * @param milliseconds The number of milliseconds to vibrate. This must be a positive number. 133 * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or 134 * {@link #DEFAULT_AMPLITUDE}. 135 * 136 * @return The desired effect. 137 */ createOneShot(long milliseconds, int amplitude)138 public static VibrationEffect createOneShot(long milliseconds, int amplitude) { 139 VibrationEffect effect = new OneShot(milliseconds, amplitude); 140 effect.validate(); 141 return effect; 142 } 143 144 /** 145 * Create a waveform vibration. 146 * 147 * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For 148 * each pair, the value in the amplitude array determines the strength of the vibration and the 149 * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no 150 * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored. 151 * <p> 152 * The amplitude array of the generated waveform will be the same size as the given 153 * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE}, 154 * starting with 0. Therefore the first timing value will be the period to wait before turning 155 * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE} 156 * strength, etc. 157 * </p><p> 158 * To cause the pattern to repeat, pass the index into the timings array at which to start the 159 * repetition, or -1 to disable repeating. 160 * </p> 161 * 162 * @param timings The pattern of alternating on-off timings, starting with off. Timing values 163 * of 0 will cause the timing / amplitude pair to be ignored. 164 * @param repeat The index into the timings array at which to repeat, or -1 if you you don't 165 * want to repeat. 166 * 167 * @return The desired effect. 168 */ createWaveform(long[] timings, int repeat)169 public static VibrationEffect createWaveform(long[] timings, int repeat) { 170 int[] amplitudes = new int[timings.length]; 171 for (int i = 0; i < (timings.length / 2); i++) { 172 amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE; 173 } 174 return createWaveform(timings, amplitudes, repeat); 175 } 176 177 /** 178 * Create a waveform vibration. 179 * 180 * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For 181 * each pair, the value in the amplitude array determines the strength of the vibration and the 182 * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no 183 * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored. 184 * </p><p> 185 * To cause the pattern to repeat, pass the index into the timings array at which to start the 186 * repetition, or -1 to disable repeating. 187 * </p> 188 * 189 * @param timings The timing values of the timing / amplitude pairs. Timing values of 0 190 * will cause the pair to be ignored. 191 * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values 192 * must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An 193 * amplitude value of 0 implies the motor is off. 194 * @param repeat The index into the timings array at which to repeat, or -1 if you you don't 195 * want to repeat. 196 * 197 * @return The desired effect. 198 */ createWaveform(long[] timings, int[] amplitudes, int repeat)199 public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) { 200 VibrationEffect effect = new Waveform(timings, amplitudes, repeat); 201 effect.validate(); 202 return effect; 203 } 204 205 /** 206 * Get a predefined vibration effect. 207 * 208 * Predefined effects are a set of common vibration effects that should be identical, regardless 209 * of the app they come from, in order to provide a cohesive experience for users across 210 * the entire device. They also may be custom tailored to the device hardware in order to 211 * provide a better experience than you could otherwise build using the generic building 212 * blocks. 213 * 214 * This will fallback to a generic pattern if one exists and there does not exist a 215 * hardware-specific implementation of the effect. 216 * 217 * @param effectId The ID of the effect to perform: 218 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK} 219 * 220 * @return The desired effect. 221 * @hide 222 */ get(int effectId)223 public static VibrationEffect get(int effectId) { 224 return get(effectId, true); 225 } 226 227 /** 228 * Get a predefined vibration effect. 229 * 230 * Predefined effects are a set of common vibration effects that should be identical, regardless 231 * of the app they come from, in order to provide a cohesive experience for users across 232 * the entire device. They also may be custom tailored to the device hardware in order to 233 * provide a better experience than you could otherwise build using the generic building 234 * blocks. 235 * 236 * Some effects you may only want to play if there's a hardware specific implementation because 237 * they may, for example, be too disruptive to the user without tuning. The {@code fallback} 238 * parameter allows you to decide whether you want to fallback to the generic implementation or 239 * only play if there's a tuned, hardware specific one available. 240 * 241 * @param effectId The ID of the effect to perform: 242 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK} 243 * @param fallback Whether to fallback to a generic pattern if a hardware specific 244 * implementation doesn't exist. 245 * 246 * @return The desired effect. 247 * @hide 248 */ get(int effectId, boolean fallback)249 public static VibrationEffect get(int effectId, boolean fallback) { 250 VibrationEffect effect = new Prebaked(effectId, fallback); 251 effect.validate(); 252 return effect; 253 } 254 255 /** 256 * Get a predefined vibration effect associated with a given URI. 257 * 258 * Predefined effects are a set of common vibration effects that should be identical, regardless 259 * of the app they come from, in order to provide a cohesive experience for users across 260 * the entire device. They also may be custom tailored to the device hardware in order to 261 * provide a better experience than you could otherwise build using the generic building 262 * blocks. 263 * 264 * @param uri The URI associated with the haptic effect. 265 * @param context The context used to get the URI to haptic effect association. 266 * 267 * @return The desired effect, or {@code null} if there's no associated effect. 268 * 269 * @hide 270 */ 271 @Nullable get(Uri uri, Context context)272 public static VibrationEffect get(Uri uri, Context context) { 273 String[] uris = context.getResources().getStringArray( 274 com.android.internal.R.array.config_ringtoneEffectUris); 275 for (int i = 0; i < uris.length && i < RINGTONES.length; i++) { 276 if (uris[i] == null) { 277 continue; 278 } 279 ContentResolver cr = context.getContentResolver(); 280 Uri mappedUri = cr.uncanonicalize(Uri.parse(uris[i])); 281 if (mappedUri == null) { 282 continue; 283 } 284 if (mappedUri.equals(uri)) { 285 return get(RINGTONES[i]); 286 } 287 } 288 return null; 289 } 290 291 @Override describeContents()292 public int describeContents() { 293 return 0; 294 } 295 296 /** @hide */ validate()297 public abstract void validate(); 298 299 /** 300 * Gets the estimated duration of the vibration in milliseconds. 301 * 302 * For effects without a defined end (e.g. a Waveform with a non-negative repeat index), this 303 * returns Long.MAX_VALUE. For effects with an unknown duration (e.g. Prebaked effects where 304 * the length is device and potentially run-time dependent), this returns -1. 305 * 306 * @hide 307 */ getDuration()308 public abstract long getDuration(); 309 310 /** 311 * Scale the amplitude with the given constraints. 312 * 313 * This assumes that the previous value was in the range [0, MAX_AMPLITUDE] 314 * @hide 315 */ scale(int amplitude, float gamma, int maxAmplitude)316 protected static int scale(int amplitude, float gamma, int maxAmplitude) { 317 float val = MathUtils.pow(amplitude / (float) MAX_AMPLITUDE, gamma); 318 return (int) (val * maxAmplitude); 319 } 320 321 /** @hide */ 322 public static class OneShot extends VibrationEffect implements Parcelable { 323 private final long mDuration; 324 private final int mAmplitude; 325 OneShot(Parcel in)326 public OneShot(Parcel in) { 327 mDuration = in.readLong(); 328 mAmplitude = in.readInt(); 329 } 330 OneShot(long milliseconds, int amplitude)331 public OneShot(long milliseconds, int amplitude) { 332 mDuration = milliseconds; 333 mAmplitude = amplitude; 334 } 335 336 @Override getDuration()337 public long getDuration() { 338 return mDuration; 339 } 340 getAmplitude()341 public int getAmplitude() { 342 return mAmplitude; 343 } 344 345 /** 346 * Scale the amplitude of this effect. 347 * 348 * @param gamma the gamma adjustment to apply 349 * @param maxAmplitude the new maximum amplitude of the effect 350 * 351 * @return A {@link OneShot} effect with the same timing but scaled amplitude. 352 */ scale(float gamma, int maxAmplitude)353 public VibrationEffect scale(float gamma, int maxAmplitude) { 354 int newAmplitude = scale(mAmplitude, gamma, maxAmplitude); 355 return new OneShot(mDuration, newAmplitude); 356 } 357 358 /** 359 * Resolve default values into integer amplitude numbers. 360 * 361 * @param defaultAmplitude the default amplitude to apply, must be between 0 and 362 * MAX_AMPLITUDE 363 * @return A {@link OneShot} effect with same physical meaning but explicitly set amplitude 364 * 365 * @hide 366 */ resolve(int defaultAmplitude)367 public OneShot resolve(int defaultAmplitude) { 368 if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) { 369 throw new IllegalArgumentException( 370 "Amplitude is negative or greater than MAX_AMPLITUDE"); 371 } 372 if (mAmplitude == DEFAULT_AMPLITUDE) { 373 return new OneShot(mDuration, defaultAmplitude); 374 } 375 return this; 376 } 377 378 @Override validate()379 public void validate() { 380 if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) { 381 throw new IllegalArgumentException( 382 "amplitude must either be DEFAULT_AMPLITUDE, " 383 + "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")"); 384 } 385 if (mDuration <= 0) { 386 throw new IllegalArgumentException( 387 "duration must be positive (duration=" + mDuration + ")"); 388 } 389 } 390 391 @Override equals(Object o)392 public boolean equals(Object o) { 393 if (!(o instanceof VibrationEffect.OneShot)) { 394 return false; 395 } 396 VibrationEffect.OneShot other = (VibrationEffect.OneShot) o; 397 return other.mDuration == mDuration && other.mAmplitude == mAmplitude; 398 } 399 400 @Override hashCode()401 public int hashCode() { 402 int result = 17; 403 result += 37 * (int) mDuration; 404 result += 37 * mAmplitude; 405 return result; 406 } 407 408 @Override toString()409 public String toString() { 410 return "OneShot{mDuration=" + mDuration + ", mAmplitude=" + mAmplitude + "}"; 411 } 412 413 @Override writeToParcel(Parcel out, int flags)414 public void writeToParcel(Parcel out, int flags) { 415 out.writeInt(PARCEL_TOKEN_ONE_SHOT); 416 out.writeLong(mDuration); 417 out.writeInt(mAmplitude); 418 } 419 420 public static final Parcelable.Creator<OneShot> CREATOR = 421 new Parcelable.Creator<OneShot>() { 422 @Override 423 public OneShot createFromParcel(Parcel in) { 424 // Skip the type token 425 in.readInt(); 426 return new OneShot(in); 427 } 428 @Override 429 public OneShot[] newArray(int size) { 430 return new OneShot[size]; 431 } 432 }; 433 } 434 435 /** @hide */ 436 public static class Waveform extends VibrationEffect implements Parcelable { 437 private final long[] mTimings; 438 private final int[] mAmplitudes; 439 private final int mRepeat; 440 Waveform(Parcel in)441 public Waveform(Parcel in) { 442 this(in.createLongArray(), in.createIntArray(), in.readInt()); 443 } 444 Waveform(long[] timings, int[] amplitudes, int repeat)445 public Waveform(long[] timings, int[] amplitudes, int repeat) { 446 mTimings = new long[timings.length]; 447 System.arraycopy(timings, 0, mTimings, 0, timings.length); 448 mAmplitudes = new int[amplitudes.length]; 449 System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length); 450 mRepeat = repeat; 451 } 452 getTimings()453 public long[] getTimings() { 454 return mTimings; 455 } 456 getAmplitudes()457 public int[] getAmplitudes() { 458 return mAmplitudes; 459 } 460 getRepeatIndex()461 public int getRepeatIndex() { 462 return mRepeat; 463 } 464 465 @Override getDuration()466 public long getDuration() { 467 if (mRepeat >= 0) { 468 return Long.MAX_VALUE; 469 } 470 long duration = 0; 471 for (long d : mTimings) { 472 duration += d; 473 } 474 return duration; 475 } 476 477 /** 478 * Scale the Waveform with the given gamma and new max amplitude. 479 * 480 * @param gamma the gamma adjustment to apply 481 * @param maxAmplitude the new maximum amplitude of the effect 482 * 483 * @return A {@link Waveform} effect with the same timings and repeat index 484 * but scaled amplitude. 485 */ scale(float gamma, int maxAmplitude)486 public VibrationEffect scale(float gamma, int maxAmplitude) { 487 if (gamma == 1.0f && maxAmplitude == MAX_AMPLITUDE) { 488 // Just return a copy of the original if there's no scaling to be done. 489 return new Waveform(mTimings, mAmplitudes, mRepeat); 490 } 491 492 int[] scaledAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length); 493 for (int i = 0; i < scaledAmplitudes.length; i++) { 494 scaledAmplitudes[i] = scale(scaledAmplitudes[i], gamma, maxAmplitude); 495 } 496 return new Waveform(mTimings, scaledAmplitudes, mRepeat); 497 } 498 499 /** 500 * Resolve default values into integer amplitude numbers. 501 * 502 * @param defaultAmplitude the default amplitude to apply, must be between 0 and 503 * MAX_AMPLITUDE 504 * @return A {@link Waveform} effect with same physical meaning but explicitly set 505 * amplitude 506 * 507 * @hide 508 */ resolve(int defaultAmplitude)509 public Waveform resolve(int defaultAmplitude) { 510 if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) { 511 throw new IllegalArgumentException( 512 "Amplitude is negative or greater than MAX_AMPLITUDE"); 513 } 514 int[] resolvedAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length); 515 for (int i = 0; i < resolvedAmplitudes.length; i++) { 516 if (resolvedAmplitudes[i] == DEFAULT_AMPLITUDE) { 517 resolvedAmplitudes[i] = defaultAmplitude; 518 } 519 } 520 return new Waveform(mTimings, resolvedAmplitudes, mRepeat); 521 } 522 523 @Override validate()524 public void validate() { 525 if (mTimings.length != mAmplitudes.length) { 526 throw new IllegalArgumentException( 527 "timing and amplitude arrays must be of equal length" 528 + " (timings.length=" + mTimings.length 529 + ", amplitudes.length=" + mAmplitudes.length + ")"); 530 } 531 if (!hasNonZeroEntry(mTimings)) { 532 throw new IllegalArgumentException("at least one timing must be non-zero" 533 + " (timings=" + Arrays.toString(mTimings) + ")"); 534 } 535 for (long timing : mTimings) { 536 if (timing < 0) { 537 throw new IllegalArgumentException("timings must all be >= 0" 538 + " (timings=" + Arrays.toString(mTimings) + ")"); 539 } 540 } 541 for (int amplitude : mAmplitudes) { 542 if (amplitude < -1 || amplitude > 255) { 543 throw new IllegalArgumentException( 544 "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255" 545 + " (amplitudes=" + Arrays.toString(mAmplitudes) + ")"); 546 } 547 } 548 if (mRepeat < -1 || mRepeat >= mTimings.length) { 549 throw new IllegalArgumentException( 550 "repeat index must be within the bounds of the timings array" 551 + " (timings.length=" + mTimings.length + ", index=" + mRepeat + ")"); 552 } 553 } 554 555 @Override equals(Object o)556 public boolean equals(Object o) { 557 if (!(o instanceof VibrationEffect.Waveform)) { 558 return false; 559 } 560 VibrationEffect.Waveform other = (VibrationEffect.Waveform) o; 561 return Arrays.equals(mTimings, other.mTimings) 562 && Arrays.equals(mAmplitudes, other.mAmplitudes) 563 && mRepeat == other.mRepeat; 564 } 565 566 @Override hashCode()567 public int hashCode() { 568 int result = 17; 569 result += 37 * Arrays.hashCode(mTimings); 570 result += 37 * Arrays.hashCode(mAmplitudes); 571 result += 37 * mRepeat; 572 return result; 573 } 574 575 @Override toString()576 public String toString() { 577 return "Waveform{mTimings=" + Arrays.toString(mTimings) 578 + ", mAmplitudes=" + Arrays.toString(mAmplitudes) 579 + ", mRepeat=" + mRepeat 580 + "}"; 581 } 582 583 @Override writeToParcel(Parcel out, int flags)584 public void writeToParcel(Parcel out, int flags) { 585 out.writeInt(PARCEL_TOKEN_WAVEFORM); 586 out.writeLongArray(mTimings); 587 out.writeIntArray(mAmplitudes); 588 out.writeInt(mRepeat); 589 } 590 hasNonZeroEntry(long[] vals)591 private static boolean hasNonZeroEntry(long[] vals) { 592 for (long val : vals) { 593 if (val != 0) { 594 return true; 595 } 596 } 597 return false; 598 } 599 600 601 public static final Parcelable.Creator<Waveform> CREATOR = 602 new Parcelable.Creator<Waveform>() { 603 @Override 604 public Waveform createFromParcel(Parcel in) { 605 // Skip the type token 606 in.readInt(); 607 return new Waveform(in); 608 } 609 @Override 610 public Waveform[] newArray(int size) { 611 return new Waveform[size]; 612 } 613 }; 614 } 615 616 /** @hide */ 617 public static class Prebaked extends VibrationEffect implements Parcelable { 618 private final int mEffectId; 619 private final boolean mFallback; 620 621 private int mEffectStrength; 622 Prebaked(Parcel in)623 public Prebaked(Parcel in) { 624 this(in.readInt(), in.readByte() != 0); 625 mEffectStrength = in.readInt(); 626 } 627 Prebaked(int effectId, boolean fallback)628 public Prebaked(int effectId, boolean fallback) { 629 mEffectId = effectId; 630 mFallback = fallback; 631 mEffectStrength = EffectStrength.MEDIUM; 632 } 633 getId()634 public int getId() { 635 return mEffectId; 636 } 637 638 /** 639 * Whether the effect should fall back to a generic pattern if there's no hardware specific 640 * implementation of it. 641 */ shouldFallback()642 public boolean shouldFallback() { 643 return mFallback; 644 } 645 646 @Override getDuration()647 public long getDuration() { 648 return -1; 649 } 650 651 /** 652 * Set the effect strength of the prebaked effect. 653 */ setEffectStrength(int strength)654 public void setEffectStrength(int strength) { 655 if (!isValidEffectStrength(strength)) { 656 throw new IllegalArgumentException("Invalid effect strength: " + strength); 657 } 658 mEffectStrength = strength; 659 } 660 661 /** 662 * Set the effect strength. 663 */ getEffectStrength()664 public int getEffectStrength() { 665 return mEffectStrength; 666 } 667 isValidEffectStrength(int strength)668 private static boolean isValidEffectStrength(int strength) { 669 switch (strength) { 670 case EffectStrength.LIGHT: 671 case EffectStrength.MEDIUM: 672 case EffectStrength.STRONG: 673 return true; 674 default: 675 return false; 676 } 677 } 678 679 @Override validate()680 public void validate() { 681 switch (mEffectId) { 682 case EFFECT_CLICK: 683 case EFFECT_DOUBLE_CLICK: 684 case EFFECT_TICK: 685 case EFFECT_THUD: 686 case EFFECT_POP: 687 case EFFECT_HEAVY_CLICK: 688 break; 689 default: 690 if (mEffectId < RINGTONES[0] || mEffectId > RINGTONES[RINGTONES.length - 1]) { 691 throw new IllegalArgumentException( 692 "Unknown prebaked effect type (value=" + mEffectId + ")"); 693 } 694 } 695 if (!isValidEffectStrength(mEffectStrength)) { 696 throw new IllegalArgumentException( 697 "Unknown prebaked effect strength (value=" + mEffectStrength + ")"); 698 } 699 } 700 701 @Override equals(Object o)702 public boolean equals(Object o) { 703 if (!(o instanceof VibrationEffect.Prebaked)) { 704 return false; 705 } 706 VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o; 707 return mEffectId == other.mEffectId 708 && mFallback == other.mFallback 709 && mEffectStrength == other.mEffectStrength; 710 } 711 712 @Override hashCode()713 public int hashCode() { 714 int result = 17; 715 result += 37 * mEffectId; 716 result += 37 * mEffectStrength; 717 return result; 718 } 719 720 @Override toString()721 public String toString() { 722 return "Prebaked{mEffectId=" + mEffectId 723 + ", mEffectStrength=" + mEffectStrength 724 + ", mFallback=" + mFallback 725 + "}"; 726 } 727 728 729 @Override writeToParcel(Parcel out, int flags)730 public void writeToParcel(Parcel out, int flags) { 731 out.writeInt(PARCEL_TOKEN_EFFECT); 732 out.writeInt(mEffectId); 733 out.writeByte((byte) (mFallback ? 1 : 0)); 734 out.writeInt(mEffectStrength); 735 } 736 737 public static final Parcelable.Creator<Prebaked> CREATOR = 738 new Parcelable.Creator<Prebaked>() { 739 @Override 740 public Prebaked createFromParcel(Parcel in) { 741 // Skip the type token 742 in.readInt(); 743 return new Prebaked(in); 744 } 745 @Override 746 public Prebaked[] newArray(int size) { 747 return new Prebaked[size]; 748 } 749 }; 750 } 751 752 public static final Parcelable.Creator<VibrationEffect> CREATOR = 753 new Parcelable.Creator<VibrationEffect>() { 754 @Override 755 public VibrationEffect createFromParcel(Parcel in) { 756 int token = in.readInt(); 757 if (token == PARCEL_TOKEN_ONE_SHOT) { 758 return new OneShot(in); 759 } else if (token == PARCEL_TOKEN_WAVEFORM) { 760 return new Waveform(in); 761 } else if (token == PARCEL_TOKEN_EFFECT) { 762 return new Prebaked(in); 763 } else { 764 throw new IllegalStateException( 765 "Unexpected vibration event type token in parcel."); 766 } 767 } 768 @Override 769 public VibrationEffect[] newArray(int size) { 770 return new VibrationEffect[size]; 771 } 772 }; 773 } 774