#if UNITY_EDITOR using System; using System.IO; using System.Net; using System.Collections.Generic; #elif UNITY_ANDROID using System; using System.Collections.Generic; using UnityEngine.Advertisements.Purchasing; #elif UNITY_IOS using System; using System.Collections.Generic; using System.Globalization; using System.Runtime.InteropServices; using AOT; using UnityEngine.Advertisements.Purchasing; #endif using UnityEngine.Advertisements; using UnityEngine.Advertisements.Utilities; namespace UnityEngine.Monetization { #if UNITY_EDITOR sealed internal class Platform : IMonetizationPlatform { public static Placeholder m_Placeholder; bool isInitialized; Configuration m_Configuration; static string s_BaseUrl = "http://editor-support.unityads.unity3d.com/games"; static string s_Version = "3.7.5"; private UnityLifecycleManager _callbackExecutor; public event EventHandler OnPlacementContentReady; public event EventHandler OnPlacementContentStateChange { add {} remove {} } public event EventHandler onError { add {} remove {} } public string version { get; } = "3.7.5"; public Platform() { var callbackExecutorGameObject = new GameObject("UnityAdsCallbackExecutorObject") { hideFlags = HideFlags.HideAndDontSave | HideFlags.HideInInspector }; _callbackExecutor = new UnityLifecycleManager(); Object.DontDestroyOnLoad(callbackExecutorGameObject); } public void Initialize(string gameId, bool testMode) { var placeHolderGameObject = new GameObject("UnityMonetizationEditorPlaceHolderObject") { hideFlags = HideFlags.HideAndDontSave | HideFlags.HideInInspector }; m_Placeholder = placeHolderGameObject.AddComponent(); string configurationUrl = string.Join("/", new string[] { s_BaseUrl, gameId, string.Join("&", new string[] { "configuration?platform=editor", "unityVersion=" + Uri.EscapeDataString(Application.unityVersion), "sdkVersionName=" + Uri.EscapeDataString(s_Version) }) }); WebRequest request = WebRequest.Create(configurationUrl); request.BeginGetResponse(result => { WebResponse response = request.EndGetResponse(result); var reader = new StreamReader(response.GetResponseStream()); string responseBody = reader.ReadToEnd(); try { m_Configuration = new Configuration(responseBody); if (!m_Configuration.enabled) { Debug.LogWarning("gameId " + gameId + " is not enabled"); } } catch (Exception exception) { Debug.LogError("Failed to parse configuration for gameId: " + gameId); Debug.Log(responseBody); Debug.LogError(exception.Message); } reader.Close(); response.Close(); if (m_Configuration != null) { foreach (KeyValuePair entry in m_Configuration.placementContents) { _callbackExecutor.Post(() => { OnPlacementContentReady?.Invoke(this, new PlacementContentReadyEventArgs(entry.Key, entry.Value)); }); } } }, null); isInitialized = true; UnityEngine.Advertisements.Advertisement.Initialize(gameId, testMode); Debug.Log("UnityMonetizationEditor: Initialize(" + gameId + ", " + testMode + ");"); } public PlacementContent GetPlacementContent(string placementID) { if (m_Configuration != null && m_Configuration.placementContents.ContainsKey(placementID)) { return m_Configuration.placementContents[placementID]; } else { return new ShowAdPlacementContent("fakeId", new EditorShowAdOperations()); } } public INativePromoAdapter CreateNativePromoAdapter(PromoAdPlacementContent placementContent) { return new EditorNativePromoAdapter(placementContent); } public void SetMetaData(MetaData metaData) { } public void SetPurchasingAdapter(IPurchasingAdapter adapter) { } public bool isSupported { get { return Application.isEditor; } } public bool IsReady(string placementID) { return isInitialized && m_Configuration != null; } } #elif UNITY_ANDROID sealed internal class Platform : AndroidJavaProxy, IMonetizationPlatform, IPurchasingEventSender { private static readonly IDictionary PlacementContentTypesMap = new Dictionary { {"SHOW_AD", PlacementContentType.ShowAd}, {"PROMO_AD", PlacementContentType.PromoAd}, {"SINK_PROMO", PlacementContentType.SinkPromo}, {"CUSTOM", PlacementContentType.Custom} }; public event EventHandler OnPlacementContentReady; public event EventHandler OnPlacementContentStateChange; public event EventHandler onError; readonly AndroidJavaObject m_CurrentActivity; readonly IUnityLifecycleManager m_CallbackExecutor; readonly Purchase m_UnityMonetizationPurchase; readonly AndroidAnalytics m_UnityMonetizationAnalytics; private IDictionary m_PlacementContents = new Dictionary(); private AndroidJavaClass m_unityMonetizationClass = new AndroidJavaClass("com.unity3d.services.monetization.UnityMonetization"); private AndroidJavaClass m_unityServicesClass = new AndroidJavaClass("com.unity3d.services.UnityServices"); private AndroidJavaClass m_unityPurchasingClass = new AndroidJavaClass("com.unity3d.services.purchasing.UnityPurchasing"); public Platform() : base("com.unity3d.services.monetization.IUnityMonetizationListener") { var unityPlayerClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); this.m_CurrentActivity = unityPlayerClass.GetStatic("currentActivity"); this.m_UnityMonetizationPurchase = new Purchase(); this.m_UnityMonetizationAnalytics = new AndroidAnalytics(); m_CallbackExecutor = new UnityLifecycleManager(); } void onPlacementContentReady(string placementId, AndroidJavaObject placementcontent) { PlacementContentType type = this.getPlacementContentTypeForAndroidObject(placementcontent); IPlacementContentOperations operations = this.getPlacementContentOperationsForType(type, placementcontent); PlacementContent m_placementcontent = this.getPlacementContentForType(type, placementId, operations); m_placementcontent.extras = JavaMapUtilities.GetDictionaryForJavaMap(placementcontent.Call("getExtras")); if (m_PlacementContents.ContainsKey(placementId)) { m_PlacementContents.Remove(placementId); } m_PlacementContents.Add(placementId, m_placementcontent); m_CallbackExecutor.Post(() => { OnPlacementContentReady?.Invoke(this, new PlacementContentReadyEventArgs(placementId, m_placementcontent)); }); } private IPlacementContentOperations getPlacementContentOperationsForType(PlacementContentType type, AndroidJavaObject placementcontent) { switch (type) { case PlacementContentType.ShowAd: return new AndroidShowAdOperations(m_CallbackExecutor, placementcontent); case PlacementContentType.PromoAd: return new AndroidPromoAdOperations(m_CallbackExecutor, placementcontent); default: return new AndroidPlacementContentOperations(placementcontent); } } private PlacementContent getPlacementContentForType(PlacementContentType type, string placementId, IPlacementContentOperations operations) { switch (type) { case PlacementContentType.ShowAd: return new ShowAdPlacementContent(placementId, operations as IShowAdOperations); case PlacementContentType.PromoAd: return new PromoAdPlacementContent(placementId, operations as IPromoAdOperations); default: return new PlacementContent(placementId, operations); } } private PlacementContentState getPlacementContentStateForJavaPlacementContentState(AndroidJavaObject javaObject) { return (PlacementContentState)JavaEnumUtilities.GetEnumOrdinal(javaObject); } void onPlacementContentStateChange(string placementId, AndroidJavaObject placementcontent, AndroidJavaObject previousState, AndroidJavaObject newState) { if (m_PlacementContents.ContainsKey(placementId)) { PlacementContent m_placementContent = m_PlacementContents[placementId]; PlacementContentState m_previousState = this.getPlacementContentStateForJavaPlacementContentState(previousState); PlacementContentState m_newState = this.getPlacementContentStateForJavaPlacementContentState(newState); m_CallbackExecutor.Post(() => { OnPlacementContentStateChange?.Invoke(this, new PlacementContentStateChangeEventArgs(placementId, m_placementContent, m_previousState, m_newState)); }); } } void onUnityServicesError(AndroidJavaObject error, string message) { var errOrdinal = error?.Call("ordinal") ?? -1; m_CallbackExecutor.Post(() => { onError?.Invoke(this, new UnityServicesErrorEventArgs(errOrdinal, message)); }); } public void Initialize(string gameId, bool testMode) { m_UnityMonetizationPurchase.Initialize(this); m_UnityMonetizationAnalytics.Initialize(); m_unityMonetizationClass.CallStatic("initialize", this.m_CurrentActivity, gameId, this, testMode); } public void SetPurchasingAdapter(IPurchasingAdapter adapter) { m_unityPurchasingClass.CallStatic("setAdapter", new AndroidPurchasingAdapter(adapter)); } public string version => m_unityServicesClass.CallStatic("getVersion"); public bool isSupported => m_unityServicesClass.CallStatic("isSupported"); private PlacementContentType getPlacementContentTypeForAndroidObject(AndroidJavaObject javaObject) { var placementcontentType = javaObject.SafeStringCall("getType"); if (PlacementContentTypesMap.ContainsKey(placementcontentType)) { return PlacementContentTypesMap[placementcontentType]; } return PlacementContentType.Custom; } public bool IsReady(string placementID) { return m_unityMonetizationClass.CallStatic("isReady", placementID); } public PlacementContent GetPlacementContent(string placementID) { if (m_PlacementContents.ContainsKey(placementID)) { return m_PlacementContents[placementID]; } return null; } public INativePromoAdapter CreateNativePromoAdapter(PromoAdPlacementContent placementContent) { return new AndroidNativePromoAdapter(placementContent); } public void SetMetaData(MetaData metaData) { var metaDataObject = new AndroidJavaObject("com.unity3d.ads.metadata.MetaData", m_CurrentActivity); metaDataObject.Call("setCategory", metaData.category); foreach (var entry in metaData) { metaDataObject.Call("set", entry.Key, entry.Value); } metaDataObject.Call("commit"); } void IPurchasingEventSender.SendPurchasingEvent(string payload) { m_UnityMonetizationPurchase.SendEvent(payload); } private class AndroidPurchasingAdapter : AndroidJavaProxy { private IPurchasingAdapter adapter; public AndroidPurchasingAdapter(IPurchasingAdapter adapter) : base("com.unity3d.services.purchasing.core.IPurchasingAdapter") { this.adapter = adapter; } public void retrieveProducts(AndroidJavaObject listener) { this.adapter.RetrieveProducts(new AndroidRetrieveProductsListener(listener)); } public void onPurchase(string productID, AndroidJavaObject javaListener, AndroidJavaObject javaExtras) { IDictionary extras = JavaMapUtilities.GetDictionaryForJavaMap(javaExtras); this.adapter.Purchase(productID, new AndroidTransactionListener(javaListener), extras); } private class AndroidRetrieveProductsListener : IRetrieveProductsListener { private AndroidJavaObject listener; public AndroidRetrieveProductsListener(AndroidJavaObject listener) { this.listener = listener; } public void OnProductsRetrieved(ICollection products) { var productsList = this.getJavaProductList(products); listener.Call("onProductsRetrieved", productsList); } private AndroidJavaObject getJavaProductList(ICollection products) { AndroidJavaObject javaProductList = new AndroidJavaObject("java.util.ArrayList"); foreach (var product in products) { javaProductList.Call("add", this.getJavaProduct(product)); } return javaProductList; } private AndroidJavaObject getJavaProduct(Product product) { AndroidJavaObject builder = new AndroidJavaClass("com.unity3d.services.purchasing.core.Product") .CallStatic("newBuilder"); builder.Call("withProductId", product.productId); builder.Call("withLocalizedPriceString", product.localizedPriceString); builder.Call("withLocalizedTitle", product.localizedTitle); builder.Call("withIsoCurrencyCode", product.isoCurrencyCode); builder.Call("withLocalizedPrice", (double)product.localizedPrice); builder.Call("withLocalizedDescription", product.localizedDescription); builder.Call("withProductType", product.productType); return builder.Call("build"); } } private class AndroidTransactionListener : ITransactionListener { private AndroidJavaObject listener; public AndroidTransactionListener(AndroidJavaObject listener) { this.listener = listener; } public void OnTransactionComplete(TransactionDetails details) { listener.Call("onTransactionComplete", AndroidNativePromoAdapter.getJavaTransactionDetails(details)); } public void OnTransactionError(TransactionErrorDetails details) { listener.Call("onTransactionError", AndroidNativePromoAdapter.getJavaTransactionErrorDetails(details)); } } } } #elif UNITY_IOS public class Platform : IMonetizationPlatform { private static readonly IDictionary PlacementContentTypesMap = new Dictionary { {"SHOW_AD", PlacementContentType.ShowAd}, {"PROMO_AD", PlacementContentType.PromoAd}, {"SINK_PROMO", PlacementContentType.SinkPromo}, {"CUSTOM", PlacementContentType.Custom} }; [DllImport("__Internal")] static extern bool UnityMonetizationIsReady(string placementId); [DllImport("__Internal")] static extern void UnityMonetizationInitialize(string gameId, bool isTestMode); [DllImport("__Internal")] static extern bool UnityMonetizationIsSupported(); [DllImport("__Internal")] static extern string UnityMonetizationGetPlacementContentType(IntPtr pPlacementContent); [DllImport("__Internal")] [return : MarshalAs(UnmanagedType.LPWStr)] static extern string UnityMonetizationGetPlacementContentExtras(IntPtr pPlacementContent); [DllImport("__Internal")] static extern void UnityAdsSetMetaData(string category, string data); [DllImport("__Internal")] static extern string UnityAdsGetVersion(); private readonly IUnityLifecycleManager _callbackExecutor; private readonly IDictionary _placementContents = new Dictionary(); public event EventHandler OnPlacementContentReady; public event EventHandler OnPlacementContentStateChange; public event EventHandler onError; public Platform() { _callbackExecutor = new UnityLifecycleManager(); } public void Initialize(string gameId, bool testMode) { PlatformCallbacksWrapper.Platform = this; new IosAnalytics().Initialize(); new PurchasingPlatform().Initialize(); UnityMonetizationInitialize(gameId, testMode); } public bool IsReady(string placementId) { return UnityMonetizationIsReady(placementId); } public void SetPurchasingAdapter(IPurchasingAdapter adapter) { PurchasingAdapter.Adapter = adapter; } public PlacementContent GetPlacementContent(string placementId) { if (_placementContents.ContainsKey(placementId)) { return _placementContents[placementId]; } return null; } public INativePromoAdapter CreateNativePromoAdapter(PromoAdPlacementContent placementContent) { return new IosNativePromoAdapter(placementContent); } public void SetMetaData(MetaData metaData) { UnityAdsSetMetaData(metaData.category, metaData.ToJSON()); } public bool isSupported => UnityMonetizationIsSupported(); public string version => UnityAdsGetVersion(); private void OnNativePlacementContentReady(string placementId, IntPtr pPlacementContent) { var type = GetPlacementContentTypeForPlacementContentPtr(pPlacementContent); var operations = GetPlacementContentOperationsForType(type, pPlacementContent); var placementContent = GetPlacementContentForType(type, placementId, operations); var extras = GetPlacementContentExtras(pPlacementContent); placementContent.extras = extras; if (_placementContents.ContainsKey(placementId)) { _placementContents.Remove(placementId); } _placementContents.Add(placementId, placementContent); _callbackExecutor.Post(() => { OnPlacementContentReady?.Invoke(this, new PlacementContentReadyEventArgs(placementId, placementContent)); }); } private IDictionary GetPlacementContentExtras(IntPtr pPlacementContent) { var json = UnityMonetizationGetPlacementContentExtras(pPlacementContent); if (json == null) { return new Dictionary(); } var deserialized = MiniJSON.Json.Deserialize(json); if (deserialized is IDictionary objects) { return objects; } return new Dictionary(); } private static PlacementContentType GetPlacementContentTypeForPlacementContentPtr(IntPtr pPlacementContent) { string type = UnityMonetizationGetPlacementContentType(pPlacementContent); return PlacementContentTypesMap[type]; } private IPlacementContentOperations GetPlacementContentOperationsForType(PlacementContentType type, IntPtr pPlacementContent) { switch (type) { case PlacementContentType.ShowAd: return new IosShowAdOperations(pPlacementContent, _callbackExecutor); case PlacementContentType.PromoAd: return new IosPromoAdOperations(pPlacementContent, _callbackExecutor); default: return new IosPlacementContentOperations(pPlacementContent); } } private PlacementContent GetPlacementContentForType(PlacementContentType type, string placementId, IPlacementContentOperations operations) { switch (type) { case PlacementContentType.ShowAd: return new ShowAdPlacementContent(placementId, operations as IShowAdOperations); case PlacementContentType.PromoAd: return new PromoAdPlacementContent(placementId, operations as IPromoAdOperations); default: return new PlacementContent(placementId, operations); } } internal void OnNativePlacementContentStateChanged(string placementId, IntPtr pPlacementContent, int previousState, int newState) { if (_placementContents.ContainsKey(placementId)) { var placementContent = _placementContents[placementId]; _callbackExecutor.Post(() => { OnPlacementContentStateChange?.Invoke(this, new PlacementContentStateChangeEventArgs(placementId, placementContent, (PlacementContentState)previousState, (PlacementContentState)newState)); }); } } private void OnNativeError(long error, string message) { onError?.Invoke(this, new UnityServicesErrorEventArgs(error, message)); } private static class PlatformCallbacksWrapper { internal static Platform Platform { get; set; } delegate void OnPlacementContentReadyCallback(string placementId, IntPtr placementContent); delegate void OnPlacementContentStateChangedCallback(string placementId, IntPtr placementContent, int previousState, int newState); delegate void OnErrorCallback(long error, string message); [StructLayout(LayoutKind.Sequential)] struct UnityMonetiztionCallbacks { public OnPlacementContentReadyCallback onPlacementContentReadyCallback; public OnPlacementContentStateChangedCallback onPlacementContentStateChangedCallback; public OnErrorCallback onErrorCallback; } [DllImport("__Internal")] private static extern void UnityMonetizationSetMonetizationCallbacks(ref UnityMonetiztionCallbacks callback); static PlatformCallbacksWrapper() { var callbacks = new UnityMonetiztionCallbacks { onPlacementContentReadyCallback = OnPlacementContentReady, onPlacementContentStateChangedCallback = OnPlacementContentChanged, onErrorCallback = OnError }; UnityMonetizationSetMonetizationCallbacks(ref callbacks); } [MonoPInvokeCallback(typeof(OnPlacementContentReadyCallback))] private static void OnPlacementContentReady(string placementId, IntPtr placementContent) { Platform?.OnNativePlacementContentReady(placementId, placementContent); } [MonoPInvokeCallback(typeof(OnPlacementContentStateChangedCallback))] private static void OnPlacementContentChanged(string placementId, IntPtr placementContent, int previousState, int newState) { Platform?.OnNativePlacementContentStateChanged(placementId, placementContent, previousState, newState); } [MonoPInvokeCallback(typeof(OnPlacementContentStateChangedCallback))] private static void OnError(long error, string message) { Platform?.OnNativeError(error, message); } } private static class PurchasingAdapter { internal static IPurchasingAdapter Adapter { private get; set; } delegate void OnRetrieveProductsCallback(IntPtr listener); delegate void OnPurchaseProductCallback(string productId, IntPtr callbacks); [StructLayout(LayoutKind.Sequential)] struct UnityPurchasingCallbacks { public OnRetrieveProductsCallback onRetrieveProductsCallback; public OnPurchaseProductCallback onPurchaseProductCallback; } [DllImport("__Internal")] private static extern void UnityPurchasingSetPurchasingAdapterCallbacks(ref UnityPurchasingCallbacks callback); static PurchasingAdapter() { var callbacks = new UnityPurchasingCallbacks { onRetrieveProductsCallback = OnRetrieveProducts, onPurchaseProductCallback = OnPurchaseProduct }; UnityPurchasingSetPurchasingAdapterCallbacks(ref callbacks); } [MonoPInvokeCallback(typeof(OnRetrieveProductsCallback))] private static void OnRetrieveProducts(IntPtr pCallback) { Adapter?.RetrieveProducts(new IosRetrieveProductsListener(pCallback)); } [MonoPInvokeCallback(typeof(OnPurchaseProductCallback))] private static void OnPurchaseProduct(string productId, IntPtr pCallbacks) { Adapter?.Purchase(productId, new IosTransactionListener(pCallbacks), new Dictionary()); } } private class IosRetrieveProductsListener : IRetrieveProductsListener { // Note, this struct layout matches UnityMonetizationPurchasingAdapter.mm [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct NativeProduct { [MarshalAs(UnmanagedType.LPWStr)] public string productId; [MarshalAs(UnmanagedType.LPWStr)] public string localizedTitle; [MarshalAs(UnmanagedType.LPWStr)] public string localizedDescription; [MarshalAs(UnmanagedType.LPWStr)] public string localizedPriceString; [MarshalAs(UnmanagedType.LPWStr)] public string isoCurrencyCode; [MarshalAs(UnmanagedType.LPWStr)] public string productType; [MarshalAs(UnmanagedType.R8)] public double localizedPrice; } [DllImport("__Internal")] private static extern IntPtr UnityPurchasingAdapterAllocateProductsArray(int num); [DllImport("__Internal")] private static extern void UnityPurchasingAddItemToProductsArray(IntPtr pArray, ref NativeProduct product); [DllImport("__Internal")] private static extern void UnityPurchasingInvokeRetrieveProductsCallback(IntPtr callback, IntPtr pArray); private readonly IntPtr _pCallback; public IosRetrieveProductsListener(IntPtr callback) { _pCallback = callback; } public void OnProductsRetrieved(ICollection products) { var pProducts = UnityPurchasingAdapterAllocateProductsArray(products.Count); foreach (var product in products) { var nativeProduct = new NativeProduct { productId = product.productId, localizedTitle = product.localizedTitle, localizedDescription = product.localizedDescription, localizedPriceString = product.localizedPriceString, isoCurrencyCode = product.isoCurrencyCode, productType = product.productType, localizedPrice = (double)product.localizedPrice }; UnityPurchasingAddItemToProductsArray(pProducts, ref nativeProduct); } UnityPurchasingInvokeRetrieveProductsCallback(_pCallback, pProducts); } } private class IosTransactionListener : ITransactionListener { [DllImport("__Internal")] private static extern void UnityPurchasingInvokeTransactionCompleteCallback(IntPtr pCallbacks, [MarshalAs(UnmanagedType.LPWStr)] string transactionDetails); [DllImport("__Internal")] private static extern void UnityPurchasingInvokeTransactionErrorCallback(IntPtr pCallbacks, [MarshalAs(UnmanagedType.LPWStr)] string transactionErrorDetails); private readonly IntPtr _pCallbacks; public IosTransactionListener(IntPtr pCallbacks) { _pCallbacks = pCallbacks; } public void OnTransactionComplete(TransactionDetails details) { UnityPurchasingInvokeTransactionCompleteCallback(_pCallbacks, MiniJSON.Json.Serialize(details.ToJsonDictionary())); } public void OnTransactionError(TransactionErrorDetails details) { UnityPurchasingInvokeTransactionErrorCallback(_pCallbacks, MiniJSON.Json.Serialize(details.ToJsonDictionary())); } } } #endif }