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
17package android.os;
18
19import android.hardware.vibrator.V1_0.Constants.Effect;
20
21import 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 */
28public 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 */
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     */
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     */
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     */
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     */
151    public static VibrationEffect get(int effectId) {
152        VibrationEffect effect = new Prebaked(effectId);
153        effect.validate();
154        return effect;
155    }
156
157    @Override
158    public int describeContents() {
159        return 0;
160    }
161
162    /** @hide */
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
170        public OneShot(Parcel in) {
171            this(in.readLong(), in.readInt());
172        }
173
174        public OneShot(long milliseconds, int amplitude) {
175            mTiming = milliseconds;
176            mAmplitude = amplitude;
177        }
178
179        public long getTiming() {
180            return mTiming;
181        }
182
183        public int getAmplitude() {
184            return mAmplitude;
185        }
186
187        @Override
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
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
210        public int hashCode() {
211            int result = 17;
212            result = 37 * (int) mTiming;
213            result = 37 * mAmplitude;
214            return result;
215        }
216
217        @Override
218        public String toString() {
219            return "OneShot{mTiming=" + mTiming +", mAmplitude=" + mAmplitude + "}";
220        }
221
222        @Override
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
250        public Waveform(Parcel in) {
251            this(in.createLongArray(), in.createIntArray(), in.readInt());
252        }
253
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
262        public long[] getTimings() {
263            return mTimings;
264        }
265
266        public int[] getAmplitudes() {
267            return mAmplitudes;
268        }
269
270        public int getRepeatIndex() {
271            return mRepeat;
272        }
273
274        @Override
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
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
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
327        public String toString() {
328            return "Waveform{mTimings=" + Arrays.toString(mTimings) +
329                ", mAmplitudes=" + Arrays.toString(mAmplitudes) +
330                ", mRepeat=" + mRepeat +
331                "}";
332        }
333
334        @Override
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
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
371        public Prebaked(Parcel in) {
372            this(in.readInt());
373        }
374
375        public Prebaked(int effectId) {
376            mEffectId = effectId;
377        }
378
379        public int getId() {
380            return mEffectId;
381        }
382
383        @Override
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
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
401        public int hashCode() {
402            return mEffectId;
403        }
404
405        @Override
406        public String toString() {
407            return "Prebaked{mEffectId=" + mEffectId + "}";
408        }
409
410
411        @Override
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