diff --git a/Assets/Zapic/Android.meta b/Assets/Zapic/Android.meta new file mode 100644 index 0000000..3782e12 --- /dev/null +++ b/Assets/Zapic/Android.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 17900b8270f67eb4f94abb41faaa7b30 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Zapic/Android/ZapicAndroidAuthenticationHandler.cs b/Assets/Zapic/Android/ZapicAndroidAuthenticationHandler.cs new file mode 100644 index 0000000..094e035 --- /dev/null +++ b/Assets/Zapic/Android/ZapicAndroidAuthenticationHandler.cs @@ -0,0 +1,135 @@ +#if !UNITY_EDITOR && UNITY_ANDROID + +using System; +using UnityEngine; + +namespace ZapicSDK +{ + /// An implementation of the ZapicPlayerAuthenticationHandler Java interface. + internal sealed class ZapicAndroidAuthenticationHandler : AndroidJavaProxy + { + /// + /// Initializes a new instance of the class. + /// + internal ZapicAndroidAuthenticationHandler() + : base("com/zapic/sdk/android/ZapicPlayerAuthenticationHandler") + { + } + + /// + /// Gets or sets the callback invoked after the player has been logged in. + /// The player that has been logged in is passed to the callback. + /// + public Action OnLogin { get; set; } + + /// + /// Gets or sets the callback invoked after the player has been logged out. + /// The player that has been logged out is passed to the callback. + /// + public Action OnLogout { get; set; } + + /// Invoked after the player has logged in. + /// The current player. + public void onLogin(AndroidJavaObject playerObject) + { + try + { + var handler = OnLogin; + if (handler == null) + { + return; + } + + ZapicPlayer player; + try + { + player = ZapicAndroidUtilities.ConvertPlayer(playerObject); + } + catch (Exception e) + { + Debug.LogError("Zapic: An error occurred converting the Java object to ZapicPlayer"); + Debug.LogException(e); + return; + } + finally + { + if (playerObject != null) + { + playerObject.Dispose(); + playerObject = null; + } + } + + try + { + handler(player); + } + catch (Exception e) + { + Debug.LogError("Zapic: An error occurred invoking the application callback"); + Debug.LogException(e); + } + } + finally + { + if (playerObject != null) + { + playerObject.Dispose(); + } + } + } + + /// Invoked after the player has logged out. + /// The previous player. + public void onLogout(AndroidJavaObject playerObject) + { + try + { + var handler = OnLogout; + if (handler == null) + { + return; + } + + ZapicPlayer player; + try + { + player = ZapicAndroidUtilities.ConvertPlayer(playerObject); + } + catch (Exception e) + { + Debug.LogError("Zapic: An error occurred converting the Java object to ZapicPlayer"); + Debug.LogException(e); + return; + } + finally + { + if (playerObject != null) + { + playerObject.Dispose(); + playerObject = null; + } + } + + try + { + handler(player); + } + catch (Exception e) + { + Debug.LogError("Zapic: An error occurred invoking the application callback"); + Debug.LogException(e); + } + } + finally + { + if (playerObject != null) + { + playerObject.Dispose(); + } + } + } + } +} + +#endif diff --git a/Assets/Zapic/ZapicPlayType.cs.meta b/Assets/Zapic/Android/ZapicAndroidAuthenticationHandler.cs.meta similarity index 83% rename from Assets/Zapic/ZapicPlayType.cs.meta rename to Assets/Zapic/Android/ZapicAndroidAuthenticationHandler.cs.meta index 10b25f3..c7da634 100644 --- a/Assets/Zapic/ZapicPlayType.cs.meta +++ b/Assets/Zapic/Android/ZapicAndroidAuthenticationHandler.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: f8d8857a98f274eaf93fdb0af1d8b02e +guid: 4899efc78318d0340894b81306029e86 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Zapic/Android/ZapicAndroidFunctionCallback{T}.cs b/Assets/Zapic/Android/ZapicAndroidFunctionCallback{T}.cs new file mode 100644 index 0000000..0c6eb38 --- /dev/null +++ b/Assets/Zapic/Android/ZapicAndroidFunctionCallback{T}.cs @@ -0,0 +1,170 @@ +#if !UNITY_EDITOR && UNITY_ANDROID + +using System; +using UnityEngine; + +namespace ZapicSDK +{ + /// A delegate-based implementation of the ZapicCallback Java interface. + /// The type of the result. + internal sealed class ZapicAndroidFunctionCallback : AndroidJavaProxy + where T : class + { + /// The callback invoked with the result of the asynchronous operation. + private readonly Action callback; + + /// The callback invoked to convert the result of the asynchronous operation. + private readonly Func convertResult; + + /// Initializes a new instance of the class. + /// The callback invoked with the result of the asynchronous operation. + /// + /// The callback invoked to convert the result of the asynchronous operation. + /// + /// + /// If or are null. + /// + internal ZapicAndroidFunctionCallback( + Action callback, + Func convertResult) + : base("com/zapic/sdk/android/ZapicCallback") + { + if (callback == null) + { + throw new ArgumentNullException("callback"); + } + + if (convertResult == null) + { + throw new ArgumentNullException("convertResult"); + } + + this.callback = callback; + this.convertResult = convertResult; + } + + /// Called when the asynchronous operation is complete. + /// The result of the asynchronous operation. + /// + /// The error thrown by the asynchronous operation. This will be {@code null} if the asynchronous operation + /// completed normally. + /// + public void onComplete(AndroidJavaObject resultObject, AndroidJavaObject errorObject) + { + try + { + ZapicException error; + try + { + error = ZapicAndroidUtilities.ConvertError(errorObject); + } + catch (Exception e) + { + Debug.LogError("Zapic: An error occurred converting the Java object to ZapicException"); + Debug.LogException(e); + + error = e as ZapicException; + if (error == null) + { + error = new ZapicException( + ZapicErrorCode.INVALID_RESPONSE, + "An error occurred converting the Java object to ZapicException", + e); + } + } + finally + { + if (errorObject != null) + { + errorObject.Dispose(); + errorObject = null; + } + } + + if (error != null) + { + if (resultObject != null) + { + resultObject.Dispose(); + resultObject = null; + } + + try + { + callback(null, error); + } + catch (Exception e) + { + Debug.LogError("Zapic: An error occurred invoking the application callback"); + Debug.LogException(e); + } + + return; + } + + T result; + try + { + result = convertResult(resultObject); + } + catch (Exception e) + { + Debug.LogError("Zapic: An error occurred converting the Java object to " + typeof(T).Name); + Debug.LogException(e); + + error = e as ZapicException; + if (error == null) + { + error = new ZapicException( + ZapicErrorCode.INVALID_RESPONSE, + "An error occurred converting the Java object to " + typeof(T).Name, + e); + } + + result = null; + } + finally + { + if (resultObject != null) + { + resultObject.Dispose(); + resultObject = null; + } + } + + try + { + if (error != null) + { + callback(null, error); + } + else + { + callback(result, null); + } + } + catch (Exception e) + { + Debug.LogError("Zapic: An error occurred invoking the application callback"); + Debug.LogException(e); + } + } + finally + { + if (errorObject != null) + { + errorObject.Dispose(); + } + + if (resultObject != null) + { + resultObject.Dispose(); + } + + javaInterface.Dispose(); + } + } + } +} + +#endif diff --git a/Assets/Zapic/Android/ZapicAndroidFunctionCallback{T}.cs.meta b/Assets/Zapic/Android/ZapicAndroidFunctionCallback{T}.cs.meta new file mode 100644 index 0000000..263225d --- /dev/null +++ b/Assets/Zapic/Android/ZapicAndroidFunctionCallback{T}.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 267976c06cdf93248b806b05ce6bace3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Zapic/Android/ZapicAndroidInterface.cs b/Assets/Zapic/Android/ZapicAndroidInterface.cs new file mode 100644 index 0000000..e2fc6fd --- /dev/null +++ b/Assets/Zapic/Android/ZapicAndroidInterface.cs @@ -0,0 +1,255 @@ +#if !UNITY_EDITOR && UNITY_ANDROID + +using System; +using System.Collections.Generic; +using UnityEngine; + +#if NET_4_6 || NET_STANDARD_2_0 +using System.Threading; +using System.Threading.Tasks; +#endif + +namespace ZapicSDK +{ + /// An that communicates with the Android SDK. + internal sealed class ZapicAndroidInterface : IZapicInterface + { + /// The authentication handler registered with the Android SDK. + private readonly ZapicAndroidAuthenticationHandler authenticationHandler; + + /// A value indicating whether the Android SDK has been initialized. + private bool initialized; + + /// Initializes a new instance of the class. + internal ZapicAndroidInterface() + { + authenticationHandler = new ZapicAndroidAuthenticationHandler(); + initialized = false; + } + + public Action OnLogin { get; set; } + + public Action OnLogout { get; set; } + + public void GetChallenges(Action callback) + { + Get("getChallenges", callback, ZapicAndroidUtilities.ConvertChallenges); + } + +#if NET_4_6 || NET_STANDARD_2_0 + public Task GetChallengesAsync(CancellationToken cancellationToken) + { + return GetAsync("getChallenges", ZapicAndroidUtilities.ConvertChallenges, cancellationToken); + } +#endif + + public void GetCompetitions(Action callback) + { + Get("getCompetitions", callback, ZapicAndroidUtilities.ConvertCompetitions); + } + +#if NET_4_6 || NET_STANDARD_2_0 + public Task GetCompetitionsAsync(CancellationToken cancellationToken) + { + return GetAsync("getCompetitions", ZapicAndroidUtilities.ConvertCompetitions, cancellationToken); + } +#endif + + public void GetPlayer(Action callback) + { + Get("getPlayer", callback, ZapicAndroidUtilities.ConvertPlayer); + } + +#if NET_4_6 || NET_STANDARD_2_0 + public Task GetPlayerAsync(CancellationToken cancellationToken) + { + return GetAsync("getPlayer", ZapicAndroidUtilities.ConvertPlayer, cancellationToken); + } +#endif + + public void GetStatistics(Action callback) + { + Get("getStatistics", callback, ZapicAndroidUtilities.ConvertStatistics); + } + +#if NET_4_6 || NET_STANDARD_2_0 + public Task GetStatisticsAsync(CancellationToken cancellationToken) + { + return GetAsync("getStatistics", ZapicAndroidUtilities.ConvertStatistics, cancellationToken); + } +#endif + + public void HandleInteraction(Dictionary parameters) + { + var json = MiniJSON.Json.Serialize(parameters); + + using (var parametersObject = new AndroidJavaObject("java/lang/String", json)) + using (var zapicClass = new AndroidJavaClass("com/zapic/sdk/android/Zapic")) + { + zapicClass.CallStatic("handleInteraction", parametersObject); + } + } + + [Obsolete] + public ZapicPlayer Player() + { + using (var zapicClass = new AndroidJavaClass("com/zapic/sdk/android/Zapic")) + using (var playerObject = zapicClass.CallStatic("getCurrentPlayer")) + { + try + { + var player = ZapicAndroidUtilities.ConvertPlayer(playerObject); + return player; + } + catch (Exception e) + { + Debug.LogError("Zapic: An error occurred converting the Java object to ZapicPlayer"); + Debug.LogException(e); + + if (e is ZapicException) + { + throw; + } + + throw new ZapicException( + ZapicErrorCode.INVALID_RESPONSE, + "An error occurred converting the Java object to ZapicPlayer", + e); + } + } + } + + public void ShowDefaultPage() + { + using (var unityPlayerClass = new AndroidJavaClass("com/unity3d/player/UnityPlayer")) + using (var activityObject = unityPlayerClass.GetStatic("currentActivity")) + using (var zapicClass = new AndroidJavaClass("com/zapic/sdk/android/Zapic")) + { + zapicClass.CallStatic("showDefaultPage", activityObject); + } + } + + public void ShowPage(ZapicPage page) + { + using (var unityPlayerClass = new AndroidJavaClass("com/unity3d/player/UnityPlayer")) + using (var activityObject = unityPlayerClass.GetStatic("currentActivity")) + using (var zapicClass = new AndroidJavaClass("com/zapic/sdk/android/Zapic")) + using (var pageObject = new AndroidJavaObject("java/lang/String", page.ToString().ToLowerInvariant())) + { + zapicClass.CallStatic("showPage", activityObject, pageObject); + } + } + + public void Start() + { + if (initialized) + { + return; + } + + using (var unityPlayerClass = new AndroidJavaClass("com/unity3d/player/UnityPlayer")) + using (var activityObject = unityPlayerClass.GetStatic("currentActivity")) + { + activityObject.Call("runOnUiThread", new AndroidJavaRunnable(StartOnUI)); + } + } + + public void SubmitEvent(Dictionary parameters) + { + var json = MiniJSON.Json.Serialize(parameters); + + using (var parametersObject = new AndroidJavaObject("java/lang/String", json)) + using (var zapicClass = new AndroidJavaClass("com/zapic/sdk/android/Zapic")) + { + zapicClass.CallStatic("submitEvent", parametersObject); + } + } + + private void Get( + string methodName, + Action callback, + Func convertResult) + where T : class + { + var callbackWrapper = new ZapicAndroidFunctionCallback(callback, convertResult); + + using (var zapicClass = new AndroidJavaClass("com/zapic/sdk/android/Zapic")) + { + var future = zapicClass.CallStatic(methodName, callbackWrapper); + if (future != null) + { + future.Dispose(); + } + } + } + +#if NET_4_6 || NET_STANDARD_2_0 + private Task GetAsync( + string methodName, + Func convertResult, + CancellationToken cancellationToken) + where T : class + { + var taskCompletionSource = new TaskCompletionSource(); + var callbackWrapper = new ZapicAndroidTaskCallback(taskCompletionSource, convertResult); + + using (var zapicClass = new AndroidJavaClass("com/zapic/sdk/android/Zapic")) + { + var future = zapicClass.CallStatic(methodName, callbackWrapper); + if (future != null) + { + if (cancellationToken.CanBeCanceled) + { + var registration = cancellationToken.Register(() => + { + if (taskCompletionSource.TrySetCanceled(cancellationToken)) + { + future.Call("cancel", true); + } + }); + + return taskCompletionSource.Task.ContinueWith( + parentTask => + { + registration.Dispose(); + future.Dispose(); + return parentTask; + }, + TaskContinuationOptions.ExecuteSynchronously).Unwrap(); + } + else + { + future.Dispose(); + } + } + + return taskCompletionSource.Task; + } + } +#endif + + private void StartOnUI() + { + if (initialized) + { + return; + } + + // Ensure initialization only runs once. + initialized = true; + + using (var zapicClass = new AndroidJavaClass("com/zapic/sdk/android/Zapic")) + { + zapicClass.CallStatic("setPlayerAuthenticationHandler", authenticationHandler); + + using (var unityPlayerClass = new AndroidJavaClass("com/unity3d/player/UnityPlayer")) + using (var activityObject = unityPlayerClass.GetStatic("currentActivity")) + { + zapicClass.CallStatic("attachFragment", activityObject); + } + } + } + } +} + +#endif diff --git a/Assets/Zapic/ZapicAndroidInterface.cs.meta b/Assets/Zapic/Android/ZapicAndroidInterface.cs.meta similarity index 100% rename from Assets/Zapic/ZapicAndroidInterface.cs.meta rename to Assets/Zapic/Android/ZapicAndroidInterface.cs.meta diff --git a/Assets/Zapic/Android/ZapicAndroidLog.cs b/Assets/Zapic/Android/ZapicAndroidLog.cs new file mode 100644 index 0000000..83ed39e --- /dev/null +++ b/Assets/Zapic/Android/ZapicAndroidLog.cs @@ -0,0 +1,30 @@ +#if !UNITY_EDITOR && UNITY_ANDROID + +using UnityEngine; + +namespace ZapicSDK +{ + /// Provides static methods to enable or disable verbose SDK logging. + internal static class ZapicAndroidLog + { + /// Disables verbose SDK logging. + internal static void Disable() + { + using (var zapicLogClass = new AndroidJavaClass("com/zapic/sdk/android/ZapicLog")) + { + zapicLogClass.CallStatic("setLogLevel", 7); + } + } + + /// Enables verbose SDK logging. + internal static void Enable() + { + using (var zapicLogClass = new AndroidJavaClass("com/zapic/sdk/android/ZapicLog")) + { + zapicLogClass.CallStatic("setLogLevel", 2); + } + } + } +} + +#endif diff --git a/Assets/Zapic/Android/ZapicAndroidLog.cs.meta b/Assets/Zapic/Android/ZapicAndroidLog.cs.meta new file mode 100644 index 0000000..ee7e543 --- /dev/null +++ b/Assets/Zapic/Android/ZapicAndroidLog.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9082889e8c4e5a541a142e2f88d00906 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Zapic/Android/ZapicAndroidTaskCallback{T}.cs b/Assets/Zapic/Android/ZapicAndroidTaskCallback{T}.cs new file mode 100644 index 0000000..66b7001 --- /dev/null +++ b/Assets/Zapic/Android/ZapicAndroidTaskCallback{T}.cs @@ -0,0 +1,154 @@ +#if !UNITY_EDITOR && UNITY_ANDROID && (NET_4_6 || NET_STANDARD_2_0) + +using System; +using System.Threading.Tasks; +using UnityEngine; + +namespace ZapicSDK +{ + /// A -based implementation of the ZapicCallback Java interface. + /// The type of the result. + internal sealed class ZapicAndroidTaskCallback : AndroidJavaProxy + where T : class + { + /// The callback invoked to convert the result of the asynchronous operation. + private readonly Func convertResult; + + /// The task completion source to receive the result of the asynchronous operation. + private readonly TaskCompletionSource task; + + /// Initializes a new instance of the class. + /// + /// The task completion source to receive the result of the asynchronous operation. + /// + /// + /// The callback invoked to convert the result of the asynchronous operation. + /// + /// + /// If or are null. + /// + internal ZapicAndroidTaskCallback(TaskCompletionSource task, Func convertResult) + : base("com/zapic/sdk/android/ZapicCallback") + { + if (task == null) + { + throw new ArgumentNullException("task"); + } + + if (convertResult == null) + { + throw new ArgumentNullException("convertResult"); + } + + this.convertResult = convertResult; + this.task = task; + } + + /// Called when the asynchronous operation is complete. + /// The result of the asynchronous operation. + /// + /// The error thrown by the asynchronous operation. This will be {@code null} if the asynchronous operation + /// completed normally. + /// + public void onComplete(AndroidJavaObject resultObject, AndroidJavaObject errorObject) + { + try + { + ZapicException error; + try + { + error = ZapicAndroidUtilities.ConvertError(errorObject); + } + catch (Exception e) + { + Debug.LogError("Zapic: An error occurred converting the Java object to ZapicException"); + Debug.LogException(e); + + error = e as ZapicException; + if (error == null) + { + error = new ZapicException( + ZapicErrorCode.INVALID_RESPONSE, + "An error occurred converting the Java object to ZapicException", + e); + } + } + finally + { + if (errorObject != null) + { + errorObject.Dispose(); + errorObject = null; + } + } + + if (error != null) + { + if (resultObject != null) + { + resultObject.Dispose(); + resultObject = null; + } + + task.TrySetException(error); + return; + } + + T result; + try + { + result = convertResult(resultObject); + } + catch (Exception e) + { + Debug.LogError("Zapic: An error occurred converting the Java object to " + typeof(T).Name); + Debug.LogException(e); + + error = e as ZapicException; + if (error == null) + { + error = new ZapicException( + ZapicErrorCode.INVALID_RESPONSE, + "An error occurred converting the Java object to " + typeof(T).Name, + e); + } + + result = null; + } + finally + { + if (resultObject != null) + { + resultObject.Dispose(); + resultObject = null; + } + } + + if (error != null) + { + task.TrySetException(error); + } + else + { + task.TrySetResult(result); + } + } + finally + { + if (errorObject != null) + { + errorObject.Dispose(); + } + + if (resultObject != null) + { + resultObject.Dispose(); + } + + javaInterface.Dispose(); + } + } + } +} + +#endif diff --git a/Assets/Zapic/Android/ZapicAndroidTaskCallback{T}.cs.meta b/Assets/Zapic/Android/ZapicAndroidTaskCallback{T}.cs.meta new file mode 100644 index 0000000..72c4b25 --- /dev/null +++ b/Assets/Zapic/Android/ZapicAndroidTaskCallback{T}.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1e0dff69044788248bac2400bdee3985 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Zapic/Android/ZapicAndroidUtilities.cs b/Assets/Zapic/Android/ZapicAndroidUtilities.cs new file mode 100644 index 0000000..2cd38f9 --- /dev/null +++ b/Assets/Zapic/Android/ZapicAndroidUtilities.cs @@ -0,0 +1,562 @@ +#if !UNITY_EDITOR && UNITY_ANDROID + +using System; +using UnityEngine; + +namespace ZapicSDK +{ + /// Provides utility methods to convert the Java class instances to C# class instances. + internal static class ZapicAndroidUtilities + { + /// + /// Converts a ZapicChallenge Java class instance to a instance. + /// + /// The ZapicChallenge Java class instance. + /// The converted instance or null. + /// If an error occurs converting the Java class instance. + internal static ZapicChallenge ConvertChallenge(AndroidJavaObject challengeObject) + { + try + { + if (challengeObject == null || challengeObject.GetRawObject() == IntPtr.Zero) + { + return null; + } + + // active + var active = challengeObject.Get("active"); + + // description + var description = challengeObject.Get("description"); + + // end.getTime() + DateTime end; + using (var endObject = challengeObject.Get("end")) + { + if (endObject == null || endObject.GetRawObject() == IntPtr.Zero) + { + throw new ZapicException( + ZapicErrorCode.INVALID_RESPONSE, + "The end date and time must not be null"); + } + +#if NET_4_6 || NET_STANDARD_2_0 + end = DateTimeOffset.FromUnixTimeMilliseconds(endObject.Call("getTime")).UtcDateTime; +#else + end = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + + TimeSpan.FromMilliseconds(endObject.Call("getTime")); +#endif + } + + // formattedScore + var formattedScore = challengeObject.Get("formattedScore"); + + // id + var id = challengeObject.Get("id"); + + // metadata + var metadata = challengeObject.Get("metadata"); + + // rank.longValue() + long? rank; + using (var rankObject = challengeObject.Get("rank")) + { + rank = rankObject == null || rankObject.GetRawObject() == IntPtr.Zero + ? (long?)null + : rankObject.Call("longValue"); + } + + // score.doubleValue() + double? score; + using (var scoreObject = challengeObject.Get("score")) + { + score = scoreObject == null || scoreObject.GetRawObject() == IntPtr.Zero + ? (double?)null + : scoreObject.Call("doubleValue"); + } + + // start.getTime() + DateTime start; + using (var startObject = challengeObject.Get("start")) + { + if (startObject == null || startObject.GetRawObject() == IntPtr.Zero) + { + throw new ZapicException( + ZapicErrorCode.INVALID_RESPONSE, + "The start date and time must not be null"); + } + +#if NET_4_6 || NET_STANDARD_2_0 + start = DateTimeOffset.FromUnixTimeMilliseconds(startObject.Call("getTime")).UtcDateTime; +#else + start = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + + TimeSpan.FromMilliseconds(startObject.Call("getTime")); +#endif + } + + // status + var status = (ZapicChallengeStatus)challengeObject.Get("status"); + + // title + var title = challengeObject.Get("title"); + + // totalUsers + var totalUsers = challengeObject.Get("totalUsers"); + + return new ZapicChallenge( + id, + title, + description, + metadata, + active, + start, + end, + totalUsers, + status, + score, + formattedScore, + rank); + } + catch (Exception e) + { + throw new ZapicException( + ZapicErrorCode.INVALID_RESPONSE, + "An error occurred converting the challenge", + e); + } + } + + /// + /// Converts an array of ZapicChallenge Java class instances to an array of + /// instances. + /// + /// The array of ZapicChallenge Java class instances. + /// The converted array of instances or null. + /// If an error occurs converting the Java class instances. + internal static ZapicChallenge[] ConvertChallenges(AndroidJavaObject arrayObject) + { + if (arrayObject == null) + { + return null; + } + + AndroidJavaObject[] itemObjects; + try + { + var arrayPointer = arrayObject.GetRawObject(); + itemObjects = arrayPointer == IntPtr.Zero + ? null + : AndroidJNIHelper.ConvertFromJNIArray(arrayPointer); + if (itemObjects == null) + { + return null; + } + } + catch (Exception e) + { + throw new ZapicException( + ZapicErrorCode.INVALID_RESPONSE, + "An error occurred converting the array of challenges", + e); + } + + var challenges = new ZapicChallenge[itemObjects.Length]; + for (var i = 0; i < itemObjects.Length; i++) + { + try + { + var itemObject = itemObjects[i]; + challenges[i] = ConvertChallenge(itemObject); + itemObject.Dispose(); + } + catch + { + for (var j = i; j < itemObjects.Length; j++) + { + itemObjects[j].Dispose(); + } + + throw; + } + } + + return challenges; + } + + /// + /// Converts a ZapicCompetition Java class instance to a instance. + /// + /// The ZapicCompetition Java class instance. + /// The converted instance or null. + /// If an error occurs converting the Java class instance. + internal static ZapicCompetition ConvertCompetition(AndroidJavaObject competitionObject) + { + try + { + if (competitionObject == null || competitionObject.GetRawObject() == IntPtr.Zero) + { + return null; + } + + // active + var active = competitionObject.Get("active"); + + // description + var description = competitionObject.Get("description"); + + // end.getTime() + DateTime end; + using (var endObject = competitionObject.Get("end")) + { + if (endObject == null || endObject.GetRawObject() == IntPtr.Zero) + { + throw new ZapicException( + ZapicErrorCode.INVALID_RESPONSE, + "The end date and time must not be null"); + } + +#if NET_4_6 || NET_STANDARD_2_0 + end = DateTimeOffset.FromUnixTimeMilliseconds(endObject.Call("getTime")).UtcDateTime; +#else + end = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + + TimeSpan.FromMilliseconds(endObject.Call("getTime")); +#endif + } + + // formattedScore + var formattedScore = competitionObject.Get("formattedScore"); + + // id + var id = competitionObject.Get("id"); + + // leaderboardRank.longValue() + long? leaderboardRank; + using (var leaderboardRankObject = competitionObject.Get("leaderboardRank")) + { + leaderboardRank = leaderboardRankObject == null || leaderboardRankObject.GetRawObject() == IntPtr.Zero + ? (long?)null + : leaderboardRankObject.Call("longValue"); + } + + // leagueRank.longValue() + long? leagueRank; + using (var leagueRankObject = competitionObject.Get("leagueRank")) + { + leagueRank = leagueRankObject == null || leagueRankObject.GetRawObject() == IntPtr.Zero + ? (long?)null + : leagueRankObject.Call("longValue"); + } + + // metadata + var metadata = competitionObject.Get("metadata"); + + // score.doubleValue() + double? score; + using (var scoreObject = competitionObject.Get("score")) + { + score = scoreObject == null || scoreObject.GetRawObject() == IntPtr.Zero + ? (double?)null + : scoreObject.Call("doubleValue"); + } + + // start.getTime() + DateTime start; + using (var startObject = competitionObject.Get("start")) + { + if (startObject == null || startObject.GetRawObject() == IntPtr.Zero) + { + throw new ZapicException( + ZapicErrorCode.INVALID_RESPONSE, + "The start date and time must not be null"); + } + +#if NET_4_6 || NET_STANDARD_2_0 + start = DateTimeOffset.FromUnixTimeMilliseconds(startObject.Call("getTime")).UtcDateTime; +#else + start = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + + TimeSpan.FromMilliseconds(startObject.Call("getTime")); +#endif + } + + // status + var status = (ZapicCompetitionStatus)competitionObject.Get("status"); + + // title + var title = competitionObject.Get("title"); + + // totalUsers + var totalUsers = competitionObject.Get("totalUsers"); + + return new ZapicCompetition( + id, + title, + description, + metadata, + active, + start, + end, + totalUsers, + status, + score, + formattedScore, + leaderboardRank, + leagueRank); + } + catch (Exception e) + { + throw new ZapicException( + ZapicErrorCode.INVALID_RESPONSE, + "An error occurred converting the competition", + e); + } + } + + /// + /// Converts an array of ZapicCompetition Java class instances to an array of + /// instances. + /// + /// The array of ZapicCompetition Java class instances. + /// The converted array of instances or null. + /// If an error occurs converting the Java class instances. + internal static ZapicCompetition[] ConvertCompetitions(AndroidJavaObject arrayObject) + { + if (arrayObject == null) + { + return null; + } + + AndroidJavaObject[] itemObjects; + try + { + var arrayPointer = arrayObject.GetRawObject(); + itemObjects = arrayPointer == IntPtr.Zero + ? null + : AndroidJNIHelper.ConvertFromJNIArray(arrayPointer); + if (itemObjects == null) + { + return null; + } + } + catch (Exception e) + { + throw new ZapicException( + ZapicErrorCode.INVALID_RESPONSE, + "An error occurred converting the array of competitions", + e); + } + + var competitions = new ZapicCompetition[itemObjects.Length]; + for (var i = 0; i < itemObjects.Length; i++) + { + try + { + var itemObject = itemObjects[i]; + competitions[i] = ConvertCompetition(itemObject); + itemObject.Dispose(); + } + catch + { + for (var j = i; j < itemObjects.Length; j++) + { + itemObjects[j].Dispose(); + } + + throw; + } + } + + return competitions; + } + + /// + /// Converts a ZapicException Java class instance to a instance. + /// + /// The ZapicException Java class instance. + /// The converted instance or null. + /// If an error occurs converting the Java class instance. + internal static ZapicException ConvertError(AndroidJavaObject errorObject) + { + try + { + if (errorObject == null || errorObject.GetRawObject() == IntPtr.Zero) + { + return null; + } + + // code + var code = errorObject.Get("code"); + + // getMessage() + var message = errorObject.Call("getMessage"); + + return new ZapicException(code, message); + } + catch (Exception e) + { + throw new ZapicException( + ZapicErrorCode.INVALID_RESPONSE, + "An error occurred converting the exception", + e); + } + } + + /// + /// Converts a ZapicPlayer Java class instance to a instance. + /// + /// The ZapicPlayer Java class instance. + /// The converted instance or null. + /// If an error occurs converting the Java class instance. + internal static ZapicPlayer ConvertPlayer(AndroidJavaObject playerObject) + { + try + { + if (playerObject == null || playerObject.GetRawObject() == IntPtr.Zero) + { + return null; + } + + // iconUrl.toString() + Uri iconUrl; + using (var iconUrlObject = playerObject.Get("iconUrl")) + { + if (iconUrlObject == null || iconUrlObject.GetRawObject() == IntPtr.Zero) + { + throw new ZapicException(ZapicErrorCode.INVALID_RESPONSE, "The icon URL must not be null"); + } + + iconUrl = new Uri(iconUrlObject.Call("toString")); + } + + // id + var id = playerObject.Get("id"); + + // name + var name = playerObject.Get("name"); + + // notificationToken + var notificationToken = playerObject.Get("notificationToken"); + + return new ZapicPlayer(id, name, iconUrl, notificationToken); + } + catch (Exception e) + { + throw new ZapicException(ZapicErrorCode.INVALID_RESPONSE, "An error occurred converting the player", e); + } + } + + /// + /// Converts a ZapicStatistic Java class instance to a instance. + /// + /// The ZapicStatistic Java class instance. + /// The converted instance or null. + /// If an error occurs converting the Java class instance. + internal static ZapicStatistic ConvertStatistic(AndroidJavaObject statisticObject) + { + try + { + if (statisticObject == null || statisticObject.GetRawObject() == IntPtr.Zero) + { + return null; + } + + // formattedScore + var formattedScore = statisticObject.Get("formattedScore"); + + // id + var id = statisticObject.Get("id"); + + // metadata + var metadata = statisticObject.Get("metadata"); + + // percentile.intValue() + int? percentile; + using (var percentileObject = statisticObject.Get("percentile")) + { + percentile = percentileObject == null || percentileObject.GetRawObject() == IntPtr.Zero + ? (int?)null + : percentileObject.Call("intValue"); + } + + // score.doubleValue() + double? score; + using (var scoreObject = statisticObject.Get("score")) + { + score = scoreObject == null || scoreObject.GetRawObject() == IntPtr.Zero + ? (double?)null + : scoreObject.Call("doubleValue"); + } + + // title + var title = statisticObject.Get("title"); + + return new ZapicStatistic(id, title, metadata, score, formattedScore, percentile); + } + catch (Exception e) + { + throw new ZapicException( + ZapicErrorCode.INVALID_RESPONSE, + "An error occurred converting the statistic", + e); + } + } + + /// + /// Converts an array of ZapicStatistic Java class instances to an array of + /// instances. + /// + /// The array of ZapicStatistic Java class instances. + /// The converted array of instances or null. + /// If an error occurs converting the Java class instances. + internal static ZapicStatistic[] ConvertStatistics(AndroidJavaObject arrayObject) + { + if (arrayObject == null) + { + return null; + } + + AndroidJavaObject[] itemObjects; + try + { + var arrayPointer = arrayObject.GetRawObject(); + itemObjects = arrayPointer == IntPtr.Zero + ? null + : AndroidJNIHelper.ConvertFromJNIArray(arrayPointer); + if (itemObjects == null) + { + return null; + } + } + catch (Exception e) + { + throw new ZapicException( + ZapicErrorCode.INVALID_RESPONSE, + "An error occurred converting the array of statistics", + e); + } + + var statistics = new ZapicStatistic[itemObjects.Length]; + for (var i = 0; i < itemObjects.Length; i++) + { + try + { + var itemObject = itemObjects[i]; + statistics[i] = ConvertStatistic(itemObject); + itemObject.Dispose(); + } + catch + { + for (var j = i; j < itemObjects.Length; j++) + { + itemObjects[j].Dispose(); + } + + throw; + } + } + + return statistics; + } + } +} + +#endif diff --git a/Assets/Zapic/Android/ZapicAndroidUtilities.cs.meta b/Assets/Zapic/Android/ZapicAndroidUtilities.cs.meta new file mode 100644 index 0000000..d9e58d6 --- /dev/null +++ b/Assets/Zapic/Android/ZapicAndroidUtilities.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9a81ad279fe91f44bbb5d38e2ad9bb72 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Zapic/Editor/ZapicDependencies.xml b/Assets/Zapic/Editor/ZapicDependencies.xml index da2e7a0..f680dee 100644 --- a/Assets/Zapic/Editor/ZapicDependencies.xml +++ b/Assets/Zapic/Editor/ZapicDependencies.xml @@ -1,5 +1,5 @@ - + diff --git a/Assets/Zapic/ZapicAndroidInterface.cs b/Assets/Zapic/ZapicAndroidInterface.cs deleted file mode 100644 index a0d75be..0000000 --- a/Assets/Zapic/ZapicAndroidInterface.cs +++ /dev/null @@ -1,323 +0,0 @@ -using System; -using System.Collections.Generic; -using UnityEngine; - -namespace ZapicSDK -{ - internal sealed class ZapicAndroidInterface : IZapicInterface - { - private ZapicPlayerAuthenticationHandler _authenticationHandler; - - internal ZapicAndroidInterface() - { - _authenticationHandler = null; - } - - public Action OnLogin { get; set; } - - public Action OnLogout { get; set; } - - public void Start() - { - var unityPlayerClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); - var gameActivityObject = unityPlayerClass.GetStatic("currentActivity"); - gameActivityObject.Call("runOnUiThread", new AndroidJavaRunnable(StartOnUI)); - } - - private void StartOnUI() - { - using(var zapicClass = new AndroidJavaClass("com.zapic.sdk.android.Zapic")) - { - using(var unityPlayerClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) - using(var gameActivityObject = unityPlayerClass.GetStatic("currentActivity")) - { - var methodId = AndroidJNI.GetStaticMethodID( - zapicClass.GetRawClass(), - "attachFragment", - "(Landroid/app/Activity;)V"); - var objectArray = new object[1]; - var argArray = AndroidJNIHelper.CreateJNIArgArray(objectArray); - try - { - argArray[0].l = gameActivityObject.GetRawObject(); - AndroidJNI.CallStaticVoidMethod(zapicClass.GetRawClass(), methodId, argArray); - } - finally - { - AndroidJNIHelper.DeleteJNIArgArray(objectArray, argArray); - } - } - - if (_authenticationHandler == null) - { - _authenticationHandler = new ZapicPlayerAuthenticationHandler(this); - var methodId = AndroidJNI.GetStaticMethodID( - zapicClass.GetRawClass(), - "setPlayerAuthenticationHandler", - "(Lcom/zapic/sdk/android/ZapicPlayerAuthenticationHandler;)V"); - var objectArray = new object[1]; - var argArray = AndroidJNIHelper.CreateJNIArgArray(objectArray); - try - { - argArray[0].l = AndroidJNIHelper.CreateJavaProxy(_authenticationHandler); - AndroidJNI.CallStaticVoidMethod(zapicClass.GetRawClass(), methodId, argArray); - } - finally - { - AndroidJNIHelper.DeleteJNIArgArray(objectArray, argArray); - } - } - } - } - - public void ShowDefaultPage() - { - using(var unityPlayerClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) - using(var gameActivityObject = unityPlayerClass.GetStatic("currentActivity")) - using(var zapicClass = new AndroidJavaClass("com.zapic.sdk.android.Zapic")) - { - var methodId = AndroidJNI.GetStaticMethodID( - zapicClass.GetRawClass(), - "showDefaultPage", - "(Landroid/app/Activity;)V"); - var objectArray = new object[1]; - var argArray = AndroidJNIHelper.CreateJNIArgArray(objectArray); - try - { - argArray[0].l = gameActivityObject.GetRawObject(); - AndroidJNI.CallStaticVoidMethod(zapicClass.GetRawClass(), methodId, argArray); - } - finally - { - AndroidJNIHelper.DeleteJNIArgArray(objectArray, argArray); - } - } - } - - public void ShowPage(ZapicPages page) - { - using(var unityPlayerClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) - using(var gameActivityObject = unityPlayerClass.GetStatic("currentActivity")) - using(var pageObject = new AndroidJavaObject("java.lang.String", page.ToString().ToLower())) - using(var zapicClass = new AndroidJavaClass("com.zapic.sdk.android.Zapic")) - { - var methodId = AndroidJNI.GetStaticMethodID( - zapicClass.GetRawClass(), - "showPage", - "(Landroid/app/Activity;Ljava/lang/String;)V"); - var objectArray = new object[2]; - var argArray = AndroidJNIHelper.CreateJNIArgArray(objectArray); - try - { - argArray[0].l = gameActivityObject.GetRawObject(); - argArray[1].l = pageObject.GetRawObject(); - AndroidJNI.CallStaticVoidMethod(zapicClass.GetRawClass(), methodId, argArray); - } - finally - { - AndroidJNIHelper.DeleteJNIArgArray(objectArray, argArray); - } - } - } - - public ZapicPlayer Player() - { - var playerPointer = IntPtr.Zero; - using(var zapicClass = new AndroidJavaClass("com.zapic.sdk.android.Zapic")) - { - var methodId = AndroidJNI.GetStaticMethodID( - zapicClass.GetRawClass(), - "getCurrentPlayer", - "()Lcom/zapic/sdk/android/ZapicPlayer;"); - var objectArray = new object[0]; - var argArray = AndroidJNIHelper.CreateJNIArgArray(objectArray); - try - { - playerPointer = AndroidJNI.CallStaticObjectMethod(zapicClass.GetRawClass(), methodId, argArray); - } - catch (Exception e) - { - if (e.Message.Contains("null")) - { - playerPointer = IntPtr.Zero; - } - else - { - throw; - } - } - finally - { - AndroidJNIHelper.DeleteJNIArgArray(objectArray, argArray); - } - } - - var player = ConvertPlayer(playerPointer); - return player; - } - - public void HandleInteraction(Dictionary data) - { - var json = MiniJSON.Json.Serialize(data); - - using(var parametersObject = new AndroidJavaObject("java.lang.String", json)) - using(var zapicClass = new AndroidJavaClass("com.zapic.sdk.android.Zapic")) - { - var methodId = AndroidJNI.GetStaticMethodID( - zapicClass.GetRawClass(), - "handleInteraction", - "(Ljava/lang/String;)V"); - var objectArray = new object[1]; - var argArray = AndroidJNIHelper.CreateJNIArgArray(objectArray); - try - { - argArray[0].l = parametersObject.GetRawObject(); - AndroidJNI.CallStaticVoidMethod(zapicClass.GetRawClass(), methodId, argArray); - } - finally - { - AndroidJNIHelper.DeleteJNIArgArray(objectArray, argArray); - } - } - } - - public void SubmitEvent(Dictionary param) - { - var json = MiniJSON.Json.Serialize(param); - - using(var parametersObject = new AndroidJavaObject("java.lang.String", json)) - using(var zapicClass = new AndroidJavaClass("com.zapic.sdk.android.Zapic")) - { - var methodId = AndroidJNI.GetStaticMethodID( - zapicClass.GetRawClass(), - "submitEvent", - "(Ljava/lang/String;)V"); - var objectArray = new object[1]; - var argArray = AndroidJNIHelper.CreateJNIArgArray(objectArray); - try - { - argArray[0].l = parametersObject.GetRawObject(); - AndroidJNI.CallStaticVoidMethod(zapicClass.GetRawClass(), methodId, argArray); - } - finally - { - AndroidJNIHelper.DeleteJNIArgArray(objectArray, argArray); - } - } - } - - private static ZapicPlayer ConvertPlayer(IntPtr playerPointer) - { - if (playerPointer == IntPtr.Zero) - { - return null; - } - - string notificationToken; - string playerId; - using(var zapicPlayerClass = new AndroidJavaClass("com.zapic.sdk.android.ZapicPlayer")) - { - var methodId = AndroidJNI.GetMethodID( - zapicPlayerClass.GetRawClass(), - "getNotificationToken", - "()Ljava/lang/String;"); - var objectArray = new object[0]; - var argArray = AndroidJNIHelper.CreateJNIArgArray(objectArray); - try - { - notificationToken = AndroidJNI.CallStringMethod(playerPointer, methodId, argArray); - } - finally - { - AndroidJNIHelper.DeleteJNIArgArray(objectArray, argArray); - } - - methodId = AndroidJNI.GetMethodID( - zapicPlayerClass.GetRawClass(), - "getPlayerId", - "()Ljava/lang/String;"); - objectArray = new object[0]; - argArray = AndroidJNIHelper.CreateJNIArgArray(objectArray); - try - { - playerId = AndroidJNI.CallStringMethod(playerPointer, methodId, argArray); - } - finally - { - AndroidJNIHelper.DeleteJNIArgArray(objectArray, argArray); - } - } - - //TODO: Kyle, use ZapicPlayer constructor - // return new ZapicPlayer - // { - // NotificationToken = notificationToken ?? string.Empty, - // PlayerId = playerId ?? string.Empty, - // }; - return null; - } - - public void GetCompetitions(Action callback) - { - //TODO:Kyle - throw new NotImplementedException(); - } - - public void GetStatistics(Action callback) - { - //TODO:Kyle - throw new NotImplementedException(); - } - - public void GetChallenges(Action callback) - { - //TODO:Kyle - throw new NotImplementedException(); - } - - public void GetPlayer(Action callback) - { - //TODO:Kyle - throw new NotImplementedException(); - } - - private sealed class ZapicPlayerAuthenticationHandler : AndroidJavaProxy, IDisposable - { - private IZapicInterface _zapicInterface; - - public ZapicPlayerAuthenticationHandler(IZapicInterface zapicInterface) : base("com.zapic.sdk.android.ZapicPlayerAuthenticationHandler") - { - _zapicInterface = zapicInterface; - } - - public void Dispose() - { - javaInterface.Dispose(); - } - - public void onLogin(AndroidJavaObject androidPlayer) - { - var handler = _zapicInterface.OnLogin; - if (handler == null) - { - return; - } - - var player = ConvertPlayer(androidPlayer.GetRawObject()); - handler(player); - } - - public void onLogout(AndroidJavaObject androidPlayer) - { - var handler = _zapicInterface.OnLogin; - if (handler == null) - { - return; - } - - var player = ConvertPlayer(androidPlayer.GetRawObject()); - handler(player); - } - } - } -} \ No newline at end of file diff --git a/Assets/Zapic/ZapicLog.cs b/Assets/Zapic/ZapicLog.cs new file mode 100644 index 0000000..a359049 --- /dev/null +++ b/Assets/Zapic/ZapicLog.cs @@ -0,0 +1,34 @@ +#if !UNITY_EDITOR +using ZapicSDK; +#endif + +#if !UNITY_EDITOR && UNITY_IOS +using UnityEngine; +#endif + +/// Provides static methods to enable or disable verbose SDK logging. +/// Added in 1.3.0. +public static class ZapicLog +{ + /// Disables verbose SDK logging. + /// Added in 1.3.0. + public static void Disable() + { +#if !UNITY_EDITOR && UNITY_ANDROID + ZapicAndroidLog.Disable(); +#elif !UNITY_EDITOR && UNITY_IOS + Debug.Log("Zapic: SDK logging is not supported on iOS"); +#endif + } + + /// Enables verbose SDK logging. + /// Added in 1.3.0. + public static void Enable() + { +#if !UNITY_EDITOR && UNITY_ANDROID + ZapicAndroidLog.Enable(); +#elif !UNITY_EDITOR && UNITY_IOS + Debug.Log("Zapic: SDK logging is not supported on iOS"); +#endif + } +} diff --git a/Assets/Zapic/ZapicLog.cs.meta b/Assets/Zapic/ZapicLog.cs.meta new file mode 100644 index 0000000..356d66f --- /dev/null +++ b/Assets/Zapic/ZapicLog.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eee4394db25d2e34987893353dfa3b16 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: