diff --git a/Src/Authorizer.cs b/Src/Authorizer.cs index 0bd48da..4ecf716 100644 --- a/Src/Authorizer.cs +++ b/Src/Authorizer.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Runtime.Serialization.Formatters.Binary; +using System.Threading.Tasks; using System.Timers; namespace EmotivUnityPlugin @@ -17,6 +18,8 @@ public class Authorizer private string _licenseID = ""; private static int _debitNo = 5000; // default value private static double _currentLoginTime = 0; // store current login time + private TaskCompletionSource _getAiDataConsentTcs; // pending GetAIDataConsent request + private TaskCompletionSource _setAiDataConsentTcs; // pending SetAIDataConsent request /// /// Timer for waiting a user login @@ -77,6 +80,8 @@ public Authorizer() _ctxClient.EULANotAccepted += OnEULANotAccepted; _ctxClient.RefreshTokenOK += OnRefreshTokenOK; _ctxClient.GetLicenseInfoDone += OnGetLicenseInfoDone; + _ctxClient.GetAiDataConsentDone += OnGetAiDataConsentDone; + _ctxClient.SetAiDataConsentDone += OnSetAiDataConsentDone; _ctxClient.ErrorMsgReceived += OnErrorMsgReceived; } @@ -101,6 +106,18 @@ private void OnErrorMsgReceived(object sender, ErrorMsgEventArgs errorInfo) UnityEngine.Debug.Log($"OnErrorMsgReceived: Code={errorInfo.Code}, Message={errorInfo.MessageError}, Method={errorInfo.MethodName}"); + // fault pending AI data consent requests so callers awaiting them do not hang + if (errorInfo.MethodName == "getAiDataConsent") + { + _getAiDataConsentTcs?.TrySetException(new Exception(errorInfo.MessageError)); + _getAiDataConsentTcs = null; + } + else if (errorInfo.MethodName == "setAiDataConsent") + { + _setAiDataConsentTcs?.TrySetException(new Exception(errorInfo.MessageError)); + _setAiDataConsentTcs = null; + } + #if UNITY_ANDROID || UNITY_IOS || USE_EMBEDDED_LIB // For mobile and embedded lib platforms bool shouldLogout = false; @@ -204,6 +221,48 @@ private void OnGetLicenseInfoDone(object sender, License lic) } } + /// + /// Request AI data usage consent information for the current logged-in user directly from Cortex, and waits for the response. + /// + public async Task GetAIDataConsent() { + string cortexToken = CortexToken; + if (String.IsNullOrEmpty(cortexToken)) + return null; + // avoid overwriting a pending request's TCS, which would leave its caller awaiting forever + if (_getAiDataConsentTcs != null && !_getAiDataConsentTcs.Task.IsCompleted) + throw new InvalidOperationException("A GetAIDataConsent request is already in progress."); + _getAiDataConsentTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + _ctxClient.GetAiDataConsent(cortexToken); + return await _getAiDataConsentTcs.Task; + } + + private void OnGetAiDataConsentDone(object sender, AIDataConsent consent) + { + UnityEngine.Debug.Log("OnGetAiDataConsentDone: " + consent.Accepted); + _getAiDataConsentTcs?.TrySetResult(consent); + } + + /// + /// Set user's consent to the use of their data for AI training purposes, and waits for the response from Cortex. + /// + public async Task SetAIDataConsent(bool accepted) { + string cortexToken = CortexToken; + if (String.IsNullOrEmpty(cortexToken)) + return null; + // avoid overwriting a pending request's TCS, which would leave its caller awaiting forever + if (_setAiDataConsentTcs != null && !_setAiDataConsentTcs.Task.IsCompleted) + throw new InvalidOperationException("A SetAIDataConsent request is already in progress."); + _setAiDataConsentTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + _ctxClient.SetAiDataConsent(cortexToken, accepted); + return await _setAiDataConsentTcs.Task; + } + + private void OnSetAiDataConsentDone(object sender, AIDataConsent consent) + { + UnityEngine.Debug.Log("OnSetAiDataConsentDone: " + consent.Accepted); + _setAiDataConsentTcs?.TrySetResult(consent); + } + private void OnRefreshTokenOK(object sender, string cortexToken) { UnityEngine.Debug.Log("The cortex token is refreshed successfully."); diff --git a/Src/BCIGameItf.cs b/Src/BCIGameItf.cs index 5df19c3..f09ed6f 100644 --- a/Src/BCIGameItf.cs +++ b/Src/BCIGameItf.cs @@ -355,6 +355,23 @@ public string GetCurrentEmotivId() return emotivUnityItf.GetCurrentEmotivId(); } + /// + /// Requests the AI data usage consent directly from Cortex and waits for the response. + /// + public async Task GetAIDataConsent() + { + return await emotivUnityItf.GetAIDataConsent(); + } + + /// + /// Sets user's consent to the use of their data for AI training purposes, and waits for the response. + /// + /// True to accept, false to decline. + public async Task SetAIDataConsent(bool accepted) + { + return await emotivUnityItf.SetAIDataConsent(accepted); + } + /// /// Get the mental command action sensitivity for the first trained action except neutral (pull action). /// Should be called after training is completed. Default sensitivity is 5 diff --git a/Src/CortexClient.cs b/Src/CortexClient.cs index 32412e5..74fe377 100644 --- a/Src/CortexClient.cs +++ b/Src/CortexClient.cs @@ -61,8 +61,10 @@ public abstract class CortexClient public event EventHandler AccessRightGrantedDone; public event EventHandler AuthorizeOK; public event EventHandler GetUserLoginDone; + public event EventHandler GetAiDataConsentDone; public event EventHandler EULAAccepted; public event EventHandler EULANotAccepted; // return cortexToken if user has not accept eula to proceed next step + public event EventHandler SetAiDataConsentDone; public event EventHandler UserLoginNotify; public event EventHandler UserLogoutNotify; public event EventHandler GetLicenseInfoDone; @@ -331,9 +333,10 @@ private void HandleResponse(string method, JToken data) License lic = new License(data["license"]); GetLicenseInfoDone(this, lic); } - else if (method == "getUserInformation") + else if (method == "getAiDataConsent") { - //TODO + AIDataConsent consent = new AIDataConsent(data["aiDataConsent"]); + GetAiDataConsentDone?.Invoke(this, consent); } else if (method == "authorize") { @@ -357,6 +360,11 @@ private void HandleResponse(string method, JToken data) string message = data["message"].ToString(); EULAAccepted(this, message); } + else if (method == "setAiDataConsent") + { + AIDataConsent consent = new AIDataConsent(data["aiDataConsent"]); + SetAiDataConsentDone?.Invoke(this, consent); + } else if (method == "createSession") { string sessionId = (string)data["id"]; @@ -674,12 +682,23 @@ public void GetLicenseInfo(string cortexToken) ); SendTextMessage(param, "getLicenseInfo", true); } - public void GetUserInformation(string cortexToken) + // get user's consent to the use of their data for AI training purposes + public void GetAiDataConsent(string cortexToken) { JObject param = new JObject( new JProperty("cortexToken", cortexToken) ); - SendTextMessage(param, "getUserInformation", true); + SendTextMessage(param, "getAiDataConsent", true); + } + + // set user's consent to the use of their data for AI training purposes + public void SetAiDataConsent(string cortexToken, bool accepted) + { + JObject param = new JObject( + new JProperty("cortexToken", cortexToken), + new JProperty("accepted", accepted) + ); + SendTextMessage(param, "setAiDataConsent", true); } // Login diff --git a/Src/EmotivUnityItf.cs b/Src/EmotivUnityItf.cs index 5aa00dc..09f62dc 100644 --- a/Src/EmotivUnityItf.cs +++ b/Src/EmotivUnityItf.cs @@ -125,6 +125,22 @@ public string GetCurrentEmotivId() return _authorizer.CurrentEmotivId; } + /// + /// Requests the AI data usage consent directly from Cortex and waits for the response. + /// + public async Task GetAIDataConsent() + { + return await _authorizer.GetAIDataConsent(); + } + + /// + /// Sets user's consent to the use of their data for AI training purposes, and waits for the response. + /// + public async Task SetAIDataConsent(bool accepted) + { + return await _authorizer.SetAIDataConsent(accepted); + } + #if USE_EMBEDDED_LIB || UNITY_ANDROID || UNITY_IOS private CrossPlatformBrowser _crossPlatformBrowser; diff --git a/Src/Types.cs b/Src/Types.cs index 26cd676..1f34c8b 100644 --- a/Src/Types.cs +++ b/Src/Types.cs @@ -369,6 +369,19 @@ public License(JToken licObj) { public int totalDebit = 0; } + public class AIDataConsent + { + public AIDataConsent(JToken consentObj) { + // null means AI Data Consent has not been accepted/declined for the latest policy version + Accepted = consentObj["accepted"] != null && consentObj["accepted"].Type != JTokenType.Null + ? (bool?)consentObj["accepted"] + : null; + Url = consentObj["licenseUrl"] != null ? consentObj["licenseUrl"].ToString() : ""; + } + public bool? Accepted { get; private set; } + public string Url { get; private set; } + } + // contain data and time of data. For example, login time and user login or token and time for token [Serializable()] public class UserDataInfo : ISerializable