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.hardware.vibrator.V1_1.Constants.Effect_1_1; 20 21 import java.util.Arrays; 22 23 /** 24 * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}. 25 * 26 * These effects may be any number of things, from single shot vibrations to complex waveforms. 27 */ 28 public abstract class VibrationEffect implements Parcelable { 29 private static final int PARCEL_TOKEN_ONE_SHOT = 1; 30 private static final int PARCEL_TOKEN_WAVEFORM = 2; 31 private static final int PARCEL_TOKEN_EFFECT = 3; 32 33 /** 34 * The default vibration strength of the device. 35 */ 36 public static final int DEFAULT_AMPLITUDE = -1; 37 38 /** 39 * A click effect. 40 * 41 * @see #get(int) 42 * @hide 43 */ 44 public static final int EFFECT_CLICK = Effect_1_1.CLICK; 45 46 /** 47 * A double click effect. 48 * 49 * @see #get(int) 50 * @hide 51 */ 52 public static final int EFFECT_DOUBLE_CLICK = Effect_1_1.DOUBLE_CLICK; 53 54 /** 55 * A tick effect. 56 * @see #get(int) 57 * @hide 58 */ 59 public static final int EFFECT_TICK = Effect_1_1.TICK; 60 61 /** @hide to prevent subclassing from outside of the framework */ 62 public VibrationEffect() { } 63 64 /** 65 * Create a one shot vibration. 66 * 67 * One shot vibrations will vibrate constantly for the specified period of time at the 68 * specified amplitude, and then stop. 69 * 70 * @param milliseconds The number of milliseconds to vibrate. This must be a positive number. 71 * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or 72 * {@link #DEFAULT_AMPLITUDE}. 73 * 74 * @return The desired effect. 75 */ 76 public static VibrationEffect createOneShot(long milliseconds, int amplitude) { 77 VibrationEffect effect = new OneShot(milliseconds, amplitude); 78 effect.validate(); 79 return effect; 80 } 81 82 /** 83 * Create a waveform vibration. 84 * 85 * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For 86 * each pair, the value in the amplitude array determines the strength of the vibration and the 87 * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no 88 * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored. 89 * <p> 90 * The amplitude array of the generated waveform will be the same size as the given 91 * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE}, 92 * starting with 0. Therefore the first timing value will be the period to wait before turning 93 * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE} 94 * strength, etc. 95 * </p><p> 96 * To cause the pattern to repeat, pass the index into the timings array at which to start the 97 * repetition, or -1 to disable repeating. 98 * </p> 99 * 100 * @param timings The pattern of alternating on-off timings, starting with off. Timing values 101 * of 0 will cause the timing / amplitude pair to be ignored. 102 * @param repeat The index into the timings array at which to repeat, or -1 if you you don't 103 * want to repeat. 104 * 105 * @return The desired effect. 106 */ 107 public static VibrationEffect createWaveform(long[] timings, int repeat) { 108 int[] amplitudes = new int[timings.length]; 109 for (int i = 0; i < (timings.length / 2); i++) { 110 amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE; 111 } 112 return createWaveform(timings, amplitudes, repeat); 113 } 114 115 /** 116 * Create a waveform vibration. 117 * 118 * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For 119 * each pair, the value in the amplitude array determines the strength of the vibration and the 120 * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no 121 * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored. 122 * </p><p> 123 * To cause the pattern to repeat, pass the index into the timings array at which to start the 124 * repetition, or -1 to disable repeating. 125 * </p> 126 * 127 * @param timings The timing values of the timing / amplitude pairs. Timing values of 0 128 * will cause the pair to be ignored. 129 * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values 130 * must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An 131 * amplitude value of 0 implies the motor is off. 132 * @param repeat The index into the timings array at which to repeat, or -1 if you you don't 133 * want to repeat. 134 * 135 * @return The desired effect. 136 */ 137 public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) { 138 VibrationEffect effect = new Waveform(timings, amplitudes, repeat); 139 effect.validate(); 140 return effect; 141 } 142 143 /** 144 * Get a predefined vibration effect. 145 * 146 * Predefined effects are a set of common vibration effects that should be identical, regardless 147 * of the app they come from, in order to provide a cohesive experience for users across 148 * the entire device. They also may be custom tailored to the device hardware in order to 149 * provide a better experience than you could otherwise build using the generic building 150 * blocks. 151 * 152 * This will fallback to a generic pattern if one exists and there does not exist a 153 * hardware-specific implementation of the effect. 154 * 155 * @param effectId The ID of the effect to perform: 156 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK} 157 * 158 * @return The desired effect. 159 * @hide 160 */ 161 public static VibrationEffect get(int effectId) { 162 return get(effectId, true); 163 } 164 165 /** 166 * Get a predefined vibration effect. 167 * 168 * Predefined effects are a set of common vibration effects that should be identical, regardless 169 * of the app they come from, in order to provide a cohesive experience for users across 170 * the entire device. They also may be custom tailored to the device hardware in order to 171 * provide a better experience than you could otherwise build using the generic building 172 * blocks. 173 * 174 * Some effects you may only want to play if there's a hardware specific implementation because 175 * they may, for example, be too disruptive to the user without tuning. The {@code fallback} 176 * parameter allows you to decide whether you want to fallback to the generic implementation or 177 * only play if there's a tuned, hardware specific one available. 178 * 179 * @param effectId The ID of the effect to perform: 180 * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK} 181 * @param fallback Whether to fallback to a generic pattern if a hardware specific 182 * implementation doesn't exist. 183 * 184 * @return The desired effect. 185 * @hide 186 */ 187 public static VibrationEffect get(int effectId, boolean fallback) { 188 VibrationEffect effect = new Prebaked(effectId, fallback); 189 effect.validate(); 190 return effect; 191 } 192 193 @Override 194 public int describeContents() { 195 return 0; 196 } 197 198 /** @hide */ 199 public abstract void validate(); 200 201 /** @hide */ 202 public static class OneShot extends VibrationEffect implements Parcelable { 203 private long mTiming; 204 private int mAmplitude; 205 206 public OneShot(Parcel in) { 207 this(in.readLong(), in.readInt()); 208 } 209 210 public OneShot(long milliseconds, int amplitude) { 211 mTiming = milliseconds; 212 mAmplitude = amplitude; 213 } 214 215 public long getTiming() { 216 return mTiming; 217 } 218 219 public int getAmplitude() { 220 return mAmplitude; 221 } 222 223 @Override 224 public void validate() { 225 if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) { 226 throw new IllegalArgumentException( 227 "amplitude must either be DEFAULT_AMPLITUDE, " + 228 "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")"); 229 } 230 if (mTiming <= 0) { 231 throw new IllegalArgumentException( 232 "timing must be positive (timing=" + mTiming + ")"); 233 } 234 } 235 236 @Override 237 public boolean equals(Object o) { 238 if (!(o instanceof VibrationEffect.OneShot)) { 239 return false; 240 } 241 VibrationEffect.OneShot other = (VibrationEffect.OneShot) o; 242 return other.mTiming == mTiming && other.mAmplitude == mAmplitude; 243 } 244 245 @Override 246 public int hashCode() { 247 int result = 17; 248 result = 37 * (int) mTiming; 249 result = 37 * mAmplitude; 250 return result; 251 } 252 253 @Override 254 public String toString() { 255 return "OneShot{mTiming=" + mTiming +", mAmplitude=" + mAmplitude + "}"; 256 } 257 258 @Override 259 public void writeToParcel(Parcel out, int flags) { 260 out.writeInt(PARCEL_TOKEN_ONE_SHOT); 261 out.writeLong(mTiming); 262 out.writeInt(mAmplitude); 263 } 264 265 public static final Parcelable.Creator<OneShot> CREATOR = 266 new Parcelable.Creator<OneShot>() { 267 @Override 268 public OneShot createFromParcel(Parcel in) { 269 // Skip the type token 270 in.readInt(); 271 return new OneShot(in); 272 } 273 @Override 274 public OneShot[] newArray(int size) { 275 return new OneShot[size]; 276 } 277 }; 278 } 279 280 /** @hide */ 281 public static class Waveform extends VibrationEffect implements Parcelable { 282 private long[] mTimings; 283 private int[] mAmplitudes; 284 private int mRepeat; 285 286 public Waveform(Parcel in) { 287 this(in.createLongArray(), in.createIntArray(), in.readInt()); 288 } 289 290 public Waveform(long[] timings, int[] amplitudes, int repeat) { 291 mTimings = new long[timings.length]; 292 System.arraycopy(timings, 0, mTimings, 0, timings.length); 293 mAmplitudes = new int[amplitudes.length]; 294 System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length); 295 mRepeat = repeat; 296 } 297 298 public long[] getTimings() { 299 return mTimings; 300 } 301 302 public int[] getAmplitudes() { 303 return mAmplitudes; 304 } 305 306 public int getRepeatIndex() { 307 return mRepeat; 308 } 309 310 @Override 311 public void validate() { 312 if (mTimings.length != mAmplitudes.length) { 313 throw new IllegalArgumentException( 314 "timing and amplitude arrays must be of equal length" + 315 " (timings.length=" + mTimings.length + 316 ", amplitudes.length=" + mAmplitudes.length + ")"); 317 } 318 if (!hasNonZeroEntry(mTimings)) { 319 throw new IllegalArgumentException("at least one timing must be non-zero" + 320 " (timings=" + Arrays.toString(mTimings) + ")"); 321 } 322 for (long timing : mTimings) { 323 if (timing < 0) { 324 throw new IllegalArgumentException("timings must all be >= 0" + 325 " (timings=" + Arrays.toString(mTimings) + ")"); 326 } 327 } 328 for (int amplitude : mAmplitudes) { 329 if (amplitude < -1 || amplitude > 255) { 330 throw new IllegalArgumentException( 331 "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255" + 332 " (amplitudes=" + Arrays.toString(mAmplitudes) + ")"); 333 } 334 } 335 if (mRepeat < -1 || mRepeat >= mTimings.length) { 336 throw new IllegalArgumentException( 337 "repeat index must be within the bounds of the timings array" + 338 " (timings.length=" + mTimings.length + ", index=" + mRepeat +")"); 339 } 340 } 341 342 @Override 343 public boolean equals(Object o) { 344 if (!(o instanceof VibrationEffect.Waveform)) { 345 return false; 346 } 347 VibrationEffect.Waveform other = (VibrationEffect.Waveform) o; 348 return Arrays.equals(mTimings, other.mTimings) && 349 Arrays.equals(mAmplitudes, other.mAmplitudes) && 350 mRepeat == other.mRepeat; 351 } 352 353 @Override 354 public int hashCode() { 355 int result = 17; 356 result = 37 * Arrays.hashCode(mTimings); 357 result = 37 * Arrays.hashCode(mAmplitudes); 358 result = 37 * mRepeat; 359 return result; 360 } 361 362 @Override 363 public String toString() { 364 return "Waveform{mTimings=" + Arrays.toString(mTimings) + 365 ", mAmplitudes=" + Arrays.toString(mAmplitudes) + 366 ", mRepeat=" + mRepeat + 367 "}"; 368 } 369 370 @Override 371 public void writeToParcel(Parcel out, int flags) { 372 out.writeInt(PARCEL_TOKEN_WAVEFORM); 373 out.writeLongArray(mTimings); 374 out.writeIntArray(mAmplitudes); 375 out.writeInt(mRepeat); 376 } 377 378 private static boolean hasNonZeroEntry(long[] vals) { 379 for (long val : vals) { 380 if (val != 0) { 381 return true; 382 } 383 } 384 return false; 385 } 386 387 388 public static final Parcelable.Creator<Waveform> CREATOR = 389 new Parcelable.Creator<Waveform>() { 390 @Override 391 public Waveform createFromParcel(Parcel in) { 392 // Skip the type token 393 in.readInt(); 394 return new Waveform(in); 395 } 396 @Override 397 public Waveform[] newArray(int size) { 398 return new Waveform[size]; 399 } 400 }; 401 } 402 403 /** @hide */ 404 public static class Prebaked extends VibrationEffect implements Parcelable { 405 private int mEffectId; 406 private boolean mFallback; 407 408 public Prebaked(Parcel in) { 409 this(in.readInt(), in.readByte() != 0); 410 } 411 412 public Prebaked(int effectId, boolean fallback) { 413 mEffectId = effectId; 414 mFallback = fallback; 415 } 416 417 public int getId() { 418 return mEffectId; 419 } 420 421 /** 422 * Whether the effect should fall back to a generic pattern if there's no hardware specific 423 * implementation of it. 424 */ 425 public boolean shouldFallback() { 426 return mFallback; 427 } 428 429 @Override 430 public void validate() { 431 switch (mEffectId) { 432 case EFFECT_CLICK: 433 case EFFECT_DOUBLE_CLICK: 434 case EFFECT_TICK: 435 break; 436 default: 437 throw new IllegalArgumentException( 438 "Unknown prebaked effect type (value=" + mEffectId + ")"); 439 } 440 } 441 442 @Override 443 public boolean equals(Object o) { 444 if (!(o instanceof VibrationEffect.Prebaked)) { 445 return false; 446 } 447 VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o; 448 return mEffectId == other.mEffectId && mFallback == other.mFallback; 449 } 450 451 @Override 452 public int hashCode() { 453 return mEffectId; 454 } 455 456 @Override 457 public String toString() { 458 return "Prebaked{mEffectId=" + mEffectId + ", mFallback=" + mFallback + "}"; 459 } 460 461 462 @Override 463 public void writeToParcel(Parcel out, int flags) { 464 out.writeInt(PARCEL_TOKEN_EFFECT); 465 out.writeInt(mEffectId); 466 out.writeByte((byte) (mFallback ? 1 : 0)); 467 } 468 469 public static final Parcelable.Creator<Prebaked> CREATOR = 470 new Parcelable.Creator<Prebaked>() { 471 @Override 472 public Prebaked createFromParcel(Parcel in) { 473 // Skip the type token 474 in.readInt(); 475 return new Prebaked(in); 476 } 477 @Override 478 public Prebaked[] newArray(int size) { 479 return new Prebaked[size]; 480 } 481 }; 482 } 483 484 public static final Parcelable.Creator<VibrationEffect> CREATOR = 485 new Parcelable.Creator<VibrationEffect>() { 486 @Override 487 public VibrationEffect createFromParcel(Parcel in) { 488 int token = in.readInt(); 489 if (token == PARCEL_TOKEN_ONE_SHOT) { 490 return new OneShot(in); 491 } else if (token == PARCEL_TOKEN_WAVEFORM) { 492 return new Waveform(in); 493 } else if (token == PARCEL_TOKEN_EFFECT) { 494 return new Prebaked(in); 495 } else { 496 throw new IllegalStateException( 497 "Unexpected vibration event type token in parcel."); 498 } 499 } 500 @Override 501 public VibrationEffect[] newArray(int size) { 502 return new VibrationEffect[size]; 503 } 504 }; 505 } 506