Interhaptics SDK for Unity 1.6
Loading...
Searching...
No Matches
GenericAndroidHapticAbstraction.cs
Go to the documentation of this file.
1/* ​
2* Copyright (c) 2023 Go Touch VR SAS. All rights reserved. ​
3* ​
4*/
5
7{
8
10 {
11 // Component Parameters
12 public static logLevel LogLevel = logLevel.Warning;
13
14 // Can be modified to tune the experience
15 // Can be changed in runtime
16 public static int AmplitudeThreshold = 100;
17
18 // Vibrator References
19 private static UnityEngine.AndroidJavaObject vibrator = null;
20 private static UnityEngine.AndroidJavaClass vibrationEffectClass = null;
21 private static int defaultAmplitude = 255;
22
23 // Api Level
24 private static int ApiLevel = 1;
25 private static bool doesSupportVibrationEffect() => ApiLevel >= 26; // available only from Api >= 26
26 private static bool doesSupportPredefinedEffect() => ApiLevel >= 29; // available only from Api >= 29
27
28 #region Initialization
29 private static bool isInitialized = false;
30
31 public static void Initialize()
32 {
33 // load references safely
34 if (isInitialized == false && UnityEngine.Application.platform == UnityEngine.RuntimePlatform.Android)
35 {
36 // Add APP VIBRATION PERMISSION to the Manifest
37 #if UNITY_ANDROID
38 if (UnityEngine.Application.isConsolePlatform)
39 {
40 UnityEngine.Handheld.Vibrate();
41 }
42 #endif
43 // Get Api Level
44 using (UnityEngine.AndroidJavaClass androidVersionClass = new UnityEngine.AndroidJavaClass("android.os.Build$VERSION"))
45 {
46 ApiLevel = androidVersionClass.GetStatic<int>("SDK_INT");
47 }
48
49 // Get UnityPlayer and CurrentActivity
50 using (UnityEngine.AndroidJavaClass unityPlayer = new UnityEngine.AndroidJavaClass("com.unity3d.player.UnityPlayer"))
51 using (UnityEngine.AndroidJavaObject currentActivity = unityPlayer.GetStatic<UnityEngine.AndroidJavaObject>("currentActivity"))
52 {
53 if (currentActivity != null)
54 {
55 vibrator = currentActivity.Call<UnityEngine.AndroidJavaObject>("getSystemService", "vibrator");
56
57 // if device supports vibration effects, get corresponding class
58 if (doesSupportVibrationEffect())
59 {
60 vibrationEffectClass = new UnityEngine.AndroidJavaClass("android.os.VibrationEffect");
61 defaultAmplitude = UnityEngine.Mathf.Clamp(vibrationEffectClass.GetStatic<int>("DEFAULT_AMPLITUDE"), -1, 255);
62 }
63
64 // if device supports predefined effects, get their IDs
65 if (doesSupportPredefinedEffect())
66 {
67 PredefinedEffect.EFFECT_CLICK = vibrationEffectClass.GetStatic<int>("EFFECT_CLICK");
68 PredefinedEffect.EFFECT_DOUBLE_CLICK = vibrationEffectClass.GetStatic<int>("EFFECT_DOUBLE_CLICK");
69 PredefinedEffect.EFFECT_HEAVY_CLICK = vibrationEffectClass.GetStatic<int>("EFFECT_HEAVY_CLICK");
70 PredefinedEffect.EFFECT_TICK = vibrationEffectClass.GetStatic<int>("EFFECT_TICK");
71 }
72 }
73 }
74
75 logAuto("Vibration component initialized", logLevel.Info);
76 isInitialized = true;
77 }
78 }
79 #endregion
80
81 #region Vibrate Public
87 public static void Vibrate(long milliseconds, int amplitude = -1, bool cancel = false)
88 {
89 string funcToStr() => string.Format("Vibrate ({0}, {1}, {2})", milliseconds, amplitude, cancel);
90 Initialize(); // make sure script is initialized
91 if (isInitialized == false)
92 {
93 logAuto(funcToStr() + ": Not initialized", logLevel.Warning);
94 }
95 else if (HasVibrator() == false)
96 {
97 logAuto(funcToStr() + ": Device doesn't have Vibrator", logLevel.Warning);
98 }
99 else
100 {
101 if (cancel)
102 {
103 Cancel();
104 }
105 if (doesSupportVibrationEffect())
106 {
107 // validate amplitude
108 amplitude = UnityEngine.Mathf.Clamp(amplitude, -1, 255);
109 if (amplitude == -1)
110 {
111 amplitude = 255; // if -1, disable amplitude (use maximum amplitude)
112 }
113 if (amplitude != 255 && HasAmplitudeControl() == false)
114 { // if amplitude was set, but not supported, notify developer
115 logAuto(funcToStr() + ": Device doesn't have Amplitude Control, but Amplitude was set", logLevel.Warning);
116 }
117 //if (amplitude == 0) amplitude = defaultAmplitude; // if 0, use device DefaultAmplitude
118
119 // if amplitude is not supported, use 255; if amplitude is -1, use systems DefaultAmplitude. Otherwise use user-defined value.
120 amplitude = HasAmplitudeControl() == false ? 255 : amplitude;
121 vibrateEffect(milliseconds, amplitude);
122 logAuto(funcToStr() + ": Effect called", logLevel.Info);
123 }
124 else
125 {
126 vibrateLegacy(milliseconds);
127 logAuto(funcToStr() + ": Legacy called", logLevel.Info);
128 }
129 }
130 }
131
138 public static void Vibrate(long[] pattern, int[] amplitudes = null, int repeat = -1, bool cancel = false)
139 {
140 string funcToStr() => string.Format("Vibrate (pattern, amplitudes, repeat, cancel)");
141
142 Initialize(); // make sure script is initialized
143 if (isInitialized == false)
144 {
145 logAuto(funcToStr() + ": Not initialized", logLevel.Warning);
146 }
147 else if (HasVibrator() == false)
148 {
149 logAuto(funcToStr() + ": Device doesn't have Vibrator", logLevel.Warning);
150 }
151 else
152 {
153 // check Amplitudes array length
154 if (amplitudes != null && amplitudes.Length != pattern.Length)
155 {
156 logAuto(funcToStr() + ": Length of Amplitudes array is not equal to Pattern array. Amplitudes will be ignored.", logLevel.Warning);
157 amplitudes = null;
158 }
159 // limit amplitudes between 1 and 255
160 if (amplitudes != null)
161 {
162 clampAmplitudesArray(amplitudes);
163 }
164
165 // vibrate
166 if (cancel)
167 {
168 Cancel();
169 }
170 if (doesSupportVibrationEffect())
171 {
172 if (amplitudes != null && HasAmplitudeControl() == true)
173 {
174 //Case High Capabilities
175 vibrateEffect(pattern, amplitudes, repeat);
176 logAuto(funcToStr() + ": Effect with amplitudes called", logLevel.Info);
177 return;
178 }
179 }
180
181 logAuto(funcToStr() + ": called but device has no amplitude control", logLevel.Warning);
182 //Case Medium & low capabilities
183 vibrateEffect(TimingsFromAmplitudes(amplitudes, pattern[1]), repeat);
184 }
185 }
186
191 public static void VibratePredefined(int effectId, bool cancel = false)
192 {
193 string funcToStr() => string.Format("VibratePredefined ({0})", effectId);
194
195 Initialize(); // make sure script is initialized
196 if (isInitialized == false)
197 {
198 logAuto(funcToStr() + ": Not initialized", logLevel.Warning);
199 }
200 else if (HasVibrator() == false)
201 {
202 logAuto(funcToStr() + ": Device doesn't have Vibrator", logLevel.Warning);
203 }
204 else if (doesSupportPredefinedEffect() == false)
205 {
206 logAuto(funcToStr() + ": Device doesn't support Predefined Effects (Api Level >= 29)", logLevel.Warning);
207 }
208 else
209 {
210 if (cancel)
211 {
212 Cancel();
213 }
214 vibrateEffectPredefined(effectId);
215 logAuto(funcToStr() + ": Predefined effect called", logLevel.Info);
216 }
217 }
218 #endregion
219
220 #region Public Properties & Controls
221 public static float PulseFromBuffer(int[] _buffer)
222 {
223 if (_buffer.Length > 0)
224 {
225 float result = 0;
226 for (int i = 0; i < _buffer.Length; i++)
227 {
228 result += _buffer[i];
229 }
230
231 return UnityEngine.Mathf.Clamp(result / _buffer.Length, 0, 255);
232 }
233
234 return 0;
235 }
236
237 public static long[] ParsePattern(string pattern)
238 {
239 if (pattern == null)
240 {
241 return new long[0];
242 }
243 pattern = pattern.Trim();
244 string[] split = pattern.Split(',');
245
246 long[] timings = new long[split.Length];
247 for (int i = 0; i < split.Length; i++)
248 {
249 if (int.TryParse(split[i].Trim(), out int duration))
250 {
251 timings[i] = duration < 0 ? 0 : duration;
252 }
253 else
254 {
255 timings[i] = 0;
256 }
257 }
258
259 return timings;
260 }
261
262 public static long[] TimingsFromAmplitudes(int[] amplitudes, long amplitudeDuration)
263 {
264 if (amplitudes == null)
265 {
266 return new long[] { 0, 0 };
267 }
268
269 System.Collections.Generic.List<long> timings = new System.Collections.Generic.List<long>();
270
271 int currentAmplitudeIndex = 0;
272 long currentTimingValue = 0;
273
274 while (currentAmplitudeIndex < amplitudes.Length)
275 {
276 //get duration under threshold
277 currentTimingValue = 0;
278 while (currentAmplitudeIndex < amplitudes.Length && amplitudes[currentAmplitudeIndex] < AmplitudeThreshold)
279 {
280 currentTimingValue += amplitudeDuration;
281 currentAmplitudeIndex++;
282 }
283 timings.Add(currentTimingValue);
284
285 //get duration over threshold
286 currentTimingValue = 0;
287 while (currentAmplitudeIndex < amplitudes.Length && amplitudes[currentAmplitudeIndex] >= AmplitudeThreshold)
288 {
289 currentTimingValue += amplitudeDuration;
290 currentAmplitudeIndex++;
291 }
292 timings.Add(currentTimingValue);
293 }
294
295 return timings.ToArray();
296 }
300 public static int GetApiLevel() => ApiLevel;
301
305 public static int GetDefaultAmplitude() => defaultAmplitude;
306
310 public static bool HasVibrator()
311 {
312 return vibrator != null && vibrator.Call<bool>("hasVibrator");
313 }
314
318 public static bool HasAmplitudeControl()
319 {
320 if (HasVibrator() && doesSupportVibrationEffect())
321 {
322 return vibrator.Call<bool>("hasAmplitudeControl"); // API 26+ specific
323 }
324 else
325 {
326 return false; // no amplitude control below API level 26
327 }
328 }
329
333 public static void Cancel()
334 {
335 if (HasVibrator())
336 {
337 vibrator.Call("cancel");
338 logAuto("Cancel (): Called", logLevel.Info);
339 }
340 }
341 #endregion
342
343 #region Vibrate Internal
344 #region Vibration Callers
345 private static void vibrateEffect(long milliseconds, int amplitude)
346 {
347 using (UnityEngine.AndroidJavaObject effect = createEffect_OneShot(milliseconds, amplitude))
348 {
349 vibrator.Call("vibrate", effect);
350 }
351 }
352
353 private static void vibrateLegacy(long milliseconds)
354 {
355 vibrator.Call("vibrate", milliseconds);
356 }
357
358 private static void vibrateEffect(long[] pattern, int repeat)
359 {
360 using (UnityEngine.AndroidJavaObject effect = createEffect_Waveform(pattern, repeat))
361 {
362 vibrator.Call("vibrate", effect);
363 }
364 }
365
366 private static void vibrateLegacy(long[] pattern, int repeat)
367 {
368 vibrator.Call("vibrate", pattern, repeat);
369 }
370
371 private static void vibrateEffect(long[] pattern, int[] amplitudes, int repeat)
372 {
373 using (UnityEngine.AndroidJavaObject effect = createEffect_Waveform(pattern, amplitudes, repeat))
374 {
375 vibrator.Call("vibrate", effect);
376 }
377 }
378
379 private static void vibrateEffectPredefined(int effectId)
380 {
381 using (UnityEngine.AndroidJavaObject effect = createEffect_Predefined(effectId))
382 {
383 vibrator.Call("vibrate", effect);
384 }
385 }
386 #endregion
387
388 #region Vibration Effect
392 private static UnityEngine.AndroidJavaObject createEffect_OneShot(long milliseconds, int amplitude)
393 {
394 return vibrationEffectClass.CallStatic<UnityEngine.AndroidJavaObject>("createOneShot", milliseconds, amplitude);
395 }
396
400 private static UnityEngine.AndroidJavaObject createEffect_Predefined(int effectId)
401 {
402 return vibrationEffectClass.CallStatic<UnityEngine.AndroidJavaObject>("createPredefined", effectId);
403 }
404
408 private static UnityEngine.AndroidJavaObject createEffect_Waveform(long[] timings, int[] amplitudes, int repeat)
409 {
410 return vibrationEffectClass.CallStatic<UnityEngine.AndroidJavaObject>("createWaveform", timings, amplitudes, repeat);
411 }
412
416 private static UnityEngine.AndroidJavaObject createEffect_Waveform(long[] timings, int repeat)
417 {
418 return vibrationEffectClass.CallStatic<UnityEngine.AndroidJavaObject>("createWaveform", timings, repeat);
419 }
420 #endregion
421 #endregion
422
423 #region Internal
424 private static void logAuto(string text, logLevel level)
425 {
426 if (level == logLevel.Disabled)
427 {
428 level = logLevel.Info;
429 }
430
431 if (text != null)
432 {
433 if (level == logLevel.Warning && LogLevel == logLevel.Warning)
434 {
435 UnityEngine.Debug.LogWarning(text);
436 }
437 else if (level == logLevel.Info && LogLevel >= logLevel.Info)
438 {
439 UnityEngine.Debug.Log(text);
440 }
441 }
442 }
443
444 private static string arrToStr(long[] array) => array == null ? "null" : string.Join(", ", array);
445 private static string arrToStr(int[] array) => array == null ? "null" : string.Join(", ", array);
446
447 private static void clampAmplitudesArray(int[] amplitudes)
448 {
449 for (int i = 0; i < amplitudes.Length; i++)
450 {
451 amplitudes[i] = UnityEngine.Mathf.Clamp(amplitudes[i], 0, 255);
452 }
453 }
454 #endregion
455
456 public static class PredefinedEffect
457 {
458 public static int EFFECT_CLICK; // public static final int EFFECT_CLICK
459 public static int EFFECT_DOUBLE_CLICK; // public static final int EFFECT_DOUBLE_CLICK
460 public static int EFFECT_HEAVY_CLICK; // public static final int EFFECT_HEAVY_CLICK
461 public static int EFFECT_TICK; // public static final int EFFECT_TICK
462 }
463
464 public enum logLevel
465 {
466 Disabled,
467 Info,
468 Warning,
469 }
470
471 }
472
473}
static int GetDefaultAmplitude()
Returns Default Amplitude of device, or 0.
static void VibratePredefined(int effectId, bool cancel=false)
Vibrate predefined effect (described in Vibration.PredefinedEffect). Available from Api Level >= 29....
static void Vibrate(long milliseconds, int amplitude=-1, bool cancel=false)
Vibrate for Milliseconds, with Amplitude (if available). If amplitude is -1, amplitude is Disabled....
static long[] TimingsFromAmplitudes(int[] amplitudes, long amplitudeDuration)
static void Vibrate(long[] pattern, int[] amplitudes=null, int repeat=-1, bool cancel=false)
Vibrate Pattern (pattern of durations, with format Off-On-Off-On and so on). Amplitudes can be Null (...
static bool HasAmplitudeControl()
Return true if device supports amplitude control.
static int GetApiLevel()
Returns Android Api Level.