// Copyright 2017 Google Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // This provider is only available on an Android device. #if UNITY_ANDROID && !UNITY_EDITOR using UnityEngine; using System; using System.Runtime.InteropServices; /// @cond namespace Gvr.Internal { /// Controller Provider that uses the native GVR C API to communicate with controllers /// via Google VR Services on Android. class AndroidNativeControllerProvider : IControllerProvider { // Note: keep structs and function signatures in sync with the C header file (gvr_controller.h). // GVR controller option flags. private const int GVR_CONTROLLER_ENABLE_ORIENTATION = 1 << 0; private const int GVR_CONTROLLER_ENABLE_TOUCH = 1 << 1; private const int GVR_CONTROLLER_ENABLE_GYRO = 1 << 2; private const int GVR_CONTROLLER_ENABLE_ACCEL = 1 << 3; private const int GVR_CONTROLLER_ENABLE_GESTURES = 1 << 4; private const int GVR_CONTROLLER_ENABLE_POSE_PREDICTION = 1 << 5; private const int GVR_CONTROLLER_ENABLE_POSITION = 1 << 6; private const int GVR_CONTROLLER_ENABLE_BATTERY = 1 << 7; private const int GVR_CONTROLLER_ENABLE_ARM_MODEL = 1 << 8; // enum gvr_controller_button: private const int GVR_CONTROLLER_BUTTON_NONE = 0; private const int GVR_CONTROLLER_BUTTON_CLICK = 1; private const int GVR_CONTROLLER_BUTTON_HOME = 2; private const int GVR_CONTROLLER_BUTTON_APP = 3; private const int GVR_CONTROLLER_BUTTON_VOLUME_UP = 4; private const int GVR_CONTROLLER_BUTTON_VOLUME_DOWN = 5; private const int GVR_CONTROLLER_BUTTON_RESERVED0 = 6; private const int GVR_CONTROLLER_BUTTON_RESERVED1 = 7; private const int GVR_CONTROLLER_BUTTON_RESERVED2 = 8; private const int GVR_CONTROLLER_BUTTON_COUNT = 9; // enum gvr_controller_connection_state: private const int GVR_CONTROLLER_DISCONNECTED = 0; private const int GVR_CONTROLLER_SCANNING = 1; private const int GVR_CONTROLLER_CONNECTING = 2; private const int GVR_CONTROLLER_CONNECTED = 3; // enum gvr_controller_api_status private const int GVR_CONTROLLER_API_OK = 0; private const int GVR_CONTROLLER_API_UNSUPPORTED = 1; private const int GVR_CONTROLLER_API_NOT_AUTHORIZED = 2; private const int GVR_CONTROLLER_API_UNAVAILABLE = 3; private const int GVR_CONTROLLER_API_SERVICE_OBSOLETE = 4; private const int GVR_CONTROLLER_API_CLIENT_OBSOLETE = 5; private const int GVR_CONTROLLER_API_MALFUNCTION = 6; // The serialization of button-state used to determine which buttons are being pressed. private readonly GvrControllerButton[] GVR_UNITY_BUTTONS = new GvrControllerButton[] { GvrControllerButton.App, GvrControllerButton.System, GvrControllerButton.TouchPadButton, GvrControllerButton.Reserved0, GvrControllerButton.Reserved1, GvrControllerButton.Reserved2 }; private readonly int[] GVR_BUTTONS = new int[] { GVR_CONTROLLER_BUTTON_APP, GVR_CONTROLLER_BUTTON_HOME, GVR_CONTROLLER_BUTTON_CLICK, GVR_CONTROLLER_BUTTON_RESERVED0, GVR_CONTROLLER_BUTTON_RESERVED1, GVR_CONTROLLER_BUTTON_RESERVED2 }; [StructLayout(LayoutKind.Sequential)] private struct gvr_quat { internal float x; internal float y; internal float z; internal float w; } [StructLayout(LayoutKind.Sequential)] private struct gvr_vec3 { internal float x; internal float y; internal float z; } [StructLayout(LayoutKind.Sequential)] private struct gvr_vec2 { internal float x; internal float y; } private const string dllName = GvrActivityHelper.GVR_DLL_NAME; [DllImport(dllName)] private static extern int gvr_controller_get_default_options(); [DllImport(dllName)] private static extern IntPtr gvr_controller_create_and_init_android( IntPtr jniEnv, IntPtr androidContext, IntPtr classLoader, int options, IntPtr context); [DllImport(dllName)] private static extern void gvr_controller_destroy(ref IntPtr api); [DllImport(dllName)] private static extern void gvr_controller_pause(IntPtr api); [DllImport(dllName)] private static extern void gvr_controller_resume(IntPtr api); [DllImport(dllName)] private static extern IntPtr gvr_controller_state_create(); [DllImport(dllName)] private static extern void gvr_controller_state_destroy(ref IntPtr state); [DllImport(dllName)] private static extern void gvr_controller_state_update(IntPtr api, int flags, IntPtr out_state); [DllImport(dllName)] private static extern int gvr_controller_state_get_api_status(IntPtr state); [DllImport(dllName)] private static extern int gvr_controller_state_get_connection_state(IntPtr state); [DllImport(dllName)] private static extern gvr_quat gvr_controller_state_get_orientation(IntPtr state); [DllImport(dllName)] private static extern gvr_vec3 gvr_controller_state_get_position(IntPtr state); [DllImport(dllName)] private static extern gvr_vec3 gvr_controller_state_get_gyro(IntPtr state); [DllImport(dllName)] private static extern gvr_vec3 gvr_controller_state_get_accel(IntPtr state); [DllImport(dllName)] private static extern byte gvr_controller_state_is_touching(IntPtr state); [DllImport(dllName)] private static extern gvr_vec2 gvr_controller_state_get_touch_pos(IntPtr state); [DllImport(dllName)] private static extern byte gvr_controller_state_get_touch_down(IntPtr state); [DllImport(dllName)] private static extern byte gvr_controller_state_get_touch_up(IntPtr state); [DllImport(dllName)] private static extern byte gvr_controller_state_get_recentered(IntPtr state); [DllImport(dllName)] private static extern byte gvr_controller_state_get_button_state(IntPtr state, int button); [DllImport(dllName)] private static extern byte gvr_controller_state_get_button_down(IntPtr state, int button); [DllImport(dllName)] private static extern byte gvr_controller_state_get_button_up(IntPtr state, int button); [DllImport(dllName)] private static extern long gvr_controller_state_get_last_orientation_timestamp(IntPtr state); [DllImport(dllName)] private static extern long gvr_controller_state_get_last_gyro_timestamp(IntPtr state); [DllImport(dllName)] private static extern long gvr_controller_state_get_last_accel_timestamp(IntPtr state); [DllImport(dllName)] private static extern long gvr_controller_state_get_last_touch_timestamp(IntPtr state); [DllImport(dllName)] private static extern long gvr_controller_state_get_last_button_timestamp(IntPtr state); [DllImport(dllName)] private static extern byte gvr_controller_state_get_battery_charging(IntPtr state); [DllImport(dllName)] private static extern int gvr_controller_state_get_battery_level(IntPtr state); [DllImport(dllName)] private static extern long gvr_controller_state_get_last_battery_timestamp(IntPtr state); [DllImport(dllName)] private static extern int gvr_controller_get_count(IntPtr api); private const string VRCORE_UTILS_CLASS = "com.google.vr.vrcore.base.api.VrCoreUtils"; private IntPtr api; private bool hasBatteryMethods = false; private AndroidJavaObject androidContext; private AndroidJavaObject classLoader; private bool error = false; private string errorDetails = string.Empty; private IntPtr statePtr; private MutablePose3D pose3d = new MutablePose3D(); private GvrControllerButton[] lastButtonsState = new GvrControllerButton[2]; public bool SupportsBatteryStatus { get { return hasBatteryMethods; } } public int MaxControllerCount { get { if (api == IntPtr.Zero) { return 0; } return gvr_controller_get_count(api); } } internal AndroidNativeControllerProvider() { // Debug.Log("Initializing Daydream controller API."); int options = gvr_controller_get_default_options(); options |= GVR_CONTROLLER_ENABLE_ACCEL; options |= GVR_CONTROLLER_ENABLE_GYRO; options |= GVR_CONTROLLER_ENABLE_POSITION; statePtr = gvr_controller_state_create(); // Get a hold of the activity, context and class loader. AndroidJavaObject activity = GvrActivityHelper.GetActivity(); if (activity == null) { error = true; errorDetails = "Failed to get Activity from Unity Player."; return; } androidContext = GvrActivityHelper.GetApplicationContext(activity); if (androidContext == null) { error = true; errorDetails = "Failed to get Android application context from Activity."; return; } classLoader = GetClassLoaderFromActivity(activity); if (classLoader == null) { error = true; errorDetails = "Failed to get class loader from Activity."; return; } // Use IntPtr instead of GetRawObject() so that Unity can shut down gracefully on // Application.Quit(). Note that GetRawObject() is not pinned by the receiver so it's not // cleaned up appropriately on shutdown, which is a known bug in Unity. IntPtr androidContextPtr = AndroidJNI.NewLocalRef(androidContext.GetRawObject()); IntPtr classLoaderPtr = AndroidJNI.NewLocalRef(classLoader.GetRawObject()); Debug.Log ("Creating and initializing GVR API controller object."); api = gvr_controller_create_and_init_android (IntPtr.Zero, androidContextPtr, classLoaderPtr, options, IntPtr.Zero); AndroidJNI.DeleteLocalRef(androidContextPtr); AndroidJNI.DeleteLocalRef(classLoaderPtr); if (IntPtr.Zero == api) { Debug.LogError("Error creating/initializing Daydream controller API."); error = true; errorDetails = "Failed to initialize Daydream controller API."; return; } try { gvr_controller_state_get_battery_charging(statePtr); gvr_controller_state_get_battery_level(statePtr); hasBatteryMethods = true; } catch (EntryPointNotFoundException) { // Older VrCore version. Does not support battery indicator. // Note that controller API is not dynamically loaded as of June 2017 (b/35662043), // so we'll need to support this case indefinitely... } // Debug.Log("GVR API successfully initialized. Now resuming it."); gvr_controller_resume(api); // Debug.Log("GVR API resumed."); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { // Debug.Log("Destroying GVR API structures."); gvr_controller_state_destroy(ref statePtr); gvr_controller_destroy(ref api); if (statePtr != IntPtr.Zero) { Debug.LogError("gvr_controller_state not zeroed after destroy"); } if (api != IntPtr.Zero) { Debug.LogError("gvr_controller_api not zeroed after destroy"); } // Debug.Log("AndroidNativeControllerProvider destroyed."); } } public void ReadState(ControllerState outState, int controller_id) { if (error) { outState.connectionState = GvrConnectionState.Error; outState.apiStatus = GvrControllerApiStatus.Error; outState.errorDetails = errorDetails; return; } if (api == IntPtr.Zero || statePtr == IntPtr.Zero) { Debug.LogError("AndroidNativeControllerProvider used after dispose."); return; } gvr_controller_state_update(api, controller_id, statePtr); outState.connectionState = ConvertConnectionState( gvr_controller_state_get_connection_state(statePtr)); outState.apiStatus = ConvertControllerApiStatus( gvr_controller_state_get_api_status(statePtr)); gvr_quat rawOri = gvr_controller_state_get_orientation(statePtr); gvr_vec3 rawAccel = gvr_controller_state_get_accel(statePtr); gvr_vec3 rawGyro = gvr_controller_state_get_gyro(statePtr); gvr_vec3 rawPos = gvr_controller_state_get_position(statePtr); // Convert GVR API orientation (right-handed) into Unity axis system (left-handed). pose3d.Set(new Vector3(rawPos.x,rawPos.y,rawPos.z), new Quaternion(rawOri.x, rawOri.y, rawOri.z, rawOri.w)); pose3d.SetRightHanded(pose3d.Matrix); outState.orientation = pose3d.Orientation; outState.position = pose3d.Position; // For accelerometer, we have to flip Z because the GVR API has Z pointing backwards // and Unity has Z pointing forward. outState.accel = new Vector3(rawAccel.x, rawAccel.y, -rawAccel.z); // Gyro in GVR represents a right-handed angular velocity about each axis (positive means // clockwise when sighting along axis). Since Unity uses a left-handed system, we flip the // signs to adjust the sign of the rotational velocity (so that positive means // counter-clockwise). In addition, since in Unity the Z axis points forward while GVR // has Z pointing backwards, we flip the Z axis sign again. So the result is that // we should use -X, -Y, +Z: outState.gyro = new Vector3(-rawGyro.x, -rawGyro.y, rawGyro.z); gvr_vec2 touchPos = gvr_controller_state_get_touch_pos(statePtr); outState.touchPos = new Vector2(touchPos.x, touchPos.y); outState.buttonsState = 0; for (int i=0; i("getClassLoader"); if (result == null) { Debug.LogErrorFormat("Failed to get class loader from Activity."); return null; } return result; } private static int GetVrCoreClientApiVersion(AndroidJavaObject activity) { try { AndroidJavaClass utilsClass = new AndroidJavaClass(VRCORE_UTILS_CLASS); int apiVersion = utilsClass.CallStatic("getVrCoreClientApiVersion", activity); // Debug.LogFormat("VrCore client API version: " + apiVersion); return apiVersion; } catch (Exception exc) { // Even though a catch-all block is normally frowned upon, in this case we really // need it because this method has to be robust to unpredictable circumstances: // VrCore might not exist in the device, the Java layer might be broken, etc, etc. // None of those should abort the app. Debug.LogError("Error obtaining VrCore client API version: " + exc); return 0; } } } } /// @endcond #endif // UNITY_ANDROID && !UNITY_EDITOR