├── LICENSE
├── README.md
├── Rumble.java
└── Rumble.kt
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Joseph Meir Rubin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Rumble-4-Android
2 | r4a abstracts away the annoying Android Vibrator API which suffers from a number of issues:
3 |
4 | - Different versions of android have **deprecated** or **added** different functions and you are expected to know which ones to use depending on the device version.
5 | - You have to make a **new object** for every single vibration you want to run.
6 | - Patterns are tedious and **not intuitive**.
7 |
8 | Instead...
9 |
10 | ## Setup *(easy!)*
11 |
12 | 1. Download ```Rumble.java``` and place it anywhere in the **source directory** of your project.
13 | 2. Add the line `````` inside the ```manifest``` tag of your project's ```AndroidManifest.xml```.
14 | 3. Call ```Rumble.init(applicationContext)``` and pass it your application's **Context**. For example, you can place the following code in your activity:
15 | >MainActivity.java
16 |
17 | ```java
18 | @Override
19 | protected void onCreate(Bundle savedInstanceState) {
20 | super.onCreate(savedInstanceState);
21 | Rumble.init(getApplicationContext());
22 | }
23 | ```
24 |
25 | ## Use *(fluent!)*
26 | **One-time** device vibration
27 | ```java Rumble.once(500); // Vibrate for 500 milliseconds.```
28 |
29 | **Patterns**
30 | ```java
31 | Rumble.makePattern()
32 | .beat(300)
33 | .rest(250)
34 | .beat(720)
35 | .playPattern();
36 | ```
37 | **Repeating patterns**
38 | ```java
39 | Rumble.makePattern()
40 | .rest(200)
41 | .beat(30)
42 | .beat(holdDuration) // Automatically adds to previous beat.
43 | .playPattern(4); // Play 4 times in a row.
44 | ```
45 | **Save patterns for later**
46 | ```java
47 | RumblePattern pattern = Rumble.makePattern()
48 | .beat(30).rest(150).beat(40).rest(40);
49 |
50 | pattern.rest(80).beat(700); // Add to a pattern later.
51 | pattern.playPattern(); // Play it whenever you like.
52 | ```
53 |
54 | **Lock patterns to prevent mutation**
55 | ```java
56 | pattern.lock();
57 | pattern.playPattern(3); // Works just fine.
58 | pattern.beat(500).rest(250) // Throws IllegalStateException.
59 | ```
60 |
--------------------------------------------------------------------------------
/Rumble.java:
--------------------------------------------------------------------------------
1 | package box.shoe.gameutils.rumble;
2 |
3 | import android.content.Context;
4 | import android.os.Build;
5 | import android.os.VibrationEffect;
6 | import android.os.Vibrator;
7 | import android.util.Log;
8 |
9 | import java.util.ArrayList;
10 | import java.util.Arrays;
11 | import java.util.List;
12 |
13 | import box.gift.gameutils.BuildConfig;
14 |
15 | public class Rumble
16 | {
17 | private static Vibrator vibrator;
18 | private static boolean rumbleDisabled;
19 |
20 | public static void init(Context applicationContext)
21 | {
22 | vibrator = (Vibrator) applicationContext.getSystemService(Context.VIBRATOR_SERVICE);
23 | rumbleDisabled = (vibrator == null || !vibrator.hasVibrator());
24 | if (rumbleDisabled && BuildConfig.DEBUG)
25 | {
26 | Log.w("Rumble", "System does not have a Vibrator, or the permission is disabled. " +
27 | "Rumble has been turned rest. Subsequent calls to static methods will have no effect.");
28 | }
29 | }
30 |
31 | private static void apiIndependentVibrate(long milliseconds)
32 | {
33 | if (rumbleDisabled)
34 | {
35 | return;
36 | }
37 |
38 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
39 | {
40 | vibrator.vibrate(VibrationEffect.createOneShot(milliseconds, VibrationEffect.DEFAULT_AMPLITUDE));
41 | }
42 | else
43 | {
44 | vibrator.vibrate(milliseconds);
45 | }
46 | }
47 |
48 | private static void apiIndependentVibrate(long[] pattern)
49 | {
50 | if (rumbleDisabled)
51 | {
52 | return;
53 | }
54 |
55 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
56 | {
57 | vibrator.vibrate(VibrationEffect.createWaveform(pattern, -1));
58 | }
59 | else
60 | {
61 | vibrator.vibrate(pattern, -1);
62 | }
63 | }
64 |
65 | public static void stop()
66 | {
67 | if (rumbleDisabled)
68 | {
69 | return;
70 | }
71 |
72 | vibrator.cancel();
73 | }
74 |
75 | public static void once(long milliseconds)
76 | {
77 | apiIndependentVibrate(milliseconds);
78 | }
79 |
80 | public static RumblePattern makePattern()
81 | {
82 | return new RumblePattern();
83 | }
84 |
85 | public static class RumblePattern
86 | {
87 | private List internalPattern;
88 | private boolean locked;
89 |
90 | private RumblePattern()
91 | {
92 | locked = false;
93 | internalPattern = new ArrayList<>();
94 | internalPattern.add(0L);
95 | }
96 |
97 | public RumblePattern beat(long milliseconds)
98 | {
99 | if (locked)
100 | {
101 | throw new IllegalStateException("RumblePattern is locked! Cannot modify its state.");
102 | }
103 |
104 | if (internalPattern.size() % 2 == 0)
105 | {
106 | internalPattern.set(internalPattern.size() - 1, internalPattern.get(internalPattern.size() - 1) + milliseconds);
107 | }
108 | else
109 | {
110 | internalPattern.add(milliseconds);
111 | }
112 | return this;
113 | }
114 |
115 | public RumblePattern rest(long milliseconds)
116 | {
117 | if (locked)
118 | {
119 | throw new IllegalStateException("RumblePattern is locked! Cannot modify its state.");
120 | }
121 |
122 | if (internalPattern.size() % 2 == 0)
123 | {
124 | internalPattern.add(milliseconds);
125 | }
126 | else
127 | {
128 | internalPattern.set(internalPattern.size() - 1, internalPattern.get(internalPattern.size() - 1) + milliseconds);
129 | }
130 | return this;
131 | }
132 |
133 | public void lock()
134 | {
135 | if (locked)
136 | {
137 | throw new IllegalStateException("RumblePattern is already locked! Use isLocked() to check.");
138 | }
139 | locked = true;
140 | }
141 |
142 | public boolean isLocked()
143 | {
144 | return locked;
145 | }
146 |
147 | public void playPattern()
148 | {
149 | playPattern(1);
150 | }
151 |
152 | public void playPattern(int numberOfTimes)
153 | {
154 | if (numberOfTimes < 0)
155 | {
156 | throw new IllegalArgumentException("numberOfTimes must be >= 0");
157 | }
158 |
159 | boolean endsWithRest = internalPattern.size() % 2 == 0;
160 |
161 | // We have a List but we need a long[]. We can't simply use toArray because that yields a Long[].
162 | // Reserve enough space to hold the full pattern as many times as necessary to play the pattern the right number of times.
163 | long[] primitiveArray = new long[internalPattern.size() * numberOfTimes - (endsWithRest ? 0 : numberOfTimes - 1)];
164 | for (int i = 0; i < internalPattern.size(); i++)
165 | {
166 | // Auto unboxing converts each Long to a long.
167 | primitiveArray[i] = internalPattern.get(i);
168 | }
169 |
170 | // Copy the array into itself to duplicate the pattern enough times.
171 | // Not a simple copy - we must overlay the copies if the pattern ends in a rest.
172 | // R B R
173 | // [100, 300, 500]
174 | // +
175 | // [100, 300, 500]
176 | for (int i = 1; i < numberOfTimes; i++)
177 | {
178 | for (int j = 0; j < internalPattern.size(); j++)
179 | {
180 | int k = j + (internalPattern.size() * i) - (endsWithRest ? 0 : i);
181 | primitiveArray[k] += primitiveArray[j];
182 | }
183 | }
184 |
185 | apiIndependentVibrate(primitiveArray);
186 | }
187 |
188 | @Override
189 | public String toString()
190 | {
191 | return "RumblePattern{" +
192 | "internalPattern=" + internalPattern +
193 | '}';
194 | }
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/Rumble.kt:
--------------------------------------------------------------------------------
1 | // TODO: Change this
2 | package box.shoe.gameutils.rumble;
3 |
4 | import android.content.Context
5 | import android.os.Build
6 | import android.os.VibrationEffect
7 | import android.os.Vibrator
8 | import android.util.Log
9 |
10 | // TODO: Change this
11 | import box.gift.gameutils.BuildConfig;
12 |
13 | import java.util.ArrayList
14 | import java.util.Arrays
15 |
16 | object Rumble {
17 | private var vibrator: Vibrator? = null
18 | private var rumbleDisabled: Boolean = false
19 |
20 | fun init(applicationContext: Context) {
21 | vibrator = applicationContext.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
22 | rumbleDisabled = vibrator == null || !vibrator!!.hasVibrator()
23 | if (rumbleDisabled && BuildConfig.DEBUG) {
24 | Log.w("Rumble", "System does not have a Vibrator, or the permission is disabled. " + "Rumble has been turned rest. Subsequent calls to static methods will have no effect.")
25 | }
26 | }
27 |
28 | private fun apiIndependentVibrate(milliseconds: Long) {
29 | if (rumbleDisabled) {
30 | return
31 | }
32 |
33 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
34 | vibrator!!.vibrate(VibrationEffect.createOneShot(milliseconds, VibrationEffect.DEFAULT_AMPLITUDE))
35 | } else {
36 | vibrator!!.vibrate(milliseconds)
37 | }
38 | }
39 |
40 | private fun apiIndependentVibrate(pattern: LongArray) {
41 | if (rumbleDisabled) {
42 | return
43 | }
44 |
45 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
46 | vibrator!!.vibrate(VibrationEffect.createWaveform(pattern, -1))
47 | } else {
48 | vibrator!!.vibrate(pattern, -1)
49 | }
50 | }
51 |
52 | fun stop() {
53 | if (rumbleDisabled) {
54 | return
55 | }
56 |
57 | vibrator!!.cancel()
58 | }
59 |
60 | fun once(milliseconds: Long) {
61 | apiIndependentVibrate(milliseconds)
62 | }
63 |
64 | fun makePattern(): RumblePattern {
65 | return RumblePattern()
66 | }
67 |
68 | class RumblePattern internal constructor() {
69 | private val internalPattern: MutableList
70 | var isLocked: Boolean = false
71 | private set
72 |
73 | init {
74 | isLocked = false
75 | internalPattern = ArrayList()
76 | internalPattern.add(0L)
77 | }
78 |
79 | fun beat(milliseconds: Long): RumblePattern {
80 | if (isLocked) {
81 | throw IllegalStateException("RumblePattern is locked! Cannot modify its state.")
82 | }
83 |
84 | if (internalPattern.size % 2 == 0) {
85 | internalPattern[internalPattern.size - 1] = internalPattern[internalPattern.size - 1] + milliseconds
86 | } else {
87 | internalPattern.add(milliseconds)
88 | }
89 | return this
90 | }
91 |
92 | fun rest(milliseconds: Long): RumblePattern {
93 | if (isLocked) {
94 | throw IllegalStateException("RumblePattern is locked! Cannot modify its state.")
95 | }
96 |
97 | if (internalPattern.size % 2 == 0) {
98 | internalPattern.add(milliseconds)
99 | } else {
100 | internalPattern[internalPattern.size - 1] = internalPattern[internalPattern.size - 1] + milliseconds
101 | }
102 | return this
103 | }
104 |
105 | fun lock() {
106 | if (isLocked) {
107 | throw IllegalStateException("RumblePattern is already locked! Use isLocked() to check.")
108 | }
109 | isLocked = true
110 | }
111 |
112 | @JvmOverloads
113 | fun playPattern(numberOfTimes: Int = 1) {
114 | if (numberOfTimes < 0) {
115 | throw IllegalArgumentException("numberOfTimes must be >= 0")
116 | }
117 |
118 | val endsWithRest = internalPattern.size % 2 == 0
119 |
120 | // We have a List but we need a long[]. We can't simply use toArray because that yields a Long[].
121 | // Reserve enough space to hold the full pattern as many times as necessary to play the pattern the right number of times.
122 | val primitiveArray = LongArray(internalPattern.size * numberOfTimes - if (endsWithRest) 0 else numberOfTimes - 1)
123 | for (i in internalPattern.indices) {
124 | // Auto unboxing converts each Long to a long.
125 | primitiveArray[i] = internalPattern[i]
126 | }
127 |
128 | // Copy the array into itself to duplicate the pattern enough times.
129 | // Not a simple copy - we must overlay the copies if the pattern ends in a rest.
130 | // R B R
131 | // [100, 300, 500]
132 | // +
133 | // [100, 300, 500]
134 | for (i in 1 until numberOfTimes) {
135 | for (j in internalPattern.indices) {
136 | val k = j + internalPattern.size * i - if (endsWithRest) 0 else i
137 | primitiveArray[k] += primitiveArray[j]
138 | }
139 | }
140 |
141 | apiIndependentVibrate(primitiveArray)
142 | }
143 |
144 | override fun toString(): String {
145 | return "RumblePattern{" +
146 | "internalPattern=" + internalPattern +
147 | "}"
148 | }
149 | }
150 | }
151 |
--------------------------------------------------------------------------------