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