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