#if UNITY_EDITOR || UNITY_ANDROID using System.Linq; using UnityEngine.InputSystem.Layouts; using UnityEngine.InputSystem.LowLevel; using UnityEngine.InputSystem.Android.LowLevel; namespace UnityEngine.InputSystem.Android { /// /// Initializes custom android devices. /// You can use 'adb shell dumpsys input' from terminal to output information about all input devices. /// #if UNITY_DISABLE_DEFAULT_INPUT_PLUGIN_INITIALIZATION public #else internal #endif class AndroidSupport { internal const string kAndroidInterface = "Android"; public static void Initialize() { InputSystem.RegisterLayout( matches: new InputDeviceMatcher() .WithInterface(kAndroidInterface) .WithDeviceClass("AndroidGameController")); InputSystem.RegisterLayout( matches: new InputDeviceMatcher() .WithInterface(kAndroidInterface) .WithDeviceClass("AndroidGameController")); ////TODO: capability matching does not yet support bitmasking so these remain handled by OnFindLayoutForDevice for now const string kDpadHatSettings = @" { ""name"" : ""dpad"", ""offset"" : 88, ""format"" : ""VEC2"", ""sizeInBits"" : 64 }, { ""name"" : ""dpad/right"", ""offset"" : 0, ""bit"" : 0, ""format"" : ""FLT"", ""parameters"" : ""clamp=3,clampConstant=0,clampMin=0,clampMax=1"" }, { ""name"" : ""dpad/left"", ""offset"" : 0, ""bit"" : 0, ""format"" : ""FLT"", ""parameters"" : ""clamp=3,clampConstant=0,clampMin=-1,clampMax=0,invert"" }, { ""name"" : ""dpad/down"", ""offset"" : 4, ""bit"" : 0, ""format"" : ""FLT"", ""parameters"" : ""clamp=3,clampConstant=0,clampMin=0,clampMax=1"" }, { ""name"" : ""dpad/up"", ""offset"" : 4, ""bit"" : 0, ""format"" : ""FLT"", ""parameters"" : ""clamp=3,clampConstant=0,clampMin=-1,clampMax=0,invert"" } "; InputSystem.RegisterLayout(@" { ""name"" : ""AndroidGamepadWithDpadAxes"", ""extend"" : ""AndroidGamepad"", ""hideInUI"" : true, ""controls"" : [ " + kDpadHatSettings + @" ] } "); InputSystem.RegisterLayout(@" { ""name"" : ""AndroidGamepadWithDpadButtons"", ""extend"" : ""AndroidGamepad"", ""hideInUI"" : true, ""controls"" : [ { ""name"" : ""dpad"", ""offset"" : 0, ""bit"" : 19, ""sizeInBits"" : 4 }, { ""name"" : ""dpad/left"", ""bit"" : 21 }, { ""name"" : ""dpad/right"", ""bit"" : 22 }, { ""name"" : ""dpad/up"", ""bit"" : 19 }, { ""name"" : ""dpad/down"", ""bit"" : 20 } ] } "); ////TODO: why do I have to set layout here for leftTrigger, shouldn't it come from child control ? InputSystem.RegisterLayout(string.Format(@" {{ ""name"" : ""AndroidGamepadXboxController"", ""extend"" : ""AndroidGamepad"", ""displayName"" : ""Android Xbox Gamepad"", ""controls"" : [ {0}, {{ ""name"" : ""leftTrigger"", ""layout"" : ""Button"", ""offset"" : {1}, ""format"" : ""FLT"", ""parameters"" : ""normalize=true,normalizeMin=-1,normalizeMax=1,normalizeZero=-1"", ""variant"" : ""{4}"" }}, {{ ""name"" : ""rightTrigger"", ""layout"" : ""Button"", ""offset"" : {2}, ""format"" : ""FLT"", ""parameters"" : ""normalize=true,normalizeMin=-1,normalizeMax=1,normalizeZero=-1"", ""variant"" : ""{4}"" }}, {{ ""name"" : ""rightStick"", ""layout"" : ""Stick"", ""offset"" : {3}, ""format"" : ""VEC2"", ""variant"" : ""{4}"" }}, {{ ""name"" : ""rightStick/x"", ""offset"" : 0, ""bit"" : 0, ""format"" : ""FLT"", ""variant"" : ""{4}"" }}, {{ ""name"" : ""rightStick/y"", ""offset"" : 4, ""bit"" : 0, ""format"" : ""FLT"", ""variant"" : ""{4}"" }} ] }}" , kDpadHatSettings , (uint)AndroidAxis.Z * sizeof(float) + AndroidGameControllerState.kAxisOffset , (uint)AndroidAxis.Rz * sizeof(float) + AndroidGameControllerState.kAxisOffset , (uint)AndroidAxis.Rx * sizeof(float) + AndroidGameControllerState.kAxisOffset , AndroidGameControllerState.kVariantGamepad)); InputSystem.RegisterProcessor(); InputSystem.RegisterProcessor(); // Add sensors InputSystem.RegisterLayout( matches: new InputDeviceMatcher() .WithInterface(kAndroidInterface) .WithDeviceClass("AndroidSensor") .WithCapability("sensorType", AndroidSensorType.Accelerometer)); InputSystem.RegisterLayout( matches: new InputDeviceMatcher() .WithInterface(kAndroidInterface) .WithDeviceClass("AndroidSensor") .WithCapability("sensorType", AndroidSensorType.MagneticField)); InputSystem.RegisterLayout( matches: new InputDeviceMatcher() .WithInterface(kAndroidInterface) .WithDeviceClass("AndroidSensor") .WithCapability("sensorType", AndroidSensorType.Gyroscope)); InputSystem.RegisterLayout( matches: new InputDeviceMatcher() .WithInterface(kAndroidInterface) .WithDeviceClass("AndroidSensor") .WithCapability("sensorType", AndroidSensorType.Light)); InputSystem.RegisterLayout( matches: new InputDeviceMatcher() .WithInterface(kAndroidInterface) .WithDeviceClass("AndroidSensor") .WithCapability("sensorType", AndroidSensorType.Pressure)); InputSystem.RegisterLayout( matches: new InputDeviceMatcher() .WithInterface(kAndroidInterface) .WithDeviceClass("AndroidSensor") .WithCapability("sensorType", AndroidSensorType.Proximity)); InputSystem.RegisterLayout( matches: new InputDeviceMatcher() .WithInterface(kAndroidInterface) .WithDeviceClass("AndroidSensor") .WithCapability("sensorType", AndroidSensorType.Gravity)); InputSystem.RegisterLayout( matches: new InputDeviceMatcher() .WithInterface(kAndroidInterface) .WithDeviceClass("AndroidSensor") .WithCapability("sensorType", AndroidSensorType.LinearAcceleration)); InputSystem.RegisterLayout( matches: new InputDeviceMatcher() .WithInterface(kAndroidInterface) .WithDeviceClass("AndroidSensor") .WithCapability("sensorType", AndroidSensorType.RotationVector)); InputSystem.RegisterLayout( matches: new InputDeviceMatcher() .WithInterface(kAndroidInterface) .WithDeviceClass("AndroidSensor") .WithCapability("sensorType", AndroidSensorType.RelativeHumidity)); InputSystem.RegisterLayout( matches: new InputDeviceMatcher() .WithInterface(kAndroidInterface) .WithDeviceClass("AndroidSensor") .WithCapability("sensorType", AndroidSensorType.AmbientTemperature)); InputSystem.RegisterLayout( matches: new InputDeviceMatcher() .WithInterface(kAndroidInterface) .WithDeviceClass("AndroidSensor") .WithCapability("sensorType", AndroidSensorType.StepCounter)); InputSystem.onFindLayoutForDevice += OnFindLayoutForDevice; } internal static string OnFindLayoutForDevice(ref InputDeviceDescription description, string matchedLayout, InputDeviceExecuteCommandDelegate executeCommandDelegate) { // If we already have a matching layout, someone registered a better match. // We only want to act as a fallback. if (!string.IsNullOrEmpty(matchedLayout) && matchedLayout != "AndroidGamepad" && matchedLayout != "AndroidJoystick") return null; if (description.interfaceName != "Android" || string.IsNullOrEmpty(description.capabilities)) return null; ////TODO: these should just be Controller and Sensor; the interface is already Android switch (description.deviceClass) { case "AndroidGameController": { var caps = AndroidDeviceCapabilities.FromJson(description.capabilities); // Note: Gamepads have both AndroidInputSource.Gamepad and AndroidInputSource.Joystick in input source, while // Joysticks don't have AndroidInputSource.Gamepad in their input source if ((caps.inputSources & AndroidInputSource.Gamepad) != AndroidInputSource.Gamepad) return "AndroidJoystick"; if (caps.motionAxes == null) return "AndroidGamepadWithDpadButtons"; // Vendor Ids, Product Ids can be found here http://www.linux-usb.org/usb.ids const int kVendorMicrosoft = 0x045e; if (caps.vendorId == kVendorMicrosoft && caps.motionAxes != null && caps.motionAxes.Contains(AndroidAxis.Rx) && caps.motionAxes.Contains(AndroidAxis.Ry) && caps.motionAxes.Contains(AndroidAxis.HatX) && caps.motionAxes.Contains(AndroidAxis.HatY)) return "AndroidGamepadXboxController"; // Fallback to generic gamepads if (caps.motionAxes.Contains(AndroidAxis.HatX) && caps.motionAxes.Contains(AndroidAxis.HatY)) return "AndroidGamepadWithDpadAxes"; return "AndroidGamepadWithDpadButtons"; } default: return null; } } } } #endif // UNITY_EDITOR || UNITY_ANDROID