diff --git a/src/Editor/UmpKitDependencies.xml b/src/Editor/UmpKitDependencies.xml
new file mode 100644
index 0000000..d97bd2a
--- /dev/null
+++ b/src/Editor/UmpKitDependencies.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/Editor/UmpKitDependencies.xml.meta b/src/Editor/UmpKitDependencies.xml.meta
new file mode 100644
index 0000000..992d1e9
--- /dev/null
+++ b/src/Editor/UmpKitDependencies.xml.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 4f9780273dd3046ea836173f85ca5e08
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/src/Runtime/Appegy.Ump.asmdef b/src/Runtime/Appegy.Ump.asmdef
index 25b5f35..e6fdd41 100644
--- a/src/Runtime/Appegy.Ump.asmdef
+++ b/src/Runtime/Appegy.Ump.asmdef
@@ -1,7 +1,9 @@
{
"name": "Appegy.Ump",
"rootNamespace": "Appegy.Ump",
- "references": [],
+ "references": [
+ "UniTask"
+ ],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
diff --git a/src/Runtime/IUmpConsentService.cs b/src/Runtime/IUmpConsentService.cs
new file mode 100644
index 0000000..cffc382
--- /dev/null
+++ b/src/Runtime/IUmpConsentService.cs
@@ -0,0 +1,18 @@
+using System.Threading;
+using Cysharp.Threading.Tasks;
+
+namespace Appegy.Ump
+{
+ public interface IUmpConsentService
+ {
+ bool IsPrivacyOptionsRequired { get; }
+
+ UniTask InitializeAsync(UmpInitializeParameters parameters, CancellationToken ct);
+
+ UniTask ShowFormIfRequiredAsync(CancellationToken ct);
+
+ UniTask ShowPrivacyOptionsFormAsync(CancellationToken ct);
+
+ UniTask ResetConsentAsync(CancellationToken ct);
+ }
+}
diff --git a/src/Runtime/IUmpConsentService.cs.meta b/src/Runtime/IUmpConsentService.cs.meta
new file mode 100644
index 0000000..75fbebb
--- /dev/null
+++ b/src/Runtime/IUmpConsentService.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 962a311bd3fba565f89ed4e22f707638
\ No newline at end of file
diff --git a/src/Runtime/Plugins.meta b/src/Runtime/Plugins.meta
new file mode 100644
index 0000000..a84c5c3
--- /dev/null
+++ b/src/Runtime/Plugins.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d1dfb4fae9c1b6c2d8d8f2d6d4f4cc6d
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/src/Runtime/Plugins/Android.meta b/src/Runtime/Plugins/Android.meta
new file mode 100644
index 0000000..ea26da7
--- /dev/null
+++ b/src/Runtime/Plugins/Android.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: c93ef962d348462599826334511b437f
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/src/Runtime/Plugins/Android/UmpKitConsent.java b/src/Runtime/Plugins/Android/UmpKitConsent.java
new file mode 100644
index 0000000..8c9c349
--- /dev/null
+++ b/src/Runtime/Plugins/Android/UmpKitConsent.java
@@ -0,0 +1,164 @@
+package com.appegy.ump;
+
+import com.google.android.ump.ConsentDebugSettings;
+import com.google.android.ump.ConsentInformation;
+import com.google.android.ump.ConsentRequestParameters;
+import com.google.android.ump.FormError;
+import com.google.android.ump.UserMessagingPlatform;
+import com.unity3d.player.UnityPlayer;
+
+import android.app.Activity;
+
+public final class UmpKitConsent
+{
+ private static final String GAME_OBJECT = "UmpKit";
+ private static final String METHOD = "OnUmpMessage";
+
+ private static ConsentInformation consentInformation;
+ private static boolean formAvailable = false;
+
+ private UmpKitConsent() {}
+
+ private static Activity activity()
+ {
+ return UnityPlayer.currentActivity;
+ }
+
+ private static int status()
+ {
+ try
+ {
+ return consentInformation == null ? 0 : consentInformation.getConsentStatus();
+ }
+ catch (Exception e)
+ {
+ return 0;
+ }
+ }
+
+ private static void send(String op, int status, int error, int nativeCode, String message)
+ {
+ if (message == null)
+ {
+ message = "";
+ }
+ String payload = op + "|" + status + "|" + error + "|" + nativeCode + "|" + message;
+ UnityPlayer.UnitySendMessage(GAME_OBJECT, METHOD, payload);
+ }
+
+ public static void Initialize(final int debugGeography, final String testDevice)
+ {
+ activity().runOnUiThread(() -> doInitialize(debugGeography, testDevice));
+ }
+
+ private static void doInitialize(int debugGeography, String testDevice)
+ {
+ ConsentRequestParameters.Builder builder = new ConsentRequestParameters.Builder()
+ .setTagForUnderAgeOfConsent(false);
+
+ if (debugGeography > 0 && testDevice != null && testDevice.length() > 0)
+ {
+ ConsentDebugSettings debug = new ConsentDebugSettings.Builder(activity())
+ .setDebugGeography(debugGeography)
+ .addTestDeviceHashedId(testDevice)
+ .build();
+ builder.setConsentDebugSettings(debug);
+ }
+
+ ConsentRequestParameters params = builder.build();
+ consentInformation = UserMessagingPlatform.getConsentInformation(activity());
+ consentInformation.requestConsentInfoUpdate(
+ activity(),
+ params,
+ () ->
+ {
+ formAvailable = consentInformation.isConsentFormAvailable();
+ send("init", status(), 0, 0, "");
+ },
+ formError ->
+ {
+ send("init", status(), 1, formError.getErrorCode(), formError.getMessage());
+ });
+ }
+
+ public static void ShowFormIfRequired()
+ {
+ activity().runOnUiThread(UmpKitConsent::doShowFormIfRequired);
+ }
+
+ private static void doShowFormIfRequired()
+ {
+ if (consentInformation == null)
+ {
+ send("show", 0, 4, 0, "not initialized");
+ return;
+ }
+
+ if (consentInformation.getConsentStatus() == ConsentInformation.ConsentStatus.REQUIRED && !formAvailable)
+ {
+ send("show", status(), 2, 0, "form not available");
+ return;
+ }
+
+ UserMessagingPlatform.loadAndShowConsentFormIfRequired(
+ activity(),
+ formError ->
+ {
+ if (formError != null)
+ {
+ send("show", status(), 3, formError.getErrorCode(), formError.getMessage());
+ }
+ else
+ {
+ send("show", status(), 0, 0, "");
+ }
+ });
+ }
+
+ public static void ShowPrivacyOptionsForm()
+ {
+ activity().runOnUiThread(UmpKitConsent::doShowPrivacyOptions);
+ }
+
+ private static void doShowPrivacyOptions()
+ {
+ if (consentInformation == null)
+ {
+ send("privacy", 0, 4, 0, "not initialized");
+ return;
+ }
+
+ UserMessagingPlatform.showPrivacyOptionsForm(
+ activity(),
+ formError ->
+ {
+ if (formError != null)
+ {
+ send("privacy", status(), 3, formError.getErrorCode(), formError.getMessage());
+ }
+ else
+ {
+ send("privacy", status(), 0, 0, "");
+ }
+ });
+ }
+
+ public static void ResetConsent()
+ {
+ if (consentInformation != null)
+ {
+ consentInformation.reset();
+ }
+ formAvailable = false;
+ }
+
+ public static boolean IsPrivacyOptionsRequired()
+ {
+ if (consentInformation == null)
+ {
+ return false;
+ }
+ return consentInformation.getPrivacyOptionsRequirementStatus()
+ == ConsentInformation.PrivacyOptionsRequirementStatus.REQUIRED;
+ }
+}
diff --git a/src/Runtime/Plugins/Android/UmpKitConsent.java.meta b/src/Runtime/Plugins/Android/UmpKitConsent.java.meta
new file mode 100644
index 0000000..bcd5edd
--- /dev/null
+++ b/src/Runtime/Plugins/Android/UmpKitConsent.java.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: c6f2e6e20f30d58528e99daa55e2738f
\ No newline at end of file
diff --git a/src/Runtime/Plugins/Android/UmpKitProguard.androidlib.meta b/src/Runtime/Plugins/Android/UmpKitProguard.androidlib.meta
new file mode 100644
index 0000000..a2e4af3
--- /dev/null
+++ b/src/Runtime/Plugins/Android/UmpKitProguard.androidlib.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 6cf3adc2d443f3c8d8d4f06cecae9524
\ No newline at end of file
diff --git a/src/Runtime/Plugins/Android/UmpKitProguard.androidlib/AndroidManifest.xml b/src/Runtime/Plugins/Android/UmpKitProguard.androidlib/AndroidManifest.xml
new file mode 100644
index 0000000..865a9a0
--- /dev/null
+++ b/src/Runtime/Plugins/Android/UmpKitProguard.androidlib/AndroidManifest.xml
@@ -0,0 +1,6 @@
+
+
+
diff --git a/src/Runtime/Plugins/Android/UmpKitProguard.androidlib/build.gradle b/src/Runtime/Plugins/Android/UmpKitProguard.androidlib/build.gradle
new file mode 100644
index 0000000..053712d
--- /dev/null
+++ b/src/Runtime/Plugins/Android/UmpKitProguard.androidlib/build.gradle
@@ -0,0 +1,17 @@
+apply plugin: 'com.android.library'
+
+android {
+ namespace "io.appegy.ump.proguard"
+ compileSdk 36
+
+ defaultConfig {
+ minSdk 29
+ consumerProguardFiles 'proguard.txt'
+ }
+
+ lint { abortOnError false }
+
+ sourceSets {
+ main { manifest.srcFile 'AndroidManifest.xml' }
+ }
+}
diff --git a/src/Runtime/Plugins/Android/UmpKitProguard.androidlib/proguard.txt b/src/Runtime/Plugins/Android/UmpKitProguard.androidlib/proguard.txt
new file mode 100644
index 0000000..547e6d1
--- /dev/null
+++ b/src/Runtime/Plugins/Android/UmpKitProguard.androidlib/proguard.txt
@@ -0,0 +1 @@
+-keep class com.appegy.ump.** { *; }
diff --git a/src/Runtime/Plugins/Android/UmpKitProguard.androidlib/project.properties b/src/Runtime/Plugins/Android/UmpKitProguard.androidlib/project.properties
new file mode 100644
index 0000000..7096c5c
--- /dev/null
+++ b/src/Runtime/Plugins/Android/UmpKitProguard.androidlib/project.properties
@@ -0,0 +1,2 @@
+target=android-9
+android.library=true
diff --git a/src/Runtime/Plugins/iOS.meta b/src/Runtime/Plugins/iOS.meta
new file mode 100644
index 0000000..3bbbe2b
--- /dev/null
+++ b/src/Runtime/Plugins/iOS.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 07b6d77aceebf87119b10c17a76d4c03
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/src/Runtime/Plugins/iOS/UmpKitConsent.m b/src/Runtime/Plugins/iOS/UmpKitConsent.m
new file mode 100644
index 0000000..d34c361
--- /dev/null
+++ b/src/Runtime/Plugins/iOS/UmpKitConsent.m
@@ -0,0 +1,119 @@
+#import
+#import "UnityInterface.h"
+
+extern void UnitySendMessage(const char *, const char *, const char *);
+
+static const char *const kGameObject = "UmpKit";
+static const char *const kMethod = "OnUmpMessage";
+
+static int UmpCanonicalStatus(UMPConsentStatus status)
+{
+ switch (status)
+ {
+ case UMPConsentStatusNotRequired: return 1;
+ case UMPConsentStatusRequired: return 2;
+ case UMPConsentStatusObtained: return 3;
+ default: return 0;
+ }
+}
+
+static int UmpCurrentStatus(void)
+{
+ return UmpCanonicalStatus(UMPConsentInformation.sharedInstance.consentStatus);
+}
+
+static void UmpSend(const char *op, int status, int error, int nativeCode, NSString *message)
+{
+ if (message == nil)
+ {
+ message = @"";
+ }
+ NSString *payload = [NSString stringWithFormat:@"%s|%d|%d|%d|%@", op, status, error, nativeCode, message];
+ UnitySendMessage(kGameObject, kMethod, [payload UTF8String]);
+}
+
+void _UmpInitialize(int debugGeography, const char *testDevice)
+{
+ UMPRequestParameters *parameters = [[UMPRequestParameters alloc] init];
+ parameters.tagForUnderAgeOfConsent = NO;
+
+ NSString *device = testDevice != NULL ? [NSString stringWithUTF8String:testDevice] : @"";
+ if (debugGeography > 0 && device.length > 0)
+ {
+ UMPDebugSettings *debug = [[UMPDebugSettings alloc] init];
+ debug.geography = (UMPDebugGeography)debugGeography;
+ debug.testDeviceIdentifiers = @[ device ];
+ parameters.debugSettings = debug;
+ }
+
+ [UMPConsentInformation.sharedInstance
+ requestConsentInfoUpdateWithParameters:parameters
+ completionHandler:^(NSError *_Nullable error)
+ {
+ if (error)
+ {
+ UmpSend("init", UmpCurrentStatus(), 1, (int)error.code, error.localizedDescription);
+ }
+ else
+ {
+ UmpSend("init", UmpCurrentStatus(), 0, 0, @"");
+ }
+ }];
+}
+
+void _UmpShowFormIfRequired(void)
+{
+ dispatch_async(dispatch_get_main_queue(), ^{
+ UIViewController *controller = UnityGetGLViewController();
+
+ if (UMPConsentInformation.sharedInstance.consentStatus == UMPConsentStatusRequired
+ && UMPConsentInformation.sharedInstance.formStatus != UMPFormStatusAvailable)
+ {
+ UmpSend("show", UmpCurrentStatus(), 2, 0, @"form not available");
+ return;
+ }
+
+ [UMPConsentForm loadAndPresentIfRequiredFromViewController:controller
+ completionHandler:^(NSError *_Nullable error)
+ {
+ if (error)
+ {
+ UmpSend("show", UmpCurrentStatus(), 3, (int)error.code, error.localizedDescription);
+ }
+ else
+ {
+ UmpSend("show", UmpCurrentStatus(), 0, 0, @"");
+ }
+ }];
+ });
+}
+
+void _UmpShowPrivacyOptionsForm(void)
+{
+ dispatch_async(dispatch_get_main_queue(), ^{
+ UIViewController *controller = UnityGetGLViewController();
+ [UMPConsentForm presentPrivacyOptionsFormFromViewController:controller
+ completionHandler:^(NSError *_Nullable error)
+ {
+ if (error)
+ {
+ UmpSend("privacy", UmpCurrentStatus(), 3, (int)error.code, error.localizedDescription);
+ }
+ else
+ {
+ UmpSend("privacy", UmpCurrentStatus(), 0, 0, @"");
+ }
+ }];
+ });
+}
+
+void _UmpResetConsent(void)
+{
+ [UMPConsentInformation.sharedInstance reset];
+}
+
+bool _UmpIsPrivacyOptionsRequired(void)
+{
+ return UMPConsentInformation.sharedInstance.privacyOptionsRequirementStatus
+ == UMPPrivacyOptionsRequirementStatusRequired;
+}
diff --git a/src/Runtime/Plugins/iOS/UmpKitConsent.m.meta b/src/Runtime/Plugins/iOS/UmpKitConsent.m.meta
new file mode 100644
index 0000000..afb6abd
--- /dev/null
+++ b/src/Runtime/Plugins/iOS/UmpKitConsent.m.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 9fd1eac7e54bcb61b8931d76d0440b17
\ No newline at end of file
diff --git a/src/Runtime/UmpCallbackReceiver.cs b/src/Runtime/UmpCallbackReceiver.cs
new file mode 100644
index 0000000..5460cee
--- /dev/null
+++ b/src/Runtime/UmpCallbackReceiver.cs
@@ -0,0 +1,18 @@
+using System;
+using UnityEngine;
+using UnityEngine.Scripting;
+
+namespace Appegy.Ump
+{
+ [Preserve]
+ internal sealed class UmpCallbackReceiver : MonoBehaviour
+ {
+ public Action OnMessage;
+
+ [Preserve]
+ public void OnUmpMessage(string payload)
+ {
+ OnMessage?.Invoke(payload);
+ }
+ }
+}
diff --git a/src/Runtime/UmpCallbackReceiver.cs.meta b/src/Runtime/UmpCallbackReceiver.cs.meta
new file mode 100644
index 0000000..f5512cb
--- /dev/null
+++ b/src/Runtime/UmpCallbackReceiver.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 71eddfd72bb801e8a96e3e067a650839
\ No newline at end of file
diff --git a/src/Runtime/UmpConsentService.cs b/src/Runtime/UmpConsentService.cs
new file mode 100644
index 0000000..5a765f2
--- /dev/null
+++ b/src/Runtime/UmpConsentService.cs
@@ -0,0 +1,170 @@
+using System;
+using System.Globalization;
+using System.Threading;
+using Cysharp.Threading.Tasks;
+using UnityEngine;
+#if UNITY_IOS && !UNITY_EDITOR
+using System.Runtime.InteropServices;
+#endif
+
+namespace Appegy.Ump
+{
+ public sealed class UmpConsentService : IUmpConsentService
+ {
+ private const string GameObjectName = "UmpKit";
+ private const string AndroidClass = "com.appegy.ump.UmpKitConsent";
+
+ private UmpCallbackReceiver _receiver;
+ private UniTaskCompletionSource _pending;
+
+#if UNITY_IOS && !UNITY_EDITOR
+ [DllImport("__Internal")] private static extern void _UmpInitialize(int debugGeography, string testDevice);
+ [DllImport("__Internal")] private static extern void _UmpShowFormIfRequired();
+ [DllImport("__Internal")] private static extern void _UmpShowPrivacyOptionsForm();
+ [DllImport("__Internal")] private static extern void _UmpResetConsent();
+ [DllImport("__Internal")] private static extern bool _UmpIsPrivacyOptionsRequired();
+#endif
+
+ public bool IsPrivacyOptionsRequired
+ {
+ get
+ {
+#if UNITY_EDITOR
+ return false;
+#elif UNITY_ANDROID
+ using var cls = new AndroidJavaClass(AndroidClass);
+ return cls.CallStatic("IsPrivacyOptionsRequired");
+#elif UNITY_IOS
+ return _UmpIsPrivacyOptionsRequired();
+#else
+ return false;
+#endif
+ }
+ }
+
+ public UniTask InitializeAsync(UmpInitializeParameters parameters, CancellationToken ct)
+ {
+#if UNITY_EDITOR
+ return UniTask.FromResult(new UmpResult(UmpConsentStatus.NotRequired));
+#else
+ return CallAsync(ct, () => NativeInitialize(parameters));
+#endif
+ }
+
+ public UniTask ShowFormIfRequiredAsync(CancellationToken ct)
+ {
+#if UNITY_EDITOR
+ return UniTask.FromResult(new UmpResult(UmpConsentStatus.NotRequired));
+#else
+ return CallAsync(ct, NativeShowFormIfRequired);
+#endif
+ }
+
+ public UniTask ShowPrivacyOptionsFormAsync(CancellationToken ct)
+ {
+#if UNITY_EDITOR
+ return UniTask.FromResult(new UmpResult(UmpConsentStatus.NotRequired));
+#else
+ return CallAsync(ct, NativeShowPrivacyOptions);
+#endif
+ }
+
+ public UniTask ResetConsentAsync(CancellationToken ct)
+ {
+#if UNITY_EDITOR
+#elif UNITY_ANDROID
+ using var cls = new AndroidJavaClass(AndroidClass);
+ cls.CallStatic("ResetConsent");
+#elif UNITY_IOS
+ _UmpResetConsent();
+#endif
+ return UniTask.CompletedTask;
+ }
+
+ private async UniTask CallAsync(CancellationToken ct, Action invokeNative)
+ {
+ EnsureReceiver();
+
+ var tcs = new UniTaskCompletionSource();
+ _pending = tcs;
+
+ using (ct.Register(() => tcs.TrySetCanceled(ct)))
+ {
+ invokeNative();
+ var raw = await tcs.Task;
+ return Parse(raw);
+ }
+ }
+
+ private void EnsureReceiver()
+ {
+ if (_receiver != null)
+ {
+ return;
+ }
+
+ var go = new GameObject(GameObjectName);
+ UnityEngine.Object.DontDestroyOnLoad(go);
+ _receiver = go.AddComponent();
+ _receiver.OnMessage = raw => _pending?.TrySetResult(raw);
+ }
+
+ private void NativeInitialize(UmpInitializeParameters parameters)
+ {
+ var geography = (int)parameters.DebugGeography;
+ var device = parameters.TestDeviceHashedId ?? string.Empty;
+#if UNITY_ANDROID && !UNITY_EDITOR
+ using var cls = new AndroidJavaClass(AndroidClass);
+ cls.CallStatic("Initialize", geography, device);
+#elif UNITY_IOS && !UNITY_EDITOR
+ _UmpInitialize(geography, device);
+#endif
+ }
+
+ private void NativeShowFormIfRequired()
+ {
+#if UNITY_ANDROID && !UNITY_EDITOR
+ using var cls = new AndroidJavaClass(AndroidClass);
+ cls.CallStatic("ShowFormIfRequired");
+#elif UNITY_IOS && !UNITY_EDITOR
+ _UmpShowFormIfRequired();
+#endif
+ }
+
+ private void NativeShowPrivacyOptions()
+ {
+#if UNITY_ANDROID && !UNITY_EDITOR
+ using var cls = new AndroidJavaClass(AndroidClass);
+ cls.CallStatic("ShowPrivacyOptionsForm");
+#elif UNITY_IOS && !UNITY_EDITOR
+ _UmpShowPrivacyOptionsForm();
+#endif
+ }
+
+ private static UmpResult Parse(string raw)
+ {
+ var parts = (raw ?? string.Empty).Split(new[] { '|' }, 5);
+ var status = (UmpConsentStatus)ParseInt(parts, 1);
+ var error = ParseInt(parts, 2);
+ if (error == 0)
+ {
+ return new UmpResult(status);
+ }
+
+ var code = (UmpErrorCode)(error - 1);
+ var nativeCode = ParseInt(parts, 3);
+ var message = parts.Length > 4 ? parts[4] : string.Empty;
+ return new UmpResult(status, new UmpError(code, nativeCode, message));
+ }
+
+ private static int ParseInt(string[] parts, int index)
+ {
+ if (parts.Length <= index)
+ {
+ return 0;
+ }
+
+ return int.TryParse(parts[index], NumberStyles.Integer, CultureInfo.InvariantCulture, out var value) ? value : 0;
+ }
+ }
+}
diff --git a/src/Runtime/UmpConsentService.cs.meta b/src/Runtime/UmpConsentService.cs.meta
new file mode 100644
index 0000000..dcc780f
--- /dev/null
+++ b/src/Runtime/UmpConsentService.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 6af2e100c09795a0ba991f57c9cc554e
\ No newline at end of file
diff --git a/src/Runtime/UmpTypes.cs b/src/Runtime/UmpTypes.cs
new file mode 100644
index 0000000..f1c92b4
--- /dev/null
+++ b/src/Runtime/UmpTypes.cs
@@ -0,0 +1,64 @@
+namespace Appegy.Ump
+{
+ public enum UmpConsentStatus
+ {
+ Unknown = 0,
+ NotRequired = 1,
+ Required = 2,
+ Obtained = 3
+ }
+
+ public enum UmpDebugGeography
+ {
+ Disabled = 0,
+ Eea = 1,
+ RegulatedUsState = 3,
+ Other = 4
+ }
+
+ public enum UmpErrorCode
+ {
+ Network = 0,
+ FormUnavailable = 1,
+ FormLoadFailed = 2,
+ Internal = 3
+ }
+
+ public readonly struct UmpError
+ {
+ public UmpErrorCode Code { get; }
+ public int NativeCode { get; }
+ public string Message { get; }
+
+ public UmpError(UmpErrorCode code, int nativeCode, string message)
+ {
+ Code = code;
+ NativeCode = nativeCode;
+ Message = message ?? string.Empty;
+ }
+
+ public override string ToString()
+ {
+ return $"{Code} (native {NativeCode}): {Message}";
+ }
+ }
+
+ public readonly struct UmpResult
+ {
+ public UmpConsentStatus Status { get; }
+ public UmpError? Error { get; }
+ public bool IsSuccess => Error == null;
+
+ public UmpResult(UmpConsentStatus status, UmpError? error = null)
+ {
+ Status = status;
+ Error = error;
+ }
+ }
+
+ public struct UmpInitializeParameters
+ {
+ public UmpDebugGeography DebugGeography;
+ public string TestDeviceHashedId;
+ }
+}
diff --git a/src/Runtime/UmpTypes.cs.meta b/src/Runtime/UmpTypes.cs.meta
new file mode 100644
index 0000000..891757f
--- /dev/null
+++ b/src/Runtime/UmpTypes.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: faca7c512062794dab3c05d20abdb276
\ No newline at end of file