using System.Collections; using System.Collections.Generic; using UnityEngine; using System.Runtime.InteropServices; using System; namespace MoreMountains.NiceVibrations { /// /// This class handles all Android haptics specific calls /// public static class MMNVAndroid { /// /// A struct used to pass data to threaded calls /// public struct MMNVAndroidVibrateThreadData { public long[] Pattern; public int[] Amplitudes; public int Repeat; public MMNVAndroidVibrateThreadData(long[] pattern, int[] amplitudes, int repeat) { Pattern = pattern; Amplitudes = amplitudes; Repeat = repeat; } } private static MMNVAltThread _androidVibrateThread; private static MMNVAndroidVibrateThreadData _androidVibrateThreadData; private static int _sdkVersion = -1; #if UNITY_ANDROID && !UNITY_EDITOR private static AndroidJavaClass UnityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); private static AndroidJavaObject CurrentActivity = UnityPlayer.GetStatic("currentActivity"); private static AndroidJavaObject AndroidVibrator = CurrentActivity.Call("getSystemService", "vibrator"); private static AndroidJavaClass VibrationEffectClass; private static AndroidJavaObject VibrationEffect; private static int DefaultAmplitude; private static IntPtr AndroidVibrateMethodRawClass = AndroidJNIHelper.GetMethodID(AndroidVibrator.GetRawClass(), "vibrate", "(J)V", false); private static jvalue[] AndroidVibrateMethodRawClassParameters = new jvalue[1]; #else private static AndroidJavaClass UnityPlayer; private static AndroidJavaObject CurrentActivity; private static AndroidJavaObject AndroidVibrator = null; private static AndroidJavaClass VibrationEffectClass = null; private static AndroidJavaObject VibrationEffect; private static int DefaultAmplitude; private static IntPtr AndroidVibrateMethodRawClass = IntPtr.Zero; private static jvalue[] AndroidVibrateMethodRawClassParameters = null; #endif /// /// Requests a default vibration on Android, for the specified duration, in milliseconds /// /// Milliseconds. public static void AndroidVibrate(long milliseconds) { if (!MMNVPlatform.Android()) { return; } AndroidVibrateMethodRawClassParameters[0].j = milliseconds; AndroidJNI.CallVoidMethod(AndroidVibrator.GetRawObject(), AndroidVibrateMethodRawClass, AndroidVibrateMethodRawClassParameters); } /// /// Requests a vibration of the specified amplitude and duration. If amplitude is not supported by the device's SDK, a default vibration will be requested /// /// Milliseconds. /// Amplitude. public static void AndroidVibrate(long milliseconds, int amplitude) { if (!MMNVPlatform.Android()) { return; } // amplitude is only supported after API26 if ((AndroidSDKVersion() < 26)) { AndroidVibrate(milliseconds); } else { AndroidVibrationEffectClassInitialization(); VibrationEffect = VibrationEffectClass.CallStatic("createOneShot", new object[] { milliseconds, amplitude }); AndroidVibrator.Call("vibrate", VibrationEffect); } } // Requests a vibration on Android for the specified pattern and optional repeat // Straight out of the Android documentation : // Pass in an array of ints that are the durations for which to turn on or off the vibrator in milliseconds. // The first value indicates the number of milliseconds to wait before turning the vibrator on. // The next value indicates the number of milliseconds for which to keep the vibrator on before turning it off. // Subsequent values alternate between durations in milliseconds to turn the vibrator off or to turn the vibrator on. // repeat: the index into pattern at which to repeat, or -1 if you don't want to repeat. public static void AndroidVibrate(long[] pattern, int repeat) { if (!MMNVPlatform.Android()) { return; } if (pattern == null) { return; } if ((AndroidSDKVersion() < 26)) { AndroidVibrator.Call("vibrate", pattern, repeat); } else { AndroidVibrationEffectClassInitialization(); try { VibrationEffect = VibrationEffectClass.CallStatic("createWaveform", new object[] { pattern, repeat }); AndroidVibrator.Call("vibrate", VibrationEffect); } catch (Exception e) { Debug.LogException(e); } } } /// /// Requests a vibration on Android for the specified pattern, amplitude and optional repeat /// /// Pattern. /// Amplitudes. /// Repeat : -1 : no repeat, 0 : infinite, 1 : repeat once, 2 : repeat twice, etc public static void AndroidVibrate(long[] pattern, int[] amplitudes, int repeat, bool threaded = false) { if (!MMNVPlatform.Android()) { return; } if ((pattern == null) || (amplitudes == null)) { return; } if ((pattern.Length == 0) || (amplitudes.Length == 0)) { return; } if ((AndroidSDKVersion() < 26)) { AndroidVibrator.Call("vibrate", pattern, repeat); } else { if (threaded) { if (_androidVibrateThread == null) { _androidVibrateThread = new MMNVAltThread(); _androidVibrateThreadData = new MMNVAndroidVibrateThreadData(); } _androidVibrateThreadData.Pattern = pattern; _androidVibrateThreadData.Amplitudes = amplitudes; _androidVibrateThreadData.Repeat = repeat; _androidVibrateThread.Run(AndroidVibrateThread, _androidVibrateThreadData); } else { AndroidVibrateNoThread(pattern, amplitudes, repeat); } } } /// /// An internal method used to vibrate on a pattern/amplitude model using a secondary thread /// If you use that, you should call MMNVAndroid.ClearThreads() on exit (where exactly depends on your game, but OnDisable should cover most cases) /// /// private static void AndroidVibrateThread(MMNVAndroidVibrateThreadData threadData) { AndroidJNI.AttachCurrentThread(); AndroidVibrateNoThread(threadData.Pattern, threadData.Amplitudes, threadData.Repeat); AndroidJNI.DetachCurrentThread(); } /// /// An internal method used to vibrate on a pattern/amplitude model using the main thread /// /// private static void AndroidVibrateNoThread(long[] pattern, int[] amplitudes, int repeat) { if ((pattern == null) || (amplitudes == null)) { return; } AndroidVibrationEffectClassInitialization(); VibrationEffect = VibrationEffectClass.CallStatic("createWaveform", new object[] { pattern, amplitudes, repeat }); AndroidVibrator.Call("vibrate", VibrationEffect); } /// /// A method to call once you're done with haptics on Android, usually OnDisable, to avoid memory leaks /// public static void ClearThreads() { _androidVibrateThread = null; _androidVibrateThread?.CloseThread(); } /// /// Stops all Android vibrations that may be active /// public static void AndroidCancelVibrations() { if (!MMNVPlatform.Android()) { return; } AndroidVibrator.Call("cancel"); } /// /// Returns true if the device running the game has vibrations /// /// public static bool AndroidHasVibrator() { if (!MMNVPlatform.Android()) { return false; } return AndroidVibrator.Call("hasVibrator"); } /// /// Returns true if the device running the game has amplitude control /// /// public static bool AndroidHasAmplitudeControl() { if ((AndroidSDKVersion() < 26)) { return false; } if (!MMNVPlatform.Android()) { return false; } return AndroidVibrator.Call("hasAmplitudeControl"); } /// /// Initializes the VibrationEffectClass if needed. /// private static void AndroidVibrationEffectClassInitialization() { if (VibrationEffectClass == null) { VibrationEffectClass = new AndroidJavaClass("android.os.VibrationEffect"); } } /// /// Returns the current Android SDK version as an int /// /// The SDK version. public static int AndroidSDKVersion() { if (_sdkVersion == -1) { int apiLevel = int.Parse(SystemInfo.operatingSystem.Substring(SystemInfo.operatingSystem.IndexOf("-") + 1, 3)); _sdkVersion = apiLevel; return apiLevel; } else { return _sdkVersion; } } } }