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