From 2902d62b4fd547fa44fb912415cb35c937c2621c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gibern=C3=A9?= Date: Fri, 11 Jul 2025 17:57:15 -0400 Subject: [PATCH 01/27] start updating unity --- Assets/UrbanAirship/Editor/UADependencies.xml | 44 +- Assets/UrbanAirship/Platforms/Airship.cs | 366 +++++++++ .../Platforms/AirshipAnalytics.cs | 63 ++ .../UrbanAirship/Platforms/AirshipChannel.cs | 113 +++ .../UrbanAirship/Platforms/AirshipContact.cs | 105 +++ Assets/UrbanAirship/Platforms/AirshipInApp.cs | 54 ++ .../UrbanAirship/Platforms/AirshipLocale.cs | 50 ++ .../Platforms/AirshipMessageCenter.cs | 235 ++++++ .../Platforms/AirshipPreferenceCenter.cs | 217 +++++ .../Platforms/AirshipPrivacyManager.cs | 71 ++ Assets/UrbanAirship/Platforms/AirshipPush.cs | 443 ++++++++++ .../UrbanAirship/Platforms/IAirshipPlugin.cs | 94 +++ .../Platforms/ScopedSubscriptionListEditor.cs | 94 +++ .../Platforms/SubscriptionListEditor.cs | 69 ++ Assets/UrbanAirship/Platforms/TagEditor.cs | 89 ++ Packages/manifest.json | 4 +- ProjectSettings/MultiplayerManager.asset | 7 + ProjectSettings/ProjectSettings.asset | 71 +- ProjectSettings/ProjectVersion.txt | 4 +- ProjectSettings/SceneTemplateSettings.json | 5 + README.md | 4 +- airship.properties | 9 +- build.gradle | 38 +- docs/build.gradle | 6 +- gradle.properties | 3 +- gradle/wrapper/gradle-wrapper.properties | 3 +- settings.gradle | 25 + unity-plugin/build.gradle | 111 ++- unity-plugin/src/main/AndroidManifest.xml | 23 +- .../unityplugin/CustomMessageActivity.java | 4 +- .../CustomMessageCenterActivity.java | 4 +- .../unityplugin/PluginLogger.java | 170 ---- .../unityplugin/UnityAutopilot.java | 132 --- .../unityplugin/UnityAutopilot.kt | 37 + .../urbanairship/unityplugin/UnityPlugin.java | 776 ------------------ .../urbanairship/unityplugin/UnityPlugin.kt | 127 +++ .../src/main/res/values-v14/style.xml | 10 +- 37 files changed, 2501 insertions(+), 1179 deletions(-) create mode 100644 Assets/UrbanAirship/Platforms/Airship.cs create mode 100644 Assets/UrbanAirship/Platforms/AirshipAnalytics.cs create mode 100644 Assets/UrbanAirship/Platforms/AirshipChannel.cs create mode 100644 Assets/UrbanAirship/Platforms/AirshipContact.cs create mode 100644 Assets/UrbanAirship/Platforms/AirshipInApp.cs create mode 100644 Assets/UrbanAirship/Platforms/AirshipLocale.cs create mode 100644 Assets/UrbanAirship/Platforms/AirshipMessageCenter.cs create mode 100644 Assets/UrbanAirship/Platforms/AirshipPreferenceCenter.cs create mode 100644 Assets/UrbanAirship/Platforms/AirshipPrivacyManager.cs create mode 100644 Assets/UrbanAirship/Platforms/AirshipPush.cs create mode 100644 Assets/UrbanAirship/Platforms/IAirshipPlugin.cs create mode 100644 Assets/UrbanAirship/Platforms/ScopedSubscriptionListEditor.cs create mode 100644 Assets/UrbanAirship/Platforms/SubscriptionListEditor.cs create mode 100644 Assets/UrbanAirship/Platforms/TagEditor.cs create mode 100644 ProjectSettings/MultiplayerManager.asset delete mode 100644 unity-plugin/src/main/java/com/urbanairship/unityplugin/PluginLogger.java delete mode 100644 unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityAutopilot.java create mode 100644 unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityAutopilot.kt delete mode 100644 unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.java create mode 100644 unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt diff --git a/Assets/UrbanAirship/Editor/UADependencies.xml b/Assets/UrbanAirship/Editor/UADependencies.xml index a3eedd20..1723ce2d 100644 --- a/Assets/UrbanAirship/Editor/UADependencies.xml +++ b/Assets/UrbanAirship/Editor/UADependencies.xml @@ -8,17 +8,51 @@ https://repo.maven.apache.org/maven2 + + - + - + - + - + + + + @@ -33,6 +67,8 @@ + + diff --git a/Assets/UrbanAirship/Platforms/Airship.cs b/Assets/UrbanAirship/Platforms/Airship.cs new file mode 100644 index 00000000..7a80f441 --- /dev/null +++ b/Assets/UrbanAirship/Platforms/Airship.cs @@ -0,0 +1,366 @@ +/* Copyright Airship and Contributors */ + +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace UrbanAirship +{ + + public class Airship + { + /// + /// Push received event handler. + /// + public delegate void PushReceivedEventHandler(PushMessage message); + + /// + /// Occurs when a push is received. + /// + public event PushReceivedEventHandler OnPushReceived; + + /// + /// Push opened event handler. + /// + public delegate void PushOpenedEventHandler(PushMessage message); + + /// + /// Occurs when a push is opened. + /// + public event PushOpenedEventHandler OnPushOpened; + + /// + /// Deep link received event handler. + /// + public delegate void DeepLinkReceivedEventHandler(string deeplink); + + /// + /// Occurs when a deep link is received. + /// + public event DeepLinkReceivedEventHandler OnDeepLinkReceived; + + /// + /// Inbox update event handler. + /// + public delegate void InboxUpdatedEventHandler(uint messageUnreadCount, uint messageCount); + + /// + /// Occurs when the inbox updates. + /// + public event InboxUpdatedEventHandler OnInboxUpdated; + + /// + /// Show inbox event handler. + /// + public delegate void ShowInboxEventHandler(string messageId); + + /// + /// Occurs when the app needs to show the inbox. + /// + public event ShowInboxEventHandler OnShowInbox; + + /// + /// Channel create event handler. + /// + public delegate void ChannelCreateEventHandler(string channelId); + + /// + /// Occurs when the channel creates. + /// + public event ChannelCreateEventHandler OnChannelCreated; + + /// + /// Preference Center display event handler. + /// + public delegate void PreferenceCenterDisplayEventHandler(string preferenceCenterId); + + /// + /// Occurs when the app displays the preference center. + /// + public event PreferenceCenterDisplayEventHandler OnPreferenceCenterDisplay; + + /// + /// Authorized settings changed event handler. + /// + public delegate void AuthorizedSettingsChangedEventHandler(AuthorizedNotificationSetting[] authorizedSettings); + + /// + /// Occurs when the authorized settings changed. + /// + public event AuthorizedSettingsChangedEventHandler OnAuthorizedSettingsChanged; + + public AirshipChannel channel; + public AirshipContact contact; + public AirshipAnalytics analytics; + public AirshipInApp inApp; + public AirshipPush push; + public AirshipMessageCenter messageCenter; + public AirshipPreferenceCenter preferenceCenter; + public AirshipPrivacyManager privacyManager; + public AirshipLocale locale; + + private IAirshipPlugin plugin; + internal GameObject gameObject; + + internal static Airship sharedAirship = new Airship(); + + /// + /// Gets the shared Airship instance. + /// + /// The shared Airship instance. + public static Airship Shared + { + get + { + return sharedAirship; + } + } + + /// + /// Creates a Airship instance with a test plugin. + /// Used only for testing. + /// + /// The test plugin. + internal Airship(object testPlugin) + { + plugin = (IAirshipPlugin)testPlugin; + + Init(); + } + + /// + /// Creates a Airship instance. + /// ] + private Airship() + { + if (Application.isEditor) + { + plugin = new StubbedAirshipPlugin(); + } + else + { +#if UNITY_ANDROID + plugin = new AirshipPluginAndroid (); +#elif UNITY_IOS + plugin = new AirshipPluginiOS (); +#else + plugin = new StubbedAirshipPlugin(); +#endif + } + + Init(); + } + + /// + /// Initialize an Airship instance. + /// ] + private void Init() + { + channel = new AirshipChannel(plugin); + contact = new AirshipContact(plugin); + analytics = new AirshipAnalytics(plugin); + inApp = new AirshipInApp(plugin); + push = new AirshipPush(plugin); + messageCenter = new AirshipMessageCenter(plugin); + preferenceCenter = new AirshipPreferenceCenter(plugin); + privacyManager = new AirshipPrivacyManager(plugin); + locale = new AirshipLocale(plugin); + // TODO finish the rest of the modules + + gameObject = new GameObject("[AirshipListener]"); + gameObject.AddComponent(); + + UnityEngine.Object.DontDestroyOnLoad(gameObject); + plugin.Listener = gameObject; + } + + /// + /// Calls takeOff. If Airship is already configured for the app session, + /// the new config will be applied on the next app init. + /// + /// The config. + /// true if airship is ready. + public bool TakeOff(AirshipConfig config) + { + return plugin.Call("takeOff", config); + } + + /// + /// Checks if Airship is ready. + /// + /// true is Airship is ready, otherwise false. + public bool IsFlying() + { + return plugin.Call("isFlying"); + } + + internal class AirshipListener : MonoBehaviour + { + void OnPushReceived(string payload) + { + PushReceivedEventHandler handler = Airship.Shared.OnPushReceived; + + if (handler == null) + { + return; + } + + PushMessage pushMessage = PushMessage.FromJson(payload); + if (pushMessage != null) + { + handler(pushMessage); + } + } + + void OnPushOpened(string payload) + { + PushOpenedEventHandler handler = Airship.Shared.OnPushOpened; + + if (handler == null) + { + return; + } + + PushMessage pushMessage = PushMessage.FromJson(payload); + if (pushMessage != null) + { + handler(pushMessage); + } + } + + void OnDeepLinkReceived(string deeplink) + { + DeepLinkReceivedEventHandler handler = Shared.OnDeepLinkReceived; + + if (handler != null) + { + handler(deeplink); + } + } + + void OnChannelCreated(string channelId) + { + ChannelCreateEventHandler handler = Shared.OnChannelCreated; + + if (handler != null) + { + handler(channelId); + } + } + + void OnInboxUpdated(string counts) + { + InboxUpdatedEventHandler handler = Shared.OnInboxUpdated; + + MessageCounts messageCounts = JsonUtility.FromJson(counts); + + if (handler != null) + { + handler(messageCounts.unread, messageCounts.total); + } + + } + + ioid OnShowInbox(string messageId) + { + ShowInboxEventHandler handler = Shared.OnShowInbox; + + if (handler != null) + { + if ((messageId == null) || (messageId.Length == 0)) + { + handler(null); + } + else + { + handler(messageId); + } + } + } + + void OnPreferenceCenterDisplay(string preferenceCenterId) + { + PreferenceCenterDisplayEventHandler handler = Shared.OnPreferenceCenterDisplay; + + if (handler != null) + { + handler(preferenceCenterId); + } + } + + void OnAuthorizedSettingsChanged(AuthorizedNotificationSetting[] authorizedSettings) + { + AuthorizedSettingsChangedEventHandler handler = Shared.OnAuthorizedSettingsChanged; + + if (handler != null) + { + handler(authorizedSettings); + } + } + } + } + + public static class Features + { + public const string FEATURE_NONE = "FEATURE_NONE"; + public const string FEATURE_IN_APP_AUTOMATION = "FEATURE_IN_APP_AUTOMATION"; + public const string FEATURE_MESSAGE_CENTER = "FEATURE_MESSAGE_CENTER"; + public const string FEATURE_PUSH = "FEATURE_PUSH"; + public const string FEATURE_CHAT = "FEATURE_CHAT"; + public const string FEATURE_ANALYTICS = "FEATURE_ANALYTICS"; + public const string FEATURE_TAGS_AND_ATTRIBUTES = "FEATURE_TAGS_AND_ATTRIBUTES"; + public const string FEATURE_CONTACTS = "FEATURE_CONTACTS"; + public const string FEATURE_LOCATION = "FEATURE_LOCATION"; + public const string FEATURE_ALL = "FEATURE_ALL"; + } + + /// + /// Airship config environment + /// + public record ConfigEnvironment + { + // App key. + public string appKey; + + // App secret. + public string appSecret; + + // Optional log level. + public LogLevel? logLevel; + + // Optional iOS config + public IOSConfig? iOS; + } + + public enum LogLevel + { + Verbose = "verbose", + Debug = "debug", + Info = "info", + Warning = "warning", + Error = "error", + None = "none" + } + + public record IOSConfig + { + /// + /// Log privacy level. By default it logs at `private`, not logging anything lower than info to the console + /// and redacting logs with string interpolation. `public` will log all configured log levels to the console + /// without redacting any of the log lines. + /// + public LogPrivacyLevel? logPrivacyLevel; + } + + public enum LogPrivacyLevel + { + Private = "private", + Public = "public" + } + + public record AirshipConfig + { + // TODO finish AirshipConfig + } +} \ No newline at end of file diff --git a/Assets/UrbanAirship/Platforms/AirshipAnalytics.cs b/Assets/UrbanAirship/Platforms/AirshipAnalytics.cs new file mode 100644 index 00000000..301571ca --- /dev/null +++ b/Assets/UrbanAirship/Platforms/AirshipAnalytics.cs @@ -0,0 +1,63 @@ +/* Copyright Airship and Contributors */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UrbanAirship { + + /// + /// Airship analytics. + /// + public class AirshipAnalytics + { + + private IAirshipPlugin plugin; + + internal AirshipAnalytics(IAirshipPlugin plugin) + { + this.plugin = plugin; + } + + /// + /// Associate a custom identifier. + /// Previous identifiers will be replaced by the new identifiers each time AssociateIdentifier is called. + /// It is a set operation. + /// + /// The custom key for the identifier. + /// The value of the identifier, or `null` to remove the identifier. + public void AssociateIdentifier(string key, string? identifier) + { + this.plugin.Call("associateIdentifier", key, identifier); + } + + /// + /// Tracks a screen. + /// + /// The screen name. `null` to stop tracking. + public void TrackScreen(string screenName) + { + this.plugin.Call("trackScreen", screenName); + } + + /// + /// Adds a custom event. + /// + /// The custom event. + public void AddCustomEvent(CustomEvent customEvent) + { + this.plugin.Call("addCustomEvent", customEvent.ToJson()); + } + + /// + /// Gets the Airship session ID. The session ID is a UUID that updates on foreground and background. + /// + /// The session ID. + public string GetSessionId() + { + return this.plugin.Call("getSessionId"); + } + } +} \ No newline at end of file diff --git a/Assets/UrbanAirship/Platforms/AirshipChannel.cs b/Assets/UrbanAirship/Platforms/AirshipChannel.cs new file mode 100644 index 00000000..cbe5f2bc --- /dev/null +++ b/Assets/UrbanAirship/Platforms/AirshipChannel.cs @@ -0,0 +1,113 @@ +/* Copyright Airship and Contributors */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UrbanAirship { + + /// + /// Airship channel. + /// + public class AirshipChannel + { + + private IAirshipPlugin plugin; + + internal AirshipChannel(IAirshipPlugin plugin) + { + this.plugin = plugin; + } + + /// + /// Gets the tags currently set for the device. + /// + /// The tags. + public IEnumerable GetTags() + { + string tagsAsJson = plugin.Call("getTags"); + JsonArray jsonArray = JsonArray.FromJson(tagsAsJson); + return jsonArray.AsEnumerable(); + } + + /// + /// Gets the channel ID associated with the device. + /// + /// The channel ID. + public string? GetChannelId() + { + return plugin.Call("getChannelId"); + } + + /// + /// Returns the channel ID. If the channel ID is not yet created the function it will wait for it before returning. + /// After the channel ID is created, this method functions the same as `getChannelId()`. + /// + /// The channel ID. + public string WaitForChannelId() + { + return plugin.Call("waitForChannelId"); + } + + /// + /// Gets the channel's subscription lists. + /// + /// The subscription lists. + public IEnumerable GetSubscriptionLists() + { + string subscriptionListsAsJson = plugin.Call("getSubscriptionLists"); + JsonArray jsonArray = JsonArray.FromJson(subscriptionListsAsJson); + return jsonArray.AsEnumerable(); + } + + /// + /// Returns an editor for channel subscription lists. + /// + /// A SubscriptionListEditor. + public SubscriptionListEditor EditSubscriptionLists() + { + return new SubscriptionListEditor((string payload) => + { + plugin.Call("editSubscriptionLists", payload); + }); + } + + /// + /// Returns an editor for channel tags. + /// + /// A TagEditor for channel tags. + public TagEditor EditTags() + { + return new TagEditor((string payload) => + { + plugin.Call("editTags", payload); + }); + } + + /// + /// Returns an editor for channel tag groups. + /// + /// A TagGroupEditor for channel tag groups. + public TagGroupEditor EditTagGroups() + { + return new TagGroupEditor((string payload) => + { + plugin.Call("editTagGroups", payload); + }); + } + + /// + /// Returns an editor for channel attributes. + /// + /// A AttributeEditor for channel attributes. + public AttributeEditor EditAttributes() + { + return new AttributeEditor((string payload) => + { + plugin.Call("editAttributes", payload); + }); + } + } +} \ No newline at end of file diff --git a/Assets/UrbanAirship/Platforms/AirshipContact.cs b/Assets/UrbanAirship/Platforms/AirshipContact.cs new file mode 100644 index 00000000..e17c3a41 --- /dev/null +++ b/Assets/UrbanAirship/Platforms/AirshipContact.cs @@ -0,0 +1,105 @@ +/* Copyright Airship and Contributors */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UrbanAirship { + + /// + /// Airship contact. + /// + public class AirshipContact + { + + private IAirshipPlugin plugin; + + internal AirshipContact(IAirshipPlugin plugin) + { + this.plugin = plugin; + } + + /// + /// Identifies the contact with a named user Id. + /// + /// The named user Id. + public void Identify(string namedUserId) + { + this.plugin.Call("identify", namedUserId); + } + + /// + /// Resets the contact. + /// + public void Reset() + { + this.plugin.Call("reset"); + } + + /// + /// Gets the named user Id. + /// + /// The named user Id. + public string? GetNamedUserId() + { + return this.plugin.Call("getNamedUserId"); + } + + /// + /// Gets the contact's subscription lists. + /// + /// The subscription lists. + public Dictionary> GetSubscriptionLists() + { + Dictionary> scopedSubscriptionLists = new Dictionary>(); + + string subscriptionListsAsJson = this.plugin.Call("getSubscriptionLists"); + ScopedSubscriptionList[] _scopedSubscriptionLists = JsonArray.FromJson(subscriptionListsAsJson).values; + + foreach (ScopedSubscriptionList subscriptionList in _scopedSubscriptionLists) + { + scopedSubscriptionLists.Add(subscriptionList.listId, subscriptionList.scopes.AsEnumerable()); + } + + return scopedSubscriptionLists; + } + + /// + /// Returns an editor for contact subscription lists. + /// + /// A SubscriptionListEditor. + public ScopedSubscriptionListEditor EditSubscriptionLists() + { + return new ScopedSubscriptionListEditor((string payload) => + { + this.plugin.Call("editSubscriptionLists", payload); + }); + } + + /// + /// Returns an editor for contact tag groups. + /// + /// A TagGroupEditor for contact tag groups. + public TagGroupEditor EditTagGroups() + { + return new TagGroupEditor((string payload) => + { + this.plugin.Call("editTagGroups", payload); + }); + } + + /// + /// Returns an editor for contact attributes. + /// + /// A AttributeEditor for contact attributes. + public AttributeEditor EditAttributes() + { + return new AttributeEditor((string payload) => + { + this.plugin.Call("editAttributes", payload); + }); + } + } +} \ No newline at end of file diff --git a/Assets/UrbanAirship/Platforms/AirshipInApp.cs b/Assets/UrbanAirship/Platforms/AirshipInApp.cs new file mode 100644 index 00000000..e5dede25 --- /dev/null +++ b/Assets/UrbanAirship/Platforms/AirshipInApp.cs @@ -0,0 +1,54 @@ +/* Copyright Airship and Contributors */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UrbanAirship { + + /// + /// Airship InApp Experiences. + /// + public class AirshipInApp { + + private IAirshipPlugin plugin; + + internal AirshipInApp (IAirshipPlugin plugin) { + this.plugin = plugin; + } + + /// + /// Pauses/resumes messages. + /// + /// true to pause, false to resume. + public void SetPaused (bool paused) { + this.plugin.Call ("setPaused", paused); + } + + /// + /// Checks if messages are paused. + /// + /// true if paused, otherwise false + public bool IsPaused () { + return this.plugin.Call ("isPaused"); + } + + /// + /// Sets the display interval for messages. + /// + /// The display interval. + public void SetDisplayInterval (TimeSpan displayInterval) { + this.plugin.Call ("setDisplayInterval", displayInterval); + } + + /// + /// Gets the messages display interval. + /// + /// The display interval. + public TimeSpan GetDisplayInterval () { + return this.plugin.Call ("getDisplayInterval"); + } + } +} \ No newline at end of file diff --git a/Assets/UrbanAirship/Platforms/AirshipLocale.cs b/Assets/UrbanAirship/Platforms/AirshipLocale.cs new file mode 100644 index 00000000..f06cedd4 --- /dev/null +++ b/Assets/UrbanAirship/Platforms/AirshipLocale.cs @@ -0,0 +1,50 @@ +/* Copyright Airship and Contributors */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UrbanAirship +{ + + /// + /// Airship Locale. Manages locale used by Airship messaging. + /// + public class AirshipLocale + { + private IAirshipPlugin plugin; + + internal AirshipLocale(IAirshipPlugin plugin) + { + this.plugin = plugin; + } + + /// + /// Sets the locale override. + /// + /// The locale identifier. + public void SetLocaleOverride(string localeIdentifier) + { + plugin.Call("setLocaleOverride", localeIdentifier); + } + + /// + /// Clears the locale override. + /// + public void ClearLocaleOverride() + { + plugin.Call("clearLocaleOverride"); + } + + /// + /// Gets the current locale. + /// + /// The current locale. + public string GetLocale() + { + return plugin.Call("getLocale"); + } + } +} \ No newline at end of file diff --git a/Assets/UrbanAirship/Platforms/AirshipMessageCenter.cs b/Assets/UrbanAirship/Platforms/AirshipMessageCenter.cs new file mode 100644 index 00000000..485e96fa --- /dev/null +++ b/Assets/UrbanAirship/Platforms/AirshipMessageCenter.cs @@ -0,0 +1,235 @@ +/* Copyright Airship and Contributors */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UrbanAirship +{ + + /// + /// Airship Message Center. + /// + public class AirshipMessageCenter + { + + private IAirshipPlugin plugin; + + internal AirshipMessageCenter(IAirshipPlugin plugin) + { + this.plugin = plugin; + } + + /// + /// Gets the number of unread messages for the message center. + /// + /// The number of unread messages. + public int GetUnReadCount() + { + return plugin.Call("getUnreadCount"); + } + + /// + /// Gets the inbox messages. + /// + /// An enumberable list of InboxMessage objects. + public IEnumerable GetMessages() + { + var inboxMessages = new List(); + + string inboxMessagesAsJson = plugin.Call("getMessages"); + InternalInboxMessage[] internalInboxMessages = JsonArray.FromJson(inboxMessagesAsJson).values; + + // Unity's JsonUtility doesn't support embedded dictionaries - constructor will create the extras dictionary + foreach (InternalInboxMessage internalInboxMessage in internalInboxMessages) + { + inboxMessages.Add(new InboxMessage(internalInboxMessage)); + } + return inboxMessages; + } + + /// + /// Mark an inbox message as having been read. + /// + /// The messageId for the message. + public void MarkMessageRead(string messageId) + { + plugin.Call("markMessageRead", messageId); + } + + /// + /// Delete an inbox message. + /// + /// The messageId for the message. + public void DeleteMessage(string messageId) + { + plugin.Call("deleteMessage", messageId); + } + + /// + /// Refreshes the inbox. + /// + public void RefreshInbox() + { + plugin.Call("refreshMessages"); + } + + /// + /// Sets the default behavior when the message center is launched from a push notification. + /// + /// true to automatically launch the default message center. If false the message center must be manually launched by the app. + public void SetAutoLaunchDefaultMessageCenter(bool enabled) + { + plugin.Call("setAutoLaunchDefaultMessageCenter", enabled); + } + + /// + /// Requests to display the Message Center. + /// + /// Will either emit an event to display the Message Center if auto launch message center is disabled, or display the OOTB message center. + /// + /// Optional message Id. + public void Display(string? messageId) + { + plugin.Call("displayMessageCenter", messageId); + } + + /// + /// Dismisses the OOTB message center if displayed. + /// + public void Dismiss() + { + plugin.Call("dismissMessageCenter"); + } + + /// + /// Overlays the message view. Should be used to display the actual message body in a custom Message Center. + /// + /// The message Id. + public void ShowMessageView(string messageId) + { + plugin.Call("showMessageView", messageId); + } + + /// + /// Overlays the message center regardless if auto launch Message Center is enabled or not. + /// + /// Optional message Id. + public void ShowMessageCenter(string? messageId) + { + plugin.Call("showMessageCenter", messageId); + } + + } + + public class InboxMessage + { + public readonly string id; + public readonly string title; + public readonly long sentDate; + public readonly bool isRead; + public readonly bool isDeleted; + public readonly Dictionary extras; + + internal InboxMessage(string id, string title, long sentDate, bool isRead, bool isDeleted, Dictionary extras) + { + this.id = id; + this.title = title; + this.sentDate = sentDate; + this.isRead = isRead; + this.isDeleted = isDeleted; + this.extras = extras; + } + + public InboxMessage(InternalInboxMessage internalInboxMessage) + { + sentDate = internalInboxMessage.sentDate; + id = internalInboxMessage.id; + title = internalInboxMessage.title; + isRead = internalInboxMessage.isRead; + isDeleted = internalInboxMessage.isDeleted; + + if (internalInboxMessage.extrasKeys != null && internalInboxMessage.extrasKeys.Count > 0) + { + // Unity's JsonUtility doesn't support embedded dictionaries - create the extras dictionary manually + extras = new Dictionary(); + for (int index = 0; index < internalInboxMessage.extrasKeys.Count; index++) + { + extras[internalInboxMessage.extrasKeys[index]] = internalInboxMessage.extrasValues[index]; + } + } + } + + public override bool Equals(object other) + { + var that = other as InboxMessage; + + if (that == null) + { + return false; + } + + if (this.id != that.id) + { + return false; + } + if (this.title != that.title) + { + return false; + } + if (this.sentDate != that.sentDate) + { + return false; + } + if (this.isRead != that.isRead) + { + return false; + } + if (this.isDeleted != that.isDeleted) + { + return false; + } + if ((this.extras == null ^ that.extras == null) || + ((this.extras != that.extras) && + (this.extras.Count != that.extras.Count || this.extras.Except(that.extras).Any()))) + { + return false; + } + + return true; + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = (id != null ? id.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (title != null ? title.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ sentDate.GetHashCode(); + hashCode = (hashCode * 397) ^ isRead.GetHashCode(); + hashCode = (hashCode * 397) ^ isDeleted.GetHashCode(); + hashCode = (hashCode * 397) ^ (extras != null ? extras.GetHashCode() : 0); + return hashCode; + } + } + } + + [Serializable] + public class InternalInboxMessage { + public string id; + public string title; + public long sentDate; + public bool isRead; + public bool isDeleted; + public List extrasKeys; + public List extrasValues; + } + + [Serializable] + public class MessageCounts { + public uint unread; + public uint total; + } +} \ No newline at end of file diff --git a/Assets/UrbanAirship/Platforms/AirshipPreferenceCenter.cs b/Assets/UrbanAirship/Platforms/AirshipPreferenceCenter.cs new file mode 100644 index 00000000..7699289e --- /dev/null +++ b/Assets/UrbanAirship/Platforms/AirshipPreferenceCenter.cs @@ -0,0 +1,217 @@ +/* Copyright Airship and Contributors */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UrbanAirship +{ + + /// + /// Airship Preference Center. + /// + public class AirshipPreferenceCenter + { + private IAirshipPlugin plugin; + + internal AirshipPreferenceCenter(IAirshipPlugin plugin) + { + this.plugin = plugin; + } + + /// + /// Requests to display a preference center. + /// + /// Will either emit an event to display the Preference Center if auto launch is disabled, or display the OOTB UI. + /// + /// The preference center Id. + public void Display(string preferenceCenterId) + { + plugin.Call("displayPreferenceCenter", preferenceCenterId); + } + + /// + /// Gets the preference center config. + /// + /// The preference center Id. + /// The preference center config. + public PreferenceCenterConfig GetConfig(string preferenceCenterId) + { + // TODO parse this from a Json into a PreferenceCenterConfig and return that + return plugin.Call("getPreferenceCenterConfig", preferenceCenterId); + } + + /// + /// Enables or disables showing the OOTB UI when requested to display by either + /// `Display` or by a notification with some other action. + /// + /// The preference center Id. + /// true to show OOTB UI, false to emit events. + public void SetAutoLaunchDefaultPreferenceCenter(string preferenceCenterId, bool autoLaunch) + { + plugin.Call("setAutoLaunchDefaultPreferenceCenter", preferenceCenterId, autoLaunch); + } + } + + [Serializable] + public class PreferenceCenterConfig + { + public string id; + public List sections; + [SerializeField] + public PreferenceCenterCommonDisplay? display; + } + + [Serializable] + public class PreferenceCenterConditionState + { + public bool notificationOptIn; + } + + [Serializable] + public enum PreferenceCenterConditionType + { + notificationOptIn + } + + [Serializable] + public abstract class PreferenceCenterCondition + { + public PreferenceCenterConditionType? type = null; + } + + [Serializable] + enum PreferenceCenterConditionOptIn + { + optIn, + optOut + } + + [Serializable] + class PreferenceCenterNotificationOptInCondition : PreferenceCenterCondition + { + public readonly override PreferenceCenterConditionType type = PreferenceCenterConditionType.notificationOptIn; + public PreferenceCenterConditionOptIn whenStatus; + } + + [Serializable] + public class PreferenceCenterCommonDisplay + { + public string title; + public string subtitle; + } + + [Serializable] + public class PreferenceCenterIconDisplay : PreferenceCenterCommonDisplay + { + public override string title; + public override string subtitle; + public string icon; + } + + [Serializable] + public enum PreferenceCenterSectionType + { + CommonSection, + LabeledSectionBreak + } + + [Serializable] + public abstract class PreferenceCenterSection + { + public PreferenceCenterSectionType type; + public PreferenceCenterCommonDisplay? display; + public List? items; + public List? conditions; + } + + [Serializable] + public class PreferenceCenterCommonSection : PreferenceCenterSection + { + public readonly override PreferenceCenterSectionType type = PreferenceCenterSectionType.CommonSection; + public readonly override PreferenceCenterCommonDisplay? display; + public readonly override List? items; + public readonly override List? conditions; + } + + [Serializable] + public class PreferenceCenterLabeledSectionBreak : PreferenceCenterSection + { + public readonly override PreferenceCenterSectionType type = PreferenceCenterSectionType.LabeledSectionBreak; + public readonly override PreferenceCenterCommonDisplay? display; + public readonly override List? items = null; + public readonly override List? conditions; + } + + [Serializable] + public enum PreferenceCenterItemType + { + ChannelSubscription, + ContactSubscription, + ContactSubscriptionGroup, + Alert + } + + [Serializable] + public abstract class PreferenceCenterItem + { + public PreferenceCenterItemType type; + public PreferenceCenterCommonDisplay display; + public List? conditions; + } + + [Serializable] + public class PreferenceCenterAlertItemButton + { + public string text; + public string contentDescription; + public Map actions; + } + + [Serializable] + public class PreferenceCenterAlertItem : PreferenceCenterItem + { + public readonly override PreferenceCenterItemType type = PreferenceCenterItemType.Alert; + public readonly override PreferenceCenterCommonDisplay display; + public readonly override List? conditions; + public readonly PreferenceCenterAlertItemButton? button; + } + + [Serializable] + public class PreferenceCenterChannelSubscriptionItem : PreferenceCenterItem + { + public readonly override PreferenceCenterItemType type = PreferenceCenterItemType.ChannelSubscription; + public readonly override PreferenceCenterCommonDisplay display; + public readonly override List? conditions; + public readonly string subscriptionId; + } + + [Serializable] + public class PreferenceCenterContactSubscriptionItem : PreferenceCenterItem + { + public readonly override PreferenceCenterItemType type = PreferenceCenterItemType.ContactSubscription; + public readonly override PreferenceCenterCommonDisplay display; + public readonly override List? conditions; + public readonly string subscriptionId; + public readonly List scopes; + } + + [Serializable] + public class PreferenceCenterContactSubscriptionGroupItemComponent + { + public List scopes; + public PreferenceCenterCommonDisplay display; + } + + [Serializable] + public class PreferenceCenterContactSubscriptionGroupItem : PreferenceCenterItem + { + public readonly override PreferenceCenterItemType type = PreferenceCenterItemType.ContactSubscriptionGroup; + public readonly override PreferenceCenterCommonDisplay display; + public readonly override List? conditions; + public readonly string subscriptionId; + public readonly List components; + } +} \ No newline at end of file diff --git a/Assets/UrbanAirship/Platforms/AirshipPrivacyManager.cs b/Assets/UrbanAirship/Platforms/AirshipPrivacyManager.cs new file mode 100644 index 00000000..d8375c74 --- /dev/null +++ b/Assets/UrbanAirship/Platforms/AirshipPrivacyManager.cs @@ -0,0 +1,71 @@ +/* Copyright Airship and Contributors */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UrbanAirship +{ + + /// + /// Airship Privacy Manager. + /// + public class AirshipPrivacyManager + { + private IAirshipPlugin plugin; + + internal AirshipPrivacyManager(IAirshipPlugin plugin) + { + this.plugin = plugin; + } + + /// + /// Sets the current set of enabled features. + /// + /// The features to set. + public void SetEnabledFeatures(string[] enabledFeatures) + { + plugin.Call("setEnabledFeatures", enabledFeatures); + } + + /// + /// Gets the current enabled features. + /// + /// The current enabled features. + public string[] GetEnabledFeatures() + { + return plugin.Call("getEnabledFeatures"); + } + + /// + /// Enables additional features. + /// + /// The features to enable. + public void EnableFeatures(string[] features) + { + plugin.Call("enableFeatures", features); + } + + /// + /// Disable features. + /// + /// The features to disable. + public void DisableFeatures(string[] features) + { + plugin.Call("disableFeatures", features); + } + + /// + /// Checks if the features are enabled or not. + /// + /// The features to check + /// true if the features are enabled, otherwise false + public bool IsFeaturesEnabled(string[] features) + { + return plugin.Call("isFeaturesEnabled", features); + } + } +} +// TODO check if we can use an enum for the features \ No newline at end of file diff --git a/Assets/UrbanAirship/Platforms/AirshipPush.cs b/Assets/UrbanAirship/Platforms/AirshipPush.cs new file mode 100644 index 00000000..2c34bbaa --- /dev/null +++ b/Assets/UrbanAirship/Platforms/AirshipPush.cs @@ -0,0 +1,443 @@ +/* Copyright Airship and Contributors */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UrbanAirship +{ + + /// + /// Airship Push. + /// + public class AirshipPush + { + private IAirshipPlugin plugin; + + /// + /// iOS only push methods. + /// + public readonly AirshipPushIOS iOS; + + /// + /// Android only push methods. + /// + public readonly AirshipPushAndroid android; + + internal AirshipPush(IAirshipPlugin plugin) + { + this.plugin = plugin; + iOS = new AirshipPushIOS(plugin); + android = new AirshipPushAndroid(plugin); + } + + /// + /// Enables/disables notifications on Airship. + /// + /// When enabled, it will cause the user to be prompted for + /// the permission on platforms that support it. + /// To get the result of the prompt, use `enableUserNotifications`. + /// + /// true to enable, false to disable. + public void SetUserNotificationsEnabled(bool enabled) + { + plugin.Call("setUserNotificationsEnabled", enabled); + } + + /// + /// Checks if user notifications are enabled or not on Airship. + /// + /// true if user notifications are enabled, otherwise false. + public bool IsUserNotificationEnabled() + { + return plugin.Call("isUserNotificationsEnabled"); + } + + /// + /// Enables user notifications. + /// + /// Optional fallback. + /// The permission result. + public bool EnableUserNotifications(PromptPermissionFallback? fallback) + { + return plugin.Call("enableUserNotifications", fallback); + } + + /// + /// Gets the notification status. + /// + /// The notification status. + public PushNotificationStatus GetNotificationStatus() + { + // TODO parse this and return a PushNotificationStatus + plugin.Call("getNotificationStatus"); + return; + } + + /// + /// Gets the registration token if generated. + /// + /// The push token. + public string GetPushToken() + { + return plugin.Call("getPushToken"); + } + + /// + /// Gets the list of active notifications. + /// + /// On Android, this list only includes notifications sent through Airship. + /// + /// The list of active notifications. + public IEnumerable GetActiveNotifications() + { + string jsonPushMessages = plugin.Call("getActiveNotifications"); + if (String.IsNullOrEmpty(jsonPushMessages)) + { + return null; + } + + var pushMessages = new List(); + foreach (string pushMessageAsJson in JsonArray.FromJson(pushMessagesAsJson).values) + { + pushMessages.Add(PushMessage.FromJson(pushMessageAsJson)); + } + + return pushMessages; + } + + /// + /// Clears all notifications for the app. + /// + public void ClearNotifications() + { + plugin.Call("clearNotifications"); + } + + /// + /// Clears a specific notification. + /// + /// On Android, you can use this method to clear notifications outside of Airship. + /// The identifier is in the format of :. + /// + /// The identifier. + public void ClearNotification(string identifier) + { + plugin.Call("clearNotification", identifier); + } + } + + /// + /// IOS Push. + /// + public class AirshipPushIOS + { + private IAirshipPlugin plugin; + + internal AirshipPushIOS(IAirshipPlugin plugin) + { + this.plugin = plugin; + } + + /// + /// Sets the foreground presentation options. + /// + /// The foreground options. + public void SetForegroundPresentationOptions(ForegroundPresentationOption options) + { + plugin.Call("setForegroundPresentationOptions", options); + } + + /// + /// Sets the notification options. + /// + /// The notification options. + public void SetNotificationOptions(NotificationOption[] options) + { + plugin.Call("setNotificationOptions", options); + } + + /// + /// Checks if autobadge is enabled. + /// + /// true if autobadge is enabled, otherwise false. + public bool IsAutobadgeEnabled() + { + return plugin.Call("isAutobadgeEnabled"); + } + + /// + /// Enables/disables autobadge. + /// + /// true to enable, false to disable. + public void SetAutobadgeEnabled(bool enabled) + { + plugin.Call("setAutobadgeEnabled", enabled); + } + + /// + /// Set the badge number. + /// + /// The badge number. + public void SetBadgeNumber(int badge) + { + plugin.Call("setBadgeNumber", badge); + } + + /// + /// Gets the badge number. + /// + /// The badge number. + public int GetBadgeNumber() + { + return plugin.Call("getBadgeNumber"); + } + + /// + /// Enables/disables quiet time. + /// + /// true to enable, false to disable. + public void SetQuietTimeEnabled(bool enabled) + { + plugin.Call("setQuietTimeEnabled", enabled); + } + + /// + /// Checks if quiet time is enabled or not. + /// + /// true if quiet time is enabled, otherwise false. + public bool IsQuietTimeEnabled() + { + return plugin.Call("isQuietTimeEnabled"); + } + + /// + /// Sets quiet time. + /// + /// The quiet time. + public void SetQuietTime(QuietTime quietTime) + { + plugin.Call("setQuietTime", quietTime); + } + + /// + /// Gets the quiet time settings. + /// + /// The quiet time. + public QuietTime? GetQuietTime() + { + return plugin.Call("getQuietTime"); + } + } + + /// + /// Android Push. + /// + public class AirshipPushAndroid + { + private IAirshipPlugin plugin; + + internal AirshipPushAndroid(IAirshipPlugin plugin) + { + this.plugin = plugin; + } + + /// + /// Checks if a notification category/channel is enabled. + /// + /// The channel name. + /// true if the channel is enabled, otherwise false. + public bool IsNotificationChannelEnabled(string channel) + { + return plugin.Call("isNotificationChannelEnabled", channel); + } + + /// + /// Sets the notification config. + /// + /// The notification config. + public void SetNotificationConfig(NotificationConfig config) + { + plugin.Call("setNotificationConfig", config); + } + + /// + /// Enables/disables showing notifications in the foreground. + /// + /// true to enable, false to disable. + public void SetForegroundNotificationsEnabled(bool enabled) + { + plugin.Call("setForegroundNotificationsEnabled", enabled); + } + } + + /// + /// Push notification status object. + /// + [Serializable] + public record PushNotificationStatus + { + /// + /// If user notifications are enabled. + /// + public bool isUserNotificationsEnabled; + + /// + /// If notifications are allowed at the system level for the application. + /// + public bool areNotificationsAllowed; + + /// + /// If the push feature is enabled on PrivacyManager. + /// + public bool isPushPrivacyFeatureEnabled; + + /// + /// If push registration was able to generate a token. + /// + public bool isPushTokenRegistered; + + /// + /// If Airship is able to send and display a push notification. + /// + public bool isOptedIn; + + /// + /// Checks for isUserNotificationsEnabled, areNotificationsAllowed, and isPushPrivacyFeatureEnabled. + /// If this flag is true but `isOptedIn` is false, that means push token was not able to be registered. + /// + public bool isUserOptedIn; + + /// + /// The notification permission status. + /// + public PermissionStatus notificationPermissionStatus; + } + + /// + /// Enum of permission status. + /// + [Serializable] + public enum PermissionStatus + { + // Permission is granted. + Granted = "granted", + // Permission is denied. + Denied = "denied", + // Permission has not yet been requested. + NotDetermined = "not_determined", + } + + /// + /// Fallback when prompting for permission and the permission is + /// already denied on iOS or is denied silently on Android. + /// + [Serializable] + public enum PromptPermissionFallback + { + // Take the user to the system settings to enable the permission. + SystemSettings = "systemSettings" + } + + /// + /// Enum of foreground notification options. + /// + [Serializable] + public enum ForegroundPresentationOption + { + // Play the sound associated with the notification. + Sound = "sound", + + // Apply the notification's badge value to the app’s icon. + Badge = "badge", + + // Show the notification in Notification Center. On iOS 13 an older, + // this will also show the notification as a banner. + List = "list", + + // Present the notification as a banner. On iOS 13 an older, + // this will also show the notification in the Notification Center. + Banner = "banner", + } + + /// + /// Enum of notification options. iOS only. + /// + [Serializable] + public enum NotificationOption + { + // Alerts. + Alert = "alert", + // Sounds. + Sound = "sound", + // Badges. + Badge = "badge", + // Car play. + CarPlay = "car_play", + // Critical Alert. + CriticalAlert = "critical_alert", + // Provides app notification settings. + ProvidesAppNotificationSettings = "provides_app_notification_settings", + // Provisional. + Provisional = "provisional" + } + + public record QuietTime + { + // Start hour. Must be 0-23. + public int startHour; + + // Start minute. Must be 0-59. + public int startMinute; + + // End hour. Must be 0-23. + public int endHour; + + // End minute. Must be 0-59. + public int endMinute; + } + + /// + /// Enum of authorized notification options. + /// + public enum AuthorizedNotificationSetting + { + // Alerts. + Alert = "alert", + // Sounds. + Sound = "sound", + // Badges. + Badge = "badge", + // CarPlay. + CarPlay = "car_play", + // Lock screen. + LockScreen = "lock_screen", + // Notification center. + NotificationCenter = "notification_center", + // Critical alert. + CriticalAlert = "critical_alert", + // Announcement. + Announcement = "announcement", + // Scheduled delivery. + ScheduledDelivery = "scheduled_delivery", + // Time sensitive. + TimeSensitive = "time_sensitive", + } + + public record NotificationConfig + { + // The icon resource name. + public string? icon; + + // The large icon resource name. + public string? largeIcon; + + // The default android notification channel ID. + public string? defaultChannelId; + + // The accent color. Must be a hex value #AARRGGBB. + public string? accentColor; + } +} \ No newline at end of file diff --git a/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs b/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs new file mode 100644 index 00000000..d480b31d --- /dev/null +++ b/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs @@ -0,0 +1,94 @@ +/* Copyright Airship and Contributors */ + +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace UrbanAirship { + + internal interface IAirshipPlugin { + + GameObject Listener { + set; + } + + void Call (string method, params object[] args); + + T Call (string method, params object[] args); + } + + internal class StubbedAirshipPlugin : IAirshipPlugin { + public GameObject Listener { set; private get; } + public void Call (string method, params object[] args) {} + public T Call (string method, params object[] args) { return default(T); } + } + + #if UNITY_ANDROID + + internal class AirshipPluginAndroid : IAirshipPlugin { + + private AndroidJavaObject androidPlugin; + + public AirshipPluginAndroid () { + try { + using (AndroidJavaClass pluginClass = new AndroidJavaClass ("com.urbanairship.unityplugin.UnityPlugin")) { + androidPlugin = pluginClass.CallStatic ("shared"); + } + } catch (Exception e) { + Debug.LogError ("Airship plugin not found : " + e); + } + } + + public void Call (string method, params object[] args) { + if (androidPlugin != null) { + androidPlugin.Call (method, args); + } + } + + public T Call (string method, params object[] args) { + if (androidPlugin != null) { + return androidPlugin.Call (method, args); + } + return default(T); + } + + public GameObject Listener { + set { + Call ("setListener", value.name); + } + } + } + + #endif + + #if UNITY_IOS + + internal class AirshipPluginiOS : IAirshipPlugin{ + + // TODO finish the iOS plugin setup + // private IAirshipPlugin plugin; + + // public AirshipPluginiOS (IAirshipPlugin plugin) { + // this.plugin = plugin; + // } + + public void Call (string method, params object[] args) { + + } + + public T Call (string method, params object[] args) { + + return default(T); + } + + public GameObject Listener { + set { + Call ("setListener", value.name); + } + } + } + + #endif + +} diff --git a/Assets/UrbanAirship/Platforms/ScopedSubscriptionListEditor.cs b/Assets/UrbanAirship/Platforms/ScopedSubscriptionListEditor.cs new file mode 100644 index 00000000..659eb694 --- /dev/null +++ b/Assets/UrbanAirship/Platforms/ScopedSubscriptionListEditor.cs @@ -0,0 +1,94 @@ +/* Copyright Airship and Contributors */ + +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UrbanAirship { + /// + /// An editor for subscription lists. + /// + public class ScopedSubscriptionListEditor { + private Action onApply; + private IList operations = new List (); + + internal ScopedSubscriptionListEditor (Action onApply) { + this.onApply = onApply; + } + + /// + /// Subscribes to a list. + /// + /// The subscription list editor. + /// The subscription list identifier. + /// The subscription scope to unsubscribe. + public ScopedSubscriptionListEditor subscribe (string subscriptionListId, string subscriptionScope) { + operations.Add (new ScopedSubscriptionListOperation ("subscribe", subscriptionListId, subscriptionScope)); + return this; + } + + /// + /// Unsubscribes from a list. + /// + /// The subscription list editor. + /// The subscription list identifier. + /// The subscription scope to unsubscribe. + public ScopedSubscriptionListEditor unsubscribe (string subscriptionListId, string subscriptionScope) { + operations.Add (new ScopedSubscriptionListOperation ("unsubscribe", subscriptionListId, subscriptionScope)); + return this; + } + + /// + /// Applies pending changes. + /// + public void Apply () { + if (onApply != null) { + JsonArray jsonArray = new JsonArray (); + jsonArray.values = operations.ToArray (); + onApply (jsonArray.ToJson ()); + } + } + + [Serializable] + internal class ScopedSubscriptionListOperation { +#pragma warning disable + // Used for JSON encoding/decoding + + [SerializeField] + private string action; + + [SerializeField] + private string listId; + + [SerializeField] + private string scope; +#pragma warning restore + + public ScopedSubscriptionListOperation (string action, string listId, string scope) { + this.action = action; + this.listId = listId; + this.scope = scope; + } + } + } + + /// + /// Subscription Scope types. + /// + public static class SubscriptionScope { + public const string APP = "app"; + public const string WEB = "web"; + public const string SMS = "sms"; + public const string EMAIL = "email"; + } + + /// + /// Scoped Subscription list. + /// + [Serializable] + internal class ScopedSubscriptionList { + public string listId; + public List scopes; + } +} diff --git a/Assets/UrbanAirship/Platforms/SubscriptionListEditor.cs b/Assets/UrbanAirship/Platforms/SubscriptionListEditor.cs new file mode 100644 index 00000000..e6d4c6d4 --- /dev/null +++ b/Assets/UrbanAirship/Platforms/SubscriptionListEditor.cs @@ -0,0 +1,69 @@ +/* Copyright Airship and Contributors */ + +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UrbanAirship { + /// + /// An editor for subscription lists. + /// + public class SubscriptionListEditor { + private Action onApply; + private IList operations = new List (); + + internal SubscriptionListEditor (Action onApply) { + this.onApply = onApply; + } + + /// + /// Subscribes to a list. + /// + /// The subscription list editor. + /// The subscription list identifier. + public SubscriptionListEditor subscribe (string subscriptionListId) { + operations.Add (new SubscriptionListOperation ("subscribe", subscriptionListId)); + return this; + } + + /// + /// Unsubscribes from a list. + /// + /// The subscription list editor. + /// The subscription list identifier. + public SubscriptionListEditor unsubscribe (string subscriptionListId) { + operations.Add (new SubscriptionListOperation ("unsubscribe", subscriptionListId)); + return this; + } + + /// + /// Applies pending changes. + /// + public void Apply () { + if (onApply != null) { + JsonArray jsonArray = new JsonArray (); + jsonArray.values = operations.ToArray (); + onApply (jsonArray.ToJson ()); + } + } + + [Serializable] + internal class SubscriptionListOperation { +#pragma warning disable + // Used for JSON encoding/decoding + + [SerializeField] + private string action; + + [SerializeField] + private string listId; +#pragma warning restore + + public SubscriptionListOperation (string action, string listId) { + this.action = action; + this.listId = listId; + } + } + } +} diff --git a/Assets/UrbanAirship/Platforms/TagEditor.cs b/Assets/UrbanAirship/Platforms/TagEditor.cs new file mode 100644 index 00000000..a4c7061d --- /dev/null +++ b/Assets/UrbanAirship/Platforms/TagEditor.cs @@ -0,0 +1,89 @@ +/* Copyright Airship and Contributors */ + +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UrbanAirship { + /// + /// An editor for tags. + /// + public class TagEditor { + private Action onApply; + private IList operations = new List (); + + internal TagEditor (Action onApply) { + this.onApply = onApply; + } + + /// + /// Adds the provided tag. + /// + /// The tag editor. + /// The tag to add. + public TagEditor AddTag (string tag) { + AddTags (new List (new [] { tag })); + return this; + } + + /// + /// Adds the provided tags. + /// + /// The tag editor. + /// The tags to add. + public TagEditor AddTags (ICollection tags) { + operations.Add (new TagOperation ("add", tags)); + return this; + } + + /// + /// Removes the provided tag. + /// + /// The tag editor. + /// The tag to remove. + public TagEditor RemoveTag (string tag) { + RemoveTags (new List (new [] { tag })); + return this; + } + + /// + /// Removes the provided tags. + /// + /// The tag editor. + /// The tags to remove. + public TagEditor RemoveTags (ICollection tags) { + operations.Add (new TagOperation ("remove", tags)); + return this; + } + + /// + /// Applies pending changes. + /// + public void Apply () { + if (onApply != null) { + JsonArray jsonArray = new JsonArray (); + jsonArray.values = operations.ToArray (); + onApply (jsonArray.ToJson ()); + } + } + + [Serializable] + internal class TagOperation { +#pragma warning disable + // Used for JSON encoding/decoding + + [SerializeField] + private string operation; + + [SerializeField] + private string[] tags; +#pragma warning restore + + public TagOperation (string operation, ICollection tags) { + this.operation = operation; + this.tags = tags.ToArray (); + } + } + } +} diff --git a/Packages/manifest.json b/Packages/manifest.json index 12ad7e3f..5907a3ae 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -1,6 +1,8 @@ { "dependencies": { - "com.unity.test-framework": "1.1.33", + "com.unity.multiplayer.center": "1.0.0", + "com.unity.test-framework": "1.4.5", + "com.unity.modules.accessibility": "1.0.0", "com.unity.modules.ai": "1.0.0", "com.unity.modules.androidjni": "1.0.0", "com.unity.modules.animation": "1.0.0", diff --git a/ProjectSettings/MultiplayerManager.asset b/ProjectSettings/MultiplayerManager.asset new file mode 100644 index 00000000..2a936644 --- /dev/null +++ b/ProjectSettings/MultiplayerManager.asset @@ -0,0 +1,7 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!655991488 &1 +MultiplayerManager: + m_ObjectHideFlags: 0 + m_EnableMultiplayerRoles: 0 + m_StrippingTypes: {} diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index 78a574af..3a61363a 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -3,7 +3,7 @@ --- !u!129 &1 PlayerSettings: m_ObjectHideFlags: 0 - serializedVersion: 26 + serializedVersion: 28 productGUID: 385d30ccc451f4056ac2a61ff15dad78 AndroidProfiler: 0 AndroidFilterTouchesWhenObscured: 0 @@ -48,6 +48,8 @@ PlayerSettings: defaultScreenHeightWeb: 600 m_StereoRenderingPath: 0 m_ActiveColorSpace: 0 + unsupportedMSAAFallback: 0 + m_SpriteBatchMaxVertexCount: 65535 m_SpriteBatchVertexThreshold: 300 m_MTRendering: 1 mipStripping: 0 @@ -69,16 +71,18 @@ PlayerSettings: androidRenderOutsideSafeArea: 1 androidUseSwappy: 1 androidBlitType: 0 - androidResizableWindow: 0 + androidResizeableActivity: 0 androidDefaultWindowWidth: 1920 androidDefaultWindowHeight: 1080 androidMinimumWindowWidth: 400 androidMinimumWindowHeight: 300 androidFullscreenMode: 1 + androidAutoRotationBehavior: 1 + androidPredictiveBackSupport: 0 + androidApplicationEntry: 1 defaultIsNativeResolution: 1 macRetinaSupport: 1 runInBackground: 0 - captureSingleScreen: 0 muteOtherAudioSources: 0 Prepare IOS For Recording: 0 Force IOS Speakers When Recording: 0 @@ -94,6 +98,7 @@ PlayerSettings: useMacAppStoreValidation: 0 macAppStoreCategory: public.app-category.games gpuSkinning: 0 + meshDeformation: 0 xboxPIXTextureCapture: 0 xboxEnableAvatar: 0 xboxEnableKinect: 0 @@ -125,16 +130,16 @@ PlayerSettings: switchAllowGpuScratchShrinking: 0 switchNVNMaxPublicTextureIDCount: 0 switchNVNMaxPublicSamplerIDCount: 0 - switchNVNGraphicsFirmwareMemory: 32 switchMaxWorkerMultiple: 8 - stadiaPresentMode: 0 - stadiaTargetFramerate: 0 + switchNVNGraphicsFirmwareMemory: 32 vulkanNumSwapchainBuffers: 3 vulkanEnableSetSRGBWrite: 0 vulkanEnablePreTransform: 0 vulkanEnableLateAcquireNextImage: 0 vulkanEnableCommandBufferRecycling: 1 loadStoreDebugModeEnabled: 0 + visionOSBundleVersion: 1.0 + tvOSBundleVersion: 1.0 bundleVersion: 1.0 preloadedAssets: [] metroInputSource: 0 @@ -156,8 +161,10 @@ PlayerSettings: resetResolutionOnWindowResize: 0 androidSupportedAspectRatio: 1 androidMaxAspectRatio: 2.1 + androidMinAspectRatio: 1 applicationIdentifier: Android: com.DefaultCompany.uaunityplugin + iPhone: com.DefaultCompany.ua-unity-plugin buildNumber: Standalone: 0 VisionOS: 0 @@ -165,7 +172,7 @@ PlayerSettings: tvOS: 0 overrideDefaultApplicationIdentifier: 0 AndroidBundleVersionCode: 1 - AndroidMinSdkVersion: 22 + AndroidMinSdkVersion: 23 AndroidTargetSdkVersion: 0 AndroidPreferredInstallLocation: 1 aotOptions: @@ -175,16 +182,18 @@ PlayerSettings: ForceInternetPermission: 0 ForceSDCardPermission: 0 CreateWallpaper: 0 - APKExpansionFiles: 0 + androidSplitApplicationBinary: 0 keepLoadedShadersAlive: 0 StripUnusedMeshComponents: 0 strictShaderVariantMatching: 0 VertexChannelCompressionMask: 4054 iPhoneSdkVersion: 988 - iOSTargetOSVersionString: 12.0 + iOSSimulatorArchitecture: 0 + iOSTargetOSVersionString: 13.0 tvOSSdkVersion: 0 + tvOSSimulatorArchitecture: 0 tvOSRequireExtendedGameController: 0 - tvOSTargetOSVersionString: 12.0 + tvOSTargetOSVersionString: 13.0 VisionOSSdkVersion: 0 VisionOSTargetOSVersionString: 1.0 uIPrerenderedIcon: 0 @@ -211,7 +220,6 @@ PlayerSettings: rgba: 0 iOSLaunchScreenFillPct: 100 iOSLaunchScreenSize: 100 - iOSLaunchScreenCustomXibPath: iOSLaunchScreeniPadType: 0 iOSLaunchScreeniPadImage: {fileID: 0} iOSLaunchScreeniPadBackgroundColor: @@ -219,7 +227,6 @@ PlayerSettings: rgba: 0 iOSLaunchScreeniPadFillPct: 100 iOSLaunchScreeniPadSize: 100 - iOSLaunchScreeniPadCustomXibPath: iOSLaunchScreenCustomStoryboardPath: iOSLaunchScreeniPadCustomStoryboardPath: iOSDeviceRequirements: [] @@ -229,6 +236,7 @@ PlayerSettings: iOSMetalForceHardShadows: 0 metalEditorSupport: 1 metalAPIValidation: 1 + metalCompileShaderBinary: 0 iOSRenderExtraFrameOnPause: 0 iosCopyPluginsCodeInsteadOfSymlink: 0 appleDeveloperTeamID: @@ -254,13 +262,13 @@ PlayerSettings: useCustomGradlePropertiesTemplate: 0 useCustomGradleSettingsTemplate: 0 useCustomProguardFile: 0 - AndroidTargetArchitectures: 1 - AndroidTargetDevices: 0 + AndroidTargetArchitectures: 3 AndroidSplashScreenScale: 0 androidSplashScreen: {fileID: 0} AndroidKeystoreName: AndroidKeyaliasName: AndroidEnableArmv9SecurityFeatures: 0 + AndroidEnableArm64MTE: 0 AndroidBuildApkPerCpuArchitecture: 0 AndroidTVCompatibility: 0 AndroidIsGame: 1 @@ -273,11 +281,12 @@ PlayerSettings: height: 180 banner: {fileID: 0} androidGamepadSupportLevel: 0 - chromeosInputEmulation: 1 AndroidMinifyRelease: 0 AndroidMinifyDebug: 0 AndroidValidateAppBundleSize: 1 AndroidAppBundleSizeToValidate: 150 + AndroidReportGooglePlayAppDependencies: 1 + androidSymbolsSizeThreshold: 800 m_BuildTargetIcons: [] m_BuildTargetPlatformIcons: - m_BuildTarget: iPhone @@ -487,14 +496,20 @@ PlayerSettings: iPhone: 1 tvOS: 1 m_BuildTargetGroupLightmapEncodingQuality: [] - m_BuildTargetGroupHDRCubemapEncodingQuality: [] m_BuildTargetGroupLightmapSettings: [] m_BuildTargetGroupLoadStoreDebugModeSettings: [] m_BuildTargetNormalMapEncoding: [] - m_BuildTargetDefaultTextureCompressionFormat: [] + m_BuildTargetDefaultTextureCompressionFormat: + - serializedVersion: 3 + m_BuildTarget: iOS + m_Formats: 03000000 + - serializedVersion: 3 + m_BuildTarget: Android + m_Formats: 01000000 playModeTestRunnerEnabled: 0 runPlayModeTestAsEditModeTest: 0 actionOnDotNetUnhandledException: 1 + editorGfxJobOverride: 1 enableInternalProfiler: 0 logObjCUncaughtExceptions: 1 enableCrashReportAPI: 0 @@ -502,7 +517,7 @@ PlayerSettings: locationUsageDescription: microphoneUsageDescription: bluetoothUsageDescription: - macOSTargetOSVersion: 10.13.0 + macOSTargetOSVersion: 11.0 switchNMETAOverride: switchNetLibKey: switchSocketMemoryPoolSize: 6144 @@ -511,7 +526,6 @@ PlayerSettings: switchScreenResolutionBehavior: 2 switchUseCPUProfiler: 0 switchEnableFileSystemTrace: 0 - switchUseGOLDLinker: 0 switchLTOSetting: 0 switchApplicationID: 0x01004b9000490000 switchNSODependencies: @@ -641,12 +655,14 @@ PlayerSettings: switchSocketBufferEfficiency: 4 switchSocketInitializeEnabled: 1 switchNetworkInterfaceManagerInitializeEnabled: 1 + switchDisableHTCSPlayerConnection: 0 switchUseNewStyleFilepaths: 1 switchUseLegacyFmodPriorities: 0 switchUseMicroSleepForYield: 1 switchEnableRamDiskSupport: 0 switchMicroSleepForYieldTime: 25 switchRamDiskSpaceSize: 12 + switchUpgradedPlayerSettingsToNMETA: 0 ps4NPAgeRating: 12 ps4NPTitleSecret: ps4NPTrophyPackPath: @@ -749,13 +765,20 @@ PlayerSettings: webGLMemoryLinearGrowthStep: 16 webGLMemoryGeometricGrowthStep: 0.2 webGLMemoryGeometricGrowthCap: 96 + webGLEnableWebGPU: 0 webGLPowerPreference: 2 + webGLWebAssemblyTable: 0 + webGLWebAssemblyBigInt: 0 + webGLCloseOnQuit: 0 + webWasm2023: 0 scriptingDefineSymbols: {} additionalCompilerArguments: {} platformArchitecture: {} - scriptingBackend: {} + scriptingBackend: + Android: 1 il2cppCompilerConfiguration: {} il2cppCodeGeneration: {} + il2cppStacktraceInformation: {} managedStrippingLevel: {} incrementalIl2cppBuild: {} suppressCommonWarnings: 1 @@ -766,6 +789,7 @@ PlayerSettings: gcIncremental: 1 gcWBarrierValidation: 0 apiCompatibilityLevelPerPlatform: {} + editorAssembliesCompatibilityLevel: 1 m_RenderingPath: 1 m_MobileRenderingPath: 1 metroPackageName: ua-unity-plugin @@ -789,6 +813,7 @@ PlayerSettings: metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0} metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, a: 1} metroSplashScreenUseBackgroundColor: 0 + syncCapabilities: 0 platformCapabilities: {} metroTargetDeviceFamilies: {} metroFTAName: @@ -838,9 +863,11 @@ PlayerSettings: hmiPlayerDataPath: hmiForceSRGBBlit: 1 embeddedLinuxEnableGamepadInput: 1 - hmiLogStartupTiming: 0 hmiCpuConfiguration: + hmiLogStartupTiming: 0 + qnxGraphicConfPath: apiCompatibilityLevel: 6 + captureStartupLogs: {} activeInputHandler: 0 windowsGamepadBackendHint: 0 cloudProjectId: @@ -854,3 +881,5 @@ PlayerSettings: platformRequiresReadableAssets: 0 virtualTexturingSupportEnabled: 0 insecureHttpOption: 0 + androidVulkanDenyFilterList: [] + androidVulkanAllowFilterList: [] diff --git a/ProjectSettings/ProjectVersion.txt b/ProjectSettings/ProjectVersion.txt index 37f39b7b..6539c21f 100644 --- a/ProjectSettings/ProjectVersion.txt +++ b/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 2022.3.11f1 -m_EditorVersionWithRevision: 2022.3.11f1 (d00248457e15) +m_EditorVersion: 6000.0.37f1 +m_EditorVersionWithRevision: 6000.0.37f1 (090b7797214c) diff --git a/ProjectSettings/SceneTemplateSettings.json b/ProjectSettings/SceneTemplateSettings.json index 5e97f839..1edced2a 100644 --- a/ProjectSettings/SceneTemplateSettings.json +++ b/ProjectSettings/SceneTemplateSettings.json @@ -61,6 +61,11 @@ "type": "UnityEngine.PhysicMaterial", "defaultInstantiationMode": 0 }, + { + "userAdded": false, + "type": "UnityEngine.PhysicsMaterial", + "defaultInstantiationMode": 0 + }, { "userAdded": false, "type": "UnityEngine.PhysicsMaterial2D", diff --git a/README.md b/README.md index 7360a215..6231674f 100644 --- a/README.md +++ b/README.md @@ -40,8 +40,8 @@ Download google-services.json into the `Assets` directory from the application's If proguard is enabled, add Airship settings to the proguard-user.txt file. ``` --keep public class com.urbanairship.unityplugin.UnityPlugin --keepclassmembers class com.urbanairship.unityplugin.UnityPlugin { +-keep public class com.urbanairship.unityplugin.OldUnityPlugin +-keepclassmembers class com.urbanairship.unityplugin.OldUnityPlugin { public ; public ; static ; diff --git a/airship.properties b/airship.properties index 0c2c24d8..b170da2d 100644 --- a/airship.properties +++ b/airship.properties @@ -7,8 +7,11 @@ iosAirshipVersion = 16.12.6 # Urban Airship Android SDK version androidAirshipVersion = 16.11.2 +# Airship Framework proxy version +airshipFrameworkProxyVersion = 13.0.1 + # Android X annotations -androidxAnnotationVersion = 1.5.0 +androidxAnnotationVersion = 1.9.1 # Android compile SDK version androidCompileSdkVersion = 34 @@ -17,9 +20,9 @@ androidCompileSdkVersion = 34 androidTargetSdkVersion = 34 # Android min SDK version -androidMinSdkVersion = 21 +androidMinSdkVersion = 23 # Google Play Services Resolver tag playServicesResolver = v1.2.178 -unityVersion = 2022.3.11f1 +unityVersion = 6000.0.37f1 diff --git a/build.gradle b/build.gradle index a1088fe9..6df8bc30 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,28 @@ import java.util.regex.Pattern +buildscript { + repositories { + google() + mavenCentral() + } + + ext { + // Gradle plugins + // gradlePluginVersion = '8.4.0' + } + + // dependencies { + // classpath "com.android.tools.build:gradle:$gradlePluginVersion" + // classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0" + // classpath 'com.android.tools.lint:lint-kotlin:26.2.1' + // } +} + +plugins { + id 'com.android.library' version '8.4.2' apply false + id 'org.jetbrains.kotlin.android' version '1.9.24' apply false +} + allprojects { ext { airshipProperties = new Properties() @@ -11,19 +34,6 @@ configurations { plugin } -repositories { - google() - ivy { - url 'https://github.com' - patternLayout { - artifact '[organisation]/[module]/archive/[revision].[ext]' - } - metadataSources { - artifact() - } - } -} - dependencies { plugin "googlesamples:unity-jar-resolver:${airshipProperties.playServicesResolver}@zip" } @@ -73,6 +83,7 @@ task preparePlugin(dependsOn: 'unity-plugin:assembleRelease') { filter { String line -> line.replaceAll(Pattern.quote("__PLUGIN_VERSION__"), airshipProperties.version) } filter { String line -> line.replaceAll(Pattern.quote("__ANDROID_AIRSHIP_VERSION__"), airshipProperties.androidAirshipVersion) } filter { String line -> line.replaceAll(Pattern.quote("__IOS_AIRSHIP_VERSION__"), airshipProperties.iosAirshipVersion) } + filter { String line -> line.replaceAll(Pattern.quote("__AIRSHIP_FRAMEWORK_PROXY_VERSION__"), airshipProperties.airshipFrameworkProxyVersion) } } // Copy UADependencies.xml and replace the version placeholders @@ -84,6 +95,7 @@ task preparePlugin(dependsOn: 'unity-plugin:assembleRelease') { filter { String line -> line.replaceAll(Pattern.quote("__ANDROID_AIRSHIP_VERSION__"), airshipProperties.androidAirshipVersion) } filter { String line -> line.replaceAll(Pattern.quote("__ANDROID_ANNOTATIONS_VERSION__"), airshipProperties.androidxAnnotationVersion) } filter { String line -> line.replaceAll(Pattern.quote("__IOS_AIRSHIP_VERSION__"), airshipProperties.iosAirshipVersion) } + filter { String line -> line.replaceAll(Pattern.quote("__AIRSHIP_FRAMEWORK_PROXY_VERSION__"), airshipProperties.airshipFrameworkProxyVersion) } } } } diff --git a/docs/build.gradle b/docs/build.gradle index b90d91da..ada8aec7 100644 --- a/docs/build.gradle +++ b/docs/build.gradle @@ -23,10 +23,10 @@ task packageDocs(type: Tar, dependsOn: 'build') { from 'build/html' } - archiveName = "${airshipProperties.version}.tar.gz" + archiveFileName = "${airshipProperties.version}.tar.gz" compression = Compression.GZIP - destinationDir file('build') - extension 'tar.gz' + destinationDirectory = file('build') + archiveExtension.set('tar.gz') compression = Compression.GZIP } diff --git a/gradle.properties b/gradle.properties index 5465fec0..5398fc6e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,3 @@ android.enableJetifier=true -android.useAndroidX=true \ No newline at end of file +android.useAndroidX=true +org.gradle.jvmargs=-Xmx2048m \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ffed3a25..d202d922 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Fri Feb 07 11:23:18 AST 2025 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle b/settings.gradle index 737cd20f..0b7a21c1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,26 @@ +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + ivy { + url 'https://github.com' + patternLayout { + artifact '[organisation]/[module]/archive/[revision].[ext]' + } + metadataSources { + artifact() + } + } + } +} + include ':unity-plugin', ':docs' diff --git a/unity-plugin/build.gradle b/unity-plugin/build.gradle index 36b0740b..8eddc235 100644 --- a/unity-plugin/build.gradle +++ b/unity-plugin/build.gradle @@ -1,55 +1,108 @@ -buildscript { - repositories { - google() - mavenCentral() - } - - ext { - // Gradle plugins - gradlePluginVersion = '7.1.2' - } - dependencies { - classpath "com.android.tools.build:gradle:$gradlePluginVersion" - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10" - classpath 'com.android.tools.lint:lint-kotlin:26.2.1' - } -} - -repositories { - google() - mavenCentral() +plugins { + id 'com.android.library' + id 'maven-publish' + id 'org.jetbrains.kotlin.android' } group = 'com.urbanairship.unitysupport' version = airshipProperties.version description = "Urban Airship Android Unity Plugin" -apply plugin: 'com.android.library' -apply plugin: 'maven-publish' android { lintOptions { abortOnError false } - compileSdkVersion airshipProperties.androidCompileSdkVersion.toInteger() + namespace 'com.urbanairship.unityplugin' + + compileSdk airshipProperties.androidCompileSdkVersion.toInteger() defaultConfig { - minSdkVersion airshipProperties.androidMinSdkVersion.toInteger() - targetSdkVersion airshipProperties.androidTargetSdkVersion.toInteger() + minSdk airshipProperties.androidMinSdkVersion.toInteger() + targetSdk airshipProperties.androidTargetSdkVersion.toInteger() buildConfigField "String", "PLUGIN_VERSION", "\"${airshipProperties.version}\"" } + + buildFeatures { + buildConfig = true + } + kotlinOptions { + jvmTarget = '1.8' + } + +// kotlinOptions { +// jvmTarget = '1.8' +// } } + dependencies { + implementation 'androidx.core:core-ktx:1.15.0' + def lifecycle = '2.7.0' +// implementation 'androidx.core:core-ktx:1.15.0' compileOnly files('libs/unity-classes.jar') implementation "androidx.annotation:annotation:$airshipProperties.androidxAnnotationVersion" - implementation "com.urbanairship.android:urbanairship-fcm:${airshipProperties.androidAirshipVersion}" - implementation "com.urbanairship.android:urbanairship-message-center:${airshipProperties.androidAirshipVersion}" - implementation "com.urbanairship.android:urbanairship-automation:${airshipProperties.androidAirshipVersion}" - implementation "com.urbanairship.android:urbanairship-preference-center:${airshipProperties.androidAirshipVersion}" +// implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle" +// implementation "androidx.lifecycle:lifecycle-process:$lifecycle" +// implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle" +// implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle" +// implementation "androidx.lifecycle:lifecycle-runtime-compose:$lifecycle" +// implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle" +// implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle" + // implementation("androidx.lifecycle:lifecycle-common-java8") { + // version { + // strictly("$lifecycle") + // } + // } + // implementation("androidx.lifecycle:lifecycle-process") { + // version { + // strictly("$lifecycle") + // } + // } + // implementation("androidx.lifecycle:lifecycle-livedata-ktx") { + // version { + // strictly("$lifecycle") + // } + // } + // implementation("androidx.lifecycle:lifecycle-runtime-ktx") { + // version { + // strictly("$lifecycle") + // } + // } + // implementation("androidx.lifecycle:lifecycle-runtime-compose") { + // version { + // strictly("$lifecycle") + // } + // } + // implementation("androidx.lifecycle:lifecycle-viewmodel") { + // version { + // strictly("$lifecycle") + // } + // } + // implementation("androidx.lifecycle:lifecycle-viewmodel-ktx") { + // version { + // strictly("$lifecycle") + // } + // } + // implementation 'androidx.startup:startup-runtime:1.2.0' + + // implementation "com.urbanairship.android:urbanairship-fcm:${airshipProperties.androidAirshipVersion}" + // implementation "com.urbanairship.android:urbanairship-message-center:${airshipProperties.androidAirshipVersion}" + // implementation "com.urbanairship.android:urbanairship-automation:${airshipProperties.androidAirshipVersion}" + // implementation "com.urbanairship.android:urbanairship-preference-center:${airshipProperties.androidAirshipVersion}" + implementation "com.urbanairship.android:airship-framework-proxy:${airshipProperties.airshipFrameworkProxyVersion}" + // implementation("com.urbanairship.android:airship-framework-proxy:${airshipProperties.airshipFrameworkProxyVersion}") { + // exclude group: 'androidx.lifecycle', module: 'lifecycle-common-java8' + // exclude group: 'androidx.lifecycle', module: 'lifecycle-process' + // exclude group: 'androidx.lifecycle', module: 'lifecycle-livedata-ktx' + // exclude group: 'androidx.lifecycle', module: 'lifecycle-runtime-ktx' + // exclude group: 'androidx.lifecycle', module: 'lifecycle-runtime-compose' + // exclude group: 'androidx.lifecycle', module: 'lifecycle-viewmodel' + // exclude group: 'androidx.lifecycle', module: 'lifecycle-viewmodel-ktx' + // } } task copyUnityClassesJar(type: Copy) { diff --git a/unity-plugin/src/main/AndroidManifest.xml b/unity-plugin/src/main/AndroidManifest.xml index e3c8e881..686318db 100644 --- a/unity-plugin/src/main/AndroidManifest.xml +++ b/unity-plugin/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + @@ -9,30 +8,30 @@ android:value="com.urbanairship.unityplugin.UnityAutopilot" /> - - + android:launchMode="singleTask" + android:exported="false"> + + + + + - + android:name="com.urbanairship.android.framework.proxy.CustomMessageActivity" + android:exported="false"> - diff --git a/unity-plugin/src/main/java/com/urbanairship/unityplugin/CustomMessageActivity.java b/unity-plugin/src/main/java/com/urbanairship/unityplugin/CustomMessageActivity.java index 2a8ab88c..6315415d 100644 --- a/unity-plugin/src/main/java/com/urbanairship/unityplugin/CustomMessageActivity.java +++ b/unity-plugin/src/main/java/com/urbanairship/unityplugin/CustomMessageActivity.java @@ -3,9 +3,9 @@ package com.urbanairship.unityplugin; -import com.urbanairship.messagecenter.MessageActivity; +//import com.urbanairship.messagecenter.ui.MessageActivity; // Copy of the MessageActivity so we can set a different theme -public class CustomMessageActivity extends MessageActivity { +public class CustomMessageActivity /*extends MessageActivity*/ { } diff --git a/unity-plugin/src/main/java/com/urbanairship/unityplugin/CustomMessageCenterActivity.java b/unity-plugin/src/main/java/com/urbanairship/unityplugin/CustomMessageCenterActivity.java index 598f1223..2b2d0ebe 100644 --- a/unity-plugin/src/main/java/com/urbanairship/unityplugin/CustomMessageCenterActivity.java +++ b/unity-plugin/src/main/java/com/urbanairship/unityplugin/CustomMessageCenterActivity.java @@ -2,8 +2,8 @@ package com.urbanairship.unityplugin; -import com.urbanairship.messagecenter.MessageCenterActivity; +//import com.urbanairship.messagecenter.ui.MessageCenterActivity; // Copy of the MessageCenterActivity so we can set a different theme -public class CustomMessageCenterActivity extends MessageCenterActivity { +public class CustomMessageCenterActivity /*extends MessageCenterActivity*/ { } diff --git a/unity-plugin/src/main/java/com/urbanairship/unityplugin/PluginLogger.java b/unity-plugin/src/main/java/com/urbanairship/unityplugin/PluginLogger.java deleted file mode 100644 index dd2a262d..00000000 --- a/unity-plugin/src/main/java/com/urbanairship/unityplugin/PluginLogger.java +++ /dev/null @@ -1,170 +0,0 @@ -/* Copyright Airship and Contributors */ - -package com.urbanairship.unityplugin; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RestrictTo; -import android.util.Log; - -import com.urbanairship.LoggingCore; - -/** - * Plugin logger for Airship. - */ -public final class PluginLogger { - - @NonNull - private static final String TAG = "UALib-Unity"; - private static LoggingCore logger = new LoggingCore(Log.ERROR, TAG); - - /** - * Private, unused constructor - */ - private PluginLogger() {} - - /** - * Sets the log level. - * - * @param logLevel The log level. - * @hide - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - public static void setLogLevel(int logLevel) { - logger.setLogLevel(logLevel); - } - - /** - * Send a warning log message. - * - * @param message The message you would like logged. - * @param args The message args. - * @hide - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - public static void warn(@NonNull String message, @Nullable Object... args) { - logger.log(Log.WARN, null, message, args); - } - - /** - * Send a warning log message. - * - * @param t An exception to log - * @param message The message you would like logged. - * @param args The message args. - * @hide - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - public static void warn(@NonNull Throwable t, @NonNull String message, @Nullable Object... args) { - logger.log(Log.WARN, t, message, args); - } - - /** - * Send a warning log message. - * - * @param t An exception to log - * @hide - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - public static void warn(@NonNull Throwable t) { - logger.log(Log.WARN, t, null, (Object[]) null); - } - - /** - * Send a verbose log message. - * - * @param message The message you would like logged. - * @param args The message args. - * @hide - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - public static void verbose(@NonNull String message, @Nullable Object... args) { - logger.log(Log.VERBOSE, null, message, args); - } - - /** - * Send a debug log message. - * - * @param message The message you would like logged. - * @param args The message args. - * @hide - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - public static void debug(@NonNull String message, @Nullable Object... args) { - logger.log(Log.DEBUG, null, message, args); - } - - /** - * Send a debug log message. - * - * @param t An exception to log - * @param message The message you would like logged. - * @param args The message args. - * @hide - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - public static void debug(@NonNull Throwable t, @NonNull String message, @Nullable Object... args) { - logger.log(Log.DEBUG, t, message, args); - } - - /** - * Send an info log message. - * - * @param message The message you would like logged. - * @param args The message args. - * @hide - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - public static void info(@NonNull String message, @NonNull Object... args) { - logger.log(Log.INFO, null, message, args); - } - - /** - * Send an info log message. - * - * @param t An exception to log - * @param message The message you would like logged. - * @param args The message args. - * @hide - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - public static void info(@NonNull Throwable t, @NonNull String message, @Nullable Object... args) { - logger.log(Log.INFO, t, message, args); - } - - /** - * Send an error log message. - * - * @param message The message you would like logged. - * @param args The message args. - * @hide - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - public static void error(@NonNull String message, @Nullable Object... args) { - logger.log(Log.ERROR, null, message, args); - } - - /** - * Send an error log message. - * - * @param t An exception to log - * @hide - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - public static void error(@NonNull Throwable t) { - logger.log(Log.ERROR, t, null, (Object[]) null); - } - - /** - * Send an error log message. - * - * @param t An exception to log - * @param message The message you would like logged. - * @param args The message args. - * @hide - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - public static void error(@NonNull Throwable t, @NonNull String message, @Nullable Object... args) { - logger.log(Log.ERROR, t, message, args); - } -} \ No newline at end of file diff --git a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityAutopilot.java b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityAutopilot.java deleted file mode 100644 index e9e9ac58..00000000 --- a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityAutopilot.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - Copyright 2018 Urban Airship and Contributors -*/ - -package com.urbanairship.unityplugin; - -import android.content.Context; -import android.preference.PreferenceManager; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.urbanairship.AirshipConfigOptions; -import com.urbanairship.Autopilot; -import com.urbanairship.UAirship; -import com.urbanairship.actions.DeepLinkListener; -import com.urbanairship.analytics.Analytics; -import com.urbanairship.channel.AirshipChannelListener; -import com.urbanairship.messagecenter.InboxListener; -import com.urbanairship.messagecenter.MessageCenter; -import com.urbanairship.push.NotificationActionButtonInfo; -import com.urbanairship.push.NotificationInfo; -import com.urbanairship.push.NotificationListener; -import com.urbanairship.push.PushListener; -import com.urbanairship.push.PushMessage; - -import static com.urbanairship.unityplugin.UnityPlugin.AUTO_LAUNCH_MESSAGE_CENTER; - -public class UnityAutopilot extends Autopilot { - - @Override - public void onAirshipReady(UAirship airship) { - - airship.getAnalytics().registerSDKExtension(Analytics.EXTENSION_UNITY, BuildConfig.PLUGIN_VERSION); - - airship.getChannel().addChannelListener(new AirshipChannelListener() { - @Override - public void onChannelCreated(@NonNull String channelId) { - UnityPlugin.shared().onChannelCreated(channelId); - } - - @Override - public void onChannelUpdated(@NonNull String channelId) { - UnityPlugin.shared().onChannelUpdated(channelId); - - } - }); - - airship.getPushManager().addPushListener(new PushListener() { - @Override - public void onPushReceived(@NonNull PushMessage message, boolean notificationPosted) { - UnityPlugin.shared().onPushReceived(message); - } - }); - - airship.getPushManager().setNotificationListener(new NotificationListener() { - @Override - public void onNotificationPosted(@NonNull NotificationInfo notificationInfo) { - - } - - @Override - public boolean onNotificationOpened(@NonNull NotificationInfo notificationInfo) { - UnityPlugin.shared().onPushOpened(notificationInfo.getMessage()); - return false; - } - - @Override - public boolean onNotificationForegroundAction(@NonNull NotificationInfo notificationInfo, @NonNull NotificationActionButtonInfo notificationActionButtonInfo) { - UnityPlugin.shared().onPushOpened(notificationInfo.getMessage()); - return false; - } - - @Override - public void onNotificationBackgroundAction(@NonNull NotificationInfo notificationInfo, @NonNull NotificationActionButtonInfo notificationActionButtonInfo) { - UnityPlugin.shared().onPushOpened(notificationInfo.getMessage()); - } - - @Override - public void onNotificationDismissed(@NonNull NotificationInfo notificationInfo) { - - } - }); - - MessageCenter.shared().setOnShowMessageCenterListener(new MessageCenter.OnShowMessageCenterListener() { - @Override - public boolean onShowMessageCenter(@Nullable String messageId) { - if (PreferenceManager.getDefaultSharedPreferences(UAirship.getApplicationContext()).getBoolean(AUTO_LAUNCH_MESSAGE_CENTER, true)) { - return false; - } else { - UnityPlugin.shared().onShowInbox(messageId); - return true; - } - } - }); - - MessageCenter.shared().getInbox().addListener(new InboxListener() { - @Override - public void onInboxUpdated() { - UnityPlugin.shared().onInboxUpdated(); - } - }); - - airship.setDeepLinkListener(new DeepLinkListener() { - @Override - public boolean onDeepLink(@NonNull String deepLink) { - if (deepLink == null) { - return false; - } - - UnityPlugin.shared().setDeepLink(deepLink); - return UnityPlugin.shared().onDeepLinkReceived(deepLink); - } - }); - } - - public AirshipConfigOptions createAirshipConfigOptions(Context context) { - int resourceId = context.getResources().getIdentifier("airship_config", "xml", context.getPackageName()); - if (resourceId <= 0) { - PluginLogger.error("airship_config.xml not found. Make sure Urban Airship is configured Window => Urban Airship => Settings."); - return null; - } - - AirshipConfigOptions options = new AirshipConfigOptions.Builder() - .applyConfig(context, resourceId) - .build(); - - PluginLogger.setLogLevel(options.logLevel); - - return options; - } -} diff --git a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityAutopilot.kt b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityAutopilot.kt new file mode 100644 index 00000000..eeee72c7 --- /dev/null +++ b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityAutopilot.kt @@ -0,0 +1,37 @@ +/* Copyright Airship and Contributors */ +package com.urbanairship.unityplugin + +import android.content.Context +import com.urbanairship.AirshipConfigOptions +import com.urbanairship.UAirship +import com.urbanairship.analytics.Extension +import com.urbanairship.android.framework.proxy.BaseAutopilot +import com.urbanairship.android.framework.proxy.ProxyLogger +import com.urbanairship.android.framework.proxy.ProxyStore + +class UnityAutopilot : BaseAutopilot() { + override fun onMigrateData(context: Context, proxyStore: ProxyStore) { + + } + + override fun onReady(context: Context, airship: UAirship) { + ProxyLogger.info("UnityAutopilot", "onAirshipReady") + airship.analytics.registerSDKExtension(Extension.UNITY, BuildConfig.PLUGIN_VERSION) + } + + override fun createAirshipConfigOptions(context: Context): AirshipConfigOptions? { + val resourceId = context.resources.getIdentifier("airship_config", "xml", context.packageName) + if (resourceId <= 0) { + ProxyLogger.error("airship_config.xml not found. Make sure Urban Airship is configured Window => Urban Airship => Settings.") + return null + } + + val options = AirshipConfigOptions.Builder() + .applyConfig(context, resourceId) + .build() + + //ProxyLogger.setLogLevel(options.logLevel) + + return options + } +} \ No newline at end of file diff --git a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.java b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.java deleted file mode 100644 index e9e2f261..00000000 --- a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.java +++ /dev/null @@ -1,776 +0,0 @@ -/* Copyright Airship and Contributors */ - -package com.urbanairship.unityplugin; - -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.preference.PreferenceManager; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.unity3d.player.UnityPlayer; -import com.urbanairship.UAirship; -import com.urbanairship.PrivacyManager; -import com.urbanairship.analytics.CustomEvent; -import com.urbanairship.channel.AirshipChannel; -import com.urbanairship.channel.AttributeEditor; -import com.urbanairship.channel.TagGroupsEditor; -import com.urbanairship.automation.InAppAutomation; -import com.urbanairship.json.JsonException; -import com.urbanairship.json.JsonList; -import com.urbanairship.json.JsonMap; -import com.urbanairship.json.JsonValue; -import com.urbanairship.messagecenter.Message; -import com.urbanairship.messagecenter.MessageCenter; -import com.urbanairship.preferencecenter.PreferenceCenter; -import com.urbanairship.push.PushMessage; -import com.urbanairship.util.UAStringUtil; - -import org.json.JSONArray; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -public class UnityPlugin { - - static final String AUTO_LAUNCH_MESSAGE_CENTER = "com.urbanairship.auto_launch_message_center"; - - private static UnityPlugin instance = new UnityPlugin(); - private String listener; - private PushMessage incomingPush; - private String deepLink; - - private static final Map featuresMap = new HashMap<>(); - static { - featuresMap.put("FEATURE_NONE", PrivacyManager.FEATURE_NONE); - featuresMap.put("FEATURE_IN_APP_AUTOMATION", PrivacyManager.FEATURE_IN_APP_AUTOMATION); - featuresMap.put("FEATURE_MESSAGE_CENTER", PrivacyManager.FEATURE_MESSAGE_CENTER); - featuresMap.put("FEATURE_PUSH", PrivacyManager.FEATURE_PUSH); - featuresMap.put("FEATURE_CHAT", PrivacyManager.FEATURE_CHAT); - featuresMap.put("FEATURE_ANALYTICS", PrivacyManager.FEATURE_ANALYTICS); - featuresMap.put("FEATURE_TAGS_AND_ATTRIBUTES", PrivacyManager.FEATURE_TAGS_AND_ATTRIBUTES); - featuresMap.put("FEATURE_CONTACTS", PrivacyManager.FEATURE_CONTACTS); - featuresMap.put("FEATURE_LOCATION", PrivacyManager.FEATURE_LOCATION); - featuresMap.put("FEATURE_ALL", PrivacyManager.FEATURE_ALL); - } - - UnityPlugin() { - } - - public static UnityPlugin shared() { - return instance; - } - - public String getDeepLink(boolean clear) { - PluginLogger.debug("UnityPlugin getDeepLink clear " + clear); - - String link = deepLink; - if (clear) { - deepLink = null; - } - return link; - } - - public void setListener(String listener) { - PluginLogger.debug("UnityPlugin setListener: " + listener); - this.listener = listener; - } - - public String getIncomingPush(boolean clear) { - PluginLogger.debug("UnityPlugin getIncomingPush clear " + clear); - - String push = getPushPayload(incomingPush); - if (clear) { - incomingPush = null; - } - return push; - } - - public boolean getUserNotificationsEnabled() { - PluginLogger.debug("UnityPlugin getUserNotificationsEnabled"); - return UAirship.shared().getPushManager().getUserNotificationsEnabled(); - } - - public void setUserNotificationsEnabled(boolean enabled) { - PluginLogger.debug("UnityPlugin setUserNotificationsEnabled: " + enabled); - UAirship.shared().getPushManager().setUserNotificationsEnabled(enabled); - } - - public String getChannelId() { - PluginLogger.debug("UnityPlugin getChannelId"); - return UAirship.shared().getChannel().getId(); - } - - public void addTag(String tag) { - PluginLogger.debug("UnityPlugin addTag: " + tag); - - UAirship.shared().getChannel().editTags().addTag(tag).apply(); - } - - public void removeTag(String tag) { - PluginLogger.debug("UnityPlugin removeTag: " + tag); - - UAirship.shared().getChannel().editTags().removeTag(tag).apply(); - } - - public String getTags() { - PluginLogger.debug("UnityPlugin getTags"); - JSONArray jsonArray = new JSONArray(); - for (String tag : UAirship.shared().getChannel().getTags()) { - jsonArray.put(tag); - } - - return jsonArray.toString(); - } - - public void addCustomEvent(String eventPayload) { - PluginLogger.debug("UnityPlugin addCustomEvent: " + eventPayload); - - if (UAStringUtil.isEmpty(eventPayload)) { - PluginLogger.error("Missing event payload."); - return; - } - - JsonMap customEventMap = null; - try { - customEventMap = JsonValue.parseString(eventPayload).getMap(); - } catch (JsonException e) { - PluginLogger.error("Failed to parse event payload", e); - } - if (customEventMap == null) { - PluginLogger.error("Event payload must define a JSON object."); - return; - } - - String eventName = customEventMap.opt("eventName").getString(); - if (eventName == null) { - return; - } - - String eventValue = customEventMap.opt("eventValue").getString(); - String transactionId = customEventMap.opt("transactionId").getString(); - String interactionType = customEventMap.opt("interactionType").getString(); - String interactionId = customEventMap.opt("interactionId").getString(); - JsonList properties = customEventMap.opt("properties").getList(); - - CustomEvent.Builder eventBuilder = new CustomEvent.Builder(eventName) - .setEventValue(eventValue); - - if (!UAStringUtil.isEmpty(transactionId)) { - eventBuilder.setTransactionId(transactionId); - } - - if (!UAStringUtil.isEmpty(interactionType) && !UAStringUtil.isEmpty(interactionId)) { - eventBuilder.setInteraction(interactionType, interactionId); - } - - if (properties != null) { - for (JsonValue property : properties) { - JsonMap jsonMap = property.getMap(); - if (jsonMap == null) { - continue; - } - - String name = jsonMap.opt("name").getString(); - String type = jsonMap.opt("type").getString(); - - if (UAStringUtil.isEmpty(name) || UAStringUtil.isEmpty(type)) { - continue; - } - - switch (type) { - case "s": - String stringValue = jsonMap.opt("stringValue").getString(); - if (stringValue != null) { - eventBuilder.addProperty(name, stringValue); - } - break; - case "d": - eventBuilder.addProperty(name, jsonMap.opt("doubleValue").getDouble(0)); - break; - case "b": - eventBuilder.addProperty(name, jsonMap.opt("boolValue").getBoolean(false)); - break; - case "sa": - JsonList stringArrayValue = jsonMap.opt("stringArrayValue").getList(); - if (stringArrayValue == null) { - break; - } - eventBuilder.addProperty(name, JsonValue.wrapOpt(stringArrayValue)); - break; - default: - continue; - } - } - } - - UAirship.shared().getAnalytics().addEvent(eventBuilder.build()); - } - - public void trackScreen(String screenName) { - if (UAStringUtil.isEmpty(screenName)) { - PluginLogger.error("Missing screen name"); - return; - } - - PluginLogger.debug("UnityPlugin trackScreen: " + screenName); - - UAirship.shared().getAnalytics().trackScreen(screenName); - } - - public void associateIdentifier(String key, String identifier) { - if (key == null) { - PluginLogger.debug("UnityPlugin associateIdentifier failed, key cannot be null"); - return; - } - - if (identifier == null) { - PluginLogger.debug("UnityPlugin associateIdentifier removed identifier for key: " + key); - UAirship.shared().getAnalytics().editAssociatedIdentifiers().removeIdentifier(key).apply(); - } else { - PluginLogger.debug("UnityPlugin associateIdentifier with identifier: " + identifier + " for key: " + key); - UAirship.shared().getAnalytics().editAssociatedIdentifiers().addIdentifier(key, identifier).apply(); - } - } - - public String getNamedUserId() { - PluginLogger.debug("UnityPlugin getNamedUserId"); - return UAirship.shared().getNamedUser().getId(); - } - - public void setNamedUserId(String namedUserId) { - PluginLogger.debug("UnityPlugin setNamedUserId: " + namedUserId); - UAirship.shared().getNamedUser().setId(namedUserId); - } - - public void displayMessageCenter() { - PluginLogger.debug("UnityPlugin displayMessageCenter"); - MessageCenter.shared().showMessageCenter(); - } - - /** - * Display an inbox message in the default message center. - * - * @param messageId The id of the message to be displayed. - */ - public void displayInboxMessage(String messageId) { - PluginLogger.debug("UnityPlugin displayInboxMessage %s", messageId); - - Intent intent = new Intent(UnityPlayer.currentActivity, CustomMessageActivity.class) - .setAction(MessageCenter.VIEW_MESSAGE_INTENT_ACTION) - .setPackage(UnityPlayer.currentActivity.getPackageName()) - .setData(Uri.fromParts(MessageCenter.MESSAGE_DATA_SCHEME, messageId, null)) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); - - UnityPlayer.currentActivity.getApplicationContext().startActivity(intent); - } - - /** - * Refresh the message center inbox. - * - * The `OnInboxUpdated` event will fire after the inbox has been successfully refreshed. - */ - public void refreshInbox() { - PluginLogger.debug("UnityPlugin refreshInbox"); - MessageCenter.shared().getInbox().fetchMessages(); // this needs to fire an event - } - - /** - * Retrieves the current inbox messages. - */ - public String getInboxMessages() { - PluginLogger.debug("UnityPlugin getInboxMessages"); - - return getInboxMessagesAsJSON(); - } - - /** - * Marks an inbox message as read. - * - * @param messageId The id of the message to be marked as read. - */ - public void markInboxMessageRead(@NonNull String messageId) { - PluginLogger.debug("UnityPlugin markInboxMessageRead %s", messageId); - Message message = MessageCenter.shared().getInbox().getMessage(messageId); - - if (message == null) { - PluginLogger.debug("Message (%s) not found.", messageId); - } else { - message.markRead(); - } - } - - /** - * Deletes an inbox message. - * - * @param messageId The id of the message to be deleted. - */ - public void deleteInboxMessage(@NonNull String messageId) { - PluginLogger.debug("UnityPlugin deleteInboxMessage %s", messageId); - Message message = MessageCenter.shared().getInbox().getMessage(messageId); - - if (message == null) { - PluginLogger.debug("Message (%s) not found.", messageId); - } else { - message.delete(); - } - } - - /** - * Sets the default behavior when the message center is launched from a push notification. If set to false the message center must be manually launched. - * - * @param enabled {@code true} to automatically launch the default message center, {@code false} to disable. Default is {@code true}. - */ - public void setAutoLaunchDefaultMessageCenter(boolean enabled) { - PluginLogger.debug("UnityPlugin setAutoLaunchDefaultMessageCenter"); - PreferenceManager.getDefaultSharedPreferences(UAirship.getApplicationContext()) - .edit() - .putBoolean(AUTO_LAUNCH_MESSAGE_CENTER, enabled) - .apply(); - } - - public int getMessageCenterUnreadCount() { - PluginLogger.debug("UnityPlugin getMessageCenterUnreadCount"); - return MessageCenter.shared().getInbox().getUnreadCount(); - } - - public int getMessageCenterCount() { - PluginLogger.debug("UnityPlugin getMessageCenterCount"); - return MessageCenter.shared().getInbox().getCount(); - } - - public void editNamedUserTagGroups(String payload) { - PluginLogger.debug("UnityPlugin editNamedUserTagGroups"); - - TagGroupsEditor editor = UAirship.shared().getNamedUser().editTagGroups(); - applyTagGroupOperations(editor, payload); - editor.apply(); - } - - public void editChannelTagGroups(String payload) { - PluginLogger.debug("UnityPlugin editChannelTagGroups"); - - TagGroupsEditor editor = UAirship.shared().getChannel().editTagGroups(); - applyTagGroupOperations(editor, payload); - editor.apply(); - } - - public void editChannelAttributes(String payload) { - PluginLogger.debug("UnityPlugin editChannelAttributes"); - - AttributeEditor editor = UAirship.shared().getChannel().editAttributes(); - applyAttributeOperations(editor, payload); - editor.apply(); - } - - public void editNamedUserAttributes(String payload) { - PluginLogger.debug("UnityPlugin editNamedUserAttributes"); - - AttributeEditor editor = UAirship.shared().getNamedUser().editAttributes(); - applyAttributeOperations(editor, payload); - editor.apply(); - } - - public boolean isInAppAutomationPaused() { - PluginLogger.debug("UnityPlugin isInAppAutomationPaused"); - return InAppAutomation.shared().isPaused(); - } - - public void setInAppAutomationPaused(boolean paused) { - PluginLogger.debug("UnityPlugin setInAppAutomationPaused %s", paused); - InAppAutomation.shared().setPaused(paused); - } - - public double getInAppAutomationDisplayInterval() { - PluginLogger.debug("UnityPlugin getInAppAutomationDisplayInterval"); - long milliseconds = InAppAutomation.shared().getInAppMessageManager().getDisplayInterval(); - return milliseconds / 1000.0; - } - - public void setInAppAutomationDisplayInterval(double seconds) { - PluginLogger.debug("UnityPlugin setInAppAutomationDisplayInterval %s", seconds); - - long milliseconds = (long) (seconds * 1000.0); - InAppAutomation.shared().getInAppMessageManager().setDisplayInterval(milliseconds, TimeUnit.MILLISECONDS); - } - - void onPushReceived(PushMessage message) { - PluginLogger.debug("UnityPlugin push received. " + message); - - if (listener != null) { - UnityPlayer.UnitySendMessage(listener, "OnPushReceived", getPushPayload(message)); - } - } - - void onPushOpened(PushMessage message) { - PluginLogger.debug("UnityPlugin push opened. " + message); - - if (listener != null) { - UnityPlayer.UnitySendMessage(listener, "OnPushOpened", getPushPayload(message)); - } - this.incomingPush = message; - } - - boolean onDeepLinkReceived(String deepLink) { - PluginLogger.debug("UnityPlugin deepLink received: " + deepLink); - - if (listener != null) { - UnityPlayer.UnitySendMessage(listener, "OnDeepLinkReceived", deepLink); - return true; - } - return false; - } - - void onChannelCreated(String channelId) { - PluginLogger.debug("UnityPlugin channel created: " + channelId); - - if (listener != null) { - UnityPlayer.UnitySendMessage(listener, "OnChannelCreated", channelId); - } - } - - void onChannelUpdated(String channelId) { - PluginLogger.debug("UnityPlugin channel updated: " + channelId); - - if (listener != null) { - UnityPlayer.UnitySendMessage(listener, "OnChannelUpdated", channelId); - } - } - - void onShowInbox(@Nullable String messageId) { - if (messageId == null) { - PluginLogger.debug("UnityPlugin show inbox"); - - if (listener != null) { - UnityPlayer.UnitySendMessage(listener, "OnShowInbox", ""); - } - } else { - PluginLogger.debug("UnityPlugin show inbox message: ", messageId); - - if (listener != null) { - UnityPlayer.UnitySendMessage(listener, "OnShowInbox", messageId); - } - } - } - - void onInboxUpdated() { - JsonMap counts = JsonMap.newBuilder() - .put("unread", MessageCenter.shared().getInbox().getUnreadCount()) - .put("total", MessageCenter.shared().getInbox().getCount()) - .build(); - PluginLogger.debug("UnityPlugin inboxUpdated (unread = %s, total = %s)", MessageCenter.shared().getInbox().getUnreadCount(), MessageCenter.shared().getInbox().getCount()); - - if (listener != null) { - UnityPlayer.UnitySendMessage(listener, "OnInboxUpdated", counts.toString()); - } - } - - private String getPushPayload(PushMessage message) { - if (message == null) { - return null; - } - - Map payloadMap = new HashMap<>(); - - List> extras = new ArrayList<>(); - - for (String key : message.getPushBundle().keySet()) { - String value; - if (!UAStringUtil.equals(key, "google.sent_time")) { - value = message.getPushBundle().getString(key); - } else { - continue; - } - - if (value == null) { - continue; - } - - Map extra = new HashMap<>(); - extra.put("key", key); - extra.put("value", value); - extras.add(extra); - } - - if (message.getAlert() != null) { - payloadMap.put("alert", message.getAlert()); - } - - if (message.getSendId() != null) { - payloadMap.put("identifier", message.getSendId()); - } - - payloadMap.put("extras", extras); - - return JsonValue.wrapOpt(payloadMap).toString(); - } - - public String getInboxMessagesAsJSON() { - List> messages = new ArrayList<>(); - for (Message message : MessageCenter.shared().getInbox().getMessages()) { - Map messageMap = new HashMap<>(); - messageMap.put("id", message.getMessageId()); - messageMap.put("title", message.getTitle()); - messageMap.put("sentDate", message.getSentDate().getTime()); - String listIconUrl = message.getListIconUrl(); - if (listIconUrl != null) { - messageMap.put("listIconUrl", listIconUrl); - } - messageMap.put("isRead", message.isRead()); - messageMap.put("isDeleted", message.isDeleted()); - - if (message.getExtras().keySet().size() > 0) { - List extrasKeys = new ArrayList<>(); - List extrasValues = new ArrayList<>(); - Bundle extras = message.getExtras(); - - for (String key : extras.keySet()) { - extrasKeys.add(key); - extrasValues.add(extras.get(key)); - } - - messageMap.put("extrasKeys", extrasKeys); - messageMap.put("extrasValues", extrasValues); - } - messages.add(messageMap); - } - return JsonValue.wrapOpt(messages).toString(); - } - - void setDeepLink(String deepLink) { - PluginLogger.debug("UnityPlugin setDeepLink: " + deepLink); - - this.deepLink = deepLink; - } - - private static void applyTagGroupOperations(TagGroupsEditor editor, String payload) { - JsonMap payloadMap; - try { - payloadMap = JsonValue.parseString(payload).getMap(); - } catch (JsonException e) { - PluginLogger.error("Unable to apply tag group operations: ", e); - return; - } - - if (payloadMap == null || !payloadMap.opt("values").isJsonList()) { - return; - } - - for (JsonValue operation : payloadMap.opt("values").optList()) { - if (!operation.isJsonMap()) { - continue; - } - - JsonList tags = operation.optMap().opt("tags").getList(); - String group = operation.optMap().opt("tagGroup").getString(); - String operationType = operation.optMap().opt("operation").getString(); - - if (tags == null || tags.isEmpty() || UAStringUtil.isEmpty(group) || UAStringUtil.isEmpty(operationType)) { - continue; - } - - HashSet tagSet = new HashSet<>(); - for (JsonValue tag : tags) { - if (tag.isString()) { - tagSet.add(tag.getString()); - } - } - - switch (operationType) { - case "add": - editor.addTags(group, tagSet); - break; - case "remove": - editor.removeTags(group, tagSet); - break; - } - } - } - - private static void applyAttributeOperations(AttributeEditor editor, String payload) { - JsonMap payloadMap; - try { - payloadMap = JsonValue.parseString(payload).optMap(); - } catch (JsonException e) { - PluginLogger.error("Unable to apply attribute operations: ", e); - return; - } - - for (JsonValue operation : payloadMap.opt("values").optList()) { - String action = operation.optMap().opt("action").getString(); - String key = operation.optMap().opt("key").getString(); - String value = operation.optMap().opt("value").getString(); - String type = operation.optMap().opt("type").getString(); - - if (UAStringUtil.isEmpty(key) || UAStringUtil.isEmpty(action)) { - PluginLogger.error("Invalid attribute operation %s ", operation); - continue; - } - - switch (action) { - case "Set": - if (UAStringUtil.isEmpty(type) || UAStringUtil.isEmpty(value)) { - PluginLogger.error("Invalid set operation: %s", operation); - continue; - } - - switch (type) { - case "String": - editor.setAttribute(key, value); - break; - case "Integer": - editor.setAttribute(key, Integer.valueOf(value)); - break; - case "Long": - editor.setAttribute(key, Long.valueOf(value)); - break; - case "Float": - editor.setAttribute(key, Float.valueOf(value)); - break; - case "Double": - editor.setAttribute(key, Double.valueOf(value)); - break; - case "Date": - editor.setAttribute(key, new Date(Double.valueOf(value).longValue())); - break; - default: - PluginLogger.error("Unexpected type: " + operation); - continue; - } - - break; - case "Remove": - editor.removeAttribute(key); - break; - } - } - } - - public void openPreferenceCenter(String preferenceCenterId) { - PluginLogger.debug("UnityPlugin openPreferenceCenter"); - PreferenceCenter.shared().open(preferenceCenterId); - } - - public void setEnabledFeatures(String features[]) { - PluginLogger.debug("UnityPlugin setEnabledFeatures"); - ArrayList featuresList = new ArrayList<>(); - Collections.addAll(featuresList, features); - if (isValidFeature(featuresList)) { - UAirship.shared().getPrivacyManager().setEnabledFeatures(stringToFeature(featuresList)); - } - } - - public void enableFeatures(String features[]) { - PluginLogger.debug("UnityPlugin enableFeatures"); - ArrayList featuresList = new ArrayList<>(); - Collections.addAll(featuresList, features); - if (isValidFeature(featuresList)) { - UAirship.shared().getPrivacyManager().enable(stringToFeature(featuresList)); - } - } - - public void disableFeatures(String features[]) { - PluginLogger.debug("UnityPlugin disableFeatures"); - ArrayList featuresList = new ArrayList<>(); - Collections.addAll(featuresList, features); - if (isValidFeature(featuresList)) { - UAirship.shared().getPrivacyManager().disable(stringToFeature(featuresList)); - } - } - - public boolean isFeatureEnabled(String features[]) { - PluginLogger.debug("UnityPlugin isFeatureEnabled"); - ArrayList featuresList = new ArrayList<>(); - Collections.addAll(featuresList, features); - if (isValidFeature(featuresList)) { - return UAirship.shared().getPrivacyManager().isEnabled(stringToFeature(featuresList)); - } else { - return false; - } - } - - public boolean isAnyFeatureEnabled(String features[]) { - PluginLogger.debug("UnityPlugin isAnyFeatureEnabled"); - ArrayList featuresList = new ArrayList<>(); - Collections.addAll(featuresList, features); - if (isValidFeature(featuresList)) { - return UAirship.shared().getPrivacyManager().isAnyEnabled(stringToFeature(featuresList)); - } else { - return false; - } - } - - public String[] getEnabledFeatures() { - PluginLogger.debug("UnityPlugin getEnabledFeatures"); - return featureToString(UAirship.shared().getPrivacyManager().getEnabledFeatures()); - } - - /** - * Helper method to check the features array is valid. - * @param features The ArrayList of features to check. - * @return {@code true} if the provided features are valid, otherwise {@code false}. - */ - private boolean isValidFeature(ArrayList features) { - if (features == null || features.size() == 0) { - PluginLogger.error("No features provided"); - return false; - } - - for (int i = 0; i < features.size(); i++) { - if (!featuresMap.containsKey(features.get(i))) { - PluginLogger.error("Invalid feature name : " + features.get(i)); - return false; - } - } - return true; - } - - /** - * Helper method to parse a String features array into {@link PrivacyManager.Feature} int array. - * @param features The String features to parse. - * @return The {@link PrivacyManager.Feature} int array. - */ - private @NonNull int[] stringToFeature(@NonNull ArrayList features) { - int[] intFeatures = new int[features.size()]; - - for (int i = 0; i < features.size(); i++) { - intFeatures[i] = featuresMap.get(features.get(i)); - } - return intFeatures; - } - - /** - * Helper method to parse a {@link PrivacyManager.Feature} int array into a String array. - * @param features The {@link PrivacyManager.Feature} int array to parse. - * @return An array of features as String. - */ - private @NonNull String[] featureToString(int features) { - List stringFeatures = new ArrayList<>(); - - if (features == PrivacyManager.FEATURE_ALL) { - stringFeatures.add("FEATURE_ALL"); - } else if (features == PrivacyManager.FEATURE_NONE) { - stringFeatures.add("FEATURE_NONE"); - } else { - for (String feature : featuresMap.keySet()) { - int intFeature = featuresMap.get(feature); - if (((intFeature & features) != 0) && (intFeature != PrivacyManager.FEATURE_ALL)) { - stringFeatures.add(feature); - } - } - } - - String[] featureArray = new String[stringFeatures.size()]; - featureArray = stringFeatures.toArray(featureArray); - return featureArray; - } -} diff --git a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt new file mode 100644 index 00000000..6e12804b --- /dev/null +++ b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt @@ -0,0 +1,127 @@ +/* Copyright Airship and Contributors */ +package com.urbanairship.unityplugin + +import com.unity3d.player.UnityPlayer +import com.unity3d.player.a.a +import com.urbanairship.UAirship +import com.urbanairship.android.framework.proxy.ProxyLogger +import com.urbanairship.android.framework.proxy.TagGroupOperation +import com.urbanairship.android.framework.proxy.proxies.AirshipProxy +import com.urbanairship.json.JsonException +import com.urbanairship.json.JsonValue +import org.json.JSONArray + +class UnityPlugin { + + private val airshipProxyInstance = AirshipProxy.shared(UnityPlayer.currentActivity.applicationContext) + + private var listener: String? = nul + + fun setListener(listener: String) { + ProxyLogger.debug("UnityPlugin setListener: $listener") + this.listener = listener + } + + // Push + + fun getUserNotificationsEnabled(): Boolean { + ProxyLogger.debug("UnityPlugin getUserNotificationsEnabled") + return airshipProxyInstance.push.isUserNotificationsEnabled() + } + + fun setUserNotificationsEnabled(enabled: Boolean) { + ProxyLogger.debug("UnityPlugin setUserNotificationsEnabled: $enabled") + airshipProxyInstance.push.setUserNotificationsEnabled(enabled) + } + + // Channel + + fun getChannelId(): String? { + ProxyLogger.debug("UnityPlugin getChannelId") + return airshipProxyInstance.channel.getChannelId() + } + + fun addTag(tag: String) { + ProxyLogger.debug("UnityPlugin addTag: $tag") + airshipProxyInstance.channel.addTag(tag) + } + + fun removeTag(tag: String) { + ProxyLogger.debug("UnityPlugin removeTag: $tag") + airshipProxyInstance.channel.removeTag(tag) + } + + fun getTags(): String { + ProxyLogger.debug("UnityPlugin getTags") + val jsonArray = JSONArray() + for (tag in airshipProxyInstance.channel.getTags()) { + jsonArray.put(tag) + } + return jsonArray.toString() + } + + // Analytics + + fun associateIdentifier(key: String, identifier: String?) { + if (identifier == null) { + ProxyLogger.debug("UnityPlugin associateIdentifier removed identifier for key: $key") + } else { + ProxyLogger.debug("UnityPlugin associateIdentifier with identifier: $identifier for key: $key") + } + airshipProxyInstance.analytics.associateIdentifier(key, identifier) + } + + fun trackScreen(screenName: String) { + ProxyLogger.debug("UnityPlugin trackScreen: $screenName") + airshipProxyInstance.analytics.trackScreen(screenName) + } + + fun addCustomEvent(eventPayload: String) { + ProxyLogger.debug("UnityPlugin addCustomEvent: $eventPayload") + try { + airshipProxyInstance.analytics.addEvent(JsonValue.parseString(eventPayload)) + } catch (e: JsonException) { + ProxyLogger.error("Failed to parse event payload", e) + } + } + + fun getSessionId(): String { + ProxyLogger.debug("UnityPlugin getSessionId") + return airshipProxyInstance.analytics.getSessionId() + } + + // Contact + + fun identify(namedUserId: String?) { + ProxyLogger.debug("UnityPlugin identify: $namedUserId") + airshipProxyInstance.contact.identify(namedUserId) + } + + fun reset() { + ProxyLogger.debug("UnityPlugin reset") + airshipProxyInstance.contact.reset() + } + + fun getNamedUserId(): String? { + ProxyLogger.debug("UnityPlugin getNamedUserId") + return airshipProxyInstance.contact.getNamedUserId() + } + + fun notifyRemoteLogin() { + ProxyLogger.debug("UnityPlugin notifyRemoteLogin") + airshipProxyInstance.contact.notifyRemoteLogin() + } + + //fun editTagGroups(operations: List) + + // TODO finish the implementation using the proxy + + companion object { + private val instance = UnityPlugin() + + @JvmStatic + fun shared(): UnityPlugin { + return instance + } + } +} \ No newline at end of file diff --git a/unity-plugin/src/main/res/values-v14/style.xml b/unity-plugin/src/main/res/values-v14/style.xml index 6af1e214..64161f1b 100644 --- a/unity-plugin/src/main/res/values-v14/style.xml +++ b/unity-plugin/src/main/res/values-v14/style.xml @@ -3,13 +3,13 @@ - + + + - + \ No newline at end of file From 9a9a37f9d5eccc38bd943926e5c2d8cd4f184067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gibern=C3=A9?= Date: Thu, 7 Aug 2025 11:48:36 -0400 Subject: [PATCH 02/27] on going --- Assets/UrbanAirship/Platforms/Airship.cs | 102 +++++- .../Platforms/AirshipAnalytics.cs | 8 +- .../UrbanAirship/Platforms/AirshipChannel.cs | 60 ++-- .../UrbanAirship/Platforms/AirshipContact.cs | 58 ++-- Assets/UrbanAirship/Platforms/AirshipInApp.cs | 8 +- .../Platforms/AirshipMessageCenter.cs | 1 + .../Platforms/AirshipPrivacyManager.cs | 3 +- Assets/UrbanAirship/Platforms/AirshipPush.cs | 4 +- airship.properties | 2 +- .../urbanairship/unityplugin/UnityPlugin.kt | 328 ++++++++++++++++-- 10 files changed, 469 insertions(+), 105 deletions(-) diff --git a/Assets/UrbanAirship/Platforms/Airship.cs b/Assets/UrbanAirship/Platforms/Airship.cs index 7a80f441..f771fb69 100644 --- a/Assets/UrbanAirship/Platforms/Airship.cs +++ b/Assets/UrbanAirship/Platforms/Airship.cs @@ -195,6 +195,8 @@ public bool IsFlying() return plugin.Call("isFlying"); } + // TODO don't forget live activity and live update. + internal class AirshipListener : MonoBehaviour { void OnPushReceived(string payload) @@ -307,11 +309,9 @@ public static class Features public const string FEATURE_IN_APP_AUTOMATION = "FEATURE_IN_APP_AUTOMATION"; public const string FEATURE_MESSAGE_CENTER = "FEATURE_MESSAGE_CENTER"; public const string FEATURE_PUSH = "FEATURE_PUSH"; - public const string FEATURE_CHAT = "FEATURE_CHAT"; public const string FEATURE_ANALYTICS = "FEATURE_ANALYTICS"; public const string FEATURE_TAGS_AND_ATTRIBUTES = "FEATURE_TAGS_AND_ATTRIBUTES"; public const string FEATURE_CONTACTS = "FEATURE_CONTACTS"; - public const string FEATURE_LOCATION = "FEATURE_LOCATION"; public const string FEATURE_ALL = "FEATURE_ALL"; } @@ -330,7 +330,7 @@ public record ConfigEnvironment public LogLevel? logLevel; // Optional iOS config - public IOSConfig? iOS; + public IOSEnvironmentConfig? iOS; } public enum LogLevel @@ -343,7 +343,7 @@ public enum LogLevel None = "none" } - public record IOSConfig + public record IOSEnvironmentConfig { /// /// Log privacy level. By default it logs at `private`, not logging anything lower than info to the console @@ -359,8 +359,98 @@ public enum LogPrivacyLevel Public = "public" } - public record AirshipConfig + public enum Site + { + US = "us", + EU = "eu" + } + + public record IOSConfig + { + // itunesId for rate app and app store deep links. + public string? itunesId; + } + + public record AndroidConfig { - // TODO finish AirshipConfig + // App store URI + public string? appStoreUri; + + // Fcm app name if using multiple FCM projects. + public string? fcmFirebaseAppName; + + // Notification config. + public AndroidNotificationConfig? notificationConfig; + + // Log privacy level. By default it logs at `private`, not logging anything lower than info to the console + // and redacting logs with string interpolation. `public` will log all configured log levels to the console + // without redacting any of the log lines. + public LogPrivacyLevel? logPrivacyLevel; + } + + public record AndroidNotificationConfig + { + // The icon resource name. + public string? icon; + + // The large icon resource name. + public string? largeIcon; + + // The default android notification channel ID. + public string? defaultChannelId; + + // The accent color. Must be a hex value #AARRGGBB. + public string? accentColor; } + + public record AirshipConfig + { + // Default environment. + public ConfigEnvironment? defaultEnvironment; + + // Development environment. Overrides default environment if inProduction is false. + public ConfigEnvironment? development; + + // Production environment. Overrides default environment if inProduction is true. + public ConfigEnvironment? production; + + // Cloud site. + public Site? site; + + // Switches the environment from development or production. + // If the value is not set, Airship will determine the value at runtime. + public bool? inProduction; + + // URL allow list. + public string[]? urlAllowList; + + // URL allow list for open URL scope. + public string[]? urlAllowListScopeOpenUrl; + + // URL allow list for JS bridge injection. + public string[]? urlAllowListScopeJavaScriptInterface; + + // Initial config URL for custom Airship domains. + // The URL should also be added to the urlAllowList. + public string? initialConfigUrl; + + // Enabled features. Defaults to all. + public Features[]? enabledFeatures; + + // Enables channel capture feature. This config is enabled by default. + public bool? isChannelCaptureEnabled; + + // Whether to suppress console error messages about missing allow list entries during takeOff. + // This config is disabled by default. + public bool? suppressAllowListError; + + // Pauses In-App Automation on launch. + public bool? autoPauseInAppAutomationOnLaunch; + + // iOS config. + public IOSConfig? ios; + + // Android config. + public AndroidConfig? android; + } } \ No newline at end of file diff --git a/Assets/UrbanAirship/Platforms/AirshipAnalytics.cs b/Assets/UrbanAirship/Platforms/AirshipAnalytics.cs index 301571ca..96835012 100644 --- a/Assets/UrbanAirship/Platforms/AirshipAnalytics.cs +++ b/Assets/UrbanAirship/Platforms/AirshipAnalytics.cs @@ -30,7 +30,7 @@ internal AirshipAnalytics(IAirshipPlugin plugin) /// The value of the identifier, or `null` to remove the identifier. public void AssociateIdentifier(string key, string? identifier) { - this.plugin.Call("associateIdentifier", key, identifier); + plugin.Call("associateIdentifier", key, identifier); } /// @@ -39,7 +39,7 @@ public void AssociateIdentifier(string key, string? identifier) /// The screen name. `null` to stop tracking. public void TrackScreen(string screenName) { - this.plugin.Call("trackScreen", screenName); + plugin.Call("trackScreen", screenName); } /// @@ -48,7 +48,7 @@ public void TrackScreen(string screenName) /// The custom event. public void AddCustomEvent(CustomEvent customEvent) { - this.plugin.Call("addCustomEvent", customEvent.ToJson()); + plugin.Call("addCustomEvent", customEvent.ToJson()); } /// @@ -57,7 +57,7 @@ public void AddCustomEvent(CustomEvent customEvent) /// The session ID. public string GetSessionId() { - return this.plugin.Call("getSessionId"); + return plugin.Call("getSessionId"); } } } \ No newline at end of file diff --git a/Assets/UrbanAirship/Platforms/AirshipChannel.cs b/Assets/UrbanAirship/Platforms/AirshipChannel.cs index cbe5f2bc..d11d16fd 100644 --- a/Assets/UrbanAirship/Platforms/AirshipChannel.cs +++ b/Assets/UrbanAirship/Platforms/AirshipChannel.cs @@ -21,17 +21,6 @@ internal AirshipChannel(IAirshipPlugin plugin) this.plugin = plugin; } - /// - /// Gets the tags currently set for the device. - /// - /// The tags. - public IEnumerable GetTags() - { - string tagsAsJson = plugin.Call("getTags"); - JsonArray jsonArray = JsonArray.FromJson(tagsAsJson); - return jsonArray.AsEnumerable(); - } - /// /// Gets the channel ID associated with the device. /// @@ -52,28 +41,16 @@ public string WaitForChannelId() } /// - /// Gets the channel's subscription lists. + /// Gets the tags currently set for the device. /// - /// The subscription lists. - public IEnumerable GetSubscriptionLists() + /// The tags. + public IEnumerable GetTags() { - string subscriptionListsAsJson = plugin.Call("getSubscriptionLists"); - JsonArray jsonArray = JsonArray.FromJson(subscriptionListsAsJson); + string tagsAsJson = plugin.Call("getTags"); + JsonArray jsonArray = JsonArray.FromJson(tagsAsJson); return jsonArray.AsEnumerable(); } - /// - /// Returns an editor for channel subscription lists. - /// - /// A SubscriptionListEditor. - public SubscriptionListEditor EditSubscriptionLists() - { - return new SubscriptionListEditor((string payload) => - { - plugin.Call("editSubscriptionLists", payload); - }); - } - /// /// Returns an editor for channel tags. /// @@ -94,7 +71,7 @@ public TagGroupEditor EditTagGroups() { return new TagGroupEditor((string payload) => { - plugin.Call("editTagGroups", payload); + plugin.Call("editChannelTagGroups", payload); }); } @@ -106,7 +83,30 @@ public AttributeEditor EditAttributes() { return new AttributeEditor((string payload) => { - plugin.Call("editAttributes", payload); + plugin.Call("editChannelAttributes", payload); + }); + } + + /// + /// Gets the channel's subscription lists. + /// + /// The subscription lists. + public IEnumerable GetSubscriptionLists() + { + string subscriptionListsAsJson = plugin.Call("getChannelSubscriptionLists"); + JsonArray jsonArray = JsonArray.FromJson(subscriptionListsAsJson); + return jsonArray.AsEnumerable(); + } + + /// + /// Returns an editor for channel subscription lists. + /// + /// A SubscriptionListEditor. + public SubscriptionListEditor EditSubscriptionLists() + { + return new SubscriptionListEditor((string payload) => + { + plugin.Call("editChannelSubscriptionLists", payload); }); } } diff --git a/Assets/UrbanAirship/Platforms/AirshipContact.cs b/Assets/UrbanAirship/Platforms/AirshipContact.cs index e17c3a41..e4c95645 100644 --- a/Assets/UrbanAirship/Platforms/AirshipContact.cs +++ b/Assets/UrbanAirship/Platforms/AirshipContact.cs @@ -27,7 +27,7 @@ internal AirshipContact(IAirshipPlugin plugin) /// The named user Id. public void Identify(string namedUserId) { - this.plugin.Call("identify", namedUserId); + plugin.Call("identify", namedUserId); } /// @@ -35,7 +35,7 @@ public void Identify(string namedUserId) /// public void Reset() { - this.plugin.Call("reset"); + plugin.Call("reset"); } /// @@ -44,7 +44,31 @@ public void Reset() /// The named user Id. public string? GetNamedUserId() { - return this.plugin.Call("getNamedUserId"); + return plugin.Call("getNamedUserId"); + } + + /// + /// Returns an editor for contact tag groups. + /// + /// A TagGroupEditor for contact tag groups. + public TagGroupEditor EditTagGroups() + { + return new TagGroupEditor((string payload) => + { + plugin.Call("editContactTagGroups", payload); + }); + } + + /// + /// Returns an editor for contact attributes. + /// + /// A AttributeEditor for contact attributes. + public AttributeEditor EditAttributes() + { + return new AttributeEditor((string payload) => + { + plugin.Call("editContactAttributes", payload); + }); } /// @@ -55,7 +79,7 @@ public Dictionary> GetSubscriptionLists() { Dictionary> scopedSubscriptionLists = new Dictionary>(); - string subscriptionListsAsJson = this.plugin.Call("getSubscriptionLists"); + string subscriptionListsAsJson = plugin.Call("getContactSubscriptionLists"); ScopedSubscriptionList[] _scopedSubscriptionLists = JsonArray.FromJson(subscriptionListsAsJson).values; foreach (ScopedSubscriptionList subscriptionList in _scopedSubscriptionLists) @@ -74,31 +98,7 @@ public ScopedSubscriptionListEditor EditSubscriptionLists() { return new ScopedSubscriptionListEditor((string payload) => { - this.plugin.Call("editSubscriptionLists", payload); - }); - } - - /// - /// Returns an editor for contact tag groups. - /// - /// A TagGroupEditor for contact tag groups. - public TagGroupEditor EditTagGroups() - { - return new TagGroupEditor((string payload) => - { - this.plugin.Call("editTagGroups", payload); - }); - } - - /// - /// Returns an editor for contact attributes. - /// - /// A AttributeEditor for contact attributes. - public AttributeEditor EditAttributes() - { - return new AttributeEditor((string payload) => - { - this.plugin.Call("editAttributes", payload); + plugin.Call("editContactSubscriptionLists", payload); }); } } diff --git a/Assets/UrbanAirship/Platforms/AirshipInApp.cs b/Assets/UrbanAirship/Platforms/AirshipInApp.cs index e5dede25..1b7e3f20 100644 --- a/Assets/UrbanAirship/Platforms/AirshipInApp.cs +++ b/Assets/UrbanAirship/Platforms/AirshipInApp.cs @@ -24,7 +24,7 @@ internal AirshipInApp (IAirshipPlugin plugin) { /// /// true to pause, false to resume. public void SetPaused (bool paused) { - this.plugin.Call ("setPaused", paused); + plugin.Call ("setPaused", paused); } /// @@ -32,7 +32,7 @@ public void SetPaused (bool paused) { /// /// true if paused, otherwise false public bool IsPaused () { - return this.plugin.Call ("isPaused"); + return plugin.Call ("isPaused"); } /// @@ -40,7 +40,7 @@ public bool IsPaused () { /// /// The display interval. public void SetDisplayInterval (TimeSpan displayInterval) { - this.plugin.Call ("setDisplayInterval", displayInterval); + plugin.Call ("setDisplayInterval", displayInterval.TotalMilliseconds); } /// @@ -48,7 +48,7 @@ public void SetDisplayInterval (TimeSpan displayInterval) { /// /// The display interval. public TimeSpan GetDisplayInterval () { - return this.plugin.Call ("getDisplayInterval"); + return TimeSpan.FromMilliseconds (plugin.Call ("getDisplayInterval")); } } } \ No newline at end of file diff --git a/Assets/UrbanAirship/Platforms/AirshipMessageCenter.cs b/Assets/UrbanAirship/Platforms/AirshipMessageCenter.cs index 485e96fa..4d4c4eb0 100644 --- a/Assets/UrbanAirship/Platforms/AirshipMessageCenter.cs +++ b/Assets/UrbanAirship/Platforms/AirshipMessageCenter.cs @@ -42,6 +42,7 @@ public IEnumerable GetMessages() string inboxMessagesAsJson = plugin.Call("getMessages"); InternalInboxMessage[] internalInboxMessages = JsonArray.FromJson(inboxMessagesAsJson).values; + // TODO verify this as the proxy provide a the extras into a map // Unity's JsonUtility doesn't support embedded dictionaries - constructor will create the extras dictionary foreach (InternalInboxMessage internalInboxMessage in internalInboxMessages) { diff --git a/Assets/UrbanAirship/Platforms/AirshipPrivacyManager.cs b/Assets/UrbanAirship/Platforms/AirshipPrivacyManager.cs index d8375c74..5fc3f36c 100644 --- a/Assets/UrbanAirship/Platforms/AirshipPrivacyManager.cs +++ b/Assets/UrbanAirship/Platforms/AirshipPrivacyManager.cs @@ -67,5 +67,4 @@ public bool IsFeaturesEnabled(string[] features) return plugin.Call("isFeaturesEnabled", features); } } -} -// TODO check if we can use an enum for the features \ No newline at end of file +} \ No newline at end of file diff --git a/Assets/UrbanAirship/Platforms/AirshipPush.cs b/Assets/UrbanAirship/Platforms/AirshipPush.cs index 2c34bbaa..9bcde07e 100644 --- a/Assets/UrbanAirship/Platforms/AirshipPush.cs +++ b/Assets/UrbanAirship/Platforms/AirshipPush.cs @@ -80,9 +80,9 @@ public PushNotificationStatus GetNotificationStatus() /// Gets the registration token if generated. /// /// The push token. - public string GetPushToken() + public string? GetPushToken() { - return plugin.Call("getPushToken"); + return plugin.Call("getPushToken"); } /// diff --git a/airship.properties b/airship.properties index b170da2d..4e931156 100644 --- a/airship.properties +++ b/airship.properties @@ -8,7 +8,7 @@ iosAirshipVersion = 16.12.6 androidAirshipVersion = 16.11.2 # Airship Framework proxy version -airshipFrameworkProxyVersion = 13.0.1 +airshipFrameworkProxyVersion = 14.6.0 # Android X annotations androidxAnnotationVersion = 1.9.1 diff --git a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt index 6e12804b..245683c2 100644 --- a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt +++ b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt @@ -2,11 +2,10 @@ package com.urbanairship.unityplugin import com.unity3d.player.UnityPlayer -import com.unity3d.player.a.a -import com.urbanairship.UAirship +import com.urbanairship.PrivacyManager import com.urbanairship.android.framework.proxy.ProxyLogger -import com.urbanairship.android.framework.proxy.TagGroupOperation import com.urbanairship.android.framework.proxy.proxies.AirshipProxy +import com.urbanairship.android.framework.proxy.proxies.EnableUserNotificationsArgs import com.urbanairship.json.JsonException import com.urbanairship.json.JsonValue import org.json.JSONArray @@ -15,23 +14,21 @@ class UnityPlugin { private val airshipProxyInstance = AirshipProxy.shared(UnityPlayer.currentActivity.applicationContext) - private var listener: String? = nul + private var listener: String? = null fun setListener(listener: String) { ProxyLogger.debug("UnityPlugin setListener: $listener") this.listener = listener } - // Push - - fun getUserNotificationsEnabled(): Boolean { - ProxyLogger.debug("UnityPlugin getUserNotificationsEnabled") - return airshipProxyInstance.push.isUserNotificationsEnabled() + fun takeOff(config: String): Boolean { + ProxyLogger.debug("UnityPlugin takeOff: $config") + return airshipProxyInstance.takeOff(JsonValue.parseString(config)) } - fun setUserNotificationsEnabled(enabled: Boolean) { - ProxyLogger.debug("UnityPlugin setUserNotificationsEnabled: $enabled") - airshipProxyInstance.push.setUserNotificationsEnabled(enabled) + fun isFlying(): Boolean { + ProxyLogger.debug("UnityPlugin isFlying") + return airshipProxyInstance.isFlying() } // Channel @@ -41,6 +38,11 @@ class UnityPlugin { return airshipProxyInstance.channel.getChannelId() } + suspend fun waitForChannelId(): String { + ProxyLogger.debug("UnityPlugin waitForChannelId") + return airshipProxyInstance.channel.waitForChannelId() + } + fun addTag(tag: String) { ProxyLogger.debug("UnityPlugin addTag: $tag") airshipProxyInstance.channel.addTag(tag) @@ -60,6 +62,111 @@ class UnityPlugin { return jsonArray.toString() } + fun editTags(payload: String) { + ProxyLogger.debug("UnityPlugin editTags: $payload") + try { + airshipProxyInstance.channel.editTags(JsonValue.parseString(payload)) + } catch (e: JsonException) { + ProxyLogger.error("Failed to parse payload", e) + } + } + + fun editChannelTagGroups(payload: String) { + ProxyLogger.debug("UnityPlugin editChannelTagGroups: $payload") + try { + airshipProxyInstance.channel.editTagGroups(JsonValue.parseString(payload)) + } catch (e: JsonException) { + ProxyLogger.error("Failed to parse payload", e) + } + } + + fun editChannelAttributes(payload: String) { + ProxyLogger.debug("UnityPlugin editChannelAttributes: $payload") + try { + airshipProxyInstance.channel.editAttributes(JsonValue.parseString(payload)) + } catch (e: JsonException) { + ProxyLogger.error("Failed to parse payload", e) + } + } + + suspend fun getChannelSubscriptionLists(): String { + ProxyLogger.debug("UnityPlugin getChannelSubscriptionLists") + val jsonArray = JSONArray() + for (tag in airshipProxyInstance.channel.getSubscriptionLists()) { + jsonArray.put(tag) + } + return jsonArray.toString() + } + + fun editChannelSubscriptionLists(payload: String) { + ProxyLogger.debug("UnityPlugin editChannelSubscriptionLists: $payload") + try { + airshipProxyInstance.channel.editSubscriptionLists(JsonValue.parseString(payload)) + } catch (e: JsonException) { + ProxyLogger.error("Failed to parse payload", e) + } + } + + // Contact + + fun identify(namedUserId: String?) { + ProxyLogger.debug("UnityPlugin identify: $namedUserId") + airshipProxyInstance.contact.identify(namedUserId) + } + + fun reset() { + ProxyLogger.debug("UnityPlugin reset") + airshipProxyInstance.contact.reset() + } + + fun getNamedUserId(): String? { + ProxyLogger.debug("UnityPlugin getNamedUserId") + return airshipProxyInstance.contact.getNamedUserId() + } + + fun notifyRemoteLogin() { + ProxyLogger.debug("UnityPlugin notifyRemoteLogin") + airshipProxyInstance.contact.notifyRemoteLogin() + } + + fun editContactTagGroups(payload: String) { + ProxyLogger.debug("UnityPlugin editContactTagGroups: $payload") + try { + airshipProxyInstance.contact.editTagGroups(JsonValue.parseString(payload)) + } catch (e: JsonException) { + ProxyLogger.error("Failed to parse payload", e) + } + } + + fun editContactAttributes(payload: String) { + ProxyLogger.debug("UnityPlugin editContactAttributes: $payload") + try { + airshipProxyInstance.contact.editAttributes(JsonValue.parseString(payload)) + } catch (e: JsonException) { + ProxyLogger.error("Failed to parse payload", e) + } + } + + fun getContactSubscriptionLists(): String { + ProxyLogger.debug("UnityPlugin getContactSubscriptionLists") + + // TODO finish this + val jsonArray = JSONArray() +// for (tag in airshipProxyInstance.contact.getSubscriptionLists()) { +// jsonArray.put(tag) +// } + return jsonArray.toString() + } + + fun editContactSubscriptionLists(payload: String) { + ProxyLogger.debug("UnityPlugin editContactSubscriptionLists: $payload") + try { + airshipProxyInstance.contact.editSubscriptionLists(JsonValue.parseString(payload)) + } catch (e: JsonException) { + ProxyLogger.error("Failed to parse payload", e) + } + } + // Analytics fun associateIdentifier(key: String, identifier: String?) { @@ -90,35 +197,202 @@ class UnityPlugin { return airshipProxyInstance.analytics.getSessionId() } - // Contact + // InApp - fun identify(namedUserId: String?) { - ProxyLogger.debug("UnityPlugin identify: $namedUserId") - airshipProxyInstance.contact.identify(namedUserId) + fun setPaused(paused: Boolean) { + ProxyLogger.debug("UnityPlugin setPaused: $paused") + airshipProxyInstance.inApp.setPaused(paused) } - fun reset() { - ProxyLogger.debug("UnityPlugin reset") - airshipProxyInstance.contact.reset() + fun isPaused(): Boolean { + ProxyLogger.debug("UnityPlugin isPaused") + return airshipProxyInstance.inApp.isPaused() } - fun getNamedUserId(): String? { - ProxyLogger.debug("UnityPlugin getNamedUserId") - return airshipProxyInstance.contact.getNamedUserId() + fun setDisplayInterval(displayInterval: Long) { + ProxyLogger.debug("UnityPlugin setDisplayInterval: $displayInterval") + airshipProxyInstance.inApp.setDisplayInterval(displayInterval) } - fun notifyRemoteLogin() { - ProxyLogger.debug("UnityPlugin notifyRemoteLogin") - airshipProxyInstance.contact.notifyRemoteLogin() + fun getDisplayInterval(): Long { + ProxyLogger.debug("UnityPlugin getDisplayInterval") + return airshipProxyInstance.inApp.getDisplayInterval() + } + + // Locale + + fun setLocaleOverride(localeIdentifier: String) { + ProxyLogger.debug("UnityPlugin setLocaleOverride: $localeIdentifier") + airshipProxyInstance.locale.setCurrentLocale(localeIdentifier) + } + + fun clearLocaleOverride() { + ProxyLogger.debug("UnityPlugin clearLocaleOverride") + airshipProxyInstance.locale.clearLocale() + } + + fun getLocale(): String { + ProxyLogger.debug("UnityPlugin getLocale") + return airshipProxyInstance.locale.getCurrentLocale() + } + + // Message Center + + suspend fun getUnreadCount(): Int { + ProxyLogger.debug("UnityPlugin getUnreadCount") + return airshipProxyInstance.messageCenter.getUnreadMessagesCount() + } + + suspend fun getMessages(): String { + ProxyLogger.debug("UnityPlugin getMessages") + return JsonValue.wrapOpt(airshipProxyInstance.messageCenter.getMessages()).toString() + } + + fun markMessageRead(messageId: String) { + ProxyLogger.debug("UnityPlugin markMessageRead: $messageId") + airshipProxyInstance.messageCenter.markMessageRead(messageId) + } + + fun deleteMessage(messageId: String) { + ProxyLogger.debug("UnityPlugin deleteMessage: $messageId") + airshipProxyInstance.messageCenter.deleteMessage(messageId) + } + + suspend fun refreshMessages() { + ProxyLogger.debug("UnityPlugin refreshMessages") + airshipProxyInstance.messageCenter.refreshInbox() + } + + fun setAutoLaunchDefaultMessageCenter(enabled: Boolean) { + ProxyLogger.debug("UnityPlugin setAutoLaunchDefaultMessageCenter: $enabled") + airshipProxyInstance.messageCenter.setAutoLaunchDefaultMessageCenter(enabled) + } + + fun displayMessageCenter(messageId: String?) { + ProxyLogger.debug("UnityPlugin displayMessageCenter: $messageId") + airshipProxyInstance.messageCenter.display(messageId) + } + + fun dismissMessageCenter() { + ProxyLogger.debug("UnityPlugin dismissMessageCenter") + airshipProxyInstance.messageCenter.dismiss() + } + + fun showMessageView(messageId: String) { + ProxyLogger.debug("UnityPlugin showMessageView: $messageId") + airshipProxyInstance.messageCenter.showMessageView(messageId) + } + + fun showMessageCenter(messageId: String?) { + ProxyLogger.debug("UnityPlugin showMessageCenter: $messageId") + airshipProxyInstance.messageCenter.showMessageCenter(messageId) + } + + // Preference Center + + fun displayPreferenceCenter(preferenceCenterId: String) { + ProxyLogger.debug("UnityPlugin displayPreferenceCenter: $preferenceCenterId") + airshipProxyInstance.preferenceCenter.displayPreferenceCenter(preferenceCenterId) + } + + suspend fun getPreferenceCenterConfig(preferenceCenterId: String): String { + ProxyLogger.debug("UnityPlugin getPreferenceCenterConfig: $preferenceCenterId") + return JsonValue.wrapOpt(airshipProxyInstance.preferenceCenter.getPreferenceCenterConfig(preferenceCenterId)).toString() + } + + fun setAutoLaunchDefaultPreferenceCenter(preferenceCenterId: String, autoLaunch: Boolean) { + ProxyLogger.debug("UnityPlugin setAutoLaunchDefaultPreferenceCenter: $preferenceCenterId, $autoLaunch") + airshipProxyInstance.preferenceCenter.setAutoLaunchPreferenceCenter(preferenceCenterId, autoLaunch) + } + + // Privacy Manager + + fun setEnabledFeatures(features: Array) { + ProxyLogger.debug("UnityPlugin setEnabledFeatures: $features") + airshipProxyInstance.privacyManager.setEnabledFeatures(features.asList()) + } + + fun getEnabledFeatures(): Array { + ProxyLogger.debug("UnityPlugin getEnabledFeatures") + return airshipProxyInstance.privacyManager.getFeatureNames().toTypedArray() + } + + fun enableFeatures(features: Array) { + ProxyLogger.debug("UnityPlugin enableFeatures: $features") + airshipProxyInstance.privacyManager.enableFeatures(features.asList()) + } + + fun disableFeatures(features: Array) { + ProxyLogger.debug("UnityPlugin disableFeatures: $features") + airshipProxyInstance.privacyManager.disableFeatures(features.asList()) } - //fun editTagGroups(operations: List) + fun isFeaturesEnabled(features: Array): Boolean { + ProxyLogger.debug("UnityPlugin isFeaturesEnabled: $features") + return airshipProxyInstance.privacyManager.isFeatureEnabled(features.asList()) + } + + // Push - // TODO finish the implementation using the proxy + fun isUserNotificationsEnabled(): Boolean { + ProxyLogger.debug("UnityPlugin isUserNotificationsEnabled") + return airshipProxyInstance.push.isUserNotificationsEnabled() + } + + fun setUserNotificationsEnabled(enabled: Boolean) { + ProxyLogger.debug("UnityPlugin setUserNotificationsEnabled: $enabled") + airshipProxyInstance.push.setUserNotificationsEnabled(enabled) + } + + suspend fun enableUserNotifications(fallback: String?): Boolean { + ProxyLogger.debug("UnityPlugin enableUserNotifications: $fallback") + return airshipProxyInstance.push.enableUserPushNotifications( + EnableUserNotificationsArgs.fromJson(JsonValue.parseString(fallback)) + ) + } + + suspend fun getNotificationStatus(): String { + ProxyLogger.debug("UnityPlugin getNotificationStatus") + return airshipProxyInstance.push.getNotificationStatus().toJsonValue().toString() + } + + fun getPushToken(): String? { + ProxyLogger.debug("UnityPlugin getPushToken") + return airshipProxyInstance.push.getRegistrationToken() + } + + fun getActiveNotifications(): String { + ProxyLogger.debug("UnityPlugin getActiveNotifications") + return JsonValue.wrapOpt(airshipProxyInstance.push.getActiveNotifications()).toString() + } + + fun clearNotifications() { + ProxyLogger.debug("UnityPlugin clearNotifications") + airshipProxyInstance.push.clearNotifications() + } + + fun clearNotification(identifier: String) { + ProxyLogger.debug("UnityPlugin clearNotification: $identifier") + airshipProxyInstance.push.clearNotification(identifier) + } + + // TODO finish the implementation companion object { private val instance = UnityPlugin() + private val featuresMap = mapOf( + "FEATURE_NONE" to PrivacyManager.Feature.NONE, + "FEATURE_IN_APP_AUTOMATION" to PrivacyManager.Feature.IN_APP_AUTOMATION, + "FEATURE_MESSAGE_CENTER" to PrivacyManager.Feature.MESSAGE_CENTER, + "FEATURE_PUSH" to PrivacyManager.Feature.PUSH, + "FEATURE_ANALYTICS" to PrivacyManager.Feature.ANALYTICS, + "FEATURE_TAGS_AND_ATTRIBUTES" to PrivacyManager.Feature.TAGS_AND_ATTRIBUTES, + "FEATURE_CONTACTS" to PrivacyManager.Feature.CONTACTS, + "FEATURE_LOCATION" to PrivacyManager.Feature.FEATURE_FLAGS, + "FEATURE_ALL" to PrivacyManager.Feature.ALL + ) + @JvmStatic fun shared(): UnityPlugin { return instance From b5e99bcb4a5b837685b152295923aeaa28c3cda8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gibern=C3=A9?= Date: Fri, 19 Sep 2025 14:44:50 -0400 Subject: [PATCH 03/27] wip --- .../iOS/UAUnityMessageViewController.h | 24 - .../iOS/UAUnityMessageViewController.m | 159 ---- Assets/Plugins/iOS/UAUnityPlugin.h | 109 --- Assets/Plugins/iOS/UAUnityPlugin.m | 786 ------------------ Assets/Plugins/iOS/UnityPlugin.swift | 107 +++ Assets/Scripts/UrbanAirshipBehaviour.cs | 193 ++--- Assets/UrbanAirship/Platforms/Airship.cs | 34 +- .../AirshipEnumStringValueAttribute.cs | 33 + .../UrbanAirship/Platforms/AirshipLocale.cs | 2 +- .../Platforms/AirshipPreferenceCenter.cs | 50 +- Assets/UrbanAirship/Platforms/AirshipPush.cs | 80 +- .../Android/UAirshipPluginAndroid.cs | 221 ----- .../UrbanAirship/Platforms/IAirshipPlugin.cs | 11 +- .../UrbanAirship/Platforms/IUAirshipPlugin.cs | 102 --- Assets/UrbanAirship/Platforms/UAirship.cs | 626 -------------- .../Platforms/iOS/UAirshipPluginIOS.cs | 304 ------- .../UrbanAirship/Tests/MessageCenterTests.cs | 688 +++++++-------- 17 files changed, 688 insertions(+), 2841 deletions(-) delete mode 100644 Assets/Plugins/iOS/UAUnityMessageViewController.h delete mode 100644 Assets/Plugins/iOS/UAUnityMessageViewController.m delete mode 100644 Assets/Plugins/iOS/UAUnityPlugin.h delete mode 100644 Assets/Plugins/iOS/UAUnityPlugin.m create mode 100644 Assets/Plugins/iOS/UnityPlugin.swift create mode 100644 Assets/UrbanAirship/Platforms/AirshipEnumStringValueAttribute.cs delete mode 100644 Assets/UrbanAirship/Platforms/Android/UAirshipPluginAndroid.cs delete mode 100644 Assets/UrbanAirship/Platforms/IUAirshipPlugin.cs delete mode 100644 Assets/UrbanAirship/Platforms/UAirship.cs delete mode 100644 Assets/UrbanAirship/Platforms/iOS/UAirshipPluginIOS.cs diff --git a/Assets/Plugins/iOS/UAUnityMessageViewController.h b/Assets/Plugins/iOS/UAUnityMessageViewController.h deleted file mode 100644 index 85c90f43..00000000 --- a/Assets/Plugins/iOS/UAUnityMessageViewController.h +++ /dev/null @@ -1,24 +0,0 @@ -/* Copyright Urban Airship and Contributors */ - -#if __has_include("UAirship.h") -#import "UAirship.h" -#import "UAMessageCenter.h" -#import "UAInboxMessage.h" -#import "UAInboxMessageList.h" -#import "UAMessageCenterMessageViewDelegate.h" -#import "UADefaultMessageCenterMessageViewController.h" -#import "UAMessageCenterResources.h" -#import "UAMessageCenterLocalization.h" -#else -@import AirshipKit; -#endif - -NS_ASSUME_NONNULL_BEGIN - -@interface UAUnityMessageViewController : UINavigationController - -- (void)loadMessageForID:(nullable NSString *)messageID; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Assets/Plugins/iOS/UAUnityMessageViewController.m b/Assets/Plugins/iOS/UAUnityMessageViewController.m deleted file mode 100644 index 7d018836..00000000 --- a/Assets/Plugins/iOS/UAUnityMessageViewController.m +++ /dev/null @@ -1,159 +0,0 @@ -/* Copyright Urban Airship and Contributors */ - -#import "UAUnityMessageViewController.h" - -@interface UAUnityMessageViewController() -@property (nonatomic, strong) UADefaultMessageCenterMessageViewController *airshipMessageViewController; -@end - -@implementation UAUnityMessageViewController - -- (instancetype)init { - self = [super init]; - - if (self) { - self.airshipMessageViewController = [[UADefaultMessageCenterMessageViewController alloc] initWithNibName:@"UADefaultMessageCenterMessageViewController" - bundle:[UAMessageCenterResources bundle]]; - self.airshipMessageViewController.delegate = self; - - UIBarButtonItem *done = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone - target:self - action:@selector(inboxMessageDone:)]; - - self.airshipMessageViewController.navigationItem.leftBarButtonItem = done; - - self.viewControllers = @[self.airshipMessageViewController]; - - self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; - self.modalPresentationStyle = UIModalPresentationFullScreen; - } - - return self; -} - -- (void)inboxMessageDone:(id)sender { - [self dismissViewControllerAnimated:true completion:nil]; -} - -- (void)loadMessageForID:(NSString *)messageID { - [self.airshipMessageViewController loadMessageForID:messageID]; -} - -#pragma mark UAMessageCenterMessageViewDelegate - -- (void)messageClosed:(NSString *)messageID { - [self dismissViewControllerAnimated:YES completion:nil]; -} - -- (void)messageLoadStarted:(NSString *)messageID { - // no-op -} - -- (void)messageLoadSucceeded:(NSString *)messageID { - // no-op -} - -- (void)displayFailedToLoadAlertOnOK:(void (^)(void))okCompletion onRetry:(void (^)(void))retryCompletion { - UIAlertController* alert = [UIAlertController alertControllerWithTitle:UAMessageCenterLocalizedString(@"ua_connection_error") - message:UAMessageCenterLocalizedString(@"ua_mc_failed_to_load") - preferredStyle:UIAlertControllerStyleAlert]; - - UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:UAMessageCenterLocalizedString(@"ua_ok") - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - if (okCompletion) { - okCompletion(); - } - }]; - - [alert addAction:defaultAction]; - - if (retryCompletion) { - UIAlertAction *retryAction = [UIAlertAction actionWithTitle:UAMessageCenterLocalizedString(@"ua_retry_button") - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * _Nonnull action) { - if (retryCompletion) { - retryCompletion(); - } - }]; - - [alert addAction:retryAction]; - } - - [self presentViewController:alert animated:YES completion:nil]; -} - -- (void)displayNoLongerAvailableAlertOnOK:(void (^)(void))okCompletion { - UIAlertController* alert = [UIAlertController alertControllerWithTitle:UAMessageCenterLocalizedString(@"ua_content_error") - message:UAMessageCenterLocalizedString(@"ua_mc_no_longer_available") - preferredStyle:UIAlertControllerStyleAlert]; - - UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:UAMessageCenterLocalizedString(@"ua_ok") - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - if (okCompletion) { - okCompletion(); - } - }]; - - [alert addAction:defaultAction]; - - [self presentViewController:alert animated:YES completion:nil]; -} - -- (void)messageLoadFailed:(NSString *)messageID error:(NSError *)error { - UA_LTRACE(@"message load failed: %@", messageID); - - void (^retry)(void) = ^{ - UA_WEAKIFY(self); - [self displayFailedToLoadAlertOnOK:^{ - UA_STRONGIFY(self) - [self dismissViewControllerAnimated:true completion:nil]; - } onRetry:^{ - UA_STRONGIFY(self) - [self loadMessageForID:messageID]; - }]; - }; - - void (^handleFailed)(void) = ^{ - UA_WEAKIFY(self); - [self displayFailedToLoadAlertOnOK:^{ - UA_STRONGIFY(self) - [self dismissViewControllerAnimated:true completion:nil]; - } onRetry:nil]; - }; - - void (^handleExpired)(void) = ^{ - UA_WEAKIFY(self); - [self displayNoLongerAvailableAlertOnOK:^{ - UA_STRONGIFY(self) - [self dismissViewControllerAnimated:true completion:nil]; - }]; - }; - - if ([error.domain isEqualToString:UAMessageCenterMessageLoadErrorDomain]) { - if (error.code == UAMessageCenterMessageLoadErrorCodeFailureStatus) { - // Encountered a failure status code - NSUInteger status = [error.userInfo[UAMessageCenterMessageLoadErrorHTTPStatusKey] unsignedIntValue]; - - if (status >= 500) { - retry(); - } else if (status == 410) { - // Gone: message has been permanently deleted from the backend. - handleExpired(); - } else { - handleFailed(); - } - } else if (error.code == UAMessageCenterMessageLoadErrorCodeMessageExpired) { - handleExpired(); - } else { - retry(); - } - } else { - // Other errors - retry(); - } -} - -@end - diff --git a/Assets/Plugins/iOS/UAUnityPlugin.h b/Assets/Plugins/iOS/UAUnityPlugin.h deleted file mode 100644 index d24fcf6c..00000000 --- a/Assets/Plugins/iOS/UAUnityPlugin.h +++ /dev/null @@ -1,109 +0,0 @@ -/* Copyright Airship and Contributors */ - -#import -#if __has_include("UAirship.h") -#import "AirshipLib.h" -#else -@import AirshipKit; -#endif - -extern void UnitySendMessage(const char *, const char *, const char *); - -#pragma mark - -#pragma mark Listener - -void UAUnityPlugin_setListener(const char* listener); - -#pragma mark - -#pragma mark Deep Links - -const char* UAUnityPlugin_getDeepLink(bool clear); - -#pragma mark - -#pragma mark UA Push Functions - -const char* UAUnityPlugin_getIncomingPush(bool clear); - -bool UAUnityPlugin_getUserNotificationsEnabled(); -void UAUnityPlugin_setUserNotificationsEnabled(bool enabled); - -const char* UAUnityPlugin_getTags(); -void UAUnityPlugin_addTag(const char* tag); -void UAUnityPlugin_removeTag(const char* tag); - -const char* UAUnityPlugin_getChannelId(); - -#pragma mark - -#pragma mark Custom Events -void UAUnityPlugin_addCustomEvent(const char *customEvent); - -#pragma mark - -#pragma mark Associated Identifier -void UAUnityPlugin_associateIdentifier(const char *key, const char *identifier); - -#pragma mark - -#pragma mark Named User -void UAUnityPlugin_setNamedUserID(const char *namedUserID); -const char* UAUnityPlugin_getNamedUserID(); - -#pragma mark - -#pragma mark Message Center -void UAUnityPlugin_displayMessageCenter(); -void UAUnityPlugin_displayInboxMessage(const char *messageId); -void UAUnityPlugin_refreshInbox(); -const char* UAUnityPlugin_getInboxMessages(); -void UAUnityPlugin_markInboxMessageRead(const char *messageID); -void UAUnityPlugin_deleteInboxMessage(const char *messageID); -void UAUnityPlugin_setAutoLaunchDefaultMessageCenter(bool enabled); -int UAUnityPlugin_getMessageCenterUnreadCount(); -int UAUnityPlugin_getMessageCenterCount(); - -#pragma mark - -#pragma mark In-app - -double UAUnityPlugin_getInAppAutomationDisplayInterval(); -void UAUnityPlugin_setInAppAutomationDisplayInterval(double value); -bool UAUnityPlugin_isInAppAutomationPaused(); -void UAUnityPlugin_setInAppAutomationPaused(bool paused); - -#pragma mark - -#pragma mark Tag Groups - -void UAUnityPlugin_editNamedUserTagGroups(const char *payload); -void UAUnityPlugin_editChannelTagGroups(const char *payload); - -#pragma mark - -#pragma mark Attributes - -void UAUnityPlugin_editChannelAttributes(const char *payload); -void UAUnityPlugin_editNamedUserAttributes(const char *payload); - -#pragma mark - -#pragma mark Data Collection -void UAUnityPlugin_setEnabledFeatures(const char *features); -void UAUnityPlugin_enableFeatures(const char *features); -void UAUnityPlugin_disableFeatures(const char *features); -bool UAUnityPlugin_isFeatureEnabled(const char *feature); -bool UAUnityPlugin_isAnyFeatureEnabled(); -const char* UAUnityPlugin_getEnabledFeatures(); - -#pragma mark - -#pragma mark Preference Center - -void UAUnityPlugin_openPreferenceCenter(NSString *preferenceCenterId); - -#pragma mark - -#pragma mark Helpers -bool isValidFeature(NSArray *features); -UAFeatures stringToFeature(NSArray *features); -NSArray * featureToString(UAFeatures features); - -@interface UAUnityPlugin : NSObject - -+ (UAUnityPlugin *)shared; - -@property (nonatomic, copy) NSString* listener; -@property (nonatomic, strong) NSDictionary* storedNotification; -@property (nonatomic, copy) NSString* storedDeepLink; - -@end diff --git a/Assets/Plugins/iOS/UAUnityPlugin.m b/Assets/Plugins/iOS/UAUnityPlugin.m deleted file mode 100644 index b285f122..00000000 --- a/Assets/Plugins/iOS/UAUnityPlugin.m +++ /dev/null @@ -1,786 +0,0 @@ -/* Copyright Airship and Contributors */ - -#import "UAUnityPlugin.h" -#import "UnityInterface.h" -#import "UAUnityMessageViewController.h" - -static UAUnityPlugin *shared_; -static dispatch_once_t onceToken_; - -NSString *const UAUnityAutoLaunchMessageCenterKey = @"com.urbanairship.auto_launch_message_center"; -NSString *const UADisplayInboxActionDefaultRegistryName = @"display_inbox_action"; -NSString *const UAUnityPluginVersionKey = @"UAUnityPluginVersion"; - -@interface UAUnityPlugin() -@property (nonatomic, strong) UAUnityMessageViewController *messageViewController; -@end - -@implementation UAUnityPlugin - -+ (void)load { - UA_LDEBUG(@"UnityPlugin class loaded"); - [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidFinishLaunchingNotification - object:nil - queue:nil usingBlock:^(NSNotification * _Nonnull note) { - [UAUnityPlugin performTakeOffWithLaunchOptions:note.userInfo]; - }]; -} -+ (void)performTakeOffWithLaunchOptions:(NSDictionary *)launchOptions { - UA_LDEBUG(@"UnityPlugin taking off"); - [UAirship takeOffWithLaunchOptions: launchOptions]; - - NSString *version = [NSBundle mainBundle].infoDictionary[UAUnityPluginVersionKey] ?: @"0.0.0"; - [[UAirship analytics] registerSDKExtension:UASDKExtensionUnity version:version]; - - // UAPush delegate and UAActionRegistry need to be set at load so that cold start launches get deeplinks - [UAirship push].pushNotificationDelegate = [UAUnityPlugin shared]; - [UAirship shared].deepLinkDelegate = [UAUnityPlugin shared]; - - // Check if the config specified default foreground presentation options - NSDictionary *customOptions = [UAirship shared].config.customConfig; - - if (customOptions) { - UNNotificationPresentationOptions options = UNNotificationPresentationOptionNone; - - if ([customOptions[@"notificationPresentationOptionAlert"] boolValue]) { - options = options | UNNotificationPresentationOptionAlert; - } - if ([customOptions[@"notificationPresentationOptionBadge"] boolValue]) { - options = options | UNNotificationPresentationOptionBadge; - } - if ([customOptions[@"notificationPresentationOptionSound"] boolValue]) { - options = options | UNNotificationPresentationOptionSound; - } - - UA_LDEBUG(@"Foreground presentation options from the config: %lu", (unsigned long)options); - - [UAirship push].defaultPresentationOptions = options; - } - - // Add observer for inbox updated event - [[NSNotificationCenter defaultCenter] addObserver:[self shared] - selector:@selector(inboxUpdated) - name:UAInboxMessageListUpdatedNotification - object:nil]; - - [[NSNotificationCenter defaultCenter] addObserver:[self shared] - selector:@selector(channelUpdated:) - name:UAChannel.channelUpdatedEvent - object:nil]; - - [UAMessageCenter shared].displayDelegate = [self shared]; -} - -+ (UAUnityPlugin *)shared { - dispatch_once(&onceToken_, ^{ - shared_ = [[UAUnityPlugin alloc] init]; - }); - - return shared_; -} - -- (id)init { - self = [super init]; - return self; -} - -// getter and setter for auto-launch message center flag -- (BOOL)autoLaunchMessageCenter { - if ([[NSUserDefaults standardUserDefaults] objectForKey:UAUnityAutoLaunchMessageCenterKey] == nil) { - [[NSUserDefaults standardUserDefaults] setBool:YES forKey:UAUnityAutoLaunchMessageCenterKey]; - return YES; - } - - return [[NSUserDefaults standardUserDefaults] boolForKey:UAUnityAutoLaunchMessageCenterKey]; -} - -- (void)setAutoLaunchMessageCenter:(BOOL)autoLaunchMessageCenter { - [[NSUserDefaults standardUserDefaults] setBool:autoLaunchMessageCenter forKey:UAUnityAutoLaunchMessageCenterKey]; -} - -#pragma mark - -#pragma mark Listeners - -void UAUnityPlugin_setListener(const char* listener) { - [UAUnityPlugin shared].listener = [NSString stringWithUTF8String:listener]; - UA_LDEBUG(@"UAUnityPlugin_setListener %@",[UAUnityPlugin shared].listener); -} - -#pragma mark - -#pragma mark Deep Links - -const char* UAUnityPlugin_getDeepLink(bool clear) { - UA_LDEBUG(@"UnityPlugin getDeepLink clear %d",clear); - - const char* dl = [UAUnityPlugin convertToJson:[UAUnityPlugin shared].storedDeepLink]; - if (clear) { - [UAUnityPlugin shared].storedDeepLink = nil; - } - return dl; -} - -#pragma mark - -#pragma mark UA Push Functions -const char* UAUnityPlugin_getIncomingPush(bool clear) { - UA_LDEBUG(@"UnityPlugin getIncomingPush clear %d",clear); - - if (![UAUnityPlugin shared].storedNotification) { - return nil; - } - - const char* payload = [UAUnityPlugin convertPushToJson:[UAUnityPlugin shared].storedNotification]; - - if (clear) { - [UAUnityPlugin shared].storedNotification = nil; - } - - return payload; -} - -bool UAUnityPlugin_getUserNotificationsEnabled() { - UA_LDEBUG(@"UnityPlugin getUserNotificationsEnabled"); - return [UAirship push].userPushNotificationsEnabled ? true : false; -} - -void UAUnityPlugin_setUserNotificationsEnabled(bool enabled) { - UA_LDEBUG(@"UnityPlugin setUserNotificationsEnabled: %d", enabled); - [UAirship push].userPushNotificationsEnabled = enabled ? YES : NO; -} - -const char* UAUnityPlugin_getTags() { - UA_LDEBUG(@"UnityPlugin getTags"); - return [UAUnityPlugin convertToJson:[UAirship channel].tags]; -} - -void UAUnityPlugin_addTag(const char* tag) { - NSString *tagString = [NSString stringWithUTF8String:tag]; - - UA_LDEBUG(@"UnityPlugin addTag %@", tagString); - [[UAirship channel] addTag:tagString]; - [[UAirship push] updateRegistration]; -} - -void UAUnityPlugin_removeTag(const char* tag) { - NSString *tagString = [NSString stringWithUTF8String:tag]; - - UA_LDEBUG(@"UnityPlugin removeTag %@", tagString); - [[UAirship channel] removeTag:tagString]; - [[UAirship push] updateRegistration]; -} - -const char* UAUnityPlugin_getChannelId() { - UA_LDEBUG(@"UnityPlugin getChannelId"); - return MakeStringCopy([[UAirship channel].identifier UTF8String]); -} - -double UAUnityPlugin_getInAppAutomationDisplayInterval() { - UA_LDEBUG(@"UnityPlugin getInAppAutomationDisplayInterval"); - return [UAInAppAutomation shared].inAppMessageManager.displayInterval; -} - -void UAUnityPlugin_setInAppAutomationDisplayInterval(double value) { - UA_LDEBUG(@"UnityPlugin setBackgroundLocationAllowed %f", value); - [UAInAppAutomation shared].inAppMessageManager.displayInterval = value; -} - -bool UAUnityPlugin_isInAppAutomationPaused() { - UA_LDEBUG(@"UnityPlugin isInAppAutomationPaused"); - return [UAInAppAutomation shared].paused; -} - -void UAUnityPlugin_setInAppAutomationPaused(bool paused) { - UA_LDEBUG(@"UnityPlugin setInAppAutomationPaused: %d", paused); - [UAInAppAutomation shared].paused = paused; -} - -#pragma mark - -#pragma mark Analytics - -void UAUnityPlugin_addCustomEvent(const char *customEvent) { - NSString *customEventString = [NSString stringWithUTF8String:customEvent]; - UA_LDEBUG(@"UnityPlugin addCustomEvent"); - id obj = [UAJSONUtils objectWithString:customEventString]; - - UACustomEvent *ce = [UACustomEvent eventWithName:[UAUnityPlugin stringOrNil:obj[@"eventName"]]]; - - NSString *valueString = [UAUnityPlugin stringOrNil:obj[@"eventValue"]]; - if (valueString) { - ce.eventValue = [NSDecimalNumber decimalNumberWithString:valueString]; - } - - ce.interactionID = [UAUnityPlugin stringOrNil:obj[@"interactionId"]]; - ce.interactionType = [UAUnityPlugin stringOrNil:obj[@"interactionType"]]; - ce.transactionID = [UAUnityPlugin stringOrNil:obj[@"transactionID"]]; - - NSMutableDictionary *properties = [NSMutableDictionary dictionary]; - - for (id property in obj[@"properties"]) { - NSString *name = [UAUnityPlugin stringOrNil:property[@"name"]]; - id value; - - NSString *type = property[@"type"]; - if ([type isEqualToString:@"s"]) { - value = property[@"stringValue"]; - } else if ([type isEqualToString:@"d"]) { - value = property[@"doubleValue"]; - } else if ([type isEqualToString:@"b"]) { - value = property[@"boolValue"]; - } else if ([type isEqualToString:@"sa"]) { - value = property[@"stringArrayValue"]; - } - - [properties setValue:value forKey:name]; - } - - ce.properties = properties.copy; - - [[UAAnalytics shared] addEvent:ce]; -} - -void UAUnityPlugin_trackScreen(const char *screenName) { - NSString *screenNameString = [NSString stringWithUTF8String:screenName]; - UA_LDEBUG(@"UnityPlugin trackScreen: %@", screenNameString); - - [[UAAnalytics shared] trackScreen:screenNameString]; -} - -void UAUnityPlugin_associateIdentifier(const char *key, const char *identifier) { - if (!key) { - UA_LDEBUG(@"UnityPlugin associateIdentifier failed, key cannot be nil"); - return; - } - - NSString *keyString = [UAUnityPlugin stringOrNil:[NSString stringWithUTF8String:key]]; - NSString *identifierString = nil; - - if (!identifier) { - UA_LDEBUG(@"UnityPlugin associateIdentifier removed identifier for key: %@", keyString); - } else { - identifierString = [UAUnityPlugin stringOrNil:[NSString stringWithUTF8String:identifier]]; - UA_LDEBUG(@"UnityPlugin associateIdentifier with identifier: %@ for key: %@", identifierString, keyString); - } - - UAAssociatedIdentifiers *identifiers = [UAirship.analytics currentAssociatedDeviceIdentifiers]; - [identifiers setIdentifier:identifierString forKey:keyString]; - [UAirship.analytics associateDeviceIdentifiers:identifiers]; -} - -void UAUnityPlugin_setNamedUserID(const char *namedUserID) { - NSString *namedUserIDString = [NSString stringWithUTF8String:namedUserID]; - UA_LDEBUG(@"UnityPlugin setNamedUserID %@", namedUserIDString); - [UAirship namedUser].identifier = namedUserIDString; -} - -const char* UAUnityPlugin_getNamedUserID() { - return MakeStringCopy([[UAirship namedUser].identifier UTF8String]); -} - - -#pragma mark - -#pragma mark MessageCenter - -void UAUnityPlugin_displayMessageCenter() { - UA_LDEBUG(@"UnityPlugin displayMessageCenter"); - UnityWillPause(); - [[UAMessageCenter shared] display]; -} - -void UAUnityPlugin_displayInboxMessage(const char *messageID) { - NSString *messageIDString = [NSString stringWithUTF8String:messageID]; - UA_LDEBUG(@"UnityPlugin displayInboxMessage %@", messageIDString); - UnityWillPause(); - [[UAUnityPlugin shared] displayInboxMessage:messageIDString]; -} - -void UAUnityPlugin_refreshInbox() { - UA_LDEBUG(@"UnityPlugin refreshInbox"); - UnityWillPause(); - [[UAMessageCenter shared].messageList retrieveMessageListWithSuccessBlock:^(){} withFailureBlock:^(){}]; -} - -const char* UAUnityPlugin_getInboxMessages() { - UA_LDEBUG(@"UnityPlugin getInboxMessages"); - return [UAUnityPlugin convertInboxMessagesToJson:[UAMessageCenter shared].messageList.messages]; -} - -void UAUnityPlugin_markInboxMessageRead(const char *messageID) { - NSString *messageIDString = [NSString stringWithUTF8String:messageID]; - UA_LDEBUG(@"UnityPlugin markInboxMessageRead %@", messageIDString); - UAInboxMessage *message = [[UAMessageCenter shared].messageList messageForID:messageIDString]; - [[UAMessageCenter shared].messageList markMessagesRead:@[message] completionHandler:nil]; -} - -void UAUnityPlugin_deleteInboxMessage(const char *messageID) { - NSString *messageIDString = [NSString stringWithUTF8String:messageID]; - UA_LDEBUG(@"UnityPlugin deleteInboxMessage %@", messageIDString); - UAInboxMessage *message = [[UAMessageCenter shared].messageList messageForID:messageIDString]; - [[UAMessageCenter shared].messageList markMessagesDeleted:@[message] completionHandler:nil]; -} - -void UAUnityPlugin_setAutoLaunchDefaultMessageCenter(bool enabled) { - UA_LDEBUG(@"UnityPlugin UAUnityPlugin_setAutoLaunchDefaultMessageCenter %@", enabled ? @"YES" : @"NO"); - [UAUnityPlugin shared].autoLaunchMessageCenter = enabled; -} - -int UAUnityPlugin_getMessageCenterUnreadCount() { - int unreadCount = (int)[UAMessageCenter shared].messageList.unreadCount; - UA_LDEBUG(@"UnityPlugin getMessageCenterUnreadCount: %d", unreadCount); - return unreadCount; -} - -int UAUnityPlugin_getMessageCenterCount() { - int messageCount = (int)[UAMessageCenter shared].messageList.messageCount; - UA_LDEBUG(@"UnityPlugin getMessageCenterCount: %d", messageCount); - return messageCount; -} - -#pragma mark - -#pragma mark Tag Groups - -void UAUnityPlugin_editChannelTagGroups(const char *payload) { - UA_LDEBUG(@"UnityPlugin editChannelTagGroups"); - id payloadMap = [UAJSONUtils objectWithString:[NSString stringWithUTF8String:payload]]; - id operations = payloadMap[@"values"]; - - for (NSDictionary *operation in operations) { - NSString *group = operation[@"tagGroup"]; - if ([operation[@"operation"] isEqualToString:@"add"]) { - [[UAirship channel] addTags:operation[@"tags"] group:group]; - } else if ([operation[@"operation"] isEqualToString:@"remove"]) { - [[UAirship channel] removeTags:operation[@"tags"] group:group]; - } - } - - [[UAirship push] updateRegistration]; -} - -void UAUnityPlugin_editNamedUserTagGroups(const char *payload) { - UA_LDEBUG(@"UnityPlugin editNamedUserTagGroups"); - id payloadMap = [UAJSONUtils objectWithString:[NSString stringWithUTF8String:payload]]; - id operations = payloadMap[@"values"]; - - for (NSDictionary *operation in operations) { - NSString *group = operation[@"tagGroup"]; - if ([operation[@"operation"] isEqualToString:@"add"]) { - [[UAirship namedUser] addTags:operation[@"tags"] group:group]; - } else if ([operation[@"operation"] isEqualToString:@"remove"]) { - [[UAirship namedUser] removeTags:operation[@"tags"] group:group]; - } - } - - [[UAirship namedUser] updateTags]; -} - -#pragma mark - -#pragma mark Attributes - -void UAUnityPlugin_editChannelAttributes(const char *payload) { - UA_LDEBUG(@"UnityPlugin editChannelAttributes"); - id payloadMap = [UAJSONUtils objectWithString:[NSString stringWithUTF8String:payload]]; - id operations = payloadMap[@"values"]; - - UAAttributeMutations *mutations = [[UAUnityPlugin shared] mutationsWithOperations:operations]; - - [[UAirship channel] applyAttributeMutations:mutations]; -} - -void UAUnityPlugin_editNamedUserAttributes(const char *payload) { - UA_LDEBUG(@"UnityPlugin editNamedUserAttributes"); - id payloadMap = [UAJSONUtils objectWithString:[NSString stringWithUTF8String:payload]]; - id operations = payloadMap[@"values"]; - - UAAttributeMutations *mutations = [[UAUnityPlugin shared] mutationsWithOperations:operations]; - - [[UAirship namedUser] applyAttributeMutations:mutations]; -} - -#pragma mark - -#pragma mark Actions! - -#pragma mark - -#pragma mark UAPushNotificationDelegate -/** - * Called when a push notification is received while the app is running in the foreground. - * - * @param userInfo The NSDictionary object representing the notification info. - */ -- (void)receivedForegroundNotification:(NSDictionary *)userInfo completionHandler:(void (^)(void))completionHandler { - UA_LDEBUG(@"receivedForegroundNotification %@",userInfo); - - if (self.listener) { - UnitySendMessage(MakeStringCopy([self.listener UTF8String]), - "OnPushReceived", - [UAUnityPlugin convertPushToJson:userInfo]); - completionHandler(); - } -} - - -/** - * Called when the app is started or resumed because a user opened a notification. - * - * @param notificationResponse UNNotificationResponse object representing the user's response - */ -- (void)receivedNotificationResponse:(UNNotificationResponse *)notificationResponse completionHandler:(void (^)(void))completionHandler { - UA_LDEBUG(@"receivedNotificationResponse %@",notificationResponse); - self.storedNotification = notificationResponse.notification.request.content.userInfo; - - if (self.listener) { - UnitySendMessage(MakeStringCopy([self.listener UTF8String]), - "OnPushOpened", - [UAUnityPlugin convertPushToJson:notificationResponse.notification.request.content.userInfo]); - completionHandler(); - } -} - -#pragma mark - -#pragma mark Channel Registration Events - - -- (void)channelUpdated:(NSNotification *)notification { - NSString *channelID = notification.userInfo[UAChannel.channelIdentifierKey]; - UA_LDEBUG(@"channelUpdated: %@", channelID); - if (self.listener && channelID) { - UnitySendMessage(MakeStringCopy([self.listener UTF8String]), - "OnChannelUpdated", - MakeStringCopy([channelID UTF8String])); - } -} - -#pragma mark - -#pragma mark UADeepLinkDelegate --(void)receivedDeepLink:(NSURL *_Nonnull)url completionHandler:(void (^_Nonnull)(void))completionHandler { - UA_LDEBUG(@"Setting dl to: %@", url); - NSString *deepLinkString = url.absoluteString; - self.storedDeepLink = deepLinkString; - id listener = [UAUnityPlugin shared].listener; - if (listener) { - UnitySendMessage(MakeStringCopy([listener UTF8String]), - "OnDeepLinkReceived", - MakeStringCopy([deepLinkString UTF8String])); - } - - completionHandler(); -} - -#pragma mark - -#pragma mark UAMessageCenterDisplayDelegate - -- (void)displayMessageCenterForMessageID:(NSString *)messageID animated:(BOOL)animated { - if (self.autoLaunchMessageCenter) { - [[UAMessageCenter shared].defaultUI displayMessageCenterForMessageID:messageID animated:true]; - } else { - UnitySendMessage(MakeStringCopy([self.listener UTF8String]), - "OnShowInbox", - MakeStringCopy([messageID UTF8String])); - } -} - -- (void)displayMessageCenterAnimated:(BOOL)animated { - if (self.autoLaunchMessageCenter) { - [[UAMessageCenter shared].defaultUI displayMessageCenterAnimated:animated]; - } else { - UnitySendMessage(MakeStringCopy([self.listener UTF8String]), - "OnShowInbox", - MakeStringCopy([@"" UTF8String])); - } -} - -- (void)dismissMessageCenterAnimated:(BOOL)animated { - if (self.autoLaunchMessageCenter) { - [[UAMessageCenter shared].defaultUI dismissMessageCenterAnimated:animated]; - } -} - -#pragma mark - -#pragma mark UAInboxMessageListUpdatedNotification -- (void)inboxUpdated { - NSDictionary *counts = @{ - @"unread" : @([UAMessageCenter shared].messageList.unreadCount), - @"total" : @([UAMessageCenter shared].messageList.messageCount) - }; - UA_LDEBUG(@"UnityPlugin inboxUpdated(unread = %@, total = %@)", counts[@"unread"], counts[@"total"]); - UnitySendMessage(MakeStringCopy([self.listener UTF8String]), - "OnInboxUpdated", - [UAUnityPlugin convertToJson:counts]); -} - -#pragma mark - -#pragma mark Data Collection - -bool UAUnityPlugin_isFeatureEnabled(const char *features) { - NSString *featureString = [NSString stringWithUTF8String:features]; - NSArray *featureArray = [featureString componentsSeparatedByString: @","]; - if ([[UAUnityPlugin shared] isValidFeature:featureArray]) { - UA_LDEBUG(@"UAUnityPlugin isFeatureEnabled %@", featureString); - return [[UAirship shared].privacyManager isEnabled:[[UAUnityPlugin shared] stringToFeature:featureArray]]; - } else { - UA_LERR(@"UAUnityPlugin Invalid feature %@", featureString); - return false; - } -} - -bool UAUnityPlugin_isAnyFeatureEnabled() { - return [[UAirship shared].privacyManager isAnyFeatureEnabled]; -} - -void UAUnityPlugin_disableFeatures(const char *features) { - NSString *featureString = [NSString stringWithUTF8String:features]; - NSArray *featureArray = [featureString componentsSeparatedByString: @","]; - if ([[UAUnityPlugin shared] isValidFeature:featureArray]) { - UA_LDEBUG(@"UAUnityPlugin disableFeatures"); - [[UAirship shared].privacyManager disableFeatures:[[UAUnityPlugin shared] stringToFeature:featureArray]]; - } else { - UA_LERR(@"UAUnityPlugin Invalid features, cancelling disableFeatures"); - } -} - -void UAUnityPlugin_enableFeatures(const char *features) { - NSString *featureString = [NSString stringWithUTF8String:features]; - NSArray *featureArray = [featureString componentsSeparatedByString: @","]; - if ([[UAUnityPlugin shared] isValidFeature:featureArray]) { - UA_LDEBUG(@"UAUnityPlugin enableFeatures"); - [[UAirship shared].privacyManager enableFeatures:[[UAUnityPlugin shared] stringToFeature:featureArray]]; - } else { - UA_LERR(@"UAUnityPlugin Invalid features, cancelling enableFeatures"); - } -} - -void UAUnityPlugin_setEnabledFeatures(const char *features) { - NSString *featureString = [NSString stringWithUTF8String:features]; - NSArray *featureArray = [featureString componentsSeparatedByString: @","]; - if ([[UAUnityPlugin shared] isValidFeature:featureArray]) { - UA_LDEBUG(@"UAUnityPlugin setEnabledFeatures"); - [[UAirship shared].privacyManager setEnabledFeatures:[[UAUnityPlugin shared] stringToFeature:featureArray]]; - } else { - UA_LERR(@"UAUnityPlugin Invalid features, cancelling setEnabledFeatures"); - } -} - -const char* UAUnityPlugin_getEnabledFeatures() { - UA_LDEBUG(@"UAUnityPlugin getEnabledFeatures"); - NSError *error = nil; - NSArray *featureArray = [[UAUnityPlugin shared] featureToString:[[UAirship shared].privacyManager enabledFeatures]]; - NSData *jsonData = [NSJSONSerialization dataWithJSONObject:featureArray options:NSJSONWritingPrettyPrinted error:&error]; - NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; - - return MakeStringCopy([jsonString UTF8String]); -} - -#pragma mark - -#pragma mark Preference Center - -void UAUnityPlugin_openPreferenceCenter(NSString *preferenceCenterId) { - UA_LDEBUG(@"UAUnityPlugin openPreferenceCenter"); - [[UAPreferenceCenter shared] openPreferenceCenter:preferenceCenterId]; -} - - - -#pragma mark - -#pragma mark Helpers - -+ (NSString *)stringOrNil:(NSString *)string { - return string.length > 0 ? string : nil; -} - -+ (const char *)convertPushToJson:(NSDictionary *)push { - NSString *alert = push[@"aps"][@"alert"]; - NSString *identifier = push[@"_"]; - - NSMutableArray *extras = [NSMutableArray array]; - for (NSString *key in push) { - if ([key isEqualToString:@"_"] || [key isEqualToString:@"aps"]) { - continue; - } - - id value = push[key]; - if (![value isKindOfClass:[NSString class]]) { - value = [UAJSONUtils stringWithObject:value]; - } - - if (!value) { - continue; - } - - [extras addObject:@{@"key": key, @"value": value}]; - } - - NSMutableDictionary *serializedPayload = [NSMutableDictionary dictionary]; - [serializedPayload setValue:alert forKey:@"alert"]; - [serializedPayload setValue:identifier forKey:@"identifier"]; - - if (extras.count) { - [serializedPayload setValue:extras forKey:@"extras"]; - } - - return [UAUnityPlugin convertToJson:serializedPayload]; -} - -+ (const char *)convertToJson:(NSObject*) obj { - NSString *JSONString = [UAJSONUtils stringWithObject:obj]; - return MakeStringCopy([JSONString UTF8String]); -} - -+ (const char *)convertInboxMessagesToJson:(NSArray *)messages { - NSMutableArray *convertedMessages = [NSMutableArray array]; - for (UAInboxMessage *message in messages) { - NSMutableDictionary *convertedMessage = [NSMutableDictionary dictionary]; - convertedMessage[@"id"] = message.messageID; - convertedMessage[@"title"] = message.title; - - NSNumber *sentDate = @([message.messageSent timeIntervalSince1970] * 1000); - convertedMessage[@"sentDate"] = sentDate; - - NSDictionary *icons = [message.rawMessageObject objectForKey:@"icons"]; - NSString *iconUrl = [icons objectForKey:@"list_icon"]; - convertedMessage[@"listIconUrl"] = iconUrl; - - convertedMessage[@"isRead"] = message.unread ? @NO : @YES; - convertedMessage[@"isDeleted"] = @(message.deleted); - - if (message.extra) { - // Unity's JsonArray doesn't support dictionaries, so break extra up into two lists. - convertedMessage[@"extrasKeys"] = message.extra.allKeys; - convertedMessage[@"extrasValues"] = message.extra.allValues; - } - - [convertedMessages addObject:convertedMessage]; - } - return [UAUnityPlugin convertToJson:convertedMessages]; -} - -- (void)displayInboxMessage:(NSString *)messageId { - UAUnityMessageViewController *mvc = [[UAUnityMessageViewController alloc] init]; - [mvc loadMessageForID:messageId]; - self.messageViewController = mvc; - - dispatch_async(dispatch_get_main_queue(), ^{ - [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:mvc animated:YES completion:nil]; - }); -} - -- (UAAttributeMutations *)mutationsWithOperations:(NSArray *)operations { - UAAttributeMutations *mutations = [UAAttributeMutations mutations]; - - for (NSDictionary *operation in operations) { - NSString *action = operation[@"action"]; - NSString *key = operation[@"key"]; - NSString *value = operation[@"value"]; - NSString *type = operation[@"type"]; - - if (!action.length || !key.length) { - UA_LERR(@"Invalid attribute operation %@", operation); - continue; - } - - if ([action isEqualToString:@"Set"]) { - if (!value.length || !type.length) { - UA_LERR(@"Invalid set operation %@", operation); - continue; - } - - if ([type isEqualToString:@"Double"]) { - [mutations setNumber:@(value.doubleValue) forAttribute:key]; - } else if ([type isEqualToString:@"Float"]) { - [mutations setNumber:@(value.floatValue) forAttribute:key]; - } else if ([type isEqualToString:@"Long"]) { - [mutations setNumber:@(value.longLongValue) forAttribute:key]; - } else if ([type isEqualToString:@"Integer"]) { - [mutations setNumber:@(value.intValue) forAttribute:key]; - } else if ([type isEqualToString:@"String"]) { - [mutations setString:value forAttribute:key]; - } else if ([type isEqualToString:@"Date"]) { - NSDate *date = [NSDate dateWithTimeIntervalSince1970:(NSTimeInterval)(value.doubleValue / 1000.0)]; - [mutations setDate:date forAttribute:key]; - } - } else if ([action isEqualToString:@"Remove"]) { - [mutations removeAttribute:key]; - } - } - return mutations; -} - -// Helper method to create C string copy -char* MakeStringCopy (const char* string) { - if (string == NULL) { - return NULL; - } - - char* res = (char*)malloc(strlen(string) + 1); - strcpy(res, string); - return res; -} - -// Helper method to check if features are authorized -- (BOOL)isValidFeature:(NSArray *)features { - UA_LDEBUG(@"checking isValidFeature"); - NSLog(@"%@", features); - if (!features || [features count] == 0) { - return NO; - } - NSDictionary *authorizedFeatures = [self authorizedFeatures]; - for (NSString *feature in features) { - if (![authorizedFeatures objectForKey:feature]) { - return NO; - } - } - return YES; -} - -// Helper method to convert features as int -- (UAFeatures)stringToFeature:(NSArray *)features { - NSDictionary *authorizedFeatures = [self authorizedFeatures]; - - NSNumber *objectFeature = authorizedFeatures[[features objectAtIndex:0]]; - UAFeatures convertedFeatures = [objectFeature longValue]; - - if ([features count] > 1) { - int i; - for (i = 1; i < [features count]; i++) { - NSNumber *objectFeature = authorizedFeatures[[features objectAtIndex:i]]; - convertedFeatures |= [objectFeature longValue]; - } - } - return convertedFeatures; -} - -// Helper method to convert features to string. -- (NSArray *)featureToString:(UAFeatures)features { - NSMutableArray *convertedFeatures = [[NSMutableArray alloc] init]; - - NSDictionary *authorizedFeatures = [self authorizedFeatures]; - - if (features == UAFeaturesAll) { - [convertedFeatures addObject:@"FEATURE_ALL"]; - } else if (features == UAFeaturesNone) { - [convertedFeatures addObject:@"FEATURE_NONE"]; - } else { - for (NSString *feature in authorizedFeatures) { - NSNumber *objectFeature = authorizedFeatures[feature]; - long longFeature = [objectFeature longValue]; - if ((longFeature & features) && (longFeature != UAFeaturesAll)) { - [convertedFeatures addObject:feature]; - } - } - } - return convertedFeatures; -} - -// Dictionary of authorized features -- (NSDictionary *)authorizedFeatures { - NSMutableDictionary *authorizedFeatures = [[NSMutableDictionary alloc] init]; - [authorizedFeatures setValue:@(UAFeaturesNone) forKey:@"FEATURE_NONE"]; - [authorizedFeatures setValue:@(UAFeaturesInAppAutomation) forKey:@"FEATURE_IN_APP_AUTOMATION"]; - [authorizedFeatures setValue:@(UAFeaturesMessageCenter) forKey:@"FEATURE_MESSAGE_CENTER"]; - [authorizedFeatures setValue:@(UAFeaturesPush) forKey:@"FEATURE_PUSH"]; - [authorizedFeatures setValue:@(UAFeaturesChat) forKey:@"FEATURE_CHAT"]; - [authorizedFeatures setValue:@(UAFeaturesAnalytics) forKey:@"FEATURE_ANALYTICS"]; - [authorizedFeatures setValue:@(UAFeaturesTagsAndAttributes) forKey:@"FEATURE_TAGS_AND_ATTRIBUTES"]; - [authorizedFeatures setValue:@(UAFeaturesContacts) forKey:@"FEATURE_CONTACTS"]; - [authorizedFeatures setValue:@(UAFeaturesLocation) forKey:@"FEATURE_LOCATION"]; - [authorizedFeatures setValue:@(UAFeaturesAll) forKey:@"FEATURE_ALL"]; - return authorizedFeatures; -} - -@end diff --git a/Assets/Plugins/iOS/UnityPlugin.swift b/Assets/Plugins/iOS/UnityPlugin.swift new file mode 100644 index 00000000..464b5442 --- /dev/null +++ b/Assets/Plugins/iOS/UnityPlugin.swift @@ -0,0 +1,107 @@ +import Foundation +import SwiftUI +import AirshipFrameworkProxy + +@_cdecl("UnityPlugin_call") +public func UnityPlugin_call(_ method: String, args: String) async throws -> (any Sendable)? { + // TODO Check the method name and call the appropriate method. + print("Hello Swift seems to work like that") + + switch method { + // Airship + case "takeOff": + return try AirshipProxy.shared.takeOff( + // TODO decode the arg + ) + + case "isFlying": + return AirshipProxy.shared.isFlying() + + // Channel + case "getChannelId": + return try AirshipProxy.shared.channel.channelID + + case "waitForChannelId": + return try await AirshipProxy.shared.channel.waitForChannelID() + + case "addTag": + + case "removeTag": + + case "getTags": + return try AirshipProxy.shared.channel.tags + + case "editTags": + + case "editChannelTagGroups": + + case "editChannelAttributes": + + case "getChannelSubscriptionLists": + return try await AirshipProxy.shared.channel.fetchSubscriptionLists() + + case "editChannelSubscriptionLists": + + + // Contact + case "identify": + case "reset": + case "getNamedUserId": + case "notifyRemoteLogin": + case "editContactTagGroups": + case "editContactAttributes": + case "getContactSubscriptionLists": + case "editContactSubscriptionLists": + + // Analytics + case "associateIdentifier": + case "trackScreen": + case "addCustomEvent": + case "getSessionId": + + // InApp + case "setPaused": + case "isPaused": + case "setDisplayInterval": + case "getDisplayInterval": + + // Locale + case "setLocaleOverride": + case "clearLocaleOverride": + case "getLocale": + + // Message Center + case "getUnreadCount": + case "getMessages": + case "markMessageRead": + case "deleteMessage": + case "refreshMessages": + case "setAutoLaunchDefaultMessageCenter": + case "displayMessageCenter": + case "dismissMessageCenter": + case "showMessageView": + case "showMessageCenter": + + // Preference Center + case "displayPreferenceCenter": + case "getPreferenceCenterConfig": + case "setAutoLaunchDefaultPreferenceCenter": + + // Privacy Manager + case "setEnabledFeatures": + case "getEnabledFeatures": + case "enableFeatures": + case "disableFeatures": + case "isFeaturesEnabled": + + // Push + case "isUserNotificationsEnabled": + case "setUserNotificationsEnabled": + case "enableUserNotifications": + case "getNotificationStatus": + case "getPushToken": + case "getActiveNotifications": + case "clearNotifications": + case "clearNotification": + } +} \ No newline at end of file diff --git a/Assets/Scripts/UrbanAirshipBehaviour.cs b/Assets/Scripts/UrbanAirshipBehaviour.cs index 16bb8f24..595ce9ac 100644 --- a/Assets/Scripts/UrbanAirshipBehaviour.cs +++ b/Assets/Scripts/UrbanAirshipBehaviour.cs @@ -10,102 +10,109 @@ public class UrbanAirshipBehaviour : MonoBehaviour { public string addTagOnStart; void Awake () { - UAirship.Shared.UserNotificationsEnabled = true; + // UAirship.Shared.UserNotificationsEnabled = true; } void Start () { - if (!string.IsNullOrEmpty (addTagOnStart)) { - UAirship.Shared.AddTag (addTagOnStart); - } - - string[] allenable = new string[] { "FEATURE_ALL" }; - UAirship.Shared.SetEnabledFeatures(allenable); - - UAirship.Shared.OnPushReceived += OnPushReceived; - UAirship.Shared.OnChannelUpdated += OnChannelUpdated; - UAirship.Shared.OnDeepLinkReceived += OnDeepLinkReceived; - UAirship.Shared.OnPushOpened += OnPushOpened; - UAirship.Shared.OnInboxUpdated += OnInboxUpdated; - UAirship.Shared.OnShowInbox += OnShowInbox; - - UAirship.Shared.TrackScreen("Main Camera"); - - UAirship.Shared.RefreshInbox(); - - UAirship.Shared.EditChannelAttributes().SetAttribute("teststring", "a_string").Apply(); - UAirship.Shared.EditChannelAttributes().SetAttribute("testint", (int) 1).Apply(); - UAirship.Shared.EditChannelAttributes().SetAttribute("testlong", (long) 1000).Apply(); - UAirship.Shared.EditChannelAttributes().SetAttribute("testfloat", (float)5.99).Apply(); - UAirship.Shared.EditChannelAttributes().SetAttribute("testdouble", (double)5555.999).Apply(); - UAirship.Shared.EditChannelAttributes().SetAttribute("testdate", DateTime.UtcNow).Apply(); + // if (!string.IsNullOrEmpty (addTagOnStart)) { + // UAirship.Shared.AddTag (addTagOnStart); + // } + + // string[] allenable = new string[] { "FEATURE_ALL" }; + // UAirship.Shared.SetEnabledFeatures(allenable); + + // UAirship.Shared.OnPushReceived += OnPushReceived; + // UAirship.Shared.OnChannelUpdated += OnChannelUpdated; + // UAirship.Shared.OnDeepLinkReceived += OnDeepLinkReceived; + // UAirship.Shared.OnPushOpened += OnPushOpened; + // UAirship.Shared.OnInboxUpdated += OnInboxUpdated; + // UAirship.Shared.OnShowInbox += OnShowInbox; + + // Airship.Shared.analytics.TrackScreen("Main Camera"); + + // CustomEvent customEvent = new CustomEvent(); + // customEvent.EventName = "event_name"; + // customEvent.EventValue = 123; + // Airship.Shared.analytics.AddCustomEvent(customEvent); + + // UAirship.Shared.RefreshInbox(); + + // Airship.Shared.Channel.EditTags().AddTag("ulrich").Apply(); + + // Airship.Shared.Channel.EditChannelAttributes().SetAttribute("teststring", "a_string").Apply(); + // Airship.Shared.Channel.EditChannelAttributes().SetAttribute("testint", (int) 1).Apply(); + // Airship.Shared.Channel.EditChannelAttributes().SetAttribute("testlong", (long) 1000).Apply(); + // Airship.Shared.Channel.EditChannelAttributes().SetAttribute("testfloat", (float)5.99).Apply(); + // Airship.Shared.Channel.EditChannelAttributes().SetAttribute("testdouble", (double)5555.999).Apply(); + // Airship.Shared.Channel.EditChannelAttributes().SetAttribute("testdate", DateTime.UtcNow).Apply(); } - void OnDestroy () { - UAirship.Shared.OnPushReceived -= OnPushReceived; - UAirship.Shared.OnChannelUpdated -= OnChannelUpdated; - UAirship.Shared.OnDeepLinkReceived -= OnDeepLinkReceived; - UAirship.Shared.OnPushOpened -= OnPushOpened; - } - - void OnPushReceived (PushMessage message) { - Debug.Log ("Received push! " + message.Alert); - - if (message.Extras != null) { - foreach (KeyValuePair kvp in message.Extras) { - Debug.Log (string.Format ("Extras Key = {0}, Value = {1}", kvp.Key, kvp.Value)); - } - } - } - - void OnPushOpened (PushMessage message) { - Debug.Log ("Opened Push! " + message.Alert); - - if (message.Extras != null) { - foreach (KeyValuePair kvp in message.Extras) { - Debug.Log (string.Format ("Extras Key = {0}, Value = {1}", kvp.Key, kvp.Value)); - } - } - } - - void OnChannelUpdated (string channelId) { - Debug.Log ("Channel updated: " + channelId); - } - - void OnDeepLinkReceived (string deeplink) { - Debug.Log ("Received deep link: " + deeplink); - } - - void OnInboxUpdated (uint messageUnreadCount, uint messageCount) - { - Debug.Log("Inbox updated - unread messages: " + messageUnreadCount + " total messages: " + messageCount); - - IEnumerableinboxMessages = UAirship.Shared.InboxMessages(); - foreach (InboxMessage inboxMessage in inboxMessages) - { - Debug.Log("Message id: " + inboxMessage.id + ", title: " + inboxMessage.title + ", sentDate: " + inboxMessage.sentDate + ", isRead: " + inboxMessage.isRead + ", isDeleted: " + inboxMessage.isDeleted); - if (inboxMessage.extras == null) - { - Debug.Log("Extras is null"); - } - else - { - foreach (KeyValuePair entry in inboxMessage.extras) - { - Debug.Log("Message extras [" + entry.Key + "] = " + entry.Value); - } - } - } - } - - void OnShowInbox (string messageId) - { - if (messageId == null) - { - Debug.Log("OnShowInbox - show inbox"); - } - else - { - Debug.Log("OnShowInbox - show message: messageId = " + messageId); - } - } + // void OnDestroy () { + // UAirship.Shared.OnPushReceived -= OnPushReceived; + // UAirship.Shared.OnChannelUpdated -= OnChannelUpdated; + // UAirship.Shared.OnDeepLinkReceived -= OnDeepLinkReceived; + // UAirship.Shared.OnPushOpened -= OnPushOpened; + // } + + // void OnPushReceived (PushMessage message) { + // Debug.Log ("Received push! " + message.Alert); + + // if (message.Extras != null) { + // foreach (KeyValuePair kvp in message.Extras) { + // Debug.Log (string.Format ("Extras Key = {0}, Value = {1}", kvp.Key, kvp.Value)); + // } + // } + // } + + // void OnPushOpened (PushMessage message) { + // Debug.Log ("Opened Push! " + message.Alert); + + // if (message.Extras != null) { + // foreach (KeyValuePair kvp in message.Extras) { + // Debug.Log (string.Format ("Extras Key = {0}, Value = {1}", kvp.Key, kvp.Value)); + // } + // } + // } + + // void OnChannelUpdated (string channelId) { + // Debug.Log ("Channel updated: " + channelId); + // } + + // void OnDeepLinkReceived (string deeplink) { + // Debug.Log ("Received deep link: " + deeplink); + // } + + // void OnInboxUpdated (uint messageUnreadCount, uint messageCount) + // { + // Debug.Log("Inbox updated - unread messages: " + messageUnreadCount + " total messages: " + messageCount); + + // IEnumerableinboxMessages = UAirship.Shared.InboxMessages(); + // foreach (InboxMessage inboxMessage in inboxMessages) + // { + // Debug.Log("Message id: " + inboxMessage.id + ", title: " + inboxMessage.title + ", sentDate: " + inboxMessage.sentDate + ", isRead: " + inboxMessage.isRead + ", isDeleted: " + inboxMessage.isDeleted); + // if (inboxMessage.extras == null) + // { + // Debug.Log("Extras is null"); + // } + // else + // { + // foreach (KeyValuePair entry in inboxMessage.extras) + // { + // Debug.Log("Message extras [" + entry.Key + "] = " + entry.Value); + // } + // } + // } + // } + + // void OnShowInbox (string messageId) + // { + // if (messageId == null) + // { + // Debug.Log("OnShowInbox - show inbox"); + // } + // else + // { + // Debug.Log("OnShowInbox - show message: messageId = " + messageId); + // } + // } } diff --git a/Assets/UrbanAirship/Platforms/Airship.cs b/Assets/UrbanAirship/Platforms/Airship.cs index f771fb69..2ec355bd 100644 --- a/Assets/UrbanAirship/Platforms/Airship.cs +++ b/Assets/UrbanAirship/Platforms/Airship.cs @@ -264,7 +264,7 @@ void OnInboxUpdated(string counts) } - ioid OnShowInbox(string messageId) + void OnShowInbox(string messageId) { ShowInboxEventHandler handler = Shared.OnShowInbox; @@ -335,12 +335,18 @@ public record ConfigEnvironment public enum LogLevel { - Verbose = "verbose", - Debug = "debug", - Info = "info", - Warning = "warning", - Error = "error", - None = "none" + [AirshipEnumStringValue("verbose")] + Verbose, + [AirshipEnumStringValue("debug")] + Debug, + [AirshipEnumStringValue("info")] + Info, + [AirshipEnumStringValue("warning")] + Warning, + [AirshipEnumStringValue("error")] + Error, + [AirshipEnumStringValue("none")] + None } public record IOSEnvironmentConfig @@ -355,14 +361,18 @@ public record IOSEnvironmentConfig public enum LogPrivacyLevel { - Private = "private", - Public = "public" + [AirshipEnumStringValue("private")] + Private, + [AirshipEnumStringValue("public")] + Public } public enum Site { - US = "us", - EU = "eu" + [AirshipEnumStringValue("us")] + US, + [AirshipEnumStringValue("eu")] + EU } public record IOSConfig @@ -435,7 +445,7 @@ public record AirshipConfig public string? initialConfigUrl; // Enabled features. Defaults to all. - public Features[]? enabledFeatures; + public string[]? enabledFeatures; // Enables channel capture feature. This config is enabled by default. public bool? isChannelCaptureEnabled; diff --git a/Assets/UrbanAirship/Platforms/AirshipEnumStringValueAttribute.cs b/Assets/UrbanAirship/Platforms/AirshipEnumStringValueAttribute.cs new file mode 100644 index 00000000..7343de84 --- /dev/null +++ b/Assets/UrbanAirship/Platforms/AirshipEnumStringValueAttribute.cs @@ -0,0 +1,33 @@ +/* Copyright Airship and Contributors */ + +using System; +using System.Reflection; + +public class AirshipEnumStringValueAttribute : Attribute +{ + public string StringValue { get; } + + public AirshipEnumStringValueAttribute(string stringValue) + { + StringValue = stringValue; + } +} + +public static class EnumExtensions +{ + public static string ToStringValue(this Enum value) + { + // Get the FieldInfo for the enum member. + FieldInfo? fieldInfo = value.GetType().GetField(value.ToString()); + if (fieldInfo == null) + { + return value.ToString(); // Fallback to default name + } + + // Check if the custom attribute exists. + AirshipEnumStringValueAttribute? attribute = fieldInfo.GetCustomAttribute(); + + // Return the string value from the attribute, or the default name if none exists. + return attribute?.StringValue ?? value.ToString(); + } +} \ No newline at end of file diff --git a/Assets/UrbanAirship/Platforms/AirshipLocale.cs b/Assets/UrbanAirship/Platforms/AirshipLocale.cs index f06cedd4..c259a532 100644 --- a/Assets/UrbanAirship/Platforms/AirshipLocale.cs +++ b/Assets/UrbanAirship/Platforms/AirshipLocale.cs @@ -44,7 +44,7 @@ public void ClearLocaleOverride() /// The current locale. public string GetLocale() { - return plugin.Call("getLocale"); + return plugin.Call("getLocale"); } } } \ No newline at end of file diff --git a/Assets/UrbanAirship/Platforms/AirshipPreferenceCenter.cs b/Assets/UrbanAirship/Platforms/AirshipPreferenceCenter.cs index 7699289e..4370bb1f 100644 --- a/Assets/UrbanAirship/Platforms/AirshipPreferenceCenter.cs +++ b/Assets/UrbanAirship/Platforms/AirshipPreferenceCenter.cs @@ -40,7 +40,7 @@ public void Display(string preferenceCenterId) public PreferenceCenterConfig GetConfig(string preferenceCenterId) { // TODO parse this from a Json into a PreferenceCenterConfig and return that - return plugin.Call("getPreferenceCenterConfig", preferenceCenterId); + return JsonUtility.FromJson (plugin.Call("getPreferenceCenterConfig", preferenceCenterId)); } /// @@ -92,7 +92,7 @@ enum PreferenceCenterConditionOptIn [Serializable] class PreferenceCenterNotificationOptInCondition : PreferenceCenterCondition { - public readonly override PreferenceCenterConditionType type = PreferenceCenterConditionType.notificationOptIn; + public readonly PreferenceCenterConditionType type = PreferenceCenterConditionType.notificationOptIn; public PreferenceCenterConditionOptIn whenStatus; } @@ -106,8 +106,8 @@ public class PreferenceCenterCommonDisplay [Serializable] public class PreferenceCenterIconDisplay : PreferenceCenterCommonDisplay { - public override string title; - public override string subtitle; + public string title; + public string subtitle; public string icon; } @@ -130,19 +130,19 @@ public abstract class PreferenceCenterSection [Serializable] public class PreferenceCenterCommonSection : PreferenceCenterSection { - public readonly override PreferenceCenterSectionType type = PreferenceCenterSectionType.CommonSection; - public readonly override PreferenceCenterCommonDisplay? display; - public readonly override List? items; - public readonly override List? conditions; + public readonly PreferenceCenterSectionType type = PreferenceCenterSectionType.CommonSection; + public readonly PreferenceCenterCommonDisplay? display; + public readonly List? items; + public readonly List? conditions; } [Serializable] public class PreferenceCenterLabeledSectionBreak : PreferenceCenterSection { - public readonly override PreferenceCenterSectionType type = PreferenceCenterSectionType.LabeledSectionBreak; - public readonly override PreferenceCenterCommonDisplay? display; - public readonly override List? items = null; - public readonly override List? conditions; + public readonly PreferenceCenterSectionType type = PreferenceCenterSectionType.LabeledSectionBreak; + public readonly PreferenceCenterCommonDisplay? display; + public readonly List? items = null; + public readonly List? conditions; } [Serializable] @@ -167,33 +167,33 @@ public class PreferenceCenterAlertItemButton { public string text; public string contentDescription; - public Map actions; + public Dictionary actions; } [Serializable] public class PreferenceCenterAlertItem : PreferenceCenterItem { - public readonly override PreferenceCenterItemType type = PreferenceCenterItemType.Alert; - public readonly override PreferenceCenterCommonDisplay display; - public readonly override List? conditions; + public readonly PreferenceCenterItemType type = PreferenceCenterItemType.Alert; + public readonly PreferenceCenterCommonDisplay display; + public readonly List? conditions; public readonly PreferenceCenterAlertItemButton? button; } [Serializable] public class PreferenceCenterChannelSubscriptionItem : PreferenceCenterItem { - public readonly override PreferenceCenterItemType type = PreferenceCenterItemType.ChannelSubscription; - public readonly override PreferenceCenterCommonDisplay display; - public readonly override List? conditions; + public readonly PreferenceCenterItemType type = PreferenceCenterItemType.ChannelSubscription; + public readonly PreferenceCenterCommonDisplay display; + public readonly List? conditions; public readonly string subscriptionId; } [Serializable] public class PreferenceCenterContactSubscriptionItem : PreferenceCenterItem { - public readonly override PreferenceCenterItemType type = PreferenceCenterItemType.ContactSubscription; - public readonly override PreferenceCenterCommonDisplay display; - public readonly override List? conditions; + public readonly PreferenceCenterItemType type = PreferenceCenterItemType.ContactSubscription; + public readonly PreferenceCenterCommonDisplay display; + public readonly List? conditions; public readonly string subscriptionId; public readonly List scopes; } @@ -208,9 +208,9 @@ public class PreferenceCenterContactSubscriptionGroupItemComponent [Serializable] public class PreferenceCenterContactSubscriptionGroupItem : PreferenceCenterItem { - public readonly override PreferenceCenterItemType type = PreferenceCenterItemType.ContactSubscriptionGroup; - public readonly override PreferenceCenterCommonDisplay display; - public readonly override List? conditions; + public readonly PreferenceCenterItemType type = PreferenceCenterItemType.ContactSubscriptionGroup; + public readonly PreferenceCenterCommonDisplay display; + public readonly List? conditions; public readonly string subscriptionId; public readonly List components; } diff --git a/Assets/UrbanAirship/Platforms/AirshipPush.cs b/Assets/UrbanAirship/Platforms/AirshipPush.cs index 9bcde07e..9c7ddd98 100644 --- a/Assets/UrbanAirship/Platforms/AirshipPush.cs +++ b/Assets/UrbanAirship/Platforms/AirshipPush.cs @@ -72,8 +72,7 @@ public bool EnableUserNotifications(PromptPermissionFallback? fallback) public PushNotificationStatus GetNotificationStatus() { // TODO parse this and return a PushNotificationStatus - plugin.Call("getNotificationStatus"); - return; + return JsonUtility.FromJson (plugin.Call("getNotificationStatus")); } /// @@ -100,7 +99,7 @@ public IEnumerable GetActiveNotifications() } var pushMessages = new List(); - foreach (string pushMessageAsJson in JsonArray.FromJson(pushMessagesAsJson).values) + foreach (string pushMessageAsJson in JsonArray.FromJson(jsonPushMessages).values) { pushMessages.Add(PushMessage.FromJson(pushMessageAsJson)); } @@ -323,11 +322,14 @@ public record PushNotificationStatus public enum PermissionStatus { // Permission is granted. - Granted = "granted", + [AirshipEnumStringValue("granted")] + Granted, // Permission is denied. - Denied = "denied", + [AirshipEnumStringValue("denied")] + Denied, // Permission has not yet been requested. - NotDetermined = "not_determined", + [AirshipEnumStringValue("not_determined")] + NotDetermined, } /// @@ -338,7 +340,8 @@ public enum PermissionStatus public enum PromptPermissionFallback { // Take the user to the system settings to enable the permission. - SystemSettings = "systemSettings" + [AirshipEnumStringValue("systemSettings")] + SystemSettings } /// @@ -348,18 +351,22 @@ public enum PromptPermissionFallback public enum ForegroundPresentationOption { // Play the sound associated with the notification. - Sound = "sound", + [AirshipEnumStringValue("sound")] + Sound, // Apply the notification's badge value to the app’s icon. - Badge = "badge", + [AirshipEnumStringValue("badge")] + Badge, // Show the notification in Notification Center. On iOS 13 an older, // this will also show the notification as a banner. - List = "list", + [AirshipEnumStringValue("list")] + List, // Present the notification as a banner. On iOS 13 an older, // this will also show the notification in the Notification Center. - Banner = "banner", + [AirshipEnumStringValue("banner")] + Banner, } /// @@ -369,19 +376,26 @@ public enum ForegroundPresentationOption public enum NotificationOption { // Alerts. - Alert = "alert", + [AirshipEnumStringValue("alert")] + Alert, // Sounds. - Sound = "sound", + [AirshipEnumStringValue("sound")] + Sound, // Badges. - Badge = "badge", + [AirshipEnumStringValue("badge")] + Badge, // Car play. - CarPlay = "car_play", + [AirshipEnumStringValue("car_play")] + CarPlay, // Critical Alert. - CriticalAlert = "critical_alert", + [AirshipEnumStringValue("critical_alert")] + CriticalAlert, // Provides app notification settings. - ProvidesAppNotificationSettings = "provides_app_notification_settings", + [AirshipEnumStringValue("provides_app_notification_settings")] + ProvidesAppNotificationSettings, // Provisional. - Provisional = "provisional" + [AirshipEnumStringValue("provisional")] + Provisional } public record QuietTime @@ -405,25 +419,35 @@ public record QuietTime public enum AuthorizedNotificationSetting { // Alerts. - Alert = "alert", + [AirshipEnumStringValue("alert")] + Alert, // Sounds. - Sound = "sound", + [AirshipEnumStringValue("sound")] + Sound, // Badges. - Badge = "badge", + [AirshipEnumStringValue("badge")] + Badge, // CarPlay. - CarPlay = "car_play", + [AirshipEnumStringValue("car_play")] + CarPlay, // Lock screen. - LockScreen = "lock_screen", + [AirshipEnumStringValue("lock_screen")] + LockScreen, // Notification center. - NotificationCenter = "notification_center", + [AirshipEnumStringValue("notification_center")] + NotificationCenter, // Critical alert. - CriticalAlert = "critical_alert", + [AirshipEnumStringValue("critical_alert")] + CriticalAlert, // Announcement. - Announcement = "announcement", + [AirshipEnumStringValue("announcement")] + Announcement, // Scheduled delivery. - ScheduledDelivery = "scheduled_delivery", + [AirshipEnumStringValue("scheduled_delivery")] + ScheduledDelivery, // Time sensitive. - TimeSensitive = "time_sensitive", + [AirshipEnumStringValue("time_sensitive")] + TimeSensitive, } public record NotificationConfig diff --git a/Assets/UrbanAirship/Platforms/Android/UAirshipPluginAndroid.cs b/Assets/UrbanAirship/Platforms/Android/UAirshipPluginAndroid.cs deleted file mode 100644 index c62eb6d5..00000000 --- a/Assets/UrbanAirship/Platforms/Android/UAirshipPluginAndroid.cs +++ /dev/null @@ -1,221 +0,0 @@ -/* Copyright Airship and Contributors */ - -#if UNITY_ANDROID - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using UnityEngine; - -namespace UrbanAirship { - - class UAirshipPluginAndroid : IUAirshipPlugin { - - private AndroidJavaObject androidPlugin; - - public UAirshipPluginAndroid () { - try { - using (AndroidJavaClass pluginClass = new AndroidJavaClass ("com.urbanairship.unityplugin.UnityPlugin")) { - androidPlugin = pluginClass.CallStatic ("shared"); - } - } catch (Exception) { - Debug.LogError ("UAirship plugin not found"); - } - } - - public bool UserNotificationsEnabled { - get { - return Call ("getUserNotificationsEnabled"); - } - set { - Call ("setUserNotificationsEnabled", value); - } - } - - public string Tags { - get { - return Call ("getTags"); - } - } - - public string ChannelId { - get { - return Call ("getChannelId"); - } - } - - public string NamedUserId { - get { - return Call ("getNamedUserId"); - } - - set { - Call ("setNamedUserId", value); - } - } - - public TimeSpan InAppAutomationDisplayInterval { - get { - return TimeSpan.FromSeconds (Call ("getInAppAutomationDisplayInterval")); - } - set { - Call ("setInAppAutomationDisplayInterval", value.TotalSeconds); - } - } - - public bool InAppAutomationPaused { - get { - return Call ("isInAppAutomationPaused"); - } - set { - Call ("setInAppAutomationPaused", value); - } - } - - public GameObject Listener { - set { - Call ("setListener", value.name); - } - } - - public string GetIncomingPush (bool clear) { - return Call ("getIncomingPush", clear); - } - - public string GetDeepLink (bool clear) { - return Call ("getDeepLink", clear); - } - - public void AddTag (string tag) { - Call ("addTag", tag); - } - - public void RemoveTag (string tag) { - Call ("removeTag", tag); - } - - public void AddCustomEvent (string customEvent) { - Call ("addCustomEvent", customEvent); - } - - public void TrackScreen (string screenName) { - Call ("trackScreen", screenName); - } - - public void AssociateIdentifier (string key, string identifier) { - Call ("associateIdentifier", key, identifier); - } - - public void DisplayMessageCenter () { - Call ("displayMessageCenter"); - } - - public void DisplayInboxMessage (string messageId) { - Call ("displayInboxMessage", messageId); - } - - public void RefreshInbox () { - Call ("refreshInbox"); - } - - public string InboxMessages () { - return Call ("getInboxMessages"); - } - - public void MarkInboxMessageRead (string messageId) { - Call ("markInboxMessageRead", messageId); - } - - public void DeleteInboxMessage (string messageId) { - Call ("deleteInboxMessage", messageId); - } - - public void SetAutoLaunchDefaultMessageCenter (bool enabled) { - Call ("setAutoLaunchDefaultMessageCenter", enabled); - } - - public int MessageCenterUnreadCount { - get { - return Call ("getMessageCenterUnreadCount"); - } - } - - public int MessageCenterCount { - get { - return Call ("getMessageCenterCount"); - } - } - - public void EditNamedUserTagGroups (string payload) { - Call ("editNamedUserTagGroups", payload); - } - - public void EditChannelTagGroups (string payload) { - Call ("editChannelTagGroups", payload); - } - - public void EditChannelAttributes (string payload) { - Call ("editChannelAttributes", payload); - } - - public void EditNamedUserAttributes (string payload) { - Call ("editNamedUserAttributes", payload); - } - - public void OpenPreferenceCenter (string preferenceCenterId) { - Call ("openPreferenceCenter", preferenceCenterId); - } - - public void SetEnabledFeatures (string[] enabledFeatures) { - Call ("setEnabledFeatures", MakeJavaArray(enabledFeatures)); - } - - public void EnableFeatures (string[] enabledFeatures) { - Call ("enableFeatures", MakeJavaArray(enabledFeatures)); - } - - public void DisableFeatures (string[] disabledFeatures) { - Call ("disableFeatures", MakeJavaArray(disabledFeatures)); - } - - public bool IsFeatureEnabled (string[] features) { - return Call ("isFeatureEnabled", MakeJavaArray(features)); - } - - public bool IsAnyFeatureEnabled (string[] features) { - return Call ("isAnyFeatureEnabled", MakeJavaArray(features)); - } - - public string[] GetEnabledFeatures () { - return Call ("getEnabledFeatures"); - } - - /// Internal method to make a Java Array with an array of String values, to be used with the - /// "setEnabledFeatures" method. - private AndroidJavaObject MakeJavaArray(string [] values) { - AndroidJavaClass arrayClass = new AndroidJavaClass("java.lang.reflect.Array"); - AndroidJavaObject arrayObject = arrayClass.CallStatic("newInstance", new AndroidJavaClass("java.lang.String"), values.Count()); - for (int i=0; i (string method, params object[] args) { - if (androidPlugin != null) { - return androidPlugin.Call (method, args); - } - return default (T); - } - } -} - -#endif diff --git a/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs b/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs index d480b31d..20b9b71f 100644 --- a/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs +++ b/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs @@ -3,6 +3,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Runtime.InteropServices; using UnityEngine; namespace UrbanAirship { @@ -66,15 +67,11 @@ public GameObject Listener { internal class AirshipPluginiOS : IAirshipPlugin{ - // TODO finish the iOS plugin setup - // private IAirshipPlugin plugin; - - // public AirshipPluginiOS (IAirshipPlugin plugin) { - // this.plugin = plugin; - // } + [DllImport ("__Internal")] + private static extern void UnityPlugin_call (string method, string args); public void Call (string method, params object[] args) { - + UnityPlugin_call(method, JsonUtility.ToJson(args)); } public T Call (string method, params object[] args) { diff --git a/Assets/UrbanAirship/Platforms/IUAirshipPlugin.cs b/Assets/UrbanAirship/Platforms/IUAirshipPlugin.cs deleted file mode 100644 index 0ea71877..00000000 --- a/Assets/UrbanAirship/Platforms/IUAirshipPlugin.cs +++ /dev/null @@ -1,102 +0,0 @@ -/* Copyright Airship and Contributors */ - -using System; -using System.Collections; -using System.Collections.Generic; -using UnityEngine; - -namespace UrbanAirship { - - interface IUAirshipPlugin { - bool UserNotificationsEnabled { - get; - set; - } - - string Tags { - get; - } - - string ChannelId { - get; - } - - string NamedUserId { - get; - set; - } - - GameObject Listener { - set; - } - - string GetDeepLink (bool clear); - - string GetIncomingPush (bool clear); - - void AddTag (string tag); - - void RemoveTag (string tag); - - void AddCustomEvent (string customEvent); - - void TrackScreen (string screenName); - - void AssociateIdentifier (string key, string identifier); - - void DisplayMessageCenter (); - - void DisplayInboxMessage (string messageId); - - void RefreshInbox (); - - string InboxMessages (); - - void MarkInboxMessageRead (string messageId); - - void DeleteInboxMessage (string messageId); - - void SetAutoLaunchDefaultMessageCenter (bool enabled); - - int MessageCenterUnreadCount { - get; - } - - int MessageCenterCount { - get; - } - - void EditNamedUserTagGroups (string payload); - - void EditChannelTagGroups (string payload); - - void EditChannelAttributes (string payload); - - void EditNamedUserAttributes (string payload); - - void OpenPreferenceCenter (string preferenceCenterId); - - void SetEnabledFeatures (string[] enabledFeatures); - - void EnableFeatures (string[] enabledFeatures); - - void DisableFeatures (string[] disabledFeatures); - - bool IsFeatureEnabled (string[] features); - - bool IsAnyFeatureEnabled (string[] features); - - string[] GetEnabledFeatures (); - - TimeSpan InAppAutomationDisplayInterval { - get; - set; - } - - bool InAppAutomationPaused { - get; - set; - } - - } -} diff --git a/Assets/UrbanAirship/Platforms/UAirship.cs b/Assets/UrbanAirship/Platforms/UAirship.cs deleted file mode 100644 index 8d5aa9f2..00000000 --- a/Assets/UrbanAirship/Platforms/UAirship.cs +++ /dev/null @@ -1,626 +0,0 @@ -/* Copyright Airship and Contributors */ - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using UnityEngine; - -namespace UrbanAirship { - /// - /// The primary manager class for the Urban Airship plugin. - /// - public class UAirship { - private IUAirshipPlugin plugin = null; - internal GameObject gameObject = null; - - /// - /// Push received event handler. - /// - public delegate void PushReceivedEventHandler (PushMessage message); - - /// - /// Occurs when a push is received. - /// - public event PushReceivedEventHandler OnPushReceived; - - /// - /// Push opened event handler. - /// - public delegate void PushOpenedEventHandler (PushMessage message); - - /// - /// Occurs when a push is opened. - /// - public event PushOpenedEventHandler OnPushOpened; - - /// - /// Deep link received event handler. - /// - public delegate void DeepLinkReceivedEventHandler (string deeplink); - - /// - /// Occurs when a deep link is received. - /// - public event DeepLinkReceivedEventHandler OnDeepLinkReceived; - - /// - /// Channel update event handler. - /// - public delegate void ChannelUpdateEventHandler (string channelId); - - /// - /// Occurs when the channel updates. - /// - public event ChannelUpdateEventHandler OnChannelUpdated; - - /// - /// Inbox update event handler. - /// - public delegate void InboxUpdatedEventHandler (uint messageUnreadCount, uint messageCount); - - /// - /// Occurs when the inbox updates. - /// - public event InboxUpdatedEventHandler OnInboxUpdated; - - /// - /// Show inbox event handler. - /// - public delegate void ShowInboxEventHandler (string messageId); - - /// - /// Occurs when the app needs to show the inbox. - /// - public event ShowInboxEventHandler OnShowInbox; - - internal static UAirship sharedAirship = new UAirship (); - - /// - /// Gets the shared UAirship instance. - /// - /// The shared UAirship instance. - public static UAirship Shared { - get { - return sharedAirship; - } - } - - /// - /// Creates a UAirship instance with a test plugin. - /// Used only for testing. - /// - /// The test plugin. - internal UAirship (object testPlugin) { - plugin = (UrbanAirship.IUAirshipPlugin) testPlugin; - - init (); - } - - /// - /// Creates a UAirship instance. - /// ] - private UAirship () { - if (Application.isEditor) { - plugin = new StubbedPlugin (); - } else { -#if UNITY_ANDROID - plugin = new UAirshipPluginAndroid (); -#elif UNITY_IOS - plugin = new UAirshipPluginIOS (); -#else - plugin = new StubbedPlugin (); -#endif - } - - init (); - } - - /// - /// Initialize a UAirship instance. - /// ] - private void init () { - gameObject = new GameObject ("[UrbanAirshipListener]"); - gameObject.AddComponent (); - - UnityEngine.Object.DontDestroyOnLoad (gameObject); - plugin.Listener = gameObject; - } - - /// - /// Determines whether user notifications are enabled. - /// - /// true if user notifications are enabled; otherwise, false. - public bool UserNotificationsEnabled { - get { - return plugin.UserNotificationsEnabled; - } - set { - plugin.UserNotificationsEnabled = value; - } - } - - /// - /// Gets the tags currently set for the device. - /// - /// The tags. - public IEnumerable Tags { - get { - string tagsAsJson = plugin.Tags; - JsonArray jsonArray = JsonArray.FromJson (tagsAsJson); - return jsonArray.AsEnumerable (); - } - } - - /// - /// Gets the channel identifier associated with the device. - /// - /// The channel identifier. - public string ChannelId { - get { - return plugin.ChannelId; - } - } - - /// - /// Gets or sets the named user identifier. - /// - /// The named user identifier. - public string NamedUserId { - get { - return plugin.NamedUserId; - } - set { - plugin.NamedUserId = value; - } - } - - /// - /// Gets or sets the In-App automation display interval. - /// - /// The display interval. - public TimeSpan InAppAutomationDisplayInterval { - get { - return plugin.InAppAutomationDisplayInterval; - } - set { - plugin.InAppAutomationDisplayInterval = value; - } - } - - /// - /// Pauses/resumes In-App automation. - /// - /// true if paused, otherwise false - public bool InAppAutomationPaused { - get { - return plugin.InAppAutomationPaused; - } - set { - plugin.InAppAutomationPaused = value; - } - } - - /// - /// Gets the last received deep link. - /// - /// The deep link. - /// If set to true clear the stored deep link after accessing it. - public string GetDeepLink (bool clear = true) { - return plugin.GetDeepLink (clear); - } - - /// - /// Gets the last stored incoming push message. - /// - /// The push message. - /// If set to true clear the stored push message after accessing it. - public PushMessage GetIncomingPush (bool clear = true) { - string jsonPushMessage = plugin.GetIncomingPush (clear); - if (String.IsNullOrEmpty (jsonPushMessage)) { - return null; - } - - PushMessage pushMessage = PushMessage.FromJson (jsonPushMessage); - return pushMessage; - } - - /// - /// Adds the provided device tag. - /// - /// The tag. - public void AddTag (string tag) { - plugin.AddTag (tag); - } - - /// - /// Removes the provided device tag. - /// - /// The tag. - public void RemoveTag (string tag) { - plugin.RemoveTag (tag); - } - - /// - /// Adds a custom event. - /// - /// The custom event. - public void AddCustomEvent (CustomEvent customEvent) { - plugin.AddCustomEvent (customEvent.ToJson ()); - } - - /// - /// Adds a screen tracking event to analytics. - /// - /// The screen name. - public void TrackScreen (string screenName) { - plugin.TrackScreen (screenName); - } - - /// - /// Associate a custom identifier. - /// Previous identifiers will be replaced by the new identifiers each time AssociateIdentifier is called. - /// It is a set operation. - /// - /// The custom key for the identifier. - /// The value of the identifier, or `null` to remove the identifier. - public void AssociateIdentifier (string key, string identifier) { - plugin.AssociateIdentifier (key, identifier); - } - - /// - /// Displays the message center. - /// - public void DisplayMessageCenter () { - plugin.DisplayMessageCenter (); - } - - /// - /// Displays an inbox message. - /// - /// The messageId for the message. - public void DisplayInboxMessage (string messageId) { - plugin.DisplayInboxMessage (messageId); - } - - /// - /// Refreshes the inbox. - /// - public void RefreshInbox () { - plugin.RefreshInbox (); - } - - /// - /// Gets the inbox messages. - /// - /// An enumberable list of InboxMessage objects. - public IEnumerable InboxMessages () { - var inboxMessages = new List (); - - string inboxMessagesAsJson = plugin.InboxMessages (); - _InboxMessage[] internalInboxMessages = JsonArray<_InboxMessage>.FromJson (inboxMessagesAsJson).values; - - // Unity's JsonUtility doesn't support embedded dictionaries - constructor will create the extras dictionary - foreach (_InboxMessage internalInboxMessage in internalInboxMessages) { - inboxMessages.Add (new InboxMessage (internalInboxMessage)); - } - return inboxMessages; - } - - /// - /// Mark an inbox message as having been read. - /// - /// The messageId for the message. - public void MarkInboxMessageRead (string messageId) { - plugin.MarkInboxMessageRead (messageId); - } - - /// - /// Delete an inbox message. - /// - /// The messageId for the message. - public void DeleteInboxMessage (string messageId) { - plugin.DeleteInboxMessage (messageId); - } - - /// - /// Sets the default behavior when the message center is launched from a push notification. - /// - /// true to automatically launch the default message center. If false the message center must be manually launched by the app. - - public void SetAutoLaunchDefaultMessageCenter (bool enabled) { - plugin.SetAutoLaunchDefaultMessageCenter (enabled); - } - - /// - /// Gets the number of unread messages for the message center. - /// - public int MessageCenterUnreadCount { - get { - return plugin.MessageCenterUnreadCount; - } - } - - /// - /// Gets the number of messages for the message center. - /// - public int MessageCenterCount { - get { - return plugin.MessageCenterCount; - } - } - - /// - /// Returns an editor for named user tag groups. - /// - /// A TagGroupEditor for named user tag groups. - public TagGroupEditor EditNamedUserTagGroups () { - return new TagGroupEditor ((string payload) => { - plugin.EditNamedUserTagGroups (payload); - }); - } - - /// - /// Returns an editor for channel tag groups. - /// - /// A TagGroupEditor for channel tag groups. - public TagGroupEditor EditChannelTagGroups () { - return new TagGroupEditor ((string payload) => { - plugin.EditChannelTagGroups (payload); - }); - } - - /// - /// Returns an editor for channel attributes. - /// - /// A AttributeEditor for channel attributes. - public AttributeEditor EditChannelAttributes () { - return new AttributeEditor ((string payload) => { - plugin.EditChannelAttributes (payload); - }); - } - - /// - /// Returns an editor for named user attributes. - /// - /// A AttributeEditor for named user attributes. - public AttributeEditor EditNamedUserAttributes () - { - return new AttributeEditor((string payload) => { - plugin.EditNamedUserAttributes(payload); - }); - } - - /// - /// Opens the Preference Center with the specified ID. - /// - /// The Preference Center's ID - public void OpenPreferenceCenter (string preferenceCenterId) { - plugin.OpenPreferenceCenter (preferenceCenterId); - } - - /// - /// Sets the enabled SDK features - /// - /// The features to enable - public void SetEnabledFeatures (string[] enabledFeatures) { - plugin.SetEnabledFeatures (enabledFeatures); - } - - /// - /// Enables the specified SDK features - /// - /// The features to enable - public void EnableFeatures (string[] enabledFeatures) { - plugin.EnableFeatures (enabledFeatures); - } - - /// - /// Disables the specified SDK features - /// - /// The features to disable - public void DisableFeatures (string[] disabledFeatures) { - plugin.DisableFeatures (disabledFeatures); - } - - /// - /// Returns a boolean if the specified SDK features are enabled - /// - /// The features to check - /// true if feature is enabled, otherwise false - public bool IsFeatureEnabled (string[] features) { - return plugin.IsFeatureEnabled (features); - } - - /// - /// Returns a boolean if any of the specified SDK feature are enabled - /// - /// The features to check - /// true if any of these features is enabled, otherwise false - public bool IsAnyFeatureEnabled (string[] features) { - return plugin.IsAnyFeatureEnabled (features); - } - - /// - /// Gets the enabled SDK features - /// - /// The features enabled - public string[] GetEnabledFeatures () { - return plugin.GetEnabledFeatures (); - } - - internal class UrbanAirshipListener : MonoBehaviour { - void OnPushReceived (string payload) { - PushReceivedEventHandler handler = UAirship.Shared.OnPushReceived; - - if (handler == null) { - return; - } - - PushMessage pushMessage = PushMessage.FromJson (payload); - if (pushMessage != null) { - handler (pushMessage); - } - } - - void OnPushOpened (string payload) { - PushOpenedEventHandler handler = UAirship.Shared.OnPushOpened; - - if (handler == null) { - return; - } - - PushMessage pushMessage = PushMessage.FromJson (payload); - if (pushMessage != null) { - handler (pushMessage); - } - } - - void OnDeepLinkReceived (string deeplink) { - DeepLinkReceivedEventHandler handler = UAirship.Shared.OnDeepLinkReceived; - - if (handler != null) { - handler (deeplink); - } - } - - void OnChannelUpdated (string channelId) { - ChannelUpdateEventHandler handler = UAirship.Shared.OnChannelUpdated; - - if (handler != null) { - handler (channelId); - } - } - - internal void OnInboxUpdated (string counts) { - InboxUpdatedEventHandler handler = UAirship.Shared.OnInboxUpdated; - - MessageCounts messageCounts = JsonUtility.FromJson (counts); - - if (handler != null) { - handler (messageCounts.unread, messageCounts.total); - } - - } - - internal void OnShowInbox (string messageId) { - ShowInboxEventHandler handler = UAirship.Shared.OnShowInbox; - - if (handler != null) { - if ((messageId == null) || (messageId.Length == 0)) { - handler (null); - } else { - handler (messageId); - } - } - } - } - } - - public class InboxMessage { - public readonly string id; - public readonly string title; - public readonly long sentDate; - public readonly bool isRead; - public readonly bool isDeleted; - public readonly Dictionary extras; - - internal InboxMessage (string id, string title, long sentDate, bool isRead, bool isDeleted, Dictionary extras) { - this.id = id; - this.title = title; - this.sentDate = sentDate; - this.isRead = isRead; - this.isDeleted = isDeleted; - this.extras = extras; - } - - public InboxMessage (_InboxMessage _inboxMessage) { - sentDate = _inboxMessage.sentDate; - id = _inboxMessage.id; - title = _inboxMessage.title; - isRead = _inboxMessage.isRead; - isDeleted = _inboxMessage.isDeleted; - - if (_inboxMessage.extrasKeys != null && _inboxMessage.extrasKeys.Count > 0) { - // Unity's JsonUtility doesn't support embedded dictionaries - create the extras dictionary manually - extras = new Dictionary (); - for (int index = 0; index < _inboxMessage.extrasKeys.Count; index++) { - extras[_inboxMessage.extrasKeys[index]] = _inboxMessage.extrasValues[index]; - } - } - } - - public override bool Equals (object other) { - var that = other as InboxMessage; - - if (that == null) { - return false; - } - - if (this.id != that.id) { - return false; - } - if (this.title != that.title) { - return false; - } - if (this.sentDate != that.sentDate) { - return false; - } - if (this.isRead != that.isRead) { - return false; - } - if (this.isDeleted != that.isDeleted) { - return false; - } - if ((this.extras == null ^ that.extras == null) || - ((this.extras != that.extras) && - (this.extras.Count != that.extras.Count || this.extras.Except (that.extras).Any ()))) { - return false; - } - - return true; - } - - public override int GetHashCode () { - unchecked { - var hashCode = (id != null ? id.GetHashCode () : 0); - hashCode = (hashCode * 397) ^ (title != null ? title.GetHashCode () : 0); - hashCode = (hashCode * 397) ^ sentDate.GetHashCode (); - hashCode = (hashCode * 397) ^ isRead.GetHashCode (); - hashCode = (hashCode * 397) ^ isDeleted.GetHashCode (); - hashCode = (hashCode * 397) ^ (extras != null ? extras.GetHashCode () : 0); - return hashCode; - } - } - } - - public static class Features { - public const string FEATURE_NONE = "FEATURE_NONE"; - public const string FEATURE_IN_APP_AUTOMATION = "FEATURE_IN_APP_AUTOMATION"; - public const string FEATURE_MESSAGE_CENTER = "FEATURE_MESSAGE_CENTER"; - public const string FEATURE_PUSH = "FEATURE_PUSH"; - public const string FEATURE_CHAT = "FEATURE_CHAT"; - public const string FEATURE_ANALYTICS = "FEATURE_ANALYTICS"; - public const string FEATURE_TAGS_AND_ATTRIBUTES = "FEATURE_TAGS_AND_ATTRIBUTES"; - public const string FEATURE_CONTACTS = "FEATURE_CONTACTS"; - public const string FEATURE_LOCATION = "FEATURE_LOCATION"; - public const string FEATURE_ALL = "FEATURE_ALL"; - } - - [Serializable] - public class _InboxMessage { - public string id; - public string title; - public long sentDate; - public bool isRead; - public bool isDeleted; - public List extrasKeys; - public List extrasValues; - } - - [Serializable] - public class MessageCounts { - public uint unread; - public uint total; - } -} diff --git a/Assets/UrbanAirship/Platforms/iOS/UAirshipPluginIOS.cs b/Assets/UrbanAirship/Platforms/iOS/UAirshipPluginIOS.cs deleted file mode 100644 index 2976942e..00000000 --- a/Assets/UrbanAirship/Platforms/iOS/UAirshipPluginIOS.cs +++ /dev/null @@ -1,304 +0,0 @@ -/* Copyright Airship and Contributors */ - -#if UNITY_IOS - -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using UnityEngine; - -namespace UrbanAirship { - - class UAirshipPluginIOS : IUAirshipPlugin { - - [DllImport ("__Internal")] - private static extern void UAUnityPlugin_setListener (string listener); - - [DllImport ("__Internal")] - private static extern string UAUnityPlugin_getDeepLink (bool clear); - - [DllImport ("__Internal")] - private static extern string UAUnityPlugin_getIncomingPush (bool clear); - - [DllImport ("__Internal")] - private static extern bool UAUnityPlugin_getUserNotificationsEnabled (); - - [DllImport ("__Internal")] - private static extern void UAUnityPlugin_setUserNotificationsEnabled (bool enabled); - - [DllImport ("__Internal")] - private static extern string UAUnityPlugin_getTags (); - - [DllImport ("__Internal")] - private static extern void UAUnityPlugin_addTag (string tag); - - [DllImport ("__Internal")] - private static extern void UAUnityPlugin_removeTag (string tag); - - [DllImport ("__Internal")] - private static extern string UAUnityPlugin_getChannelId (); - - // Analytics Function Imports - [DllImport ("__Internal")] - private static extern void UAUnityPlugin_addCustomEvent (string customEvent); - - [DllImport ("__Internal")] - private static extern void UAUnityPlugin_trackScreen (string screenName); - - [DllImport ("__Internal")] - private static extern void UAUnityPlugin_associateIdentifier (string key, string identifier); - - [DllImport ("__Internal")] - private static extern string UAUnityPlugin_getNamedUserID (); - - [DllImport ("__Internal")] - private static extern void UAUnityPlugin_setNamedUserID (string namedUserID); - - [DllImport ("__Internal")] - private static extern void UAUnityPlugin_displayMessageCenter (); - - [DllImport ("__Internal")] - private static extern void UAUnityPlugin_displayInboxMessage (string messageId); - - [DllImport ("__Internal")] - private static extern void UAUnityPlugin_refreshInbox (); - - [DllImport ("__Internal")] - private static extern string UAUnityPlugin_getInboxMessages (); - - [DllImport ("__Internal")] - private static extern void UAUnityPlugin_markInboxMessageRead (string messageId); - - [DllImport ("__Internal")] - private static extern void UAUnityPlugin_deleteInboxMessage (string messageId); - - [DllImport ("__Internal")] - private static extern void UAUnityPlugin_setAutoLaunchDefaultMessageCenter (bool enabled); - - [DllImport ("__Internal")] - private static extern int UAUnityPlugin_getMessageCenterUnreadCount (); - - [DllImport ("__Internal")] - private static extern int UAUnityPlugin_getMessageCenterCount (); - - [DllImport ("__Internal")] - private static extern void UAUnityPlugin_editNamedUserTagGroups (string payload); - - [DllImport ("__Internal")] - private static extern void UAUnityPlugin_editChannelTagGroups (string payload); - - [DllImport ("__Internal")] - private static extern void UAUnityPlugin_editChannelAttributes (string payload); - - [DllImport("__Internal")] - private static extern void UAUnityPlugin_editNamedUserAttributes (string payload); - - [DllImport("__Internal")] - private static extern void UAUnityPlugin_openPreferenceCenter (string preferenceCenterId); - - [DllImport("__Internal")] - private static extern void UAUnityPlugin_setEnabledFeatures (string enabledFeatures); - - [DllImport("__Internal")] - private static extern void UAUnityPlugin_enableFeatures (string enabledFeatures); - - [DllImport("__Internal")] - private static extern void UAUnityPlugin_disableFeatures (string disabledFeatures); - - [DllImport("__Internal")] - private static extern bool UAUnityPlugin_isFeatureEnabled (string features); - - [DllImport("__Internal")] - private static extern bool UAUnityPlugin_isAnyFeatureEnabled (); - - [DllImport("__Internal")] - private static extern string UAUnityPlugin_getEnabledFeatures (); - - [DllImport ("__Internal")] - private static extern double UAUnityPlugin_getInAppAutomationDisplayInterval (); - - [DllImport ("__Internal")] - private static extern void UAUnityPlugin_setInAppAutomationDisplayInterval (double seconds); - - [DllImport ("__Internal")] - private static extern bool UAUnityPlugin_isInAppAutomationPaused (); - - [DllImport ("__Internal")] - private static extern void UAUnityPlugin_setInAppAutomationPaused (bool paused); - - public bool UserNotificationsEnabled { - get { - return UAUnityPlugin_getUserNotificationsEnabled (); - } - set { - UAUnityPlugin_setUserNotificationsEnabled (value); - } - } - - public string Tags { - get { - return UAUnityPlugin_getTags (); - } - } - - public string ChannelId { - get { - return UAUnityPlugin_getChannelId (); - } - } - - public string NamedUserId { - get { - return UAUnityPlugin_getNamedUserID (); - } - - set { - UAUnityPlugin_setNamedUserID (value); - } - } - - public TimeSpan InAppAutomationDisplayInterval { - get { - return TimeSpan.FromSeconds (UAUnityPlugin_getInAppAutomationDisplayInterval ()); - } - set { - UAUnityPlugin_setInAppAutomationDisplayInterval (value.TotalSeconds); - } - } - - public bool InAppAutomationPaused { - get { - return UAUnityPlugin_isInAppAutomationPaused (); - } - set { - UAUnityPlugin_setInAppAutomationPaused (value); - } - } - - public GameObject Listener { - set { - UAUnityPlugin_setListener (value.name); - } - } - - public string GetDeepLink (bool clear) { - return UAUnityPlugin_getDeepLink (clear); - } - - public string GetIncomingPush (bool clear) { - return UAUnityPlugin_getIncomingPush (clear); - } - - public void AddTag (string tag) { - UAUnityPlugin_addTag (tag); - } - - public void RemoveTag (string tag) { - UAUnityPlugin_removeTag (tag); - } - - public void AddCustomEvent (string customEvent) { - UAUnityPlugin_addCustomEvent (customEvent); - } - - public void TrackScreen (string screenName) { - UAUnityPlugin_trackScreen (screenName); - } - - public void AssociateIdentifier (string key, string identifier) { - UAUnityPlugin_associateIdentifier (key, identifier); - } - - public void DisplayMessageCenter () { - UAUnityPlugin_displayMessageCenter (); - } - - public void DisplayInboxMessage (string messageId) { - UAUnityPlugin_displayInboxMessage (messageId); - } - - public void RefreshInbox () { - UAUnityPlugin_refreshInbox (); - } - - public string InboxMessages () { - return UAUnityPlugin_getInboxMessages (); - } - - public void MarkInboxMessageRead (string messageId) { - UAUnityPlugin_markInboxMessageRead (messageId); - } - - public void DeleteInboxMessage (string messageId) { - UAUnityPlugin_deleteInboxMessage (messageId); - } - - public void SetAutoLaunchDefaultMessageCenter (bool enabled) { - UAUnityPlugin_setAutoLaunchDefaultMessageCenter (enabled); - } - - public int MessageCenterUnreadCount { - get { - return UAUnityPlugin_getMessageCenterUnreadCount (); - } - } - - public int MessageCenterCount { - get { - return UAUnityPlugin_getMessageCenterCount (); - } - } - - public void EditNamedUserTagGroups (string payload) { - UAUnityPlugin_editNamedUserTagGroups (payload); - } - - public void EditChannelTagGroups (string payload) { - UAUnityPlugin_editChannelTagGroups (payload); - } - - public void EditChannelAttributes (string payload) { - UAUnityPlugin_editChannelAttributes (payload); - } - - public void EditNamedUserAttributes (string payload) - { - UAUnityPlugin_editNamedUserAttributes (payload); - } - - public void OpenPreferenceCenter (string preferenceCenterId) - { - UAUnityPlugin_openPreferenceCenter (preferenceCenterId); - } - - public void SetEnabledFeatures (string[] enabledFeatures) - { - UAUnityPlugin_setEnabledFeatures (String.Join(",", enabledFeatures)); - } - - public void EnableFeatures (string[] enabledFeatures) - { - UAUnityPlugin_enableFeatures (String.Join(",", enabledFeatures)); - } - - public void DisableFeatures (string[] disabledFeatures) - { - UAUnityPlugin_disableFeatures (String.Join(",", disabledFeatures)); - } - - public bool IsFeatureEnabled (string[] features) { - return UAUnityPlugin_isFeatureEnabled (String.Join(",", features)); - } - - public bool IsAnyFeatureEnabled (string[] features) { - //iOS method doesn't take any parameter - return UAUnityPlugin_isAnyFeatureEnabled (); - } - - public string[] GetEnabledFeatures () { - return UAUnityPlugin_getEnabledFeatures().Split(','); - } - } -} - -#endif diff --git a/Assets/UrbanAirship/Tests/MessageCenterTests.cs b/Assets/UrbanAirship/Tests/MessageCenterTests.cs index bb26ce76..93e1e7d2 100644 --- a/Assets/UrbanAirship/Tests/MessageCenterTests.cs +++ b/Assets/UrbanAirship/Tests/MessageCenterTests.cs @@ -10,353 +10,353 @@ namespace Tests { public class MessageCenterTests { - private UAirship sharedAirship; - private IUAirshipPlugin mockPlugin; + // private UAirship sharedAirship; + // private IUAirshipPlugin mockPlugin; - [SetUp] - public void Setup () - { - mockPlugin = Substitute.For(); - sharedAirship = new UAirship(mockPlugin); - UAirship.sharedAirship = sharedAirship; - } + // [SetUp] + // public void Setup () + // { + // mockPlugin = Substitute.For(); + // sharedAirship = new UAirship(mockPlugin); + // UAirship.sharedAirship = sharedAirship; + // } - [Test] - public void TestUserNotificationsEnabled () - { - sharedAirship.UserNotificationsEnabled = true; - mockPlugin.Received().UserNotificationsEnabled = true; - } + // [Test] + // public void TestUserNotificationsEnabled () + // { + // sharedAirship.UserNotificationsEnabled = true; + // mockPlugin.Received().UserNotificationsEnabled = true; + // } - [Test] - public void TestUserNotificationsDisabled () - { - sharedAirship.UserNotificationsEnabled = false; - mockPlugin.Received().UserNotificationsEnabled = false; - } + // [Test] + // public void TestUserNotificationsDisabled () + // { + // sharedAirship.UserNotificationsEnabled = false; + // mockPlugin.Received().UserNotificationsEnabled = false; + // } - [Test] - public void TestTags () - { - var expectedTagsAsJson = @"[ ""abc"", ""def"" ]"; - mockPlugin.Tags.Returns(expectedTagsAsJson); - - List expectedTagsAsList = new List() - { - "abc", - "def" - }; - - Assert.AreEqual(expectedTagsAsList, sharedAirship.Tags); - } - - [Test] - public void TestChannelId () - { - var expectedChannelId = "channel-id"; - - mockPlugin.ChannelId.Returns(expectedChannelId); - - Assert.AreEqual(expectedChannelId, sharedAirship.ChannelId); - } - - [Test] - public void TestGetNamedUserId () - { - var expectedNamedUserId = "JohnDoe"; - - mockPlugin.NamedUserId.Returns(expectedNamedUserId); - - Assert.AreEqual(expectedNamedUserId, sharedAirship.NamedUserId); - } - - [Test] - public void TestSetNamedUserId () - { - var expectedNamedUserId = "JohnDoe"; - - sharedAirship.NamedUserId = expectedNamedUserId; - - mockPlugin.Received().NamedUserId = expectedNamedUserId; - } - - [Test] - public void TestGetDeepLink () - { - var expectedDeepLink = "/deep/link"; - - mockPlugin.GetDeepLink(false).Returns(expectedDeepLink); - - Assert.AreEqual(expectedDeepLink, sharedAirship.GetDeepLink(false)); - - mockPlugin.GetDeepLink(true).Returns(expectedDeepLink); - - Assert.AreEqual(expectedDeepLink, sharedAirship.GetDeepLink(true)); - - Received.InOrder(() => - { - mockPlugin.GetDeepLink(false); - mockPlugin.GetDeepLink(true); - }); - } - - [Test] - public void TestMessageCenterUnreadCount () - { - var expectedUnreadCount = 99; - - mockPlugin.MessageCenterUnreadCount.Returns(expectedUnreadCount); - - Assert.AreEqual(expectedUnreadCount, sharedAirship.MessageCenterUnreadCount); - } - - [Test] - public void TestMessageCenterCount () - { - var expectedCount = 55; - - mockPlugin.MessageCenterCount.Returns(expectedCount); - - Assert.AreEqual(expectedCount, sharedAirship.MessageCenterCount); - } - - [Test] - public void TestInboxMessagesNoMessages () - { - var expectedMessagesAsJson = @"[ - ]"; - mockPlugin.InboxMessages().Returns(expectedMessagesAsJson); - - var expectedMessagesAsList = new List() - { - }; - - Assert.AreEqual(expectedMessagesAsList, sharedAirship.InboxMessages()); - } - - [Test] - public void TestInboxMessagesOneMessage () - { - var expectedMessagesAsJson = @"[ - { - ""sentDate"": 1547670565000, - ""id"": ""ahpPUBnNEemxogJC_nHsWw"", - ""title"": ""Title of the message"", - ""isRead"": true, - ""isDeleted"": false, - ""extrasKeys"": [ - ""com.urbanairship.listing.field1"", - ""com.urbanairship.listing.field2"", - ""com.urbanairship.listing.template"" - ], - ""extrasValues"":[ - """", - """", - ""text"" - ] - } - ]"; - mockPlugin.InboxMessages().Returns(expectedMessagesAsJson); - - var expectedMessagesAsList = new List() - { - new InboxMessage( - id: @"ahpPUBnNEemxogJC_nHsWw", - title: "Title of the message", - sentDate: 1547670565000, - isRead: true, - isDeleted: false, - extras: new Dictionary() - { - { "com.urbanairship.listing.field1", "" }, - { "com.urbanairship.listing.field2", "" }, - { "com.urbanairship.listing.template", "text" } - } - ) - }; - - Assert.AreEqual(expectedMessagesAsList, sharedAirship.InboxMessages()); - } - - [Test] - public void TestInboxMessagesMultipleMessages () - { - var expectedMessagesAsJson = @"[ - { - ""sentDate"": 1547670565000, - ""id"": ""ahpPUBnNEemxogJC_nHsWw"", - ""title"": ""Title of the message"", - ""isRead"": true, - ""isDeleted"": false, - ""extrasKeys"": [ - ""com.urbanairship.listing.field1"", - ""com.urbanairship.listing.field2"", - ""com.urbanairship.listing.template"" - ], - ""extrasValues"":[ - """", - """", - ""text"" - ] - }, - { - ""sentDate"": 10, - ""id"": ""anidentifier"", - ""title"": ""Title"", - ""isRead"": false, - ""isDeleted"": true, - ""extrasKeys"": [ - ], - ""extrasValues"":[ - ] - }, - { - ""sentDate"": 200, - ""id"": ""anotheridentifier"", - ""title"": ""Another Title"", - ""isRead"": false, - ""isDeleted"": false - } - ]"; - mockPlugin.InboxMessages().Returns(expectedMessagesAsJson); - - var expectedMessagesAsList = new List() - { - new InboxMessage( - id: @"ahpPUBnNEemxogJC_nHsWw", - title: "Title of the message", - sentDate: 1547670565000, - isRead: true, - isDeleted: false, - extras: new Dictionary() - { - { "com.urbanairship.listing.field1", "" }, - { "com.urbanairship.listing.field2", "" }, - { "com.urbanairship.listing.template", "text" } - } - ), - new InboxMessage( - id: @"anidentifier", - title: "Title", - sentDate: 10, - isRead: false, - isDeleted: true, - extras: null - ), - new InboxMessage( - id: @"anotheridentifier", - title: "Another Title", - sentDate: 200, - isRead: false, - isDeleted: false, - extras: null - ) - }; - - Assert.AreEqual(expectedMessagesAsList, sharedAirship.InboxMessages()); - } - - [Test] - public void TestMarkInboxMessageRead () - { - string messageId = "AMessageId"; - sharedAirship.MarkInboxMessageRead(messageId); - mockPlugin.Received().MarkInboxMessageRead(messageId); - } - - [Test] - public void TestDeleteInboxMessage () - { - string messageId = "AMessageId"; - sharedAirship.DeleteInboxMessage(messageId); - mockPlugin.Received().DeleteInboxMessage(messageId); - } - - [Test] - public void TestDisplayMessageCenter () - { - sharedAirship.DisplayMessageCenter(); - mockPlugin.Received().DisplayMessageCenter(); - } - - [Test] - public void TestDisplayInboxMessage () - { - string messageId = "AMessageId"; - - sharedAirship.DisplayInboxMessage(messageId); - mockPlugin.Received().DisplayInboxMessage(messageId); - } - - [Test] - public void TestRefreshInbox () - { - sharedAirship.RefreshInbox(); - mockPlugin.Received().RefreshInbox(); - } - - [Test] - public void TestSetAutoLaunchDefaultMessageCenter () - { - sharedAirship.SetAutoLaunchDefaultMessageCenter(true); - mockPlugin.Received().SetAutoLaunchDefaultMessageCenter(true); - - mockPlugin.ClearReceivedCalls(); - - sharedAirship.SetAutoLaunchDefaultMessageCenter(false); - mockPlugin.Received().SetAutoLaunchDefaultMessageCenter(false); - } - - [Test] - public void TestOnInboxUpdated () - { - var wasCalled = false; - sharedAirship.OnInboxUpdated += (messageUnreadCount, messageCount) => - { - Assert.AreEqual(messageUnreadCount, 1); - Assert.AreEqual(messageCount, 2); - wasCalled = true; - }; - - var counts = @"{ ""unread"": 1, ""total"": 2 }"; - UAirship.UrbanAirshipListener listener = sharedAirship.gameObject.GetComponent() as UAirship.UrbanAirshipListener; - listener.OnInboxUpdated(counts); - - Assert.True(wasCalled); - } - - [Test] - public void TestOnShowInboxSpecificMessage () - { - var wasCalled = false; - sharedAirship.OnShowInbox += (theMessageId) => - { - Assert.AreEqual(theMessageId, "AMessageId"); - wasCalled = true; - }; - - var messageId = "AMessageId"; - UAirship.UrbanAirshipListener listener = sharedAirship.gameObject.GetComponent() as UAirship.UrbanAirshipListener; - listener.OnShowInbox(messageId); - - Assert.True(wasCalled); - } - - [Test] - public void TestOnShowInboxAllMessages () - { - var wasCalled = false; - sharedAirship.OnShowInbox += (theMessageId) => - { - Assert.IsNull(theMessageId); - wasCalled = true; - }; - - string messageId = ""; - UAirship.UrbanAirshipListener listener = sharedAirship.gameObject.GetComponent() as UAirship.UrbanAirshipListener; - listener.OnShowInbox(messageId); - - Assert.True(wasCalled); - } + // [Test] + // public void TestTags () + // { + // var expectedTagsAsJson = @"[ ""abc"", ""def"" ]"; + // mockPlugin.Tags.Returns(expectedTagsAsJson); + + // List expectedTagsAsList = new List() + // { + // "abc", + // "def" + // }; + + // Assert.AreEqual(expectedTagsAsList, sharedAirship.Tags); + // } + + // [Test] + // public void TestChannelId () + // { + // var expectedChannelId = "channel-id"; + + // mockPlugin.ChannelId.Returns(expectedChannelId); + + // Assert.AreEqual(expectedChannelId, sharedAirship.ChannelId); + // } + + // [Test] + // public void TestGetNamedUserId () + // { + // var expectedNamedUserId = "JohnDoe"; + + // mockPlugin.NamedUserId.Returns(expectedNamedUserId); + + // Assert.AreEqual(expectedNamedUserId, sharedAirship.NamedUserId); + // } + + // [Test] + // public void TestSetNamedUserId () + // { + // var expectedNamedUserId = "JohnDoe"; + + // sharedAirship.NamedUserId = expectedNamedUserId; + + // mockPlugin.Received().NamedUserId = expectedNamedUserId; + // } + + // [Test] + // public void TestGetDeepLink () + // { + // var expectedDeepLink = "/deep/link"; + + // mockPlugin.GetDeepLink(false).Returns(expectedDeepLink); + + // Assert.AreEqual(expectedDeepLink, sharedAirship.GetDeepLink(false)); + + // mockPlugin.GetDeepLink(true).Returns(expectedDeepLink); + + // Assert.AreEqual(expectedDeepLink, sharedAirship.GetDeepLink(true)); + + // Received.InOrder(() => + // { + // mockPlugin.GetDeepLink(false); + // mockPlugin.GetDeepLink(true); + // }); + // } + + // [Test] + // public void TestMessageCenterUnreadCount () + // { + // var expectedUnreadCount = 99; + + // mockPlugin.MessageCenterUnreadCount.Returns(expectedUnreadCount); + + // Assert.AreEqual(expectedUnreadCount, sharedAirship.MessageCenterUnreadCount); + // } + + // [Test] + // public void TestMessageCenterCount () + // { + // var expectedCount = 55; + + // mockPlugin.MessageCenterCount.Returns(expectedCount); + + // Assert.AreEqual(expectedCount, sharedAirship.MessageCenterCount); + // } + + // [Test] + // public void TestInboxMessagesNoMessages () + // { + // var expectedMessagesAsJson = @"[ + // ]"; + // mockPlugin.InboxMessages().Returns(expectedMessagesAsJson); + + // var expectedMessagesAsList = new List() + // { + // }; + + // Assert.AreEqual(expectedMessagesAsList, sharedAirship.InboxMessages()); + // } + + // [Test] + // public void TestInboxMessagesOneMessage () + // { + // var expectedMessagesAsJson = @"[ + // { + // ""sentDate"": 1547670565000, + // ""id"": ""ahpPUBnNEemxogJC_nHsWw"", + // ""title"": ""Title of the message"", + // ""isRead"": true, + // ""isDeleted"": false, + // ""extrasKeys"": [ + // ""com.urbanairship.listing.field1"", + // ""com.urbanairship.listing.field2"", + // ""com.urbanairship.listing.template"" + // ], + // ""extrasValues"":[ + // """", + // """", + // ""text"" + // ] + // } + // ]"; + // mockPlugin.InboxMessages().Returns(expectedMessagesAsJson); + + // var expectedMessagesAsList = new List() + // { + // new InboxMessage( + // id: @"ahpPUBnNEemxogJC_nHsWw", + // title: "Title of the message", + // sentDate: 1547670565000, + // isRead: true, + // isDeleted: false, + // extras: new Dictionary() + // { + // { "com.urbanairship.listing.field1", "" }, + // { "com.urbanairship.listing.field2", "" }, + // { "com.urbanairship.listing.template", "text" } + // } + // ) + // }; + + // Assert.AreEqual(expectedMessagesAsList, sharedAirship.InboxMessages()); + // } + + // [Test] + // public void TestInboxMessagesMultipleMessages () + // { + // var expectedMessagesAsJson = @"[ + // { + // ""sentDate"": 1547670565000, + // ""id"": ""ahpPUBnNEemxogJC_nHsWw"", + // ""title"": ""Title of the message"", + // ""isRead"": true, + // ""isDeleted"": false, + // ""extrasKeys"": [ + // ""com.urbanairship.listing.field1"", + // ""com.urbanairship.listing.field2"", + // ""com.urbanairship.listing.template"" + // ], + // ""extrasValues"":[ + // """", + // """", + // ""text"" + // ] + // }, + // { + // ""sentDate"": 10, + // ""id"": ""anidentifier"", + // ""title"": ""Title"", + // ""isRead"": false, + // ""isDeleted"": true, + // ""extrasKeys"": [ + // ], + // ""extrasValues"":[ + // ] + // }, + // { + // ""sentDate"": 200, + // ""id"": ""anotheridentifier"", + // ""title"": ""Another Title"", + // ""isRead"": false, + // ""isDeleted"": false + // } + // ]"; + // mockPlugin.InboxMessages().Returns(expectedMessagesAsJson); + + // var expectedMessagesAsList = new List() + // { + // new InboxMessage( + // id: @"ahpPUBnNEemxogJC_nHsWw", + // title: "Title of the message", + // sentDate: 1547670565000, + // isRead: true, + // isDeleted: false, + // extras: new Dictionary() + // { + // { "com.urbanairship.listing.field1", "" }, + // { "com.urbanairship.listing.field2", "" }, + // { "com.urbanairship.listing.template", "text" } + // } + // ), + // new InboxMessage( + // id: @"anidentifier", + // title: "Title", + // sentDate: 10, + // isRead: false, + // isDeleted: true, + // extras: null + // ), + // new InboxMessage( + // id: @"anotheridentifier", + // title: "Another Title", + // sentDate: 200, + // isRead: false, + // isDeleted: false, + // extras: null + // ) + // }; + + // Assert.AreEqual(expectedMessagesAsList, sharedAirship.InboxMessages()); + // } + + // [Test] + // public void TestMarkInboxMessageRead () + // { + // string messageId = "AMessageId"; + // sharedAirship.MarkInboxMessageRead(messageId); + // mockPlugin.Received().MarkInboxMessageRead(messageId); + // } + + // [Test] + // public void TestDeleteInboxMessage () + // { + // string messageId = "AMessageId"; + // sharedAirship.DeleteInboxMessage(messageId); + // mockPlugin.Received().DeleteInboxMessage(messageId); + // } + + // [Test] + // public void TestDisplayMessageCenter () + // { + // sharedAirship.DisplayMessageCenter(); + // mockPlugin.Received().DisplayMessageCenter(); + // } + + // [Test] + // public void TestDisplayInboxMessage () + // { + // string messageId = "AMessageId"; + + // sharedAirship.DisplayInboxMessage(messageId); + // mockPlugin.Received().DisplayInboxMessage(messageId); + // } + + // [Test] + // public void TestRefreshInbox () + // { + // sharedAirship.RefreshInbox(); + // mockPlugin.Received().RefreshInbox(); + // } + + // [Test] + // public void TestSetAutoLaunchDefaultMessageCenter () + // { + // sharedAirship.SetAutoLaunchDefaultMessageCenter(true); + // mockPlugin.Received().SetAutoLaunchDefaultMessageCenter(true); + + // mockPlugin.ClearReceivedCalls(); + + // sharedAirship.SetAutoLaunchDefaultMessageCenter(false); + // mockPlugin.Received().SetAutoLaunchDefaultMessageCenter(false); + // } + + // [Test] + // public void TestOnInboxUpdated () + // { + // var wasCalled = false; + // sharedAirship.OnInboxUpdated += (messageUnreadCount, messageCount) => + // { + // Assert.AreEqual(messageUnreadCount, 1); + // Assert.AreEqual(messageCount, 2); + // wasCalled = true; + // }; + + // var counts = @"{ ""unread"": 1, ""total"": 2 }"; + // UAirship.UrbanAirshipListener listener = sharedAirship.gameObject.GetComponent() as UAirship.UrbanAirshipListener; + // listener.OnInboxUpdated(counts); + + // Assert.True(wasCalled); + // } + + // [Test] + // public void TestOnShowInboxSpecificMessage () + // { + // var wasCalled = false; + // sharedAirship.OnShowInbox += (theMessageId) => + // { + // Assert.AreEqual(theMessageId, "AMessageId"); + // wasCalled = true; + // }; + + // var messageId = "AMessageId"; + // UAirship.UrbanAirshipListener listener = sharedAirship.gameObject.GetComponent() as UAirship.UrbanAirshipListener; + // listener.OnShowInbox(messageId); + + // Assert.True(wasCalled); + // } + + // [Test] + // public void TestOnShowInboxAllMessages () + // { + // var wasCalled = false; + // sharedAirship.OnShowInbox += (theMessageId) => + // { + // Assert.IsNull(theMessageId); + // wasCalled = true; + // }; + + // string messageId = ""; + // UAirship.UrbanAirshipListener listener = sharedAirship.gameObject.GetComponent() as UAirship.UrbanAirshipListener; + // listener.OnShowInbox(messageId); + + // Assert.True(wasCalled); + // } } } From b12e1637408eb14af9b32a70f2fbb9fee1f2d688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gibern=C3=A9?= Date: Wed, 24 Sep 2025 17:11:50 -0400 Subject: [PATCH 04/27] wip --- Assets/Plugins/iOS/UnityPlugin.swift | 333 +++++++++++++++++- Assets/UrbanAirship/Platforms/AirshipPush.cs | 4 + .../UrbanAirship/Platforms/IAirshipPlugin.cs | 4 +- README.md | 4 +- .../urbanairship/unityplugin/UnityPlugin.kt | 4 + 5 files changed, 338 insertions(+), 11 deletions(-) diff --git a/Assets/Plugins/iOS/UnityPlugin.swift b/Assets/Plugins/iOS/UnityPlugin.swift index 464b5442..01cf19bf 100644 --- a/Assets/Plugins/iOS/UnityPlugin.swift +++ b/Assets/Plugins/iOS/UnityPlugin.swift @@ -3,16 +3,18 @@ import SwiftUI import AirshipFrameworkProxy @_cdecl("UnityPlugin_call") -public func UnityPlugin_call(_ method: String, args: String) async throws -> (any Sendable)? { - // TODO Check the method name and call the appropriate method. - print("Hello Swift seems to work like that") +public func UnityPlugin_call(_ method: String, args: Any...) async throws -> (any Sendable)? { + + AirshipLogger.debug("UnityPlugin \(method): \(args.first)") switch method { + case "setListener": + let listener = requireAnyString(args.first) + // TODO + // Airship case "takeOff": - return try AirshipProxy.shared.takeOff( - // TODO decode the arg - ) + return try AirshipProxy.shared.takeOff(json: requireAnyArg(args.first)) case "isFlying": return AirshipProxy.shared.isFlying() @@ -25,83 +27,400 @@ public func UnityPlugin_call(_ method: String, args: String) async throws -> (an return try await AirshipProxy.shared.channel.waitForChannelID() case "addTag": + try AirshipProxy.shared.channel.addTags(requireStringArg(args.first)) + return nil case "removeTag": + try AirshipProxy.shared.channel.removeTags(requireStringArg(args.first)) + return nil case "getTags": return try AirshipProxy.shared.channel.tags case "editTags": + try AirshipProxy.shared.channel.editTags( + operations: try requireCodableArg(args.first) + ) + return nil case "editChannelTagGroups": + try AirshipProxy.shared.channel.editTagGroups( + operations: try requireCodableArg(args.first) + ) + return nil case "editChannelAttributes": + try AirshipProxy.shared.channel.editAttributes( + operations: try requireCodableArg(args.first) + ) + return nil case "getChannelSubscriptionLists": return try await AirshipProxy.shared.channel.fetchSubscriptionLists() case "editChannelSubscriptionLists": - + try AirshipProxy.shared.channel.editSubscriptionLists( + json: try requireAnyArg(args.first) + ) + return nil // Contact case "identify": + try AirshipProxy.shared.contact.identify(try requireStringArg(args.first)) + return nil + case "reset": + try AirshipProxy.shared.contact.reset() + return nil + case "getNamedUserId": + return try await AirshipProxy.shared.contact.namedUserID + case "notifyRemoteLogin": + try AirshipProxy.shared.contact.notifyRemoteLogin() + return nil + case "editContactTagGroups": + try AirshipProxy.shared.contact.editTagGroups( + operations: try requireCodableArg(args.first) + ) + return nil + case "editContactAttributes": + try AirshipProxy.shared.contact.editAttributes( + operations: try requireCodableArg(args.first) + ) + return nil + case "getContactSubscriptionLists": + return try await AirshipProxy.shared.contact.getSubscriptionLists() + case "editContactSubscriptionLists": + try AirshipProxy.shared.contact.editSubscriptionLists( + operations: try requireCodableArg(args.first) + ) + return nil // Analytics case "associateIdentifier": + guard args.count == 1 || args.count == 2 else { + throw AirshipErrors.error("associateIdentifier call requires 1 to 2 strings parameters.") + } + try AirshipProxy.shared.analytics.associateIdentifier( + identifier: args.count == 2 ? args[1] : nil, + key: args[0] + ) + return nil + case "trackScreen": + try AirshipProxy.shared.analytics.trackScreen( + try? requireStringArg(args.first) + ) + return nil + case "addCustomEvent": + try AirshipProxy.shared.analytics.addEvent( + requireAnyArg(args.first) + ) + return nil + case "getSessionId": + return try AirshipProxy.shared.analytics.getSessionID() // InApp case "setPaused": + try AirshipProxy.shared.inApp.setPaused(try requireBooleanArg(args.first)) + return nil + case "isPaused": + return try AirshipProxy.shared.inApp.isPaused() + case "setDisplayInterval": + try AirshipProxy.shared.inApp.setDisplayInterval( + milliseconds: try requireIntArg(args.first) + ) + return nil + case "getDisplayInterval": + return try AirshipProxy.shared.inApp.getDisplayInterval() // Locale case "setLocaleOverride": + try AirshipProxy.shared.locale.setCurrentLocale( + try requireStringArg(args.first) + ) + return nil + case "clearLocaleOverride": + try AirshipProxy.shared.locale.clearLocale() + return nil + case "getLocale": + return try AirshipProxy.shared.locale.currentLocale // Message Center case "getUnreadCount": + return try await AirshipProxy.shared.messageCenter.unreadCount + case "getMessages": + return try await AirshipProxy.shared.messageCenter.messages + case "markMessageRead": + try await AirshipProxy.shared.messageCenter.markMessageRead( + messageID: requireStringArg(args.first) + ) + return nil + case "deleteMessage": + try await AirshipProxy.shared.messageCenter.deleteMessage( + messageID: requireStringArg(args.first) + ) + return nil + case "refreshMessages": + try await AirshipProxy.shared.messageCenter.refresh() + return nil + case "setAutoLaunchDefaultMessageCenter": + AirshipProxy.shared.messageCenter.setAutoLaunchDefaultMessageCenter( + try requireBooleanArg(args.first) + ) + return nil + case "displayMessageCenter": + try AirshipProxy.shared.messageCenter.display( + messageID: try? requireStringArg(args.first) + ) + return nil + case "dismissMessageCenter": + try AirshipProxy.shared.messageCenter.dismiss() + return nil + case "showMessageView": + try AirshipProxy.shared.messageCenter.showMessageView( + messageID: try requireStringArg(args.first) + ) + return nil + case "showMessageCenter": + try AirshipProxy.shared.messageCenter.showMessageCenter( + messageID: try? requireStringArg(args.first) + ) + return nil // Preference Center case "displayPreferenceCenter": + try AirshipProxy.shared.preferenceCenter.displayPreferenceCenter( + preferenceCenterID: try requireStringArg(args.first) + ) + return nil + case "getPreferenceCenterConfig": + return try await AirshipProxy.shared.preferenceCenter.getPreferenceCenterConfig( + preferenceCenterID: try requireStringArg(args.first) + ) + case "setAutoLaunchDefaultPreferenceCenter": + guard + args.count == 2, + let identifier: String = args[0] as? String, + let autoLaunch: Bool = args[1] as? Bool + else { + throw AirshipErrors.error("setAutoLaunchDefaultPreferenceCenter call requires [String, Bool]") + } + + AirshipProxy.shared.preferenceCenter.setAutoLaunchPreferenceCenter( + autoLaunch, + preferenceCenterID: identifier + ) + return nil // Privacy Manager case "setEnabledFeatures": + try AirshipProxy.shared.privacyManager.setEnabled( + featureNames: try requireStringArrayArg(args.first) + ) + return nil + case "getEnabledFeatures": + return try AirshipProxy.shared.privacyManager.getEnabledNames() + case "enableFeatures": + try AirshipProxy.shared.privacyManager.enable( + featureNames: try requireStringArrayArg(args.first) + ) + return nil + case "disableFeatures": + try AirshipProxy.shared.privacyManager.disable( + featureNames: try requireStringArrayArg(args.first) + ) + return nil + case "isFeaturesEnabled": + return try AirshipProxy.shared.privacyManager.isEnabled( + featuresNames: try requireStringArrayArg(args.first) + ) // Push case "isUserNotificationsEnabled": + return try AirshipProxy.shared.push.isUserNotificationsEnabled() + case "setUserNotificationsEnabled": + try AirshipProxy.shared.push.setUserNotificationsEnabled( + try requireBooleanArg(args.first) + ) + return nil + case "enableUserNotifications": + return try await AirshipProxy.shared.push.enableUserPushNotifications( + args: try optionalCodableArg(args.first) + ) + case "getNotificationStatus": + return try await AirshipProxy.shared.push.notificationStatus + case "getPushToken": + return try AirshipProxy.shared.push.getRegistrationToken() + case "getActiveNotifications": + return try await AirshipProxy.shared.push.getActiveNotifications() + case "clearNotifications": + AirshipProxy.shared.push.clearNotifications() + return nil + case "clearNotification": + AirshipProxy.shared.push.clearNotification( + try requireStringArg(args.first) + ) + return nil + + case "setForegroundPresentationOptions": + try AirshipProxy.shared.push.setForegroundPresentationOptions( + names: try requireStringArrayArg(args.first) + ) + return nil + + case "setNotificationOptions": + try AirshipProxy.shared.push.setNotificationOptions( + names: try requireStringArrayArg(args.first) + ) + return nil + + case "isAutobadgeEnabled": + return try AirshipProxy.shared.push.isAutobadgeEnabled() + + case "setAutobadgeEnabled": + try AirshipProxy.shared.push.setAutobadgeEnabled( + try requireBooleanArg(args.first) + ) + return nil + + case "setBadgeNumber": + try await AirshipProxy.shared.push.setBadgeNumber( + try requireIntArg(args.first) + ) + return nil + + case "getBadgeNumber": + return try AirshipProxy.shared.push.getBadgeNumber() + + case "setQuietTimeEnabled": + try AirshipProxy.shared.push.setQuietTimeEnabled( + try requireBooleanArg(args.first) + ) + return nil + + case "isQuietTimeEnabled": + return try AirshipProxy.shared.push.isQuietTimeEnabled() + + case "setQuietTime": + try AirshipProxy.shared.push.setQuietTime( + try requireCodableArg(args.first) + ) + return nil + + case "getQuietTime": + return try AirshipJSON.wrap(try AirshipProxy.shared.push.getQuietTime()) + } +} + +private func requireAnyArg(_ arg: Any) throws -> Any { + guard let value: Any = arg else { + throw AirshipErrors.error("Argument must not be null") + } + return value +} + +private func requireStringArg(_ arg: Any) throws -> String { + guard let value: String = arg as? String else { + throw AirshipErrors.error("Argument must be a string") + } + return value +} + +private func requireBoolArg(_ arg: Any) throws -> Bool { + guard let value: Bool = arg as? Bool else { + throw AirshipErrors.error("Argument must be a bool") + } + return value +} + +private func requireIntArg(_ arg: Any) throws -> Int { + let value = try requireAnyArg() + + if let int = value as? Int { + return int + } + + if let double = value as? Double { + return Int(double) + } + + if let number = value as? NSNumber { + return number.intValue + } + + throw AirshipErrors.error("Argument must be an int") +} + +private func requireDoubleArg(_ arg: Any) throws -> Double { + let value = try requireAnyArg() + + if let double = value as? Double { + return double + } + + if let int = value as? Int { + return Double(int) + } + + if let number = value as? NSNumber { + return number.doubleValue + } + + throw AirshipErrors.error("Argument must be a double") +} + +private func requireCodableArg(_ arg: Any) throws -> T { + guard let value: Any = arg else { + throw AirshipErrors.error("Missing argument") + } + return try AirshipJSON.wrap(value).decode() +} + +private func optionalCodableArg(_ arg: Any) throws -> T? { + guard let value: Any = arg else { + return nil + } + return try AirshipJSON.wrap(value).decode() +} + +private func requireStringArrayArg(_ arg: Any) throws -> [String] { + guard let value: [String] = arg as? [String] else { + throw AirshipErrors.error("Argument must be a string array") } + return value } \ No newline at end of file diff --git a/Assets/UrbanAirship/Platforms/AirshipPush.cs b/Assets/UrbanAirship/Platforms/AirshipPush.cs index 9c7ddd98..5ff2eb93 100644 --- a/Assets/UrbanAirship/Platforms/AirshipPush.cs +++ b/Assets/UrbanAirship/Platforms/AirshipPush.cs @@ -229,6 +229,8 @@ public void SetQuietTime(QuietTime quietTime) { return plugin.Call("getQuietTime"); } + + // TODO Just noticed I forgot some methods, I need to add that } /// @@ -270,6 +272,8 @@ public void SetForegroundNotificationsEnabled(bool enabled) { plugin.Call("setForegroundNotificationsEnabled", enabled); } + + // TODO Just noticed I forgot isForegroundNotificationsEnabled method, I need to add that } /// diff --git a/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs b/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs index 20b9b71f..13dc96b6 100644 --- a/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs +++ b/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs @@ -71,12 +71,12 @@ internal class AirshipPluginiOS : IAirshipPlugin{ private static extern void UnityPlugin_call (string method, string args); public void Call (string method, params object[] args) { - UnityPlugin_call(method, JsonUtility.ToJson(args)); + UnityPlugin_call(method, args); } public T Call (string method, params object[] args) { - return default(T); + return UnityPlugin_call(method, args); } public GameObject Listener { diff --git a/README.md b/README.md index 6231674f..7360a215 100644 --- a/README.md +++ b/README.md @@ -40,8 +40,8 @@ Download google-services.json into the `Assets` directory from the application's If proguard is enabled, add Airship settings to the proguard-user.txt file. ``` --keep public class com.urbanairship.unityplugin.OldUnityPlugin --keepclassmembers class com.urbanairship.unityplugin.OldUnityPlugin { +-keep public class com.urbanairship.unityplugin.UnityPlugin +-keepclassmembers class com.urbanairship.unityplugin.UnityPlugin { public ; public ; static ; diff --git a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt index 245683c2..99718914 100644 --- a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt +++ b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt @@ -21,6 +21,8 @@ class UnityPlugin { this.listener = listener } + // Airship + fun takeOff(config: String): Boolean { ProxyLogger.debug("UnityPlugin takeOff: $config") return airshipProxyInstance.takeOff(JsonValue.parseString(config)) @@ -376,6 +378,8 @@ class UnityPlugin { airshipProxyInstance.push.clearNotification(identifier) } + // TODO Just noticed I forgot to implement the android specific push methods, I need to add that + // TODO finish the implementation companion object { From 69ef67e2a4aae21f3eb935f70a85799465e3cf7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gibern=C3=A9?= Date: Thu, 2 Oct 2025 12:01:55 -0400 Subject: [PATCH 05/27] wip --- Assets/Plugins/iOS/UnityPlugin.h | 5 + Assets/Plugins/iOS/UnityPlugin.swift | 907 +++++++++++------- .../UrbanAirship/Platforms/IAirshipPlugin.cs | 8 +- .../urbanairship/unityplugin/UnityPlugin.kt | 115 +++ 4 files changed, 666 insertions(+), 369 deletions(-) create mode 100644 Assets/Plugins/iOS/UnityPlugin.h diff --git a/Assets/Plugins/iOS/UnityPlugin.h b/Assets/Plugins/iOS/UnityPlugin.h new file mode 100644 index 00000000..636070f6 --- /dev/null +++ b/Assets/Plugins/iOS/UnityPlugin.h @@ -0,0 +1,5 @@ +/* Copyright Airship and Contributors */ + +#import + +extern void UnitySendMessage(const char *, const char *, const char *); diff --git a/Assets/Plugins/iOS/UnityPlugin.swift b/Assets/Plugins/iOS/UnityPlugin.swift index 01cf19bf..cf2fcadb 100644 --- a/Assets/Plugins/iOS/UnityPlugin.swift +++ b/Assets/Plugins/iOS/UnityPlugin.swift @@ -2,425 +2,596 @@ import Foundation import SwiftUI import AirshipFrameworkProxy -@_cdecl("UnityPlugin_call") -public func UnityPlugin_call(_ method: String, args: Any...) async throws -> (any Sendable)? { - AirshipLogger.debug("UnityPlugin \(method): \(args.first)") +class UnityPlugin: NSObject { - switch method { - case "setListener": - let listener = requireAnyString(args.first) - // TODO + @_cdecl("UnityPlugin_shared") + static let shared = UnityPlugin() - // Airship - case "takeOff": - return try AirshipProxy.shared.takeOff(json: requireAnyArg(args.first)) + public let listener: String + public let storedDeepLink: String - case "isFlying": - return AirshipProxy.shared.isFlying() - - // Channel - case "getChannelId": - return try AirshipProxy.shared.channel.channelID - - case "waitForChannelId": - return try await AirshipProxy.shared.channel.waitForChannelID() - - case "addTag": - try AirshipProxy.shared.channel.addTags(requireStringArg(args.first)) - return nil - - case "removeTag": - try AirshipProxy.shared.channel.removeTags(requireStringArg(args.first)) - return nil - - case "getTags": - return try AirshipProxy.shared.channel.tags - - case "editTags": - try AirshipProxy.shared.channel.editTags( - operations: try requireCodableArg(args.first) - ) - return nil - - case "editChannelTagGroups": - try AirshipProxy.shared.channel.editTagGroups( - operations: try requireCodableArg(args.first) - ) - return nil - - case "editChannelAttributes": - try AirshipProxy.shared.channel.editAttributes( - operations: try requireCodableArg(args.first) - ) - return nil - - case "getChannelSubscriptionLists": - return try await AirshipProxy.shared.channel.fetchSubscriptionLists() - - case "editChannelSubscriptionLists": - try AirshipProxy.shared.channel.editSubscriptionLists( - json: try requireAnyArg(args.first) - ) - return nil - - // Contact - case "identify": - try AirshipProxy.shared.contact.identify(try requireStringArg(args.first)) - return nil - - case "reset": - try AirshipProxy.shared.contact.reset() - return nil - - case "getNamedUserId": - return try await AirshipProxy.shared.contact.namedUserID - - case "notifyRemoteLogin": - try AirshipProxy.shared.contact.notifyRemoteLogin() - return nil - - case "editContactTagGroups": - try AirshipProxy.shared.contact.editTagGroups( - operations: try requireCodableArg(args.first) - ) - return nil - - case "editContactAttributes": - try AirshipProxy.shared.contact.editAttributes( - operations: try requireCodableArg(args.first) - ) - return nil - - case "getContactSubscriptionLists": - return try await AirshipProxy.shared.contact.getSubscriptionLists() - - case "editContactSubscriptionLists": - try AirshipProxy.shared.contact.editSubscriptionLists( - operations: try requireCodableArg(args.first) - ) - return nil - - // Analytics - case "associateIdentifier": - guard args.count == 1 || args.count == 2 else { - throw AirshipErrors.error("associateIdentifier call requires 1 to 2 strings parameters.") - } - try AirshipProxy.shared.analytics.associateIdentifier( - identifier: args.count == 2 ? args[1] : nil, - key: args[0] - ) - return nil - - case "trackScreen": - try AirshipProxy.shared.analytics.trackScreen( - try? requireStringArg(args.first) - ) - return nil - - case "addCustomEvent": - try AirshipProxy.shared.analytics.addEvent( - requireAnyArg(args.first) - ) - return nil - - case "getSessionId": - return try AirshipProxy.shared.analytics.getSessionID() - - // InApp - case "setPaused": - try AirshipProxy.shared.inApp.setPaused(try requireBooleanArg(args.first)) - return nil - - case "isPaused": - return try AirshipProxy.shared.inApp.isPaused() - - case "setDisplayInterval": - try AirshipProxy.shared.inApp.setDisplayInterval( - milliseconds: try requireIntArg(args.first) - ) - return nil - - case "getDisplayInterval": - return try AirshipProxy.shared.inApp.getDisplayInterval() - - // Locale - case "setLocaleOverride": - try AirshipProxy.shared.locale.setCurrentLocale( - try requireStringArg(args.first) - ) - return nil - - case "clearLocaleOverride": - try AirshipProxy.shared.locale.clearLocale() - return nil - - case "getLocale": - return try AirshipProxy.shared.locale.currentLocale - - // Message Center - case "getUnreadCount": - return try await AirshipProxy.shared.messageCenter.unreadCount - - case "getMessages": - return try await AirshipProxy.shared.messageCenter.messages - - case "markMessageRead": - try await AirshipProxy.shared.messageCenter.markMessageRead( - messageID: requireStringArg(args.first) - ) - return nil - - case "deleteMessage": - try await AirshipProxy.shared.messageCenter.deleteMessage( - messageID: requireStringArg(args.first) - ) - return nil - - case "refreshMessages": - try await AirshipProxy.shared.messageCenter.refresh() - return nil - - case "setAutoLaunchDefaultMessageCenter": - AirshipProxy.shared.messageCenter.setAutoLaunchDefaultMessageCenter( - try requireBooleanArg(args.first) - ) - return nil + private override init() { + super.init() + } - case "displayMessageCenter": - try AirshipProxy.shared.messageCenter.display( - messageID: try? requireStringArg(args.first) - ) - return nil + private static let _ = { + AirshipLogger.debug("UnityPlugin class loaded") + + // Add Notification Observer + NotificationCenter.default.addObserver(forName: UIApplication.didFinishLaunchingNotification, + object: nil, + queue: nil) { notification in + // TODO let's see how we handle take off + // UnityPlugin.performTakeOff(withLaunchOptions: notification.userInfo) + } + }() + + @_cdecl("UnityPlugin_call") + public func UnityPlugin_call(_ method: String, args: Any...) async throws -> (any Sendable)? { + + AirshipLogger.debug("UnityPlugin \(method): \(args.first)") + + // TODO check how to handle the class attributes called in the static method + // let instance = shared + + switch method { + case "setListener": + // shared.listener = requireAnyString(args.first) + listener = requireAnyString(args.first) + return nil + + case "getDeepLink": + let deepLink = convertToJson(storedDeepLink) + if (requireBoolArg(args.first)) { + storedDeepLink = nil + } + return deepLink + + // Airship + case "takeOff": + return try AirshipProxy.shared.takeOff(json: requireAnyArg(args.first)) + + case "isFlying": + return AirshipProxy.shared.isFlying() + + // Channel + case "getChannelId": + return try AirshipProxy.shared.channel.channelID + + case "waitForChannelId": + return try await AirshipProxy.shared.channel.waitForChannelID() + + case "addTag": + try AirshipProxy.shared.channel.addTags(requireStringArg(args.first)) + return nil + + case "removeTag": + try AirshipProxy.shared.channel.removeTags(requireStringArg(args.first)) + return nil + + case "getTags": + return try AirshipProxy.shared.channel.tags + + case "editTags": + try AirshipProxy.shared.channel.editTags( + operations: try requireCodableArg(args.first) + ) + return nil + + case "editChannelTagGroups": + try AirshipProxy.shared.channel.editTagGroups( + operations: try requireCodableArg(args.first) + ) + return nil + + case "editChannelAttributes": + try AirshipProxy.shared.channel.editAttributes( + operations: try requireCodableArg(args.first) + ) + return nil + + case "getChannelSubscriptionLists": + return try await AirshipProxy.shared.channel.fetchSubscriptionLists() + + case "editChannelSubscriptionLists": + try AirshipProxy.shared.channel.editSubscriptionLists( + json: try requireAnyArg(args.first) + ) + return nil + + // Contact + case "identify": + try AirshipProxy.shared.contact.identify(try requireStringArg(args.first)) + return nil + + case "reset": + try AirshipProxy.shared.contact.reset() + return nil + + case "getNamedUserId": + return try await AirshipProxy.shared.contact.namedUserID + + case "notifyRemoteLogin": + try AirshipProxy.shared.contact.notifyRemoteLogin() + return nil + + case "editContactTagGroups": + try AirshipProxy.shared.contact.editTagGroups( + operations: try requireCodableArg(args.first) + ) + return nil + + case "editContactAttributes": + try AirshipProxy.shared.contact.editAttributes( + operations: try requireCodableArg(args.first) + ) + return nil + + case "getContactSubscriptionLists": + return try await AirshipProxy.shared.contact.getSubscriptionLists() + + case "editContactSubscriptionLists": + try AirshipProxy.shared.contact.editSubscriptionLists( + operations: try requireCodableArg(args.first) + ) + return nil + + // Analytics + case "associateIdentifier": + guard args.count == 1 || args.count == 2 else { + throw AirshipErrors.error("associateIdentifier call requires 1 to 2 strings parameters.") + } + try AirshipProxy.shared.analytics.associateIdentifier( + identifier: args.count == 2 ? args[1] : nil, + key: args[0] + ) + return nil + + case "trackScreen": + try AirshipProxy.shared.analytics.trackScreen( + try? requireStringArg(args.first) + ) + return nil + + case "addCustomEvent": + try AirshipProxy.shared.analytics.addEvent( + requireAnyArg(args.first) + ) + return nil + + case "getSessionId": + return try AirshipProxy.shared.analytics.getSessionID() + + // InApp + case "setPaused": + try AirshipProxy.shared.inApp.setPaused(try requireBooleanArg(args.first)) + return nil + + case "isPaused": + return try AirshipProxy.shared.inApp.isPaused() + + case "setDisplayInterval": + try AirshipProxy.shared.inApp.setDisplayInterval( + milliseconds: try requireIntArg(args.first) + ) + return nil + + case "getDisplayInterval": + return try AirshipProxy.shared.inApp.getDisplayInterval() + + // Locale + case "setLocaleOverride": + try AirshipProxy.shared.locale.setCurrentLocale( + try requireStringArg(args.first) + ) + return nil + + case "clearLocaleOverride": + try AirshipProxy.shared.locale.clearLocale() + return nil + + case "getLocale": + return try AirshipProxy.shared.locale.currentLocale + + // Message Center + case "getUnreadCount": + return try await AirshipProxy.shared.messageCenter.unreadCount + + case "getMessages": + return try await AirshipProxy.shared.messageCenter.messages + + case "markMessageRead": + try await AirshipProxy.shared.messageCenter.markMessageRead( + messageID: requireStringArg(args.first) + ) + return nil + + case "deleteMessage": + try await AirshipProxy.shared.messageCenter.deleteMessage( + messageID: requireStringArg(args.first) + ) + return nil + + case "refreshMessages": + try await AirshipProxy.shared.messageCenter.refresh() + return nil + + case "setAutoLaunchDefaultMessageCenter": + AirshipProxy.shared.messageCenter.setAutoLaunchDefaultMessageCenter( + try requireBooleanArg(args.first) + ) + return nil + + case "displayMessageCenter": + try AirshipProxy.shared.messageCenter.display( + messageID: try? requireStringArg(args.first) + ) + return nil + + case "dismissMessageCenter": + try AirshipProxy.shared.messageCenter.dismiss() + return nil + + case "showMessageView": + try AirshipProxy.shared.messageCenter.showMessageView( + messageID: try requireStringArg(args.first) + ) + return nil + + case "showMessageCenter": + try AirshipProxy.shared.messageCenter.showMessageCenter( + messageID: try? requireStringArg(args.first) + ) + return nil + + // Preference Center + case "displayPreferenceCenter": + try AirshipProxy.shared.preferenceCenter.displayPreferenceCenter( + preferenceCenterID: try requireStringArg(args.first) + ) + return nil + + case "getPreferenceCenterConfig": + return try await AirshipProxy.shared.preferenceCenter.getPreferenceCenterConfig( + preferenceCenterID: try requireStringArg(args.first) + ) + + case "setAutoLaunchDefaultPreferenceCenter": + guard + args.count == 2, + let identifier: String = args[0] as? String, + let autoLaunch: Bool = args[1] as? Bool + else { + throw AirshipErrors.error("setAutoLaunchDefaultPreferenceCenter call requires [String, Bool]") + } + + AirshipProxy.shared.preferenceCenter.setAutoLaunchPreferenceCenter( + autoLaunch, + preferenceCenterID: identifier + ) + return nil + + // Privacy Manager + case "setEnabledFeatures": + try AirshipProxy.shared.privacyManager.setEnabled( + featureNames: try requireStringArrayArg(args.first) + ) + return nil + + case "getEnabledFeatures": + return try AirshipProxy.shared.privacyManager.getEnabledNames() + + case "enableFeatures": + try AirshipProxy.shared.privacyManager.enable( + featureNames: try requireStringArrayArg(args.first) + ) + return nil + + case "disableFeatures": + try AirshipProxy.shared.privacyManager.disable( + featureNames: try requireStringArrayArg(args.first) + ) + return nil + + case "isFeaturesEnabled": + return try AirshipProxy.shared.privacyManager.isEnabled( + featuresNames: try requireStringArrayArg(args.first) + ) + + // Push + case "isUserNotificationsEnabled": + return try AirshipProxy.shared.push.isUserNotificationsEnabled() + + case "setUserNotificationsEnabled": + try AirshipProxy.shared.push.setUserNotificationsEnabled( + try requireBooleanArg(args.first) + ) + return nil + + case "enableUserNotifications": + return try await AirshipProxy.shared.push.enableUserPushNotifications( + args: try optionalCodableArg(args.first) + ) + + case "getNotificationStatus": + return try await AirshipProxy.shared.push.notificationStatus + + case "getPushToken": + return try AirshipProxy.shared.push.getRegistrationToken() + + case "getActiveNotifications": + return try await AirshipProxy.shared.push.getActiveNotifications() + + case "clearNotifications": + AirshipProxy.shared.push.clearNotifications() + return nil + + case "clearNotification": + AirshipProxy.shared.push.clearNotification( + try requireStringArg(args.first) + ) + return nil + + // Push iOS + case "setForegroundPresentationOptions": + try AirshipProxy.shared.push.setForegroundPresentationOptions( + names: try requireStringArrayArg(args.first) + ) + return nil + + case "setNotificationOptions": + try AirshipProxy.shared.push.setNotificationOptions( + names: try requireStringArrayArg(args.first) + ) + return nil + + case "isAutobadgeEnabled": + return try AirshipProxy.shared.push.isAutobadgeEnabled() + + case "setAutobadgeEnabled": + try AirshipProxy.shared.push.setAutobadgeEnabled( + try requireBooleanArg(args.first) + ) + return nil + + case "setBadgeNumber": + try await AirshipProxy.shared.push.setBadgeNumber( + try requireIntArg(args.first) + ) + return nil + + case "getBadgeNumber": + return try AirshipProxy.shared.push.getBadgeNumber() + + case "setQuietTimeEnabled": + try AirshipProxy.shared.push.setQuietTimeEnabled( + try requireBooleanArg(args.first) + ) + return nil + + case "isQuietTimeEnabled": + return try AirshipProxy.shared.push.isQuietTimeEnabled() + + case "setQuietTime": + try AirshipProxy.shared.push.setQuietTime( + try requireCodableArg(args.first) + ) + return nil + + case "getQuietTime": + return try AirshipJSON.wrap(try AirshipProxy.shared.push.getQuietTime()) + } + } - case "dismissMessageCenter": - try AirshipProxy.shared.messageCenter.dismiss() - return nil + // Push Notification Delegates - case "showMessageView": - try AirshipProxy.shared.messageCenter.showMessageView( - messageID: try requireStringArg(args.first) - ) - return nil + public func receivedForegroundNotification(_ userInfo: [AnyHashable: Any], completionHandler: @escaping () -> Void) { + AirshipLogger.debug("UnityPlugin receivedForegroundNotification \(userInfo)") - case "showMessageCenter": - try AirshipProxy.shared.messageCenter.showMessageCenter( - messageID: try? requireStringArg(args.first) - ) - return nil + if let listener = self.listener { + callUnitySendMessage(listener, "OnPushReceived", convertPushToJson(userInfo)) + completionHandler() + } + } - // Preference Center - case "displayPreferenceCenter": - try AirshipProxy.shared.preferenceCenter.displayPreferenceCenter( - preferenceCenterID: try requireStringArg(args.first) - ) - return nil + public func receivedNotificationResponse(_ notificationResponse: UNNotificationResponse, completionHandler: @escaping () -> Void) { + AirshipLogger.debug("UnityPlugin receivedNotificationResponse \(notificationResponse)") - case "getPreferenceCenterConfig": - return try await AirshipProxy.shared.preferenceCenter.getPreferenceCenterConfig( - preferenceCenterID: try requireStringArg(args.first) - ) - - case "setAutoLaunchDefaultPreferenceCenter": - guard - args.count == 2, - let identifier: String = args[0] as? String, - let autoLaunch: Bool = args[1] as? Bool - else { - throw AirshipErrors.error("setAutoLaunchDefaultPreferenceCenter call requires [String, Bool]") - } + if let listener = self.listener { + callUnitySendMessage(listener, "OnPushOpened", convertPushToJson(notificationResponse.notification.request.content.userInfo)) + completionHandler() + } + } - AirshipProxy.shared.preferenceCenter.setAutoLaunchPreferenceCenter( - autoLaunch, - preferenceCenterID: identifier - ) - return nil + // Airship DeepLink Delegate - // Privacy Manager - case "setEnabledFeatures": - try AirshipProxy.shared.privacyManager.setEnabled( - featureNames: try requireStringArrayArg(args.first) - ) - return nil + public func receivedDeepLink(_ url: URL, completionHandler: @escaping () -> Void) { + AirshipLogger.debug("UnityPlugin receivedDeepLink \(url)") - case "getEnabledFeatures": - return try AirshipProxy.shared.privacyManager.getEnabledNames() - - case "enableFeatures": - try AirshipProxy.shared.privacyManager.enable( - featureNames: try requireStringArrayArg(args.first) - ) - return nil - - case "disableFeatures": - try AirshipProxy.shared.privacyManager.disable( - featureNames: try requireStringArrayArg(args.first) - ) - return nil + let deepLinkString = url.absoluteString + self.storedDeepLink = deepLinkString - case "isFeaturesEnabled": - return try AirshipProxy.shared.privacyManager.isEnabled( - featuresNames: try requireStringArrayArg(args.first) - ) + if let listener = self.listener { + callUnitySendMessage(listener, "OnDeepLinkReceived", deepLinkString) + } + completionHandler() + } - // Push - case "isUserNotificationsEnabled": - return try AirshipProxy.shared.push.isUserNotificationsEnabled() + // Channel Registration Events + + public func channelCreated(_ notification: Notification) { + guard let channelID = notification.userInfo?[AirshipChannel.channelIdentifierKey] as? String else { + return + } + AirshipLogger.debug("UnityPlugin channelCreated: \(channelID)") + + if let listener = self.listener { + callUnitySendMessage(listener, "OnChannelUpdated", channelID) + } + } - case "setUserNotificationsEnabled": - try AirshipProxy.shared.push.setUserNotificationsEnabled( - try requireBooleanArg(args.first) - ) - return nil + // Inbox Message List Updated Notification + public func inboxUpdated() { + let unreadCount = try await AirshipProxy.shared.messageCenter.unreadCount + let totalCount = try await AirshipProxy.shared.messageCenter.messages.count - case "enableUserNotifications": - return try await AirshipProxy.shared.push.enableUserPushNotifications( - args: try optionalCodableArg(args.first) - ) + let counts : [String: Any] = [ + "unread": unreadCount, + "total": totalCount + ] - case "getNotificationStatus": - return try await AirshipProxy.shared.push.notificationStatus + AirshipLogger.debug("UnityPlugin inboxUpdated(unread = \(unreadCount), total = \(totalCount))") - case "getPushToken": - return try AirshipProxy.shared.push.getRegistrationToken() + if let listener = self.listener { + callUnitySendMessage(listener, "OnInboxUpdated", convertToJson(counts)) + } + } - case "getActiveNotifications": - return try await AirshipProxy.shared.push.getActiveNotifications() + // TODO Message Center Display Delegates - case "clearNotifications": - AirshipProxy.shared.push.clearNotifications() - return nil + // TODO Implement the rest of the delegates (PC and AuthorizedSettings) - case "clearNotification": - AirshipProxy.shared.push.clearNotification( - try requireStringArg(args.first) - ) - return nil + private func requireAnyArg(_ arg: Any) throws -> Any { + guard let value: Any = arg else { + throw AirshipErrors.error("Argument must not be null") + } + return value + } - case "setForegroundPresentationOptions": - try AirshipProxy.shared.push.setForegroundPresentationOptions( - names: try requireStringArrayArg(args.first) - ) - return nil + private func requireStringArg(_ arg: Any) throws -> String { + guard let value: String = arg as? String else { + throw AirshipErrors.error("Argument must be a string") + } + return value + } - case "setNotificationOptions": - try AirshipProxy.shared.push.setNotificationOptions( - names: try requireStringArrayArg(args.first) - ) - return nil + private func requireBoolArg(_ arg: Any) throws -> Bool { + guard let value: Bool = arg as? Bool else { + throw AirshipErrors.error("Argument must be a bool") + } + return value + } - case "isAutobadgeEnabled": - return try AirshipProxy.shared.push.isAutobadgeEnabled() + private func requireIntArg(_ arg: Any) throws -> Int { + let value = try requireAnyArg() - case "setAutobadgeEnabled": - try AirshipProxy.shared.push.setAutobadgeEnabled( - try requireBooleanArg(args.first) - ) - return nil + if let int = value as? Int { + return int + } - case "setBadgeNumber": - try await AirshipProxy.shared.push.setBadgeNumber( - try requireIntArg(args.first) - ) - return nil + if let double = value as? Double { + return Int(double) + } - case "getBadgeNumber": - return try AirshipProxy.shared.push.getBadgeNumber() + if let number = value as? NSNumber { + return number.intValue + } - case "setQuietTimeEnabled": - try AirshipProxy.shared.push.setQuietTimeEnabled( - try requireBooleanArg(args.first) - ) - return nil + throw AirshipErrors.error("Argument must be an int") + } - case "isQuietTimeEnabled": - return try AirshipProxy.shared.push.isQuietTimeEnabled() + private func requireDoubleArg(_ arg: Any) throws -> Double { + let value = try requireAnyArg() - case "setQuietTime": - try AirshipProxy.shared.push.setQuietTime( - try requireCodableArg(args.first) - ) - return nil + if let double = value as? Double { + return double + } - case "getQuietTime": - return try AirshipJSON.wrap(try AirshipProxy.shared.push.getQuietTime()) - } -} + if let int = value as? Int { + return Double(int) + } -private func requireAnyArg(_ arg: Any) throws -> Any { - guard let value: Any = arg else { - throw AirshipErrors.error("Argument must not be null") - } - return value -} + if let number = value as? NSNumber { + return number.doubleValue + } -private func requireStringArg(_ arg: Any) throws -> String { - guard let value: String = arg as? String else { - throw AirshipErrors.error("Argument must be a string") + throw AirshipErrors.error("Argument must be a double") } - return value -} -private func requireBoolArg(_ arg: Any) throws -> Bool { - guard let value: Bool = arg as? Bool else { - throw AirshipErrors.error("Argument must be a bool") + private func requireCodableArg(_ arg: Any) throws -> T { + guard let value: Any = arg else { + throw AirshipErrors.error("Missing argument") + } + return try AirshipJSON.wrap(value).decode() } - return value -} - -private func requireIntArg(_ arg: Any) throws -> Int { - let value = try requireAnyArg() - if let int = value as? Int { - return int + private func optionalCodableArg(_ arg: Any) throws -> T? { + guard let value: Any = arg else { + return nil + } + return try AirshipJSON.wrap(value).decode() } - if let double = value as? Double { - return Int(double) + private func requireStringArrayArg(_ arg: Any) throws -> [String] { + guard let value: [String] = arg as? [String] else { + throw AirshipErrors.error("Argument must be a string array") + } + return value } - if let number = value as? NSNumber { - return number.intValue + private func callUnitySendMessage(objectName: String, methodName: String, message: String) { + UnitySendMessage(objectName, methodName, message) } - throw AirshipErrors.error("Argument must be an int") -} - -private func requireDoubleArg(_ arg: Any) throws -> Double { - let value = try requireAnyArg() + /// Converts a push notification payload to a JSON string. + /// + /// - Parameter push: The push notification payload. + /// - Returns: A JSON string representation of the push. + private static func convertPushToJson(push: [AnyHashable: Any]) -> String { + let alert = (push["aps"] as? [String: Any])?["alert"] as? String + let identifier = push["_"] as? String - if let double = value as? Double { - return double - } + var extras = [[String: Any]]() - if let int = value as? Int { - return Double(int) - } + for (key, value) in push { + guard let keyString = key as? String else { + continue + } + + // Skip "aps" and "_" keys. + if keyString == "_" || keyString == "aps" { + continue + } - if let number = value as? NSNumber { - return number.doubleValue - } + var extraValue = value + + if !(extraValue is String) { + if let jsonString = convertToJson(extraValue) { + extraValue = jsonString + } else { + continue + } + } + + extras.append(["key": keyString, "value": extraValue]) + } - throw AirshipErrors.error("Argument must be a double") -} + var serializedPayload = [String: Any]() + serializedPayload["alert"] = alert + serializedPayload["identifier"] = identifier -private func requireCodableArg(_ arg: Any) throws -> T { - guard let value: Any = arg else { - throw AirshipErrors.error("Missing argument") - } - return try AirshipJSON.wrap(value).decode() -} + if !extras.isEmpty { + serializedPayload["extras"] = extras + } -private func optionalCodableArg(_ arg: Any) throws -> T? { - guard let value: Any = arg else { - return nil + return convertToJson(serializedPayload) } - return try AirshipJSON.wrap(value).decode() -} -private func requireStringArrayArg(_ arg: Any) throws -> [String] { - guard let value: [String] = arg as? [String] else { - throw AirshipErrors.error("Argument must be a string array") + /// Converts an object to a JSON string. + /// + /// - Parameter obj: The object to be serialized. + /// - Returns: A JSON string representation of the object, or "{}" if serialization fails. + static func convertToJson(_ obj: Any) -> String { + do { + let data = try JSONSerialization.data(withJSONObject: obj, options: []) + if let jsonString = String(data: data, encoding: .utf8) { + return jsonString + } + } catch { + print("Error converting object to JSON: \(error.localizedDescription)") + } + + return "{}" } - return value } \ No newline at end of file diff --git a/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs b/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs index 13dc96b6..1371dcf5 100644 --- a/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs +++ b/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs @@ -67,6 +67,13 @@ public GameObject Listener { internal class AirshipPluginiOS : IAirshipPlugin{ + // [DllImport ("__Internal")] + // private static extern void UnityPlugin_shared (); + + // public AirshipPluginiOS() { + // UnityPlugin_shared(); + // } + [DllImport ("__Internal")] private static extern void UnityPlugin_call (string method, string args); @@ -75,7 +82,6 @@ public void Call (string method, params object[] args) { } public T Call (string method, params object[] args) { - return UnityPlugin_call(method, args); } diff --git a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt index 99718914..a6dc69b8 100644 --- a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt +++ b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt @@ -7,9 +7,13 @@ import com.urbanairship.android.framework.proxy.ProxyLogger import com.urbanairship.android.framework.proxy.proxies.AirshipProxy import com.urbanairship.android.framework.proxy.proxies.EnableUserNotificationsArgs import com.urbanairship.json.JsonException +import com.urbanairship.json.JsonMap import com.urbanairship.json.JsonValue +import com.urbanairship.push.PushMessage +import com.urbanairship.util.UAStringUtil import org.json.JSONArray + class UnityPlugin { private val airshipProxyInstance = AirshipProxy.shared(UnityPlayer.currentActivity.applicationContext) @@ -382,6 +386,117 @@ class UnityPlugin { // TODO finish the implementation + fun onPushReceived(message: PushMessage?) { + ProxyLogger.debug("UnityPlugin push received: $message") + + if (listener != null) { + UnityPlayer.UnitySendMessage(listener, "OnPushReceived", getPushPayload(message)) + } + } + + fun onPushOpened(message: PushMessage?) { + ProxyLogger.debug("UnityPlugin push opened: $message") + + if (listener != null) { + UnityPlayer.UnitySendMessage(listener, "OnPushOpened", getPushPayload(message)) + } + } + + fun onDeepLinkReceived(deepLink: String?): Boolean { + ProxyLogger.debug("UnityPlugin deepLink received: $deepLink") + + if (listener != null) { + UnityPlayer.UnitySendMessage(listener, "OnDeepLinkReceived", deepLink) + return true + } + return false + } + + fun onChannelCreated(channelId: String?) { + ProxyLogger.debug("UnityPlugin channel created: $channelId") + + if (listener != null) { + UnityPlayer.UnitySendMessage(listener, "OnChannelCreated", channelId) + } + } + + fun onShowInbox(messageId: String?) { + if (messageId == null) { + ProxyLogger.debug("UnityPlugin show inbox") + + if (listener != null) { + UnityPlayer.UnitySendMessage(listener, "OnShowInbox", "") + } + } else { + ProxyLogger.debug("UnityPlugin show inbox message: ", messageId) + + if (listener != null) { + UnityPlayer.UnitySendMessage(listener, "OnShowInbox", messageId) + } + } + } + + // TODO Implement the rest of the listeners (PreferenceCenter) + + suspend fun onInboxUpdated() { + val unreadCount = getUnreadCount() + val totalCount = getMessages().count() + val counts: JsonMap = JsonMap.newBuilder() + .put("unread", unreadCount) + .put("total", totalCount) + .build() + ProxyLogger.debug( + "UnityPlugin inboxUpdated (unread = %s, total = %s)", + unreadCount, totalCount + ) + + if (listener != null) { + UnityPlayer.UnitySendMessage(listener, "OnInboxUpdated", counts.toString()) + } + } + + + + private fun getPushPayload(message: PushMessage?): String? { + if (message == null) { + return null + } + + val payloadMap: MutableMap = HashMap() + + val extras: MutableList?> = ArrayList() + + for (key in message.getPushBundle().keySet()) { + val value: String? + if (!UAStringUtil.equals(key, "google.sent_time")) { + value = message.getPushBundle().getString(key) + } else { + continue + } + + if (value == null) { + continue + } + + val extra: MutableMap = HashMap() + extra.put("key", key) + extra.put("value", value) + extras.add(extra) + } + + if (message.alert != null) { + payloadMap.put("alert", message.alert) + } + + if (message.sendId != null) { + payloadMap.put("identifier", message.sendId) + } + + payloadMap.put("extras", extras) + + return JsonValue.wrapOpt(payloadMap).toString() + } + companion object { private val instance = UnityPlugin() From a3d7230355f9bdc881ad446da15d1e7839bf757b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gibern=C3=A9?= Date: Wed, 22 Oct 2025 13:57:05 -0400 Subject: [PATCH 06/27] wip --- Assets/Plugins/iOS/UnityPlugin.swift | 23 +++++++++- Assets/Scripts/UrbanAirshipBehaviour.cs | 33 +++++++------- Assets/UrbanAirship/Editor/UADependencies.xml | 4 +- .../UrbanAirship/Platforms/IAirshipPlugin.cs | 21 +++++++-- .../UrbanAirship/Platforms/StubbedPlugin.cs | 43 ------------------- .../unityplugin/UnityAutopilot.kt | 12 +++--- 6 files changed, 64 insertions(+), 72 deletions(-) delete mode 100644 Assets/UrbanAirship/Platforms/StubbedPlugin.cs diff --git a/Assets/Plugins/iOS/UnityPlugin.swift b/Assets/Plugins/iOS/UnityPlugin.swift index cf2fcadb..9038f56a 100644 --- a/Assets/Plugins/iOS/UnityPlugin.swift +++ b/Assets/Plugins/iOS/UnityPlugin.swift @@ -28,9 +28,28 @@ class UnityPlugin: NSObject { }() @_cdecl("UnityPlugin_call") - public func UnityPlugin_call(_ method: String, args: Any...) async throws -> (any Sendable)? { + public func UnityPlugin_call(_ method: String, argsJson: String) -> UnsafePointer? { + do { + let args = try AirshipJSON.wrap(argsJson).decode() as? [String: [Any]] + } catch { + AirshipLogger.error("Failed to deserialize arguments for method \(method): \(error)") + return strdup("{}") + } + + let result: Any? + + do { + result = try UnityPlugin.shared.handleCall(method: method, args: args) + } catch { + AirshipLogger.error("Error executing method \(method): \(error)") + return strdup("{}") + } + + return strdup(AirshipJSON.wrap(result)) + } - AirshipLogger.debug("UnityPlugin \(method): \(args.first)") + func handleCall(method: String, args: [Any]) throws -> Any? { + AirshipLogger.debug("UnityPlugin \(method): \(args.first?)") // TODO check how to handle the class attributes called in the static method // let instance = shared diff --git a/Assets/Scripts/UrbanAirshipBehaviour.cs b/Assets/Scripts/UrbanAirshipBehaviour.cs index 595ce9ac..277841d1 100644 --- a/Assets/Scripts/UrbanAirshipBehaviour.cs +++ b/Assets/Scripts/UrbanAirshipBehaviour.cs @@ -10,7 +10,8 @@ public class UrbanAirshipBehaviour : MonoBehaviour { public string addTagOnStart; void Awake () { - // UAirship.Shared.UserNotificationsEnabled = true; + //Airship.Shared.push.SetUserNotificationsEnabled(true); + //Airship.Shared.push.UserNotificationsEnabled = true; } void Start () { @@ -18,8 +19,8 @@ void Start () { // UAirship.Shared.AddTag (addTagOnStart); // } - // string[] allenable = new string[] { "FEATURE_ALL" }; - // UAirship.Shared.SetEnabledFeatures(allenable); + string[] allenable = new string[] { "FEATURE_ALL" }; + //Airship.Shared.privacyManager.SetEnabledFeatures(allenable); // UAirship.Shared.OnPushReceived += OnPushReceived; // UAirship.Shared.OnChannelUpdated += OnChannelUpdated; @@ -28,23 +29,23 @@ void Start () { // UAirship.Shared.OnInboxUpdated += OnInboxUpdated; // UAirship.Shared.OnShowInbox += OnShowInbox; - // Airship.Shared.analytics.TrackScreen("Main Camera"); + Airship.Shared.analytics.TrackScreen("Main Camera"); - // CustomEvent customEvent = new CustomEvent(); - // customEvent.EventName = "event_name"; - // customEvent.EventValue = 123; - // Airship.Shared.analytics.AddCustomEvent(customEvent); + CustomEvent customEvent = new CustomEvent(); + customEvent.EventName = "event_name"; + customEvent.EventValue = 123; + Airship.Shared.analytics.AddCustomEvent(customEvent); - // UAirship.Shared.RefreshInbox(); + Airship.Shared.messageCenter.RefreshInbox(); - // Airship.Shared.Channel.EditTags().AddTag("ulrich").Apply(); + Airship.Shared.channel.EditTags().AddTag("ulrich").Apply(); - // Airship.Shared.Channel.EditChannelAttributes().SetAttribute("teststring", "a_string").Apply(); - // Airship.Shared.Channel.EditChannelAttributes().SetAttribute("testint", (int) 1).Apply(); - // Airship.Shared.Channel.EditChannelAttributes().SetAttribute("testlong", (long) 1000).Apply(); - // Airship.Shared.Channel.EditChannelAttributes().SetAttribute("testfloat", (float)5.99).Apply(); - // Airship.Shared.Channel.EditChannelAttributes().SetAttribute("testdouble", (double)5555.999).Apply(); - // Airship.Shared.Channel.EditChannelAttributes().SetAttribute("testdate", DateTime.UtcNow).Apply(); + Airship.Shared.channel.EditAttributes().SetAttribute("teststring", "a_string").Apply(); + Airship.Shared.channel.EditAttributes().SetAttribute("testint", (int) 1).Apply(); + Airship.Shared.channel.EditAttributes().SetAttribute("testlong", (long) 1000).Apply(); + Airship.Shared.channel.EditAttributes().SetAttribute("testfloat", (float)5.99).Apply(); + Airship.Shared.channel.EditAttributes().SetAttribute("testdouble", (double)5555.999).Apply(); + Airship.Shared.channel.EditAttributes().SetAttribute("testdate", DateTime.UtcNow).Apply(); } // void OnDestroy () { diff --git a/Assets/UrbanAirship/Editor/UADependencies.xml b/Assets/UrbanAirship/Editor/UADependencies.xml index 1723ce2d..0d3bd5a8 100644 --- a/Assets/UrbanAirship/Editor/UADependencies.xml +++ b/Assets/UrbanAirship/Editor/UADependencies.xml @@ -61,11 +61,11 @@ - + diff --git a/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs b/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs index 1371dcf5..c46313d7 100644 --- a/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs +++ b/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs @@ -75,14 +75,22 @@ internal class AirshipPluginiOS : IAirshipPlugin{ // } [DllImport ("__Internal")] - private static extern void UnityPlugin_call (string method, string args); + private static extern string UnityPlugin_call (string method, string argsJson); public void Call (string method, params object[] args) { - UnityPlugin_call(method, args); + string argsJson = SerializeArgs(args); + UnityPlugin_call(method, argsJson); } public T Call (string method, params object[] args) { - return UnityPlugin_call(method, args); + string argsJson = SerializeArgs(args); + string resultJson = UnityPlugin_call(method, argsJson); + + if (string.IsNullOrEmpty(resultJson) || resultJson == "{}") { + return default(T); + } + + return JsonUtility.FromJson(resultJson); } public GameObject Listener { @@ -90,6 +98,13 @@ public GameObject Listener { Call ("setListener", value.name); } } + + public static string SerializeArgs(params object[] args) { + if (args == null || args.Length == 0) { + return "{}"; // Return empty JSON object if no arguments + } + return JsonUtility.ToJson(args); + } } #endif diff --git a/Assets/UrbanAirship/Platforms/StubbedPlugin.cs b/Assets/UrbanAirship/Platforms/StubbedPlugin.cs deleted file mode 100644 index 54edace8..00000000 --- a/Assets/UrbanAirship/Platforms/StubbedPlugin.cs +++ /dev/null @@ -1,43 +0,0 @@ -/* Copyright Airship and Contributors */ - -using System; -using UnityEngine; - -namespace UrbanAirship { - class StubbedPlugin : IUAirshipPlugin { - public bool UserNotificationsEnabled { get; set; } - public string Tags { get { return null; } } - public string ChannelId { get { return null; } } - public string NamedUserId { get; set; } - public TimeSpan InAppAutomationDisplayInterval { get; set; } - public bool InAppAutomationPaused { get; set; } - public GameObject Listener { set; private get; } - public string GetDeepLink (bool clear) { return null; } - public string GetIncomingPush (bool clear) { return null; } - public void AddTag (string tag) { } - public void RemoveTag (string tag) { } - public void AddCustomEvent (string customEvent) { } - public void TrackScreen (string screenName) { } - public void AssociateIdentifier (string key, string identifier) { } - public void DisplayMessageCenter () { } - public void DisplayInboxMessage (string messageId) { } - public void RefreshInbox () { } - public string InboxMessages () { return null; } - public void MarkInboxMessageRead (string messageId) { } - public void DeleteInboxMessage (string messageId) { } - public void SetAutoLaunchDefaultMessageCenter (bool enabled) { } - public int MessageCenterUnreadCount { get; private set; } - public int MessageCenterCount { get; private set; } - public void EditNamedUserTagGroups (string payload) { } - public void EditChannelTagGroups (string payload) { } - public void EditChannelAttributes (string payload) { } - public void EditNamedUserAttributes (string payload) { } - public void OpenPreferenceCenter (string preferenceCenterId) { } - public void SetEnabledFeatures (string[] enabledFeatures) { } - public void EnableFeatures (string[] enabledFeatures) { } - public void DisableFeatures (string[] disabledFeatures) { } - public bool IsFeatureEnabled (string[] features) { return false; } - public bool IsAnyFeatureEnabled (string[] features) { return false; } - public string[] GetEnabledFeatures () { return null; } - } -} diff --git a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityAutopilot.kt b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityAutopilot.kt index eeee72c7..9a5fb163 100644 --- a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityAutopilot.kt +++ b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityAutopilot.kt @@ -15,23 +15,23 @@ class UnityAutopilot : BaseAutopilot() { } override fun onReady(context: Context, airship: UAirship) { - ProxyLogger.info("UnityAutopilot", "onAirshipReady") airship.analytics.registerSDKExtension(Extension.UNITY, BuildConfig.PLUGIN_VERSION) } - override fun createAirshipConfigOptions(context: Context): AirshipConfigOptions? { + // TODO add the listeners + + override fun createConfigBuilder(context: Context): AirshipConfigOptions.Builder { val resourceId = context.resources.getIdentifier("airship_config", "xml", context.packageName) if (resourceId <= 0) { ProxyLogger.error("airship_config.xml not found. Make sure Urban Airship is configured Window => Urban Airship => Settings.") - return null + return super.createConfigBuilder(context) } - val options = AirshipConfigOptions.Builder() + val builder = AirshipConfigOptions.newBuilder() .applyConfig(context, resourceId) - .build() //ProxyLogger.setLogLevel(options.logLevel) - return options + return builder } } \ No newline at end of file From e56f220e810b006652410ee38e75525736a03185 Mon Sep 17 00:00:00 2001 From: Ulrich GIBERNE Date: Tue, 4 Nov 2025 11:35:07 -0400 Subject: [PATCH 07/27] wip --- Assets/Plugins/iOS/UnityPlugin.swift | 95 +++++++++++++---------- Assets/Plugins/iOS/module.modulemap | 7 ++ Assets/UrbanAirship/Editor/UAPostBuild.cs | 57 ++++++++++++++ Packages/manifest.json | 2 +- ProjectSettings/ProjectSettings.asset | 11 ++- ProjectSettings/ProjectVersion.txt | 4 +- airship.properties | 2 +- 7 files changed, 134 insertions(+), 44 deletions(-) create mode 100644 Assets/Plugins/iOS/module.modulemap diff --git a/Assets/Plugins/iOS/UnityPlugin.swift b/Assets/Plugins/iOS/UnityPlugin.swift index 9038f56a..1774dea6 100644 --- a/Assets/Plugins/iOS/UnityPlugin.swift +++ b/Assets/Plugins/iOS/UnityPlugin.swift @@ -1,15 +1,37 @@ +/* Copyright Airship and Contributors */ + import Foundation import SwiftUI import AirshipFrameworkProxy +import AirshipUnityCBridge + +@_cdecl("UnityPlugin_call") +public func UnityPlugin_call(_ method: String, argsJson: String) -> UnsafePointer? { + do { + let args = try AirshipJSON.wrap(argsJson).decode() as? [String: [Any]] + } catch { + AirshipLogger.error("Failed to deserialize arguments for method \(method): \(error)") + return strdup("{}") + } + + let result: Any? + + do { + result = try UnityPlugin.shared.handleCall(method: method, args: args) + } catch { + AirshipLogger.error("Error executing method \(method): \(error)") + return strdup("{}") + } + return strdup(AirshipJSON.wrap(result)) +} class UnityPlugin: NSObject { - @_cdecl("UnityPlugin_shared") static let shared = UnityPlugin() - public let listener: String - public let storedDeepLink: String + public var listener: String? = nil + public var storedDeepLink: String? = nil private override init() { super.init() @@ -27,27 +49,6 @@ class UnityPlugin: NSObject { } }() - @_cdecl("UnityPlugin_call") - public func UnityPlugin_call(_ method: String, argsJson: String) -> UnsafePointer? { - do { - let args = try AirshipJSON.wrap(argsJson).decode() as? [String: [Any]] - } catch { - AirshipLogger.error("Failed to deserialize arguments for method \(method): \(error)") - return strdup("{}") - } - - let result: Any? - - do { - result = try UnityPlugin.shared.handleCall(method: method, args: args) - } catch { - AirshipLogger.error("Error executing method \(method): \(error)") - return strdup("{}") - } - - return strdup(AirshipJSON.wrap(result)) - } - func handleCall(method: String, args: [Any]) throws -> Any? { AirshipLogger.debug("UnityPlugin \(method): \(args.first?)") @@ -400,6 +401,9 @@ class UnityPlugin: NSObject { case "getQuietTime": return try AirshipJSON.wrap(try AirshipProxy.shared.push.getQuietTime()) + + default: + return nil } } @@ -409,7 +413,10 @@ class UnityPlugin: NSObject { AirshipLogger.debug("UnityPlugin receivedForegroundNotification \(userInfo)") if let listener = self.listener { - callUnitySendMessage(listener, "OnPushReceived", convertPushToJson(userInfo)) + callUnitySendMessage(objectName: listener, + methodName: "OnPushReceived", + message: UnityPlugin.convertPushToJson(push: userInfo) + ) completionHandler() } } @@ -418,7 +425,12 @@ class UnityPlugin: NSObject { AirshipLogger.debug("UnityPlugin receivedNotificationResponse \(notificationResponse)") if let listener = self.listener { - callUnitySendMessage(listener, "OnPushOpened", convertPushToJson(notificationResponse.notification.request.content.userInfo)) + callUnitySendMessage(objectName: listener, + methodName: "OnPushOpened", + message: UnityPlugin.convertPushToJson( + push: notificationResponse.notification.request.content.userInfo + ) + ) completionHandler() } } @@ -432,7 +444,10 @@ class UnityPlugin: NSObject { self.storedDeepLink = deepLinkString if let listener = self.listener { - callUnitySendMessage(listener, "OnDeepLinkReceived", deepLinkString) + callUnitySendMessage(objectName: listener, + methodName: "OnDeepLinkReceived", + message: deepLinkString + ) } completionHandler() } @@ -446,12 +461,15 @@ class UnityPlugin: NSObject { AirshipLogger.debug("UnityPlugin channelCreated: \(channelID)") if let listener = self.listener { - callUnitySendMessage(listener, "OnChannelUpdated", channelID) + callUnitySendMessage(objectName: listener, + methodName: "OnChannelUpdated", + message: channelID + ) } } // Inbox Message List Updated Notification - public func inboxUpdated() { + public func inboxUpdated() async { let unreadCount = try await AirshipProxy.shared.messageCenter.unreadCount let totalCount = try await AirshipProxy.shared.messageCenter.messages.count @@ -463,7 +481,10 @@ class UnityPlugin: NSObject { AirshipLogger.debug("UnityPlugin inboxUpdated(unread = \(unreadCount), total = \(totalCount))") if let listener = self.listener { - callUnitySendMessage(listener, "OnInboxUpdated", convertToJson(counts)) + callUnitySendMessage(objectName: listener, + methodName: "OnInboxUpdated", + message: UnityPlugin.convertToJson(counts) + ) } } @@ -471,7 +492,7 @@ class UnityPlugin: NSObject { // TODO Implement the rest of the delegates (PC and AuthorizedSettings) - private func requireAnyArg(_ arg: Any) throws -> Any { + private func requireAnyArg(_ arg: Any? = nil) throws -> Any { guard let value: Any = arg else { throw AirshipErrors.error("Argument must not be null") } @@ -528,14 +549,14 @@ class UnityPlugin: NSObject { throw AirshipErrors.error("Argument must be a double") } - private func requireCodableArg(_ arg: Any) throws -> T { + private func requireCodableArg(_ arg: Any? = nil) throws -> T { guard let value: Any = arg else { throw AirshipErrors.error("Missing argument") } return try AirshipJSON.wrap(value).decode() } - private func optionalCodableArg(_ arg: Any) throws -> T? { + private func optionalCodableArg(_ arg: Any? = nil) throws -> T? { guard let value: Any = arg else { return nil } @@ -576,11 +597,7 @@ class UnityPlugin: NSObject { var extraValue = value if !(extraValue is String) { - if let jsonString = convertToJson(extraValue) { - extraValue = jsonString - } else { - continue - } + extraValue = convertToJson(extraValue) } extras.append(["key": keyString, "value": extraValue]) @@ -613,4 +630,4 @@ class UnityPlugin: NSObject { return "{}" } -} \ No newline at end of file +} diff --git a/Assets/Plugins/iOS/module.modulemap b/Assets/Plugins/iOS/module.modulemap new file mode 100644 index 00000000..0a0b6fe6 --- /dev/null +++ b/Assets/Plugins/iOS/module.modulemap @@ -0,0 +1,7 @@ +module AirshipUnityCBridge { + // This makes the contents of the header file available to Swift. + header "UnityPlugin.h" + + // This makes the C functions available for linking. + export * +} diff --git a/Assets/UrbanAirship/Editor/UAPostBuild.cs b/Assets/UrbanAirship/Editor/UAPostBuild.cs index 3bb74cb2..06985cf5 100644 --- a/Assets/UrbanAirship/Editor/UAPostBuild.cs +++ b/Assets/UrbanAirship/Editor/UAPostBuild.cs @@ -57,6 +57,24 @@ private static void UpdatePbxProject (string projectPath, string buildPath) { proj.AddFileToBuild (target, airshipGUID); } + + + // string targetGuid = proj.GetUnityMainTargetGuid(); + // string bridgingHeaderPath = "Libraries/Plugins/iOS/UnityPlugin.h"; + + // // 2. Set the 'Objective-C Bridging Header' build property. + // proj.SetBuildProperty(targetGuid, "SWIFT_OBJC_BRIDGING_HEADER", bridgingHeaderPath); + + // // 3. IMPORTANT: Also set the 'Defines Module' property to YES. + // // This is generally required for modern Swift/Objective-C interop. + // proj.SetBuildProperty(targetGuid, "DEFINES_MODULE", "YES"); + + + + // Update the Header Search Paths + // so the Swift compiler finds the module.modulemap file. + UpdateHeaderSearchPaths(buildPath, proj); + File.WriteAllText (projectPath, proj.WriteToString ()); } @@ -69,6 +87,45 @@ private static void UpdateProjectPlist (string plistPath) { rootDict.SetString ("UAUnityPluginVersion", UrbanAirship.PluginInfo.Version); File.WriteAllText (plistPath, plist.WriteToString ()); } + + private static void UpdateHeaderSearchPaths (string buildPath, PBXProject proj) { + // Copy the Airship modulemap into the iOS project + CopyModuleMap (buildPath, proj); + +#if UNITY_2019_3_OR_NEWER + string[] targets = { + proj.GetUnityFrameworkTargetGuid () + }; +#else + // Fallback for older Unity versions + string[] targets = { + proj.TargetGuidByName (PBXProject.GetUnityTargetName ()) + }; +#endif + + // Add the path to HEADER_SEARCH_PATHS for all relevant targets. + // This allows the Swift compiler to find the module.modulemap file. + foreach (string target in targets) { + proj.AddBuildProperty(target, "HEADER_SEARCH_PATHS", "$(SRCROOT)/Libraries/Plugins/iOS"); + // Ensure existing paths are inherited + proj.AddBuildProperty(target, "HEADER_SEARCH_PATHS", "$(inherited)"); + } + } + + private static void CopyModuleMap (string buildPath, PBXProject proj) { + string sourceDir = Path.Combine(Application.dataPath, "Plugins/iOS"); + string destinationDir = Path.Combine(buildPath, "Libraries/Plugins/iOS"); + + if (!Directory.Exists (destinationDir)) { + Directory.CreateDirectory (destinationDir); + } + + // Copy module.modulemap + string mapFileName = "module.modulemap"; + File.Copy(Path.Combine(sourceDir, mapFileName), + Path.Combine(destinationDir, mapFileName), + true); // 'true' overwrites if file exists + } #endif } } diff --git a/Packages/manifest.json b/Packages/manifest.json index 5907a3ae..d26afc17 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -1,7 +1,7 @@ { "dependencies": { "com.unity.multiplayer.center": "1.0.0", - "com.unity.test-framework": "1.4.5", + "com.unity.test-framework": "1.6.0", "com.unity.modules.accessibility": "1.0.0", "com.unity.modules.ai": "1.0.0", "com.unity.modules.androidjni": "1.0.0", diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index 3a61363a..e5b4ae91 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -70,6 +70,7 @@ PlayerSettings: androidStartInFullscreen: 1 androidRenderOutsideSafeArea: 1 androidUseSwappy: 1 + androidDisplayOptions: 1 androidBlitType: 0 androidResizeableActivity: 0 androidDefaultWindowWidth: 1920 @@ -86,6 +87,7 @@ PlayerSettings: muteOtherAudioSources: 0 Prepare IOS For Recording: 0 Force IOS Speakers When Recording: 0 + audioSpatialExperience: 0 deferSystemGesturesMode: 0 hideHomeButton: 0 submitAnalytics: 1 @@ -132,6 +134,7 @@ PlayerSettings: switchNVNMaxPublicSamplerIDCount: 0 switchMaxWorkerMultiple: 8 switchNVNGraphicsFirmwareMemory: 32 + switchGraphicsJobsSyncAfterKick: 1 vulkanNumSwapchainBuffers: 3 vulkanEnableSetSRGBWrite: 0 vulkanEnablePreTransform: 0 @@ -272,6 +275,9 @@ PlayerSettings: AndroidBuildApkPerCpuArchitecture: 0 AndroidTVCompatibility: 0 AndroidIsGame: 1 + androidAppCategory: 3 + useAndroidAppCategory: 1 + androidAppCategoryOther: AndroidEnableTango: 0 androidEnableBanner: 1 androidUseLowAccuracyLocation: 0 @@ -496,6 +502,7 @@ PlayerSettings: iPhone: 1 tvOS: 1 m_BuildTargetGroupLightmapEncodingQuality: [] + m_BuildTargetGroupHDRCubemapEncodingQuality: [] m_BuildTargetGroupLightmapSettings: [] m_BuildTargetGroupLoadStoreDebugModeSettings: [] m_BuildTargetNormalMapEncoding: [] @@ -765,12 +772,12 @@ PlayerSettings: webGLMemoryLinearGrowthStep: 16 webGLMemoryGeometricGrowthStep: 0.2 webGLMemoryGeometricGrowthCap: 96 - webGLEnableWebGPU: 0 webGLPowerPreference: 2 webGLWebAssemblyTable: 0 webGLWebAssemblyBigInt: 0 webGLCloseOnQuit: 0 webWasm2023: 0 + webEnableSubmoduleStrippingCompatibility: 0 scriptingDefineSymbols: {} additionalCompilerArguments: {} platformArchitecture: {} @@ -883,3 +890,5 @@ PlayerSettings: insecureHttpOption: 0 androidVulkanDenyFilterList: [] androidVulkanAllowFilterList: [] + androidVulkanDeviceFilterListAsset: {fileID: 0} + d3d12DeviceFilterListAsset: {fileID: 0} diff --git a/ProjectSettings/ProjectVersion.txt b/ProjectSettings/ProjectVersion.txt index 6539c21f..a9844fe4 100644 --- a/ProjectSettings/ProjectVersion.txt +++ b/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 6000.0.37f1 -m_EditorVersionWithRevision: 6000.0.37f1 (090b7797214c) +m_EditorVersion: 6000.2.8f1 +m_EditorVersionWithRevision: 6000.2.8f1 (c9992ac36c34) diff --git a/airship.properties b/airship.properties index 4e931156..11b9fac2 100644 --- a/airship.properties +++ b/airship.properties @@ -25,4 +25,4 @@ androidMinSdkVersion = 23 # Google Play Services Resolver tag playServicesResolver = v1.2.178 -unityVersion = 6000.0.37f1 +unityVersion = 6000.2.8f1 From 75eac89f652b48073d20a483131ea18c1664a1b0 Mon Sep 17 00:00:00 2001 From: Ulrich GIBERNE Date: Tue, 4 Nov 2025 11:36:07 -0400 Subject: [PATCH 08/27] wip --- Assets/UrbanAirship/Editor/UAPostBuild.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/Assets/UrbanAirship/Editor/UAPostBuild.cs b/Assets/UrbanAirship/Editor/UAPostBuild.cs index 06985cf5..7ce99e72 100644 --- a/Assets/UrbanAirship/Editor/UAPostBuild.cs +++ b/Assets/UrbanAirship/Editor/UAPostBuild.cs @@ -57,20 +57,6 @@ private static void UpdatePbxProject (string projectPath, string buildPath) { proj.AddFileToBuild (target, airshipGUID); } - - - // string targetGuid = proj.GetUnityMainTargetGuid(); - // string bridgingHeaderPath = "Libraries/Plugins/iOS/UnityPlugin.h"; - - // // 2. Set the 'Objective-C Bridging Header' build property. - // proj.SetBuildProperty(targetGuid, "SWIFT_OBJC_BRIDGING_HEADER", bridgingHeaderPath); - - // // 3. IMPORTANT: Also set the 'Defines Module' property to YES. - // // This is generally required for modern Swift/Objective-C interop. - // proj.SetBuildProperty(targetGuid, "DEFINES_MODULE", "YES"); - - - // Update the Header Search Paths // so the Swift compiler finds the module.modulemap file. UpdateHeaderSearchPaths(buildPath, proj); From 06d3c73e0615f5d5a8bbcf79e2040efa0859fe1c Mon Sep 17 00:00:00 2001 From: Ulrich GIBERNE Date: Mon, 10 Nov 2025 12:30:52 -0400 Subject: [PATCH 09/27] wip fix parsing and comment suspend methods --- .../com/urbanairship/unityplugin/UnityPlugin.kt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt index a6dc69b8..98bfe1d7 100644 --- a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt +++ b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt @@ -89,7 +89,7 @@ class UnityPlugin { fun editChannelAttributes(payload: String) { ProxyLogger.debug("UnityPlugin editChannelAttributes: $payload") try { - airshipProxyInstance.channel.editAttributes(JsonValue.parseString(payload)) + airshipProxyInstance.channel.editAttributes(JsonValue.parseString(payload).optMap().opt("values")) } catch (e: JsonException) { ProxyLogger.error("Failed to parse payload", e) } @@ -107,7 +107,7 @@ class UnityPlugin { fun editChannelSubscriptionLists(payload: String) { ProxyLogger.debug("UnityPlugin editChannelSubscriptionLists: $payload") try { - airshipProxyInstance.channel.editSubscriptionLists(JsonValue.parseString(payload)) + airshipProxyInstance.channel.editSubscriptionLists(JsonValue.parseString(payload).optMap().opt("values")) } catch (e: JsonException) { ProxyLogger.error("Failed to parse payload", e) } @@ -138,7 +138,7 @@ class UnityPlugin { fun editContactTagGroups(payload: String) { ProxyLogger.debug("UnityPlugin editContactTagGroups: $payload") try { - airshipProxyInstance.contact.editTagGroups(JsonValue.parseString(payload)) + airshipProxyInstance.contact.editTagGroups(JsonValue.parseString(payload).optMap().opt("values")) } catch (e: JsonException) { ProxyLogger.error("Failed to parse payload", e) } @@ -147,7 +147,7 @@ class UnityPlugin { fun editContactAttributes(payload: String) { ProxyLogger.debug("UnityPlugin editContactAttributes: $payload") try { - airshipProxyInstance.contact.editAttributes(JsonValue.parseString(payload)) + airshipProxyInstance.contact.editAttributes(JsonValue.parseString(payload).optMap().opt("values")) } catch (e: JsonException) { ProxyLogger.error("Failed to parse payload", e) } @@ -167,7 +167,7 @@ class UnityPlugin { fun editContactSubscriptionLists(payload: String) { ProxyLogger.debug("UnityPlugin editContactSubscriptionLists: $payload") try { - airshipProxyInstance.contact.editSubscriptionLists(JsonValue.parseString(payload)) + airshipProxyInstance.contact.editSubscriptionLists(JsonValue.parseString(payload).optMap().opt("values")) } catch (e: JsonException) { ProxyLogger.error("Failed to parse payload", e) } @@ -264,9 +264,10 @@ class UnityPlugin { airshipProxyInstance.messageCenter.deleteMessage(messageId) } - suspend fun refreshMessages() { + fun refreshMessages() { ProxyLogger.debug("UnityPlugin refreshMessages") - airshipProxyInstance.messageCenter.refreshInbox() + // TODO handle suspend fun + //airshipProxyInstance.messageCenter.refreshInbox() } fun setAutoLaunchDefaultMessageCenter(enabled: Boolean) { From 3989757ffb7df4f69978b43ddf0d70e9150e9e9d Mon Sep 17 00:00:00 2001 From: Ulrich GIBERNE Date: Fri, 21 Nov 2025 17:03:05 -0400 Subject: [PATCH 10/27] wip fixing build issues --- Assets/Plugins/iOS/UnityPlugin.swift | 48 +++++++++++++++++----------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/Assets/Plugins/iOS/UnityPlugin.swift b/Assets/Plugins/iOS/UnityPlugin.swift index 1774dea6..932324b1 100644 --- a/Assets/Plugins/iOS/UnityPlugin.swift +++ b/Assets/Plugins/iOS/UnityPlugin.swift @@ -5,13 +5,20 @@ import SwiftUI import AirshipFrameworkProxy import AirshipUnityCBridge +#if canImport(AirshipKit) +import AirshipKit +#elseif canImport(AirshipCore) +import AirshipCore +#endif + @_cdecl("UnityPlugin_call") public func UnityPlugin_call(_ method: String, argsJson: String) -> UnsafePointer? { + let args: [String: [Any]] do { - let args = try AirshipJSON.wrap(argsJson).decode() as? [String: [Any]] + args = try AirshipJSON.wrap(argsJson).decode() as? [String: [Any]] ?? [:] } catch { AirshipLogger.error("Failed to deserialize arguments for method \(method): \(error)") - return strdup("{}") + return UnsafePointer(strdup("{}")) } let result: Any? @@ -20,10 +27,15 @@ public func UnityPlugin_call(_ method: String, argsJson: String) -> UnsafePointe result = try UnityPlugin.shared.handleCall(method: method, args: args) } catch { AirshipLogger.error("Error executing method \(method): \(error)") - return strdup("{}") + return UnsafePointer(strdup("{}")) } - return strdup(AirshipJSON.wrap(result)) + do { + let jsonResult = try AirshipJSON.wrap(result).toString() + return UnsafePointer(strdup(jsonResult)) + } catch { + return UnsafePointer(strdup("{}")) + } } class UnityPlugin: NSObject { @@ -37,7 +49,7 @@ class UnityPlugin: NSObject { super.init() } - private static let _ = { + private static let initializeOnce: Void = { AirshipLogger.debug("UnityPlugin class loaded") // Add Notification Observer @@ -58,7 +70,7 @@ class UnityPlugin: NSObject { switch method { case "setListener": // shared.listener = requireAnyString(args.first) - listener = requireAnyString(args.first) + listener = try requireStringArg(args.first) return nil case "getDeepLink": @@ -185,7 +197,7 @@ class UnityPlugin: NSObject { // InApp case "setPaused": - try AirshipProxy.shared.inApp.setPaused(try requireBooleanArg(args.first)) + try AirshipProxy.shared.inApp.setPaused(try requireBoolArg(args.first)) return nil case "isPaused": @@ -239,7 +251,7 @@ class UnityPlugin: NSObject { case "setAutoLaunchDefaultMessageCenter": AirshipProxy.shared.messageCenter.setAutoLaunchDefaultMessageCenter( - try requireBooleanArg(args.first) + try requireBoolArg(args.first) ) return nil @@ -325,7 +337,7 @@ class UnityPlugin: NSObject { case "setUserNotificationsEnabled": try AirshipProxy.shared.push.setUserNotificationsEnabled( - try requireBooleanArg(args.first) + try requireBoolArg(args.first) ) return nil @@ -371,7 +383,7 @@ class UnityPlugin: NSObject { case "setAutobadgeEnabled": try AirshipProxy.shared.push.setAutobadgeEnabled( - try requireBooleanArg(args.first) + try requireBoolArg(args.first) ) return nil @@ -386,7 +398,7 @@ class UnityPlugin: NSObject { case "setQuietTimeEnabled": try AirshipProxy.shared.push.setQuietTimeEnabled( - try requireBooleanArg(args.first) + try requireBoolArg(args.first) ) return nil @@ -415,7 +427,7 @@ class UnityPlugin: NSObject { if let listener = self.listener { callUnitySendMessage(objectName: listener, methodName: "OnPushReceived", - message: UnityPlugin.convertPushToJson(push: userInfo) + message: convertPushToJson(push: userInfo) ) completionHandler() } @@ -427,7 +439,7 @@ class UnityPlugin: NSObject { if let listener = self.listener { callUnitySendMessage(objectName: listener, methodName: "OnPushOpened", - message: UnityPlugin.convertPushToJson( + message: convertPushToJson( push: notificationResponse.notification.request.content.userInfo ) ) @@ -483,7 +495,7 @@ class UnityPlugin: NSObject { if let listener = self.listener { callUnitySendMessage(objectName: listener, methodName: "OnInboxUpdated", - message: UnityPlugin.convertToJson(counts) + message: convertToJson(counts) ) } } @@ -514,7 +526,7 @@ class UnityPlugin: NSObject { } private func requireIntArg(_ arg: Any) throws -> Int { - let value = try requireAnyArg() + let value = try requireAnyArg(arg) if let int = value as? Int { return int @@ -532,7 +544,7 @@ class UnityPlugin: NSObject { } private func requireDoubleArg(_ arg: Any) throws -> Double { - let value = try requireAnyArg() + let value = try requireAnyArg(arg) if let double = value as? Double { return double @@ -578,7 +590,7 @@ class UnityPlugin: NSObject { /// /// - Parameter push: The push notification payload. /// - Returns: A JSON string representation of the push. - private static func convertPushToJson(push: [AnyHashable: Any]) -> String { + private func convertPushToJson(push: [AnyHashable: Any]) -> String { let alert = (push["aps"] as? [String: Any])?["alert"] as? String let identifier = push["_"] as? String @@ -618,7 +630,7 @@ class UnityPlugin: NSObject { /// /// - Parameter obj: The object to be serialized. /// - Returns: A JSON string representation of the object, or "{}" if serialization fails. - static func convertToJson(_ obj: Any) -> String { + private func convertToJson(_ obj: Any) -> String { do { let data = try JSONSerialization.data(withJSONObject: obj, options: []) if let jsonString = String(data: data, encoding: .utf8) { From c62a372a7a6b3b6a922be1e4c6ae3308a0217069 Mon Sep 17 00:00:00 2001 From: Ulrich GIBERNE Date: Mon, 1 Dec 2025 12:56:56 -0400 Subject: [PATCH 11/27] start handling async methods on the plugin side --- .../UrbanAirship/Platforms/AirshipChannel.cs | 31 +++++-- .../Platforms/AirshipCoroutineHelper.cs | 87 +++++++++++++++++++ .../Platforms/AirshipMessageCenter.cs | 76 ++++++++++++---- .../Platforms/AirshipPreferenceCenter.cs | 16 ++-- Assets/UrbanAirship/Platforms/AirshipPush.cs | 31 +++++-- .../UrbanAirship/Platforms/AttributeEditor.cs | 26 +++--- .../urbanairship/unityplugin/UnityPlugin.kt | 62 +++++++++---- 7 files changed, 260 insertions(+), 69 deletions(-) create mode 100644 Assets/UrbanAirship/Platforms/AirshipCoroutineHelper.cs diff --git a/Assets/UrbanAirship/Platforms/AirshipChannel.cs b/Assets/UrbanAirship/Platforms/AirshipChannel.cs index d11d16fd..cef2e333 100644 --- a/Assets/UrbanAirship/Platforms/AirshipChannel.cs +++ b/Assets/UrbanAirship/Platforms/AirshipChannel.cs @@ -31,13 +31,21 @@ internal AirshipChannel(IAirshipPlugin plugin) } /// - /// Returns the channel ID. If the channel ID is not yet created the function it will wait for it before returning. + /// Waits for the channel ID asynchronously using a coroutine. + /// If the channel ID is not yet created, this method will wait for it before completing. /// After the channel ID is created, this method functions the same as `getChannelId()`. + /// This method does not block Unity's main thread. /// - /// The channel ID. - public string WaitForChannelId() + /// Callback invoked with the channel ID when the operation completes. + /// Optional callback invoked if an error occurs. + /// A coroutine that can be started with StartCoroutine. + public IEnumerator WaitForChannelId(Action onComplete, Action onError = null) { - return plugin.Call("waitForChannelId"); + yield return AirshipCoroutineHelper.RunAsync( + () => plugin.Call("waitForChannelId"), + onComplete, + onError + ); } /// @@ -88,14 +96,23 @@ public AttributeEditor EditAttributes() } /// - /// Gets the channel's subscription lists. + /// Gets the channel's subscription lists asynchronously using a coroutine. + /// This method does not block Unity's main thread. /// - /// The subscription lists. - public IEnumerable GetSubscriptionLists() + /// Callback invoked with the subscription lists when the operation completes. + /// Optional callback invoked if an error occurs. + /// A coroutine that can be started with StartCoroutine. + public IEnumerator GetSubscriptionLists(Action> onComplete, Action onError = null) { + yield return AirshipCoroutineHelper.RunAsync( + () => { string subscriptionListsAsJson = plugin.Call("getChannelSubscriptionLists"); JsonArray jsonArray = JsonArray.FromJson(subscriptionListsAsJson); return jsonArray.AsEnumerable(); + }, + onComplete, + onError + ); } /// diff --git a/Assets/UrbanAirship/Platforms/AirshipCoroutineHelper.cs b/Assets/UrbanAirship/Platforms/AirshipCoroutineHelper.cs new file mode 100644 index 00000000..93ae97b1 --- /dev/null +++ b/Assets/UrbanAirship/Platforms/AirshipCoroutineHelper.cs @@ -0,0 +1,87 @@ +/* Copyright Airship and Contributors */ + +using System; +using System.Collections; +using UnityEngine; + +namespace UrbanAirship { + + /// + /// Helper class to run blocking operations in coroutines. + /// AndroidJavaObject.Call() must run on Unity's main thread, so we call it directly + /// from the coroutine (which runs on the main thread). We yield first to let Unity + /// process a frame, then execute the blocking call. The blocking happens on the main + /// thread, but Unity has had a chance to process input/rendering first. + /// + internal static class AirshipCoroutineHelper { + + /// + /// Runs a blocking operation on the main thread. Yields first to let Unity process, + /// then executes the blocking call. Since coroutines run on the main thread, this + /// ensures AndroidJavaObject.Call() works correctly. + /// + /// The return type + /// The blocking operation to run + /// Callback invoked when the operation completes + /// Optional callback invoked if an error occurs + /// A coroutine + public static IEnumerator RunAsync(Func operation, Action onComplete, Action onError = null) { + // Yield first to let Unity process a frame + yield return null; + + T result = default(T); + Exception exception = null; + + try { + result = operation(); + } catch (Exception e) { + exception = e; + Debug.LogError($"[AirshipCoroutineHelper] Exception: {e.Message}\n{e.StackTrace}"); + } + + // Yield again before invoking callbacks to ensure we're still on main thread + yield return null; + + // Handle result or error on main thread + if (exception != null) { + onError?.Invoke(exception); + } else { + onComplete?.Invoke(result); + } + } + + /// + /// Runs a blocking operation on the main thread. Yields first to let Unity process, + /// then executes the blocking call. Since coroutines run on the main thread, this + /// ensures AndroidJavaObject.Call() works correctly. + /// + /// The blocking operation to run + /// Callback invoked when the operation completes + /// Optional callback invoked if an error occurs + /// A coroutine + public static IEnumerator RunAsync(Action operation, Action onComplete = null, Action onError = null) { + // Yield first to let Unity process a frame + yield return null; + + Exception exception = null; + + try { + operation(); + } catch (Exception e) { + exception = e; + Debug.LogError($"[AirshipCoroutineHelper] Exception: {e.Message}\n{e.StackTrace}"); + } + + // Yield again before invoking callbacks to ensure we're still on main thread + yield return null; + + // Handle result or error on main thread + if (exception != null) { + onError?.Invoke(exception); + } else { + onComplete?.Invoke(); + } + } + } +} + diff --git a/Assets/UrbanAirship/Platforms/AirshipMessageCenter.cs b/Assets/UrbanAirship/Platforms/AirshipMessageCenter.cs index 4d4c4eb0..8e5eec0a 100644 --- a/Assets/UrbanAirship/Platforms/AirshipMessageCenter.cs +++ b/Assets/UrbanAirship/Platforms/AirshipMessageCenter.cs @@ -23,58 +23,96 @@ internal AirshipMessageCenter(IAirshipPlugin plugin) } /// - /// Gets the number of unread messages for the message center. + /// Gets the number of unread messages for the message center asynchronously using a coroutine. + /// This method does not block Unity's main thread. /// - /// The number of unread messages. - public int GetUnReadCount() + /// Callback invoked with the unread count when the operation completes. + /// Optional callback invoked if an error occurs. + /// A coroutine that can be started with StartCoroutine. + public IEnumerator GetUnReadCount(Action onComplete, Action onError = null) { - return plugin.Call("getUnreadCount"); + yield return AirshipCoroutineHelper.RunAsync( + () => plugin.Call("getUnreadCount"), + onComplete, + onError + ); } /// - /// Gets the inbox messages. + /// Gets the inbox messages asynchronously using a coroutine. + /// This method does not block Unity's main thread. /// - /// An enumberable list of InboxMessage objects. - public IEnumerable GetMessages() + /// Callback invoked with the messages when the operation completes. + /// Optional callback invoked if an error occurs. + /// A coroutine that can be started with StartCoroutine. + public IEnumerator GetMessages(Action> onComplete, Action onError = null) { + yield return AirshipCoroutineHelper.RunAsync( + () => { var inboxMessages = new List(); - string inboxMessagesAsJson = plugin.Call("getMessages"); InternalInboxMessage[] internalInboxMessages = JsonArray.FromJson(inboxMessagesAsJson).values; - // TODO verify this as the proxy provide a the extras into a map // Unity's JsonUtility doesn't support embedded dictionaries - constructor will create the extras dictionary foreach (InternalInboxMessage internalInboxMessage in internalInboxMessages) { inboxMessages.Add(new InboxMessage(internalInboxMessage)); } - return inboxMessages; + return (IEnumerable)inboxMessages; + }, + onComplete, + onError + ); } /// - /// Mark an inbox message as having been read. + /// Mark an inbox message as having been read asynchronously using a coroutine. + /// This method does not block Unity's main thread. /// /// The messageId for the message. - public void MarkMessageRead(string messageId) + /// Optional callback invoked when the operation completes. + /// Optional callback invoked if an error occurs. + /// A coroutine that can be started with StartCoroutine. + public IEnumerator MarkMessageRead(string messageId, Action onComplete = null, Action onError = null) { - plugin.Call("markMessageRead", messageId); + yield return AirshipCoroutineHelper.RunAsync( + () => plugin.Call("markMessageRead", messageId), + onComplete, + onError + ); } /// - /// Delete an inbox message. + /// Delete an inbox message asynchronously using a coroutine. + /// This method does not block Unity's main thread. /// /// The messageId for the message. - public void DeleteMessage(string messageId) + /// Optional callback invoked when the operation completes. + /// Optional callback invoked if an error occurs. + /// A coroutine that can be started with StartCoroutine. + public IEnumerator DeleteMessage(string messageId, Action onComplete = null, Action onError = null) { - plugin.Call("deleteMessage", messageId); + yield return AirshipCoroutineHelper.RunAsync( + () => plugin.Call("deleteMessage", messageId), + onComplete, + onError + ); } /// - /// Refreshes the inbox. + /// Refreshes the inbox asynchronously using a coroutine. + /// This method does not block Unity's main thread. /// - public void RefreshInbox() + /// Optional callback invoked when the operation completes. + /// Optional callback invoked if an error occurs. + /// A coroutine that can be started with StartCoroutine. + public IEnumerator RefreshInbox(Action onComplete = null, Action onError = null) { - plugin.Call("refreshMessages"); + yield return AirshipCoroutineHelper.RunAsync( + () => plugin.Call("refreshMessages"), + onComplete, + onError + ); } /// diff --git a/Assets/UrbanAirship/Platforms/AirshipPreferenceCenter.cs b/Assets/UrbanAirship/Platforms/AirshipPreferenceCenter.cs index 4370bb1f..05e5ad08 100644 --- a/Assets/UrbanAirship/Platforms/AirshipPreferenceCenter.cs +++ b/Assets/UrbanAirship/Platforms/AirshipPreferenceCenter.cs @@ -33,14 +33,20 @@ public void Display(string preferenceCenterId) } /// - /// Gets the preference center config. + /// Gets the preference center config asynchronously using a coroutine. + /// This method does not block Unity's main thread. /// /// The preference center Id. - /// The preference center config. - public PreferenceCenterConfig GetConfig(string preferenceCenterId) + /// Callback invoked with the config when the operation completes. + /// Optional callback invoked if an error occurs. + /// A coroutine that can be started with StartCoroutine. + public IEnumerator GetConfig(string preferenceCenterId, Action onComplete, Action onError = null) { - // TODO parse this from a Json into a PreferenceCenterConfig and return that - return JsonUtility.FromJson (plugin.Call("getPreferenceCenterConfig", preferenceCenterId)); + yield return AirshipCoroutineHelper.RunAsync( + () => JsonUtility.FromJson(plugin.Call("getPreferenceCenterConfig", preferenceCenterId)), + onComplete, + onError + ); } /// diff --git a/Assets/UrbanAirship/Platforms/AirshipPush.cs b/Assets/UrbanAirship/Platforms/AirshipPush.cs index 5ff2eb93..1bca59cb 100644 --- a/Assets/UrbanAirship/Platforms/AirshipPush.cs +++ b/Assets/UrbanAirship/Platforms/AirshipPush.cs @@ -56,23 +56,36 @@ public bool IsUserNotificationEnabled() } /// - /// Enables user notifications. + /// Enables user notifications asynchronously using a coroutine. + /// This method does not block Unity's main thread. /// /// Optional fallback. - /// The permission result. - public bool EnableUserNotifications(PromptPermissionFallback? fallback) + /// Callback invoked with the permission result when the operation completes. + /// Optional callback invoked if an error occurs. + /// A coroutine that can be started with StartCoroutine. + public IEnumerator EnableUserNotifications(PromptPermissionFallback? fallback, Action onComplete, Action onError = null) { - return plugin.Call("enableUserNotifications", fallback); + yield return AirshipCoroutineHelper.RunAsync( + () => plugin.Call("enableUserNotifications", fallback), + onComplete, + onError + ); } /// - /// Gets the notification status. + /// Gets the notification status asynchronously using a coroutine. + /// This method does not block Unity's main thread. /// - /// The notification status. - public PushNotificationStatus GetNotificationStatus() + /// Callback invoked with the notification status when the operation completes. + /// Optional callback invoked if an error occurs. + /// A coroutine that can be started with StartCoroutine. + public IEnumerator GetNotificationStatus(Action onComplete, Action onError = null) { - // TODO parse this and return a PushNotificationStatus - return JsonUtility.FromJson (plugin.Call("getNotificationStatus")); + yield return AirshipCoroutineHelper.RunAsync( + () => JsonUtility.FromJson(plugin.Call("getNotificationStatus")), + onComplete, + onError + ); } /// diff --git a/Assets/UrbanAirship/Platforms/AttributeEditor.cs b/Assets/UrbanAirship/Platforms/AttributeEditor.cs index 62e264c2..37fe6aca 100644 --- a/Assets/UrbanAirship/Platforms/AttributeEditor.cs +++ b/Assets/UrbanAirship/Platforms/AttributeEditor.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using UnityEngine; namespace UrbanAirship { @@ -43,7 +44,7 @@ public AttributeEditor SetAttribute (string key, int value) { if (IsInvalidField (key)) { return this; } - operations.Add (new AttributeMutation (AttributeAction.Set, key, value.ToString(CultureInfo.InvariantCulture), AttributeType.Integer)); + operations.Add (new AttributeMutation (AttributeAction.Set, key, value.ToString(CultureInfo.InvariantCulture), AttributeType.Number)); return this; } @@ -57,7 +58,7 @@ public AttributeEditor SetAttribute (string key, long value) { if (IsInvalidField (key)) { return this; } - operations.Add (new AttributeMutation (AttributeAction.Set, key, value.ToString(CultureInfo.InvariantCulture), AttributeType.Long)); + operations.Add (new AttributeMutation (AttributeAction.Set, key, value.ToString(CultureInfo.InvariantCulture), AttributeType.Number)); return this; } @@ -74,7 +75,7 @@ public AttributeEditor SetAttribute (string key, float value) { if (float.IsNaN (value) || float.IsInfinity (value)) { throw new FormatException ("Infinity or NaN: " + value); } - operations.Add (new AttributeMutation (AttributeAction.Set, key, value.ToString(CultureInfo.InvariantCulture), AttributeType.Float)); + operations.Add (new AttributeMutation (AttributeAction.Set, key, value.ToString(CultureInfo.InvariantCulture), AttributeType.Number)); return this; } @@ -91,7 +92,7 @@ public AttributeEditor SetAttribute (string key, double value) { if (double.IsNaN (value) || double.IsInfinity (value)) { throw new FormatException ("Infinity or NaN: " + value); } - operations.Add (new AttributeMutation (AttributeAction.Set, key, value.ToString(CultureInfo.InvariantCulture), AttributeType.Double)); + operations.Add (new AttributeMutation (AttributeAction.Set, key, value.ToString(CultureInfo.InvariantCulture), AttributeType.Number)); return this; } @@ -125,7 +126,7 @@ public AttributeEditor RemoveAttribute (string key) { if (IsInvalidField (key)) { return this; } - operations.Add (new AttributeMutation (AttributeAction.Remove, key, null, AttributeType.None)); + operations.Add (new AttributeMutation (AttributeAction.Remove, key, null, null)); return this; } @@ -148,7 +149,11 @@ public void Apply () { if (onApply != null) { JsonArray jsonArray = new JsonArray (); jsonArray.values = operations.ToArray (); - onApply (jsonArray.ToJson ()); + string json = jsonArray.ToJson (); + // Remove empty type fields from JSON (Unity's JsonUtility serializes null strings as empty strings) + json = Regex.Replace(json, @",\s*""type""\s*:\s*""""", ""); + json = Regex.Replace(json, @"""type""\s*:\s*""""\s*,", ""); + onApply (json); } } @@ -159,7 +164,8 @@ internal enum AttributeType { Float, Double, String, - Date + Date, + Number } internal enum AttributeAction { @@ -183,14 +189,14 @@ internal class AttributeMutation { private string value; [SerializeField] - private string type; + private string? type; #pragma warning restore - public AttributeMutation (AttributeAction action, string key, string value, AttributeType type) { + public AttributeMutation (AttributeAction action, string key, string value, AttributeType? type) { this.action = action.ToString(); this.key = key; this.value = value; - this.type = type.ToString(); + this.type = type?.ToString(); } } } diff --git a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt index 98bfe1d7..cdff6d57 100644 --- a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt +++ b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt @@ -11,6 +11,8 @@ import com.urbanairship.json.JsonMap import com.urbanairship.json.JsonValue import com.urbanairship.push.PushMessage import com.urbanairship.util.UAStringUtil +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking import org.json.JSONArray @@ -44,9 +46,11 @@ class UnityPlugin { return airshipProxyInstance.channel.getChannelId() } - suspend fun waitForChannelId(): String { + fun waitForChannelId(): String { ProxyLogger.debug("UnityPlugin waitForChannelId") - return airshipProxyInstance.channel.waitForChannelId() + return runBlocking(Dispatchers.IO) { + airshipProxyInstance.channel.waitForChannelId() + } } fun addTag(tag: String) { @@ -95,13 +99,15 @@ class UnityPlugin { } } - suspend fun getChannelSubscriptionLists(): String { + fun getChannelSubscriptionLists(): String { ProxyLogger.debug("UnityPlugin getChannelSubscriptionLists") + return runBlocking(Dispatchers.IO) { val jsonArray = JSONArray() for (tag in airshipProxyInstance.channel.getSubscriptionLists()) { jsonArray.put(tag) } - return jsonArray.toString() + jsonArray.toString() + } } fun editChannelSubscriptionLists(payload: String) { @@ -244,30 +250,39 @@ class UnityPlugin { // Message Center - suspend fun getUnreadCount(): Int { + fun getUnreadCount(): Int { ProxyLogger.debug("UnityPlugin getUnreadCount") - return airshipProxyInstance.messageCenter.getUnreadMessagesCount() + return runBlocking(Dispatchers.IO) { + airshipProxyInstance.messageCenter.getUnreadMessagesCount() + } } - suspend fun getMessages(): String { + fun getMessages(): String { ProxyLogger.debug("UnityPlugin getMessages") - return JsonValue.wrapOpt(airshipProxyInstance.messageCenter.getMessages()).toString() + return runBlocking(Dispatchers.IO) { + JsonValue.wrapOpt(airshipProxyInstance.messageCenter.getMessages()).toString() + } } fun markMessageRead(messageId: String) { ProxyLogger.debug("UnityPlugin markMessageRead: $messageId") + runBlocking(Dispatchers.IO) { airshipProxyInstance.messageCenter.markMessageRead(messageId) + } } fun deleteMessage(messageId: String) { ProxyLogger.debug("UnityPlugin deleteMessage: $messageId") + runBlocking(Dispatchers.IO) { airshipProxyInstance.messageCenter.deleteMessage(messageId) + } } fun refreshMessages() { ProxyLogger.debug("UnityPlugin refreshMessages") - // TODO handle suspend fun - //airshipProxyInstance.messageCenter.refreshInbox() + runBlocking(Dispatchers.IO) { + airshipProxyInstance.messageCenter.refreshInbox() + } } fun setAutoLaunchDefaultMessageCenter(enabled: Boolean) { @@ -302,9 +317,11 @@ class UnityPlugin { airshipProxyInstance.preferenceCenter.displayPreferenceCenter(preferenceCenterId) } - suspend fun getPreferenceCenterConfig(preferenceCenterId: String): String { + fun getPreferenceCenterConfig(preferenceCenterId: String): String { ProxyLogger.debug("UnityPlugin getPreferenceCenterConfig: $preferenceCenterId") - return JsonValue.wrapOpt(airshipProxyInstance.preferenceCenter.getPreferenceCenterConfig(preferenceCenterId)).toString() + return runBlocking(Dispatchers.IO) { + JsonValue.wrapOpt(airshipProxyInstance.preferenceCenter.getPreferenceCenterConfig(preferenceCenterId)).toString() + } } fun setAutoLaunchDefaultPreferenceCenter(preferenceCenterId: String, autoLaunch: Boolean) { @@ -351,16 +368,20 @@ class UnityPlugin { airshipProxyInstance.push.setUserNotificationsEnabled(enabled) } - suspend fun enableUserNotifications(fallback: String?): Boolean { + fun enableUserNotifications(fallback: String?): Boolean { ProxyLogger.debug("UnityPlugin enableUserNotifications: $fallback") - return airshipProxyInstance.push.enableUserPushNotifications( + return runBlocking(Dispatchers.IO) { + airshipProxyInstance.push.enableUserPushNotifications( EnableUserNotificationsArgs.fromJson(JsonValue.parseString(fallback)) ) + } } - suspend fun getNotificationStatus(): String { + fun getNotificationStatus(): String { ProxyLogger.debug("UnityPlugin getNotificationStatus") - return airshipProxyInstance.push.getNotificationStatus().toJsonValue().toString() + return runBlocking(Dispatchers.IO) { + airshipProxyInstance.push.getNotificationStatus().toJsonValue().toString() + } } fun getPushToken(): String? { @@ -439,9 +460,11 @@ class UnityPlugin { // TODO Implement the rest of the listeners (PreferenceCenter) - suspend fun onInboxUpdated() { - val unreadCount = getUnreadCount() - val totalCount = getMessages().count() + fun onInboxUpdated() { + runBlocking(Dispatchers.IO) { + val unreadCount = airshipProxyInstance.messageCenter.getUnreadMessagesCount() + val messages = airshipProxyInstance.messageCenter.getMessages() + val totalCount = messages.size val counts: JsonMap = JsonMap.newBuilder() .put("unread", unreadCount) .put("total", totalCount) @@ -453,6 +476,7 @@ class UnityPlugin { if (listener != null) { UnityPlayer.UnitySendMessage(listener, "OnInboxUpdated", counts.toString()) + } } } From d97787b6205b761b39dca6b7e4cded290a11e050 Mon Sep 17 00:00:00 2001 From: Ulrich GIBERNE Date: Mon, 1 Dec 2025 17:30:10 -0400 Subject: [PATCH 12/27] wip --- Assets/Scripts/UrbanAirshipBehaviour.cs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/Assets/Scripts/UrbanAirshipBehaviour.cs b/Assets/Scripts/UrbanAirshipBehaviour.cs index 277841d1..d0a96783 100644 --- a/Assets/Scripts/UrbanAirshipBehaviour.cs +++ b/Assets/Scripts/UrbanAirshipBehaviour.cs @@ -36,8 +36,6 @@ void Start () { customEvent.EventValue = 123; Airship.Shared.analytics.AddCustomEvent(customEvent); - Airship.Shared.messageCenter.RefreshInbox(); - Airship.Shared.channel.EditTags().AddTag("ulrich").Apply(); Airship.Shared.channel.EditAttributes().SetAttribute("teststring", "a_string").Apply(); @@ -46,6 +44,27 @@ void Start () { Airship.Shared.channel.EditAttributes().SetAttribute("testfloat", (float)5.99).Apply(); Airship.Shared.channel.EditAttributes().SetAttribute("testdouble", (double)5555.999).Apply(); Airship.Shared.channel.EditAttributes().SetAttribute("testdate", DateTime.UtcNow).Apply(); + + Airship.Shared.channel.EditAttributes().RemoveAttribute("teststring").Apply(); + Airship.Shared.channel.EditAttributes().RemoveAttribute("testint").Apply(); + + StartCoroutine(Airship.Shared.messageCenter.RefreshInbox( + onComplete: () => { + Debug.Log("Refresh inbox complete"); + }, + onError: (error) => { + Debug.LogError("Error refreshing inbox: " + error.Message); + } + )); + + StartCoroutine(Airship.Shared.channel.WaitForChannelId( + onComplete: (channelId) => { + Debug.Log($"Channel ID received: {channelId}"); + }, + onError: (error) => { + Debug.LogError($"Error getting channel ID: {error.Message}"); + } + )); } // void OnDestroy () { From 238caea84d54d9eda35fc4be8c6a85fbf60eb76b Mon Sep 17 00:00:00 2001 From: Ulrich GIBERNE Date: Tue, 20 Jan 2026 10:51:17 -0400 Subject: [PATCH 13/27] wip --- Assets/Scripts/UrbanAirshipBehaviour.cs | 224 ++++++++++++++++-- Assets/UrbanAirship/Platforms/Airship.cs | 82 ++++--- .../UrbanAirship/Platforms/AirshipChannel.cs | 6 +- Assets/UrbanAirship/Platforms/AirshipInApp.cs | 2 +- .../Platforms/AirshipMessageCenter.cs | 28 +-- .../Platforms/AirshipPrivacyManager.cs | 32 ++- Assets/UrbanAirship/Platforms/AirshipPush.cs | 41 ++-- Assets/UrbanAirship/Platforms/AirshipUtils.cs | 182 ++++++++++++++ .../UrbanAirship/Platforms/IAirshipPlugin.cs | 18 ++ .../Platforms/ScopedSubscriptionListEditor.cs | 4 +- .../Platforms/SubscriptionListEditor.cs | 4 +- Assets/UrbanAirship/Platforms/TagEditor.cs | 4 +- .../UrbanAirship/Platforms/TagGroupEditor.cs | 8 +- .../urbanairship/unityplugin/UnityPlugin.kt | 164 +++++++------ 14 files changed, 610 insertions(+), 189 deletions(-) create mode 100644 Assets/UrbanAirship/Platforms/AirshipUtils.cs diff --git a/Assets/Scripts/UrbanAirshipBehaviour.cs b/Assets/Scripts/UrbanAirshipBehaviour.cs index d0a96783..4eeb9981 100644 --- a/Assets/Scripts/UrbanAirshipBehaviour.cs +++ b/Assets/Scripts/UrbanAirshipBehaviour.cs @@ -10,18 +10,27 @@ public class UrbanAirshipBehaviour : MonoBehaviour { public string addTagOnStart; void Awake () { - //Airship.Shared.push.SetUserNotificationsEnabled(true); - //Airship.Shared.push.UserNotificationsEnabled = true; + Airship.Shared.TakeOff(new AirshipConfig() { + defaultEnvironment = new ConfigEnvironment() { + appKey = "APP_KEY", + appSecret = "APP_SECRET", + logLevel = LogLevel.Verbose, + }, + site = Site.US, + inProduction = false, + urlAllowList = new string[] { "*" }, + }); } void Start () { + Debug.Log("Airship is flying: " + Airship.Shared.IsFlying()); + + Airship.Shared.push.SetUserNotificationsEnabled(true); + // if (!string.IsNullOrEmpty (addTagOnStart)) { // UAirship.Shared.AddTag (addTagOnStart); // } - string[] allenable = new string[] { "FEATURE_ALL" }; - //Airship.Shared.privacyManager.SetEnabledFeatures(allenable); - // UAirship.Shared.OnPushReceived += OnPushReceived; // UAirship.Shared.OnChannelUpdated += OnChannelUpdated; // UAirship.Shared.OnDeepLinkReceived += OnDeepLinkReceived; @@ -29,14 +38,73 @@ void Start () { // UAirship.Shared.OnInboxUpdated += OnInboxUpdated; // UAirship.Shared.OnShowInbox += OnShowInbox; + // PrivacyManager + Debug.Log("Set Enabled features to none"); + Airship.Shared.privacyManager.SetEnabledFeatures(new string[] { "none" }); + Debug.Log("Enabled features: " + string.Join(", ", Airship.Shared.privacyManager.GetEnabledFeatures())); + + Debug.Log("Enable push and analytics features"); + Airship.Shared.privacyManager.EnableFeatures(new string[] { "push", "analytics" }); + Debug.Log("Enabled features: " + string.Join(", ", Airship.Shared.privacyManager.GetEnabledFeatures())); + + Debug.Log("Disable analytics feature"); + Airship.Shared.privacyManager.DisableFeatures(new string[] { "analytics" }); + Debug.Log("Enabled features: " + string.Join(", ", Airship.Shared.privacyManager.GetEnabledFeatures())); + + Debug.Log("Is push feature enabled: " + Airship.Shared.privacyManager.IsFeaturesEnabled(new string[] { "push" })); + Debug.Log("Is analytics feature enabled: " + Airship.Shared.privacyManager.IsFeaturesEnabled(new string[] { "analytics" })); + + Debug.Log("Set Enabled features to all"); + Airship.Shared.privacyManager.SetEnabledFeatures(new string[] { "all" }); + + // Analytics Airship.Shared.analytics.TrackScreen("Main Camera"); + Airship.Shared.analytics.AssociateIdentifier("identifier", "my_identifier"); + CustomEvent customEvent = new CustomEvent(); - customEvent.EventName = "event_name"; + customEvent.EventName = "my_event"; customEvent.EventValue = 123; Airship.Shared.analytics.AddCustomEvent(customEvent); - Airship.Shared.channel.EditTags().AddTag("ulrich").Apply(); + Debug.Log("Session ID: " + Airship.Shared.analytics.GetSessionId()); + + // Channel + Debug.Log("Channel ID: " + Airship.Shared.channel.GetChannelId()); + + // StartCoroutine(Airship.Shared.channel.WaitForChannelId( + // onComplete: (channelId) => { + // Debug.Log($"Channel ID received: {channelId}"); + // }, + // onError: (error) => { + // Debug.LogError($"Error getting channel ID: {error.Message}"); + // } + // )); + + Airship.Shared.channel.EditTags().AddTag("unity_tag").Apply(); + Airship.Shared.channel.EditTags().AddTag("tag_to_remove_1").Apply(); + Airship.Shared.channel.EditTags().AddTags(new string[] { "tag_to_remove_2", "tag_to_remove_3" }).Apply(); + Debug.Log("Tags: " + string.Join(", ", Airship.Shared.channel.GetTags())); + Airship.Shared.channel.EditTags().RemoveTag("tag_to_remove_1").Apply(); + Airship.Shared.channel.EditTags().RemoveTags(new string[] { "tag_to_remove_2", "tag_to_remove_3" }).Apply(); + Debug.Log("Tags: " + string.Join(", ", Airship.Shared.channel.GetTags())); + + Airship.Shared.channel.EditTagGroups().AddTag("unity_tag_group", "tag_1").Apply(); + Airship.Shared.channel.EditTagGroups().AddTags("unity_tag_group", new string[] { "tag_2", "tag_3" }).Apply(); + Airship.Shared.channel.EditTagGroups().RemoveTag("unity_tag_group", "tag_2").Apply(); + Airship.Shared.channel.EditTagGroups().RemoveTags("unity_tag_group", new string[] { "tag_3" }).Apply(); + + Airship.Shared.channel.EditSubscriptionLists().Subscribe("unity_subscription_list").Apply(); + Airship.Shared.channel.EditSubscriptionLists().Subscribe("unity_subscription_list_to_remove").Apply(); + // StartCoroutine(Airship.Shared.channel.GetSubscriptionLists( + // onComplete: (subscriptionLists) => { + // Debug.Log("Channel Subscription lists: " + string.Join(", ", subscriptionLists)); + // }, + // onError: (error) => { + // Debug.LogError("Error getting subscription lists: " + error.Message); + // } + // )); + Airship.Shared.channel.EditSubscriptionLists().Unsubscribe("unity_subscription_list_to_remove").Apply(); Airship.Shared.channel.EditAttributes().SetAttribute("teststring", "a_string").Apply(); Airship.Shared.channel.EditAttributes().SetAttribute("testint", (int) 1).Apply(); @@ -44,27 +112,151 @@ void Start () { Airship.Shared.channel.EditAttributes().SetAttribute("testfloat", (float)5.99).Apply(); Airship.Shared.channel.EditAttributes().SetAttribute("testdouble", (double)5555.999).Apply(); Airship.Shared.channel.EditAttributes().SetAttribute("testdate", DateTime.UtcNow).Apply(); - + Airship.Shared.channel.EditAttributes().RemoveAttribute("teststring").Apply(); Airship.Shared.channel.EditAttributes().RemoveAttribute("testint").Apply(); - StartCoroutine(Airship.Shared.messageCenter.RefreshInbox( - onComplete: () => { - Debug.Log("Refresh inbox complete"); + // Contact + Airship.Shared.contact.Identify("my_named_user"); + Debug.Log("Named user ID: " + Airship.Shared.contact.GetNamedUserId()); + Airship.Shared.contact.Reset(); + Debug.Log("Named user ID after reset: " + Airship.Shared.contact.GetNamedUserId()); + + Airship.Shared.contact.EditTagGroups().AddTag("unity_tag_group", "tag_1").Apply(); + Airship.Shared.contact.EditTagGroups().AddTags("unity_tag_group", new string[] { "tag_2", "tag_3" }).Apply(); + Airship.Shared.contact.EditTagGroups().RemoveTag("unity_tag_group", "tag_2").Apply(); + Airship.Shared.contact.EditTagGroups().RemoveTags("unity_tag_group", new string[] { "tag_3" }).Apply(); + + Airship.Shared.contact.EditAttributes().SetAttribute("teststring", "a_string").Apply(); + Airship.Shared.contact.EditAttributes().SetAttribute("testint", (int) 1).Apply(); + Airship.Shared.contact.EditAttributes().SetAttribute("testlong", (long) 1000).Apply(); + Airship.Shared.contact.EditAttributes().SetAttribute("testfloat", (float)5.99).Apply(); + Airship.Shared.contact.EditAttributes().SetAttribute("testdouble", (double)5555.999).Apply(); + Airship.Shared.contact.EditAttributes().SetAttribute("testdate", DateTime.UtcNow).Apply(); + Airship.Shared.contact.EditAttributes().RemoveAttribute("teststring").Apply(); + Airship.Shared.contact.EditAttributes().RemoveAttribute("testint").Apply(); + + Airship.Shared.contact.EditSubscriptionLists().Subscribe("unity_subscription_list", SubscriptionScope.APP).Apply(); + Airship.Shared.contact.EditSubscriptionLists().Subscribe("unity_subscription_list_to_remove", SubscriptionScope.APP).Apply(); + Airship.Shared.contact.EditSubscriptionLists().Unsubscribe("unity_subscription_list_to_remove", SubscriptionScope.APP).Apply(); + Debug.Log("Contact Subscription lists: " + string.Join(", ", Airship.Shared.contact.GetSubscriptionLists())); + // StartCoroutine(Airship.Shared.contact.GetSubscriptionLists( + // onComplete: (subscriptionLists) => { + // Debug.Log("Contact Subscription lists: " + string.Join(", ", subscriptionLists)); + // }, + // onError: (error) => { + // Debug.LogError("Error getting subscription lists: " + error.Message); + // } + // )); + + // InApp + Airship.Shared.inApp.SetPaused(true); + Debug.Log("InApp paused after true: " + Airship.Shared.inApp.IsPaused()); + Airship.Shared.inApp.SetPaused(false); + Debug.Log("InApp paused after false: " + Airship.Shared.inApp.IsPaused()); + + Airship.Shared.inApp.SetDisplayInterval(TimeSpan.FromSeconds(10)); + Debug.Log("InApp display interval: " + Airship.Shared.inApp.GetDisplayInterval()); + + // Locale + Airship.Shared.locale.SetLocaleOverride("en_US"); + Airship.Shared.locale.ClearLocaleOverride(); + // Debug.Log("Locale: " + Airship.Shared.locale.GetLocale()); + + // Message Center + // StartCoroutine(Airship.Shared.messageCenter.RefreshInbox( + // onComplete: () => { + // Debug.Log("Refresh inbox complete"); + // }, + // onError: (error) => { + // Debug.LogError("Error refreshing inbox: " + error.Message); + // } + // )); + + Airship.Shared.messageCenter.SetAutoLaunchDefaultMessageCenter(true); + + // StartCoroutine(Airship.Shared.messageCenter.GetUnReadCount( + // onComplete: (unreadCount) => { + // Debug.Log("Unread count: " + unreadCount); + // }, + // onError: (error) => { + // Debug.LogError("Error getting unread count: " + error.Message); + // } + // )); + + // StartCoroutine(Airship.Shared.messageCenter.GetMessages( + // onComplete: (messages) => { + // Debug.Log("Messages: " + string.Join(", ", messages)); + // }, + // onError: (error) => { + // Debug.LogError("Error getting messages: " + error.Message); + // } + // )); + + Airship.Shared.messageCenter.Display(null); + // Airship.Shared.messageCenter.ShowMessageCenter(null); + Airship.Shared.messageCenter.Dismiss(); + + // Preference Center + Airship.Shared.preferenceCenter.SetAutoLaunchDefaultPreferenceCenter("neat", true); + // Airship.Shared.preferenceCenter.Display("neat"); + // StartCoroutine(Airship.Shared.preferenceCenter.GetConfig("neat", + // onComplete: (config) => { + // Debug.Log("Config: " + JsonUtility.ToJson(config)); + // }, + // onError: (error) => { + // Debug.LogError("Error getting config: " + error.Message); + // } + // )); + + // Push + + Airship.Shared.push.SetUserNotificationsEnabled(false); + Debug.Log("User notifications enabled after set to false: " + Airship.Shared.push.IsUserNotificationEnabled()); + // Airship.Shared.push.SetUserNotificationsEnabled(true); + // Debug.Log("User notifications enabled after set to true: " + Airship.Shared.push.IsUserNotificationEnabled()); + + StartCoroutine(Airship.Shared.push.EnableUserNotifications( + new EnabledUserPushNotificationsArgs() { + fallback = PromptPermissionFallback.SystemSettings + }, + onComplete: (result) => { + Debug.Log("User notifications enabled: " + result); }, onError: (error) => { - Debug.LogError("Error refreshing inbox: " + error.Message); + Debug.LogError("Error enabling user notifications: " + error.Message); } )); - StartCoroutine(Airship.Shared.channel.WaitForChannelId( - onComplete: (channelId) => { - Debug.Log($"Channel ID received: {channelId}"); + StartCoroutine(Airship.Shared.push.GetNotificationStatus( + onComplete: (status) => { + Debug.Log("Notification status: " + status); }, onError: (error) => { - Debug.LogError($"Error getting channel ID: {error.Message}"); + Debug.LogError("Error getting notification status: " + error.Message); } )); + + Debug.Log("Push token: " + Airship.Shared.push.GetPushToken()); + + Debug.Log("Active notifications: " + string.Join(", ", Airship.Shared.push.GetActiveNotifications())); + + // Airship.Shared.push.ClearNotifications(); + // Debug.Log("Notifications cleared"); + + Debug.Log("Is notification channel enabled: " + Airship.Shared.push.android.IsNotificationChannelEnabled("test_channel")); + + Airship.Shared.push.android.SetNotificationConfig(new AndroidNotificationConfig() { + icon = "ic_notification", + largeIcon = "ic_notification_large", + defaultChannelId = "test_channel", + accentColor = "#FF0000", + }); + + Airship.Shared.push.android.SetForegroundNotificationsEnabled(true); + // Debug.Log("Foreground notifications enabled: " + Airship.Shared.push.android.IsForegroundNotificationsEnabled()); + // Airship.Shared.push.android.SetForegroundNotificationsEnabled(false); + // Debug.Log("Foreground notifications enabled after false: " + Airship.Shared.push.android.IsForegroundNotificationsEnabled()); } // void OnDestroy () { diff --git a/Assets/UrbanAirship/Platforms/Airship.cs b/Assets/UrbanAirship/Platforms/Airship.cs index 2ec355bd..593c8f9c 100644 --- a/Assets/UrbanAirship/Platforms/Airship.cs +++ b/Assets/UrbanAirship/Platforms/Airship.cs @@ -183,7 +183,8 @@ private void Init() /// true if airship is ready. public bool TakeOff(AirshipConfig config) { - return plugin.Call("takeOff", config); + string json = AirshipUtils.Serialize(config); + return plugin.Call("takeOff", json); } /// @@ -318,6 +319,7 @@ public static class Features /// /// Airship config environment /// + [Serializable] public record ConfigEnvironment { // App key. @@ -333,6 +335,7 @@ public record ConfigEnvironment public IOSEnvironmentConfig? iOS; } + [Serializable] public enum LogLevel { [AirshipEnumStringValue("verbose")] @@ -349,6 +352,7 @@ public enum LogLevel None } + [Serializable] public record IOSEnvironmentConfig { /// @@ -359,6 +363,7 @@ public record IOSEnvironmentConfig public LogPrivacyLevel? logPrivacyLevel; } + [Serializable] public enum LogPrivacyLevel { [AirshipEnumStringValue("private")] @@ -367,6 +372,7 @@ public enum LogPrivacyLevel Public } + [Serializable] public enum Site { [AirshipEnumStringValue("us")] @@ -375,12 +381,14 @@ public enum Site EU } + [Serializable] public record IOSConfig { // itunesId for rate app and app store deep links. public string? itunesId; } + [Serializable] public record AndroidConfig { // App store URI @@ -398,6 +406,7 @@ public record AndroidConfig public LogPrivacyLevel? logPrivacyLevel; } + [Serializable] public record AndroidNotificationConfig { // The icon resource name. @@ -413,54 +422,55 @@ public record AndroidNotificationConfig public string? accentColor; } + [Serializable] public record AirshipConfig - { - // Default environment. - public ConfigEnvironment? defaultEnvironment; + { + // Default environment. + public ConfigEnvironment? defaultEnvironment; - // Development environment. Overrides default environment if inProduction is false. - public ConfigEnvironment? development; + // Development environment. Overrides default environment if inProduction is false. + public ConfigEnvironment? development; - // Production environment. Overrides default environment if inProduction is true. - public ConfigEnvironment? production; + // Production environment. Overrides default environment if inProduction is true. + public ConfigEnvironment? production; - // Cloud site. - public Site? site; + // Cloud site. + public Site? site; - // Switches the environment from development or production. - // If the value is not set, Airship will determine the value at runtime. - public bool? inProduction; + // Switches the environment from development or production. + // If the value is not set, Airship will determine the value at runtime. + public bool? inProduction; - // URL allow list. - public string[]? urlAllowList; + // URL allow list. + public string[]? urlAllowList; - // URL allow list for open URL scope. - public string[]? urlAllowListScopeOpenUrl; + // URL allow list for open URL scope. + public string[]? urlAllowListScopeOpenUrl; - // URL allow list for JS bridge injection. - public string[]? urlAllowListScopeJavaScriptInterface; + // URL allow list for JS bridge injection. + public string[]? urlAllowListScopeJavaScriptInterface; - // Initial config URL for custom Airship domains. - // The URL should also be added to the urlAllowList. - public string? initialConfigUrl; + // Initial config URL for custom Airship domains. + // The URL should also be added to the urlAllowList. + public string? initialConfigUrl; - // Enabled features. Defaults to all. - public string[]? enabledFeatures; + // Enabled features. Defaults to all. + public string[]? enabledFeatures; - // Enables channel capture feature. This config is enabled by default. - public bool? isChannelCaptureEnabled; + // Enables channel capture feature. This config is enabled by default. + public bool? isChannelCaptureEnabled; - // Whether to suppress console error messages about missing allow list entries during takeOff. - // This config is disabled by default. - public bool? suppressAllowListError; + // Whether to suppress console error messages about missing allow list entries during takeOff. + // This config is disabled by default. + public bool? suppressAllowListError; - // Pauses In-App Automation on launch. - public bool? autoPauseInAppAutomationOnLaunch; + // Pauses In-App Automation on launch. + public bool? autoPauseInAppAutomationOnLaunch; - // iOS config. - public IOSConfig? ios; + // iOS config. + public IOSConfig? ios; - // Android config. - public AndroidConfig? android; - } + // Android config. + public AndroidConfig? android; + } } \ No newline at end of file diff --git a/Assets/UrbanAirship/Platforms/AirshipChannel.cs b/Assets/UrbanAirship/Platforms/AirshipChannel.cs index cef2e333..44423422 100644 --- a/Assets/UrbanAirship/Platforms/AirshipChannel.cs +++ b/Assets/UrbanAirship/Platforms/AirshipChannel.cs @@ -106,9 +106,9 @@ public IEnumerator GetSubscriptionLists(Action> onComplete, { yield return AirshipCoroutineHelper.RunAsync( () => { - string subscriptionListsAsJson = plugin.Call("getChannelSubscriptionLists"); - JsonArray jsonArray = JsonArray.FromJson(subscriptionListsAsJson); - return jsonArray.AsEnumerable(); + string subscriptionListsAsJson = plugin.Call("getChannelSubscriptionLists"); + JsonArray jsonArray = JsonArray.FromJson(subscriptionListsAsJson); + return jsonArray.AsEnumerable(); }, onComplete, onError diff --git a/Assets/UrbanAirship/Platforms/AirshipInApp.cs b/Assets/UrbanAirship/Platforms/AirshipInApp.cs index 1b7e3f20..48fdd7a6 100644 --- a/Assets/UrbanAirship/Platforms/AirshipInApp.cs +++ b/Assets/UrbanAirship/Platforms/AirshipInApp.cs @@ -40,7 +40,7 @@ public bool IsPaused () { /// /// The display interval. public void SetDisplayInterval (TimeSpan displayInterval) { - plugin.Call ("setDisplayInterval", displayInterval.TotalMilliseconds); + plugin.Call ("setDisplayInterval", (long)displayInterval.TotalMilliseconds); } /// diff --git a/Assets/UrbanAirship/Platforms/AirshipMessageCenter.cs b/Assets/UrbanAirship/Platforms/AirshipMessageCenter.cs index 8e5eec0a..219b18b4 100644 --- a/Assets/UrbanAirship/Platforms/AirshipMessageCenter.cs +++ b/Assets/UrbanAirship/Platforms/AirshipMessageCenter.cs @@ -66,37 +66,21 @@ public IEnumerator GetMessages(Action> onComplete, Act } /// - /// Mark an inbox message as having been read asynchronously using a coroutine. - /// This method does not block Unity's main thread. + /// Mark an inbox message as having been read. /// /// The messageId for the message. - /// Optional callback invoked when the operation completes. - /// Optional callback invoked if an error occurs. - /// A coroutine that can be started with StartCoroutine. - public IEnumerator MarkMessageRead(string messageId, Action onComplete = null, Action onError = null) + public void MarkMessageRead(string messageId) { - yield return AirshipCoroutineHelper.RunAsync( - () => plugin.Call("markMessageRead", messageId), - onComplete, - onError - ); + plugin.Call("markMessageRead", messageId); } /// - /// Delete an inbox message asynchronously using a coroutine. - /// This method does not block Unity's main thread. + /// Delete an inbox message. /// /// The messageId for the message. - /// Optional callback invoked when the operation completes. - /// Optional callback invoked if an error occurs. - /// A coroutine that can be started with StartCoroutine. - public IEnumerator DeleteMessage(string messageId, Action onComplete = null, Action onError = null) + public void DeleteMessage(string messageId) { - yield return AirshipCoroutineHelper.RunAsync( - () => plugin.Call("deleteMessage", messageId), - onComplete, - onError - ); + plugin.Call("deleteMessage", messageId); } /// diff --git a/Assets/UrbanAirship/Platforms/AirshipPrivacyManager.cs b/Assets/UrbanAirship/Platforms/AirshipPrivacyManager.cs index 5fc3f36c..371d9151 100644 --- a/Assets/UrbanAirship/Platforms/AirshipPrivacyManager.cs +++ b/Assets/UrbanAirship/Platforms/AirshipPrivacyManager.cs @@ -27,7 +27,13 @@ internal AirshipPrivacyManager(IAirshipPlugin plugin) /// The features to set. public void SetEnabledFeatures(string[] enabledFeatures) { - plugin.Call("setEnabledFeatures", enabledFeatures); + #if UNITY_ANDROID + plugin.Call("setEnabledFeatures", ((AirshipPluginAndroid)plugin).MakeJavaArray(enabledFeatures)); + #elif UNITY_IOS + plugin.Call("setEnabledFeatures", string.Join(",", enabledFeatures)); + #else + plugin.Call("setEnabledFeatures", enabledFeatures); + #endif } /// @@ -45,7 +51,13 @@ public string[] GetEnabledFeatures() /// The features to enable. public void EnableFeatures(string[] features) { - plugin.Call("enableFeatures", features); + #if UNITY_ANDROID + plugin.Call("enableFeatures", ((AirshipPluginAndroid)plugin).MakeJavaArray(features)); + #elif UNITY_IOS + plugin.Call("enableFeatures", string.Join(",", features)); + #else + plugin.Call("enableFeatures", features); + #endif } /// @@ -54,7 +66,13 @@ public void EnableFeatures(string[] features) /// The features to disable. public void DisableFeatures(string[] features) { - plugin.Call("disableFeatures", features); + #if UNITY_ANDROID + plugin.Call("disableFeatures", ((AirshipPluginAndroid)plugin).MakeJavaArray(features)); + #elif UNITY_IOS + plugin.Call("disableFeatures", string.Join(",", features)); + #else + plugin.Call("disableFeatures", features); + #endif } /// @@ -64,7 +82,13 @@ public void DisableFeatures(string[] features) /// true if the features are enabled, otherwise false public bool IsFeaturesEnabled(string[] features) { - return plugin.Call("isFeaturesEnabled", features); + #if UNITY_ANDROID + return plugin.Call("isFeaturesEnabled", ((AirshipPluginAndroid)plugin).MakeJavaArray(features)); + #elif UNITY_IOS + return plugin.Call("isFeaturesEnabled", string.Join(",", features)); + #else + return plugin.Call("isFeaturesEnabled", features); + #endif } } } \ No newline at end of file diff --git a/Assets/UrbanAirship/Platforms/AirshipPush.cs b/Assets/UrbanAirship/Platforms/AirshipPush.cs index 1bca59cb..8c19357e 100644 --- a/Assets/UrbanAirship/Platforms/AirshipPush.cs +++ b/Assets/UrbanAirship/Platforms/AirshipPush.cs @@ -63,10 +63,13 @@ public bool IsUserNotificationEnabled() /// Callback invoked with the permission result when the operation completes. /// Optional callback invoked if an error occurs. /// A coroutine that can be started with StartCoroutine. - public IEnumerator EnableUserNotifications(PromptPermissionFallback? fallback, Action onComplete, Action onError = null) + public IEnumerator EnableUserNotifications(EnabledUserPushNotificationsArgs? fallback, Action onComplete, Action onError = null) { yield return AirshipCoroutineHelper.RunAsync( - () => plugin.Call("enableUserNotifications", fallback), + () => { + string json = AirshipUtils.Serialize(fallback); + return plugin.Call("enableUserNotifications", json); + }, onComplete, onError ); @@ -272,9 +275,9 @@ public bool IsNotificationChannelEnabled(string channel) /// Sets the notification config. /// /// The notification config. - public void SetNotificationConfig(NotificationConfig config) + public void SetNotificationConfig(AndroidNotificationConfig config) { - plugin.Call("setNotificationConfig", config); + plugin.Call("setNotificationConfig", AirshipUtils.Serialize(config)); } /// @@ -286,7 +289,14 @@ public void SetForegroundNotificationsEnabled(bool enabled) plugin.Call("setForegroundNotificationsEnabled", enabled); } - // TODO Just noticed I forgot isForegroundNotificationsEnabled method, I need to add that + /// + /// Checks if notifications show in the foreground. + /// + /// true if notifications show in the foreground, otherwise false. + public bool IsForegroundNotificationsEnabled() + { + return plugin.Call("isForegroundNotificationsEnabled"); + } } /// @@ -361,6 +371,11 @@ public enum PromptPermissionFallback SystemSettings } + [Serializable] + public record EnabledUserPushNotificationsArgs { + public PromptPermissionFallback? fallback; + } + /// /// Enum of foreground notification options. /// @@ -415,6 +430,7 @@ public enum NotificationOption Provisional } + [Serializable] public record QuietTime { // Start hour. Must be 0-23. @@ -433,6 +449,7 @@ public record QuietTime /// /// Enum of authorized notification options. /// + [Serializable] public enum AuthorizedNotificationSetting { // Alerts. @@ -467,18 +484,4 @@ public enum AuthorizedNotificationSetting TimeSensitive, } - public record NotificationConfig - { - // The icon resource name. - public string? icon; - - // The large icon resource name. - public string? largeIcon; - - // The default android notification channel ID. - public string? defaultChannelId; - - // The accent color. Must be a hex value #AARRGGBB. - public string? accentColor; - } } \ No newline at end of file diff --git a/Assets/UrbanAirship/Platforms/AirshipUtils.cs b/Assets/UrbanAirship/Platforms/AirshipUtils.cs new file mode 100644 index 00000000..9842275e --- /dev/null +++ b/Assets/UrbanAirship/Platforms/AirshipUtils.cs @@ -0,0 +1,182 @@ +/* Copyright Airship and Contributors */ + +using System; +using System.Collections; +using System.Reflection; +using System.Text; + +namespace UrbanAirship +{ + /// + /// Utility class for Airship serialization and helper methods. + /// + public static class AirshipUtils + { + /// + /// Serializes an object to JSON, handling enums with [AirshipEnumStringValue] attributes, + /// nullable types, arrays, and nested objects/records. + /// + /// The object to serialize. + /// JSON string representation of the object. + public static string Serialize(object obj) + { + if (obj == null) + { + return "{}"; + } + + var jsonBuilder = new StringBuilder(); + jsonBuilder.Append("{"); + + Type type = obj.GetType(); + PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); + FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance); + + bool first = true; + + // Process properties + foreach (PropertyInfo prop in properties) + { + if (!prop.CanRead) continue; + + object? value = prop.GetValue(obj); + if (value == null) continue; + + if (!first) jsonBuilder.Append(","); + first = false; + + jsonBuilder.Append($"\"{prop.Name}\":"); + jsonBuilder.Append(SerializeValue(value)); + } + + // Process fields + foreach (FieldInfo field in fields) + { + object? value = field.GetValue(obj); + if (value == null) continue; + + if (!first) jsonBuilder.Append(","); + first = false; + + jsonBuilder.Append($"\"{field.Name}\":"); + jsonBuilder.Append(SerializeValue(value)); + } + + jsonBuilder.Append("}"); + return jsonBuilder.ToString(); + } + + /// + /// Serializes a single value, handling enums with [AirshipEnumStringValue] attributes, + /// nullable types, arrays, and nested objects/records. + /// + /// The value to serialize. + /// JSON string representation of the value. + private static string SerializeValue(object value) + { + if (value == null) + { + return "null"; + } + + Type valueType = value.GetType(); + + // Handle nullable types - get the underlying type + Type? underlyingType = Nullable.GetUnderlyingType(valueType); + Type actualType = underlyingType ?? valueType; + + // Handle nullable enums + if (underlyingType != null && underlyingType.IsEnum) + { + Enum enumValue = (Enum)value; + string stringValue = enumValue.ToStringValue(); + return $"\"{EscapeJsonString(stringValue)}\""; + } + + // Handle non-nullable enums + if (actualType.IsEnum) + { + Enum enumValue = (Enum)value; + string stringValue = enumValue.ToStringValue(); + return $"\"{EscapeJsonString(stringValue)}\""; + } + + // Handle arrays (including nullable arrays) + if (actualType.IsArray) + { + Array array = (Array)value; + var arrayBuilder = new StringBuilder(); + arrayBuilder.Append("["); + + for (int i = 0; i < array.Length; i++) + { + if (i > 0) arrayBuilder.Append(","); + arrayBuilder.Append(SerializeValue(array.GetValue(i))); + } + + arrayBuilder.Append("]"); + return arrayBuilder.ToString(); + } + + // Handle IList/ICollection (for generic collections) + if (value is IEnumerable enumerable && !(value is string)) + { + var listBuilder = new StringBuilder(); + listBuilder.Append("["); + + bool first = true; + foreach (object item in enumerable) + { + if (!first) listBuilder.Append(","); + first = false; + listBuilder.Append(SerializeValue(item)); + } + + listBuilder.Append("]"); + return listBuilder.ToString(); + } + + // Handle strings + if (actualType == typeof(string)) + { + return $"\"{EscapeJsonString((string)value)}\""; + } + + // Handle booleans (including nullable) + if (actualType == typeof(bool)) + { + return value.ToString().ToLower(); + } + + // Handle numbers (including nullable) + if (actualType.IsPrimitive || actualType == typeof(decimal) || actualType == typeof(float) || valueType == typeof(double)) + { + return value.ToString(); + } + + // Handle nested objects/records - recursively serialize them + // This handles ConfigEnvironment, IOSConfig, AndroidConfig, etc. + return Serialize(value); + } + + /// + /// Escapes special characters in JSON strings. + /// + /// The string to escape. + /// The escaped string. + private static string EscapeJsonString(string str) + { + if (string.IsNullOrEmpty(str)) + { + return str; + } + + return str + .Replace("\\", "\\\\") + .Replace("\"", "\\\"") + .Replace("\n", "\\n") + .Replace("\r", "\\r") + .Replace("\t", "\\t"); + } + } +} diff --git a/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs b/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs index c46313d7..9f785a08 100644 --- a/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs +++ b/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs @@ -41,6 +41,24 @@ public AirshipPluginAndroid () { } } + /// Internal method to make a Java Array with an array of String values, + /// to be used with the PrivacyManager methods. + public AndroidJavaObject MakeJavaArray(string[] values) { + if (values == null) { + return null; + } + + // Create a Java String[] array using reflection + AndroidJavaClass arrayClass = new AndroidJavaClass("java.lang.reflect.Array"); + AndroidJavaObject arrayObject = arrayClass.CallStatic("newInstance", new AndroidJavaClass("java.lang.String"), values.Length); + + for (int i = 0; i < values.Length; i++) { + arrayClass.CallStatic("set", arrayObject, i, new AndroidJavaObject("java.lang.String", values[i])); + } + + return arrayObject; + } + public void Call (string method, params object[] args) { if (androidPlugin != null) { androidPlugin.Call (method, args); diff --git a/Assets/UrbanAirship/Platforms/ScopedSubscriptionListEditor.cs b/Assets/UrbanAirship/Platforms/ScopedSubscriptionListEditor.cs index 659eb694..2df09e97 100644 --- a/Assets/UrbanAirship/Platforms/ScopedSubscriptionListEditor.cs +++ b/Assets/UrbanAirship/Platforms/ScopedSubscriptionListEditor.cs @@ -23,7 +23,7 @@ internal ScopedSubscriptionListEditor (Action onApply) { /// The subscription list editor. /// The subscription list identifier. /// The subscription scope to unsubscribe. - public ScopedSubscriptionListEditor subscribe (string subscriptionListId, string subscriptionScope) { + public ScopedSubscriptionListEditor Subscribe (string subscriptionListId, string subscriptionScope) { operations.Add (new ScopedSubscriptionListOperation ("subscribe", subscriptionListId, subscriptionScope)); return this; } @@ -34,7 +34,7 @@ public ScopedSubscriptionListEditor subscribe (string subscriptionListId, string /// The subscription list editor. /// The subscription list identifier. /// The subscription scope to unsubscribe. - public ScopedSubscriptionListEditor unsubscribe (string subscriptionListId, string subscriptionScope) { + public ScopedSubscriptionListEditor Unsubscribe (string subscriptionListId, string subscriptionScope) { operations.Add (new ScopedSubscriptionListOperation ("unsubscribe", subscriptionListId, subscriptionScope)); return this; } diff --git a/Assets/UrbanAirship/Platforms/SubscriptionListEditor.cs b/Assets/UrbanAirship/Platforms/SubscriptionListEditor.cs index e6d4c6d4..e96442a6 100644 --- a/Assets/UrbanAirship/Platforms/SubscriptionListEditor.cs +++ b/Assets/UrbanAirship/Platforms/SubscriptionListEditor.cs @@ -22,7 +22,7 @@ internal SubscriptionListEditor (Action onApply) { /// /// The subscription list editor. /// The subscription list identifier. - public SubscriptionListEditor subscribe (string subscriptionListId) { + public SubscriptionListEditor Subscribe (string subscriptionListId) { operations.Add (new SubscriptionListOperation ("subscribe", subscriptionListId)); return this; } @@ -32,7 +32,7 @@ public SubscriptionListEditor subscribe (string subscriptionListId) { /// /// The subscription list editor. /// The subscription list identifier. - public SubscriptionListEditor unsubscribe (string subscriptionListId) { + public SubscriptionListEditor Unsubscribe (string subscriptionListId) { operations.Add (new SubscriptionListOperation ("unsubscribe", subscriptionListId)); return this; } diff --git a/Assets/UrbanAirship/Platforms/TagEditor.cs b/Assets/UrbanAirship/Platforms/TagEditor.cs index a4c7061d..76ee52a9 100644 --- a/Assets/UrbanAirship/Platforms/TagEditor.cs +++ b/Assets/UrbanAirship/Platforms/TagEditor.cs @@ -74,14 +74,14 @@ internal class TagOperation { // Used for JSON encoding/decoding [SerializeField] - private string operation; + private string operationType; [SerializeField] private string[] tags; #pragma warning restore public TagOperation (string operation, ICollection tags) { - this.operation = operation; + this.operationType = operation; this.tags = tags.ToArray (); } } diff --git a/Assets/UrbanAirship/Platforms/TagGroupEditor.cs b/Assets/UrbanAirship/Platforms/TagGroupEditor.cs index 60bd4b9a..f3a6eb92 100755 --- a/Assets/UrbanAirship/Platforms/TagGroupEditor.cs +++ b/Assets/UrbanAirship/Platforms/TagGroupEditor.cs @@ -78,18 +78,18 @@ internal class TagGroupOperation { // Used for JSON encoding/decoding [SerializeField] - private string operation; + private string operationType; [SerializeField] - private string tagGroup; + private string group; [SerializeField] private string[] tags; #pragma warning restore public TagGroupOperation (string operation, string tagGroup, ICollection tags) { - this.operation = operation; - this.tagGroup = tagGroup; + this.operationType = operation; + this.group = tagGroup; this.tags = tags.ToArray (); } } diff --git a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt index cdff6d57..be6cbdde 100644 --- a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt +++ b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt @@ -1,6 +1,11 @@ /* Copyright Airship and Contributors */ package com.urbanairship.unityplugin +import android.R.attr.enabled +import android.R.attr.identifier +import android.os.Build +import androidx.annotation.RequiresApi +import com.google.android.datatransport.runtime.scheduling.SchedulingConfigModule_ConfigFactory.config import com.unity3d.player.UnityPlayer import com.urbanairship.PrivacyManager import com.urbanairship.android.framework.proxy.ProxyLogger @@ -23,48 +28,38 @@ class UnityPlugin { private var listener: String? = null fun setListener(listener: String) { - ProxyLogger.debug("UnityPlugin setListener: $listener") + ProxyLogger.debug("UnityPlugin setListener method call with: $listener") this.listener = listener } // Airship fun takeOff(config: String): Boolean { - ProxyLogger.debug("UnityPlugin takeOff: $config") + ProxyLogger.debug("UnityPlugin takeOff method call with: $config") return airshipProxyInstance.takeOff(JsonValue.parseString(config)) } fun isFlying(): Boolean { - ProxyLogger.debug("UnityPlugin isFlying") + ProxyLogger.debug("UnityPlugin isFlying method call") return airshipProxyInstance.isFlying() } // Channel fun getChannelId(): String? { - ProxyLogger.debug("UnityPlugin getChannelId") + ProxyLogger.debug("UnityPlugin getChannelId method call") return airshipProxyInstance.channel.getChannelId() } fun waitForChannelId(): String { - ProxyLogger.debug("UnityPlugin waitForChannelId") + ProxyLogger.debug("UnityPlugin waitForChannelId method call") return runBlocking(Dispatchers.IO) { airshipProxyInstance.channel.waitForChannelId() } } - fun addTag(tag: String) { - ProxyLogger.debug("UnityPlugin addTag: $tag") - airshipProxyInstance.channel.addTag(tag) - } - - fun removeTag(tag: String) { - ProxyLogger.debug("UnityPlugin removeTag: $tag") - airshipProxyInstance.channel.removeTag(tag) - } - fun getTags(): String { - ProxyLogger.debug("UnityPlugin getTags") + ProxyLogger.debug("UnityPlugin getTags method call") val jsonArray = JSONArray() for (tag in airshipProxyInstance.channel.getTags()) { jsonArray.put(tag) @@ -73,25 +68,25 @@ class UnityPlugin { } fun editTags(payload: String) { - ProxyLogger.debug("UnityPlugin editTags: $payload") + ProxyLogger.debug("UnityPlugin editTags method call with: $payload") try { - airshipProxyInstance.channel.editTags(JsonValue.parseString(payload)) + airshipProxyInstance.channel.editTags(JsonValue.parseString(payload).optMap().opt("values")) } catch (e: JsonException) { ProxyLogger.error("Failed to parse payload", e) } } fun editChannelTagGroups(payload: String) { - ProxyLogger.debug("UnityPlugin editChannelTagGroups: $payload") + ProxyLogger.debug("UnityPlugin editChannelTagGroups method call with: $payload") try { - airshipProxyInstance.channel.editTagGroups(JsonValue.parseString(payload)) + airshipProxyInstance.channel.editTagGroups(JsonValue.parseString(payload).optMap().opt("values")) } catch (e: JsonException) { ProxyLogger.error("Failed to parse payload", e) } } fun editChannelAttributes(payload: String) { - ProxyLogger.debug("UnityPlugin editChannelAttributes: $payload") + ProxyLogger.debug("UnityPlugin editChannelAttributes method call with: $payload") try { airshipProxyInstance.channel.editAttributes(JsonValue.parseString(payload).optMap().opt("values")) } catch (e: JsonException) { @@ -100,7 +95,7 @@ class UnityPlugin { } fun getChannelSubscriptionLists(): String { - ProxyLogger.debug("UnityPlugin getChannelSubscriptionLists") + ProxyLogger.debug("UnityPlugin getChannelSubscriptionLists method call") return runBlocking(Dispatchers.IO) { val jsonArray = JSONArray() for (tag in airshipProxyInstance.channel.getSubscriptionLists()) { @@ -111,7 +106,7 @@ class UnityPlugin { } fun editChannelSubscriptionLists(payload: String) { - ProxyLogger.debug("UnityPlugin editChannelSubscriptionLists: $payload") + ProxyLogger.debug("UnityPlugin editChannelSubscriptionLists method call with: $payload") try { airshipProxyInstance.channel.editSubscriptionLists(JsonValue.parseString(payload).optMap().opt("values")) } catch (e: JsonException) { @@ -122,27 +117,27 @@ class UnityPlugin { // Contact fun identify(namedUserId: String?) { - ProxyLogger.debug("UnityPlugin identify: $namedUserId") + ProxyLogger.debug("UnityPlugin identify method call with: $namedUserId") airshipProxyInstance.contact.identify(namedUserId) } fun reset() { - ProxyLogger.debug("UnityPlugin reset") + ProxyLogger.debug("UnityPlugin reset method call") airshipProxyInstance.contact.reset() } fun getNamedUserId(): String? { - ProxyLogger.debug("UnityPlugin getNamedUserId") + ProxyLogger.debug("UnityPlugin getNamedUserId method call") return airshipProxyInstance.contact.getNamedUserId() } fun notifyRemoteLogin() { - ProxyLogger.debug("UnityPlugin notifyRemoteLogin") + ProxyLogger.debug("UnityPlugin notifyRemoteLogin method call") airshipProxyInstance.contact.notifyRemoteLogin() } fun editContactTagGroups(payload: String) { - ProxyLogger.debug("UnityPlugin editContactTagGroups: $payload") + ProxyLogger.debug("UnityPlugin editContactTagGroups method call with: $payload") try { airshipProxyInstance.contact.editTagGroups(JsonValue.parseString(payload).optMap().opt("values")) } catch (e: JsonException) { @@ -151,7 +146,7 @@ class UnityPlugin { } fun editContactAttributes(payload: String) { - ProxyLogger.debug("UnityPlugin editContactAttributes: $payload") + ProxyLogger.debug("UnityPlugin editContactAttributes method call with: $payload") try { airshipProxyInstance.contact.editAttributes(JsonValue.parseString(payload).optMap().opt("values")) } catch (e: JsonException) { @@ -160,7 +155,7 @@ class UnityPlugin { } fun getContactSubscriptionLists(): String { - ProxyLogger.debug("UnityPlugin getContactSubscriptionLists") + ProxyLogger.debug("UnityPlugin getContactSubscriptionLists method call") // TODO finish this val jsonArray = JSONArray() @@ -171,7 +166,7 @@ class UnityPlugin { } fun editContactSubscriptionLists(payload: String) { - ProxyLogger.debug("UnityPlugin editContactSubscriptionLists: $payload") + ProxyLogger.debug("UnityPlugin editContactSubscriptionLists method call with: $payload") try { airshipProxyInstance.contact.editSubscriptionLists(JsonValue.parseString(payload).optMap().opt("values")) } catch (e: JsonException) { @@ -183,20 +178,20 @@ class UnityPlugin { fun associateIdentifier(key: String, identifier: String?) { if (identifier == null) { - ProxyLogger.debug("UnityPlugin associateIdentifier removed identifier for key: $key") + ProxyLogger.debug("UnityPlugin associateIdentifier method call removed identifier for key: $key") } else { - ProxyLogger.debug("UnityPlugin associateIdentifier with identifier: $identifier for key: $key") + ProxyLogger.debug("UnityPlugin associateIdentifier method call with identifier: $identifier for key: $key") } airshipProxyInstance.analytics.associateIdentifier(key, identifier) } fun trackScreen(screenName: String) { - ProxyLogger.debug("UnityPlugin trackScreen: $screenName") + ProxyLogger.debug("UnityPlugin trackScreen method call with: $screenName") airshipProxyInstance.analytics.trackScreen(screenName) } fun addCustomEvent(eventPayload: String) { - ProxyLogger.debug("UnityPlugin addCustomEvent: $eventPayload") + ProxyLogger.debug("UnityPlugin addCustomEvent method call with: $eventPayload") try { airshipProxyInstance.analytics.addEvent(JsonValue.parseString(eventPayload)) } catch (e: JsonException) { @@ -205,206 +200,221 @@ class UnityPlugin { } fun getSessionId(): String { - ProxyLogger.debug("UnityPlugin getSessionId") + ProxyLogger.debug("UnityPlugin getSessionId method call") return airshipProxyInstance.analytics.getSessionId() } // InApp fun setPaused(paused: Boolean) { - ProxyLogger.debug("UnityPlugin setPaused: $paused") + ProxyLogger.debug("UnityPlugin setPaused method call with: $paused") airshipProxyInstance.inApp.setPaused(paused) } fun isPaused(): Boolean { - ProxyLogger.debug("UnityPlugin isPaused") + ProxyLogger.debug("UnityPlugin isPaused method call") return airshipProxyInstance.inApp.isPaused() } fun setDisplayInterval(displayInterval: Long) { - ProxyLogger.debug("UnityPlugin setDisplayInterval: $displayInterval") + ProxyLogger.debug("UnityPlugin setDisplayInterval method call with: $displayInterval") airshipProxyInstance.inApp.setDisplayInterval(displayInterval) } fun getDisplayInterval(): Long { - ProxyLogger.debug("UnityPlugin getDisplayInterval") + ProxyLogger.debug("UnityPlugin getDisplayInterval method call") return airshipProxyInstance.inApp.getDisplayInterval() } // Locale fun setLocaleOverride(localeIdentifier: String) { - ProxyLogger.debug("UnityPlugin setLocaleOverride: $localeIdentifier") + ProxyLogger.debug("UnityPlugin setLocaleOverride method call with: $localeIdentifier") airshipProxyInstance.locale.setCurrentLocale(localeIdentifier) } fun clearLocaleOverride() { - ProxyLogger.debug("UnityPlugin clearLocaleOverride") + ProxyLogger.debug("UnityPlugin clearLocaleOverride method call") airshipProxyInstance.locale.clearLocale() } fun getLocale(): String { - ProxyLogger.debug("UnityPlugin getLocale") + ProxyLogger.debug("UnityPlugin getLocale method call") return airshipProxyInstance.locale.getCurrentLocale() } // Message Center fun getUnreadCount(): Int { - ProxyLogger.debug("UnityPlugin getUnreadCount") + ProxyLogger.debug("UnityPlugin getUnreadCount method call") return runBlocking(Dispatchers.IO) { airshipProxyInstance.messageCenter.getUnreadMessagesCount() } } fun getMessages(): String { - ProxyLogger.debug("UnityPlugin getMessages") + ProxyLogger.debug("UnityPlugin getMessages method call") return runBlocking(Dispatchers.IO) { JsonValue.wrapOpt(airshipProxyInstance.messageCenter.getMessages()).toString() } } fun markMessageRead(messageId: String) { - ProxyLogger.debug("UnityPlugin markMessageRead: $messageId") - runBlocking(Dispatchers.IO) { + ProxyLogger.debug("UnityPlugin markMessageRead method call with: $messageId") airshipProxyInstance.messageCenter.markMessageRead(messageId) - } } fun deleteMessage(messageId: String) { - ProxyLogger.debug("UnityPlugin deleteMessage: $messageId") - runBlocking(Dispatchers.IO) { + ProxyLogger.debug("UnityPlugin deleteMessage method call with: $messageId") airshipProxyInstance.messageCenter.deleteMessage(messageId) - } } fun refreshMessages() { - ProxyLogger.debug("UnityPlugin refreshMessages") + ProxyLogger.debug("UnityPlugin refreshMessages method call") runBlocking(Dispatchers.IO) { airshipProxyInstance.messageCenter.refreshInbox() } } fun setAutoLaunchDefaultMessageCenter(enabled: Boolean) { - ProxyLogger.debug("UnityPlugin setAutoLaunchDefaultMessageCenter: $enabled") + ProxyLogger.debug("UnityPlugin setAutoLaunchDefaultMessageCenter method call with: $enabled") airshipProxyInstance.messageCenter.setAutoLaunchDefaultMessageCenter(enabled) } fun displayMessageCenter(messageId: String?) { - ProxyLogger.debug("UnityPlugin displayMessageCenter: $messageId") + ProxyLogger.debug("UnityPlugin displayMessageCenter method call with: $messageId") airshipProxyInstance.messageCenter.display(messageId) } fun dismissMessageCenter() { - ProxyLogger.debug("UnityPlugin dismissMessageCenter") + ProxyLogger.debug("UnityPlugin dismissMessageCenter method call") airshipProxyInstance.messageCenter.dismiss() } fun showMessageView(messageId: String) { - ProxyLogger.debug("UnityPlugin showMessageView: $messageId") + ProxyLogger.debug("UnityPlugin showMessageView method call with: $messageId") airshipProxyInstance.messageCenter.showMessageView(messageId) } fun showMessageCenter(messageId: String?) { - ProxyLogger.debug("UnityPlugin showMessageCenter: $messageId") + ProxyLogger.debug("UnityPlugin showMessageCenter method call with: $messageId") airshipProxyInstance.messageCenter.showMessageCenter(messageId) } // Preference Center fun displayPreferenceCenter(preferenceCenterId: String) { - ProxyLogger.debug("UnityPlugin displayPreferenceCenter: $preferenceCenterId") + ProxyLogger.debug("UnityPlugin displayPreferenceCenter method call with: $preferenceCenterId") airshipProxyInstance.preferenceCenter.displayPreferenceCenter(preferenceCenterId) } fun getPreferenceCenterConfig(preferenceCenterId: String): String { - ProxyLogger.debug("UnityPlugin getPreferenceCenterConfig: $preferenceCenterId") + ProxyLogger.debug("UnityPlugin getPreferenceCenterConfig method call with: $preferenceCenterId") return runBlocking(Dispatchers.IO) { JsonValue.wrapOpt(airshipProxyInstance.preferenceCenter.getPreferenceCenterConfig(preferenceCenterId)).toString() } } fun setAutoLaunchDefaultPreferenceCenter(preferenceCenterId: String, autoLaunch: Boolean) { - ProxyLogger.debug("UnityPlugin setAutoLaunchDefaultPreferenceCenter: $preferenceCenterId, $autoLaunch") + ProxyLogger.debug("UnityPlugin setAutoLaunchDefaultPreferenceCenter method call with: $preferenceCenterId, $autoLaunch") airshipProxyInstance.preferenceCenter.setAutoLaunchPreferenceCenter(preferenceCenterId, autoLaunch) } // Privacy Manager fun setEnabledFeatures(features: Array) { - ProxyLogger.debug("UnityPlugin setEnabledFeatures: $features") + ProxyLogger.debug("UnityPlugin setEnabledFeatures method call with: ${features.joinToString()}") airshipProxyInstance.privacyManager.setEnabledFeatures(features.asList()) } fun getEnabledFeatures(): Array { - ProxyLogger.debug("UnityPlugin getEnabledFeatures") + ProxyLogger.debug("UnityPlugin getEnabledFeatures method call") return airshipProxyInstance.privacyManager.getFeatureNames().toTypedArray() } fun enableFeatures(features: Array) { - ProxyLogger.debug("UnityPlugin enableFeatures: $features") + ProxyLogger.debug("UnityPlugin enableFeatures method call with: ${features.joinToString()}") airshipProxyInstance.privacyManager.enableFeatures(features.asList()) } fun disableFeatures(features: Array) { - ProxyLogger.debug("UnityPlugin disableFeatures: $features") + ProxyLogger.debug("UnityPlugin disableFeatures method call with: ${features.joinToString()}") airshipProxyInstance.privacyManager.disableFeatures(features.asList()) } fun isFeaturesEnabled(features: Array): Boolean { - ProxyLogger.debug("UnityPlugin isFeaturesEnabled: $features") + ProxyLogger.debug("UnityPlugin isFeaturesEnabled method call with: ${features.joinToString()}") return airshipProxyInstance.privacyManager.isFeatureEnabled(features.asList()) } // Push fun isUserNotificationsEnabled(): Boolean { - ProxyLogger.debug("UnityPlugin isUserNotificationsEnabled") + ProxyLogger.debug("UnityPlugin isUserNotificationsEnabled method call") return airshipProxyInstance.push.isUserNotificationsEnabled() } fun setUserNotificationsEnabled(enabled: Boolean) { - ProxyLogger.debug("UnityPlugin setUserNotificationsEnabled: $enabled") + ProxyLogger.debug("UnityPlugin setUserNotificationsEnabled method call with: $enabled") airshipProxyInstance.push.setUserNotificationsEnabled(enabled) } fun enableUserNotifications(fallback: String?): Boolean { - ProxyLogger.debug("UnityPlugin enableUserNotifications: $fallback") + ProxyLogger.debug("UnityPlugin enableUserNotifications method call with: $fallback") return runBlocking(Dispatchers.IO) { airshipProxyInstance.push.enableUserPushNotifications( - EnableUserNotificationsArgs.fromJson(JsonValue.parseString(fallback)) - ) + EnableUserNotificationsArgs.fromJson(JsonValue.parseString(fallback)) + ) } } fun getNotificationStatus(): String { - ProxyLogger.debug("UnityPlugin getNotificationStatus") + ProxyLogger.debug("UnityPlugin getNotificationStatus method call") return runBlocking(Dispatchers.IO) { airshipProxyInstance.push.getNotificationStatus().toJsonValue().toString() } } fun getPushToken(): String? { - ProxyLogger.debug("UnityPlugin getPushToken") + ProxyLogger.debug("UnityPlugin getPushToken method call") return airshipProxyInstance.push.getRegistrationToken() } fun getActiveNotifications(): String { - ProxyLogger.debug("UnityPlugin getActiveNotifications") + ProxyLogger.debug("UnityPlugin getActiveNotifications method call") return JsonValue.wrapOpt(airshipProxyInstance.push.getActiveNotifications()).toString() } fun clearNotifications() { - ProxyLogger.debug("UnityPlugin clearNotifications") + ProxyLogger.debug("UnityPlugin clearNotifications method call") airshipProxyInstance.push.clearNotifications() } fun clearNotification(identifier: String) { - ProxyLogger.debug("UnityPlugin clearNotification: $identifier") + ProxyLogger.debug("UnityPlugin clearNotification method call with: $identifier") airshipProxyInstance.push.clearNotification(identifier) } - // TODO Just noticed I forgot to implement the android specific push methods, I need to add that + @RequiresApi(api = Build.VERSION_CODES.O) + fun isNotificationChannelEnabled(channelId: String): Boolean { + ProxyLogger.debug("UnityPlugin isNotificationChannelEnabled method call with: $channelId") + return airshipProxyInstance.push.isNotificationChannelEnabled(channelId) + } + + fun setNotificationConfig(config: String) { + ProxyLogger.debug("UnityPlugin setNotificationConfig method call with: $config") + airshipProxyInstance.push.setNotificationConfig(JsonValue.parseString(config)) + } + + fun setForegroundNotificationsEnabled(enabled: Boolean) { + ProxyLogger.debug("UnityPlugin setForegroundNotificationsEnabled method call with: $enabled") + airshipProxyInstance.push.isForegroundNotificationsEnabled = enabled + } + + fun isForegroundNotificationsEnabled(): Boolean { + ProxyLogger.debug("UnityPlugin isForegroundNotificationsEnabled method call") + return airshipProxyInstance.push.isForegroundNotificationsEnabled + } // TODO finish the implementation @@ -480,8 +490,6 @@ class UnityPlugin { } } - - private fun getPushPayload(message: PushMessage?): String? { if (message == null) { return null From 68baee01d21b6fe0cdd3cbdb4a5b7afb2d5949c6 Mon Sep 17 00:00:00 2001 From: Ulrich GIBERNE Date: Thu, 22 Jan 2026 17:26:39 -0400 Subject: [PATCH 14/27] wip --- Assets/Scripts/UrbanAirshipBehaviour.cs | 270 +++++++++--------- Assets/UrbanAirship/Platforms/Airship.cs | 12 +- .../UrbanAirship/Platforms/AirshipContact.cs | 31 +- .../Platforms/AirshipCoroutineHelper.cs | 93 +++--- .../urbanairship/unityplugin/UnityPlugin.kt | 60 ++-- 5 files changed, 264 insertions(+), 202 deletions(-) diff --git a/Assets/Scripts/UrbanAirshipBehaviour.cs b/Assets/Scripts/UrbanAirshipBehaviour.cs index 4eeb9981..dcde1273 100644 --- a/Assets/Scripts/UrbanAirshipBehaviour.cs +++ b/Assets/Scripts/UrbanAirshipBehaviour.cs @@ -31,12 +31,13 @@ void Start () { // UAirship.Shared.AddTag (addTagOnStart); // } - // UAirship.Shared.OnPushReceived += OnPushReceived; - // UAirship.Shared.OnChannelUpdated += OnChannelUpdated; - // UAirship.Shared.OnDeepLinkReceived += OnDeepLinkReceived; - // UAirship.Shared.OnPushOpened += OnPushOpened; - // UAirship.Shared.OnInboxUpdated += OnInboxUpdated; - // UAirship.Shared.OnShowInbox += OnShowInbox; + Airship.Shared.OnPushReceived += OnPushReceived; + Airship.Shared.OnPushOpened += OnPushOpened; + Airship.Shared.OnChannelCreated += OnChannelCreated; + Airship.Shared.OnDeepLinkReceived += OnDeepLinkReceived; + Airship.Shared.OnInboxUpdated += OnInboxUpdated; + Airship.Shared.OnShowInbox += OnShowInbox; + Airship.Shared.OnPreferenceCenterDisplay += OnPreferenceCenterDisplay; // PrivacyManager Debug.Log("Set Enabled features to none"); @@ -72,14 +73,14 @@ void Start () { // Channel Debug.Log("Channel ID: " + Airship.Shared.channel.GetChannelId()); - // StartCoroutine(Airship.Shared.channel.WaitForChannelId( - // onComplete: (channelId) => { - // Debug.Log($"Channel ID received: {channelId}"); - // }, - // onError: (error) => { - // Debug.LogError($"Error getting channel ID: {error.Message}"); - // } - // )); + StartCoroutine(Airship.Shared.channel.WaitForChannelId( + onComplete: (channelId) => { + Debug.Log($"Channel ID received: {channelId}"); + }, + onError: (error) => { + Debug.LogError($"Error getting channel ID: {error.Message}"); + } + )); Airship.Shared.channel.EditTags().AddTag("unity_tag").Apply(); Airship.Shared.channel.EditTags().AddTag("tag_to_remove_1").Apply(); @@ -96,14 +97,14 @@ void Start () { Airship.Shared.channel.EditSubscriptionLists().Subscribe("unity_subscription_list").Apply(); Airship.Shared.channel.EditSubscriptionLists().Subscribe("unity_subscription_list_to_remove").Apply(); - // StartCoroutine(Airship.Shared.channel.GetSubscriptionLists( - // onComplete: (subscriptionLists) => { - // Debug.Log("Channel Subscription lists: " + string.Join(", ", subscriptionLists)); - // }, - // onError: (error) => { - // Debug.LogError("Error getting subscription lists: " + error.Message); - // } - // )); + StartCoroutine(Airship.Shared.channel.GetSubscriptionLists( + onComplete: (subscriptionLists) => { + Debug.Log("Channel Subscription lists: " + string.Join(", ", subscriptionLists)); + }, + onError: (error) => { + Debug.LogError("Error getting subscription lists: " + error.Message); + } + )); Airship.Shared.channel.EditSubscriptionLists().Unsubscribe("unity_subscription_list_to_remove").Apply(); Airship.Shared.channel.EditAttributes().SetAttribute("teststring", "a_string").Apply(); @@ -139,15 +140,17 @@ void Start () { Airship.Shared.contact.EditSubscriptionLists().Subscribe("unity_subscription_list", SubscriptionScope.APP).Apply(); Airship.Shared.contact.EditSubscriptionLists().Subscribe("unity_subscription_list_to_remove", SubscriptionScope.APP).Apply(); Airship.Shared.contact.EditSubscriptionLists().Unsubscribe("unity_subscription_list_to_remove", SubscriptionScope.APP).Apply(); - Debug.Log("Contact Subscription lists: " + string.Join(", ", Airship.Shared.contact.GetSubscriptionLists())); - // StartCoroutine(Airship.Shared.contact.GetSubscriptionLists( - // onComplete: (subscriptionLists) => { - // Debug.Log("Contact Subscription lists: " + string.Join(", ", subscriptionLists)); - // }, - // onError: (error) => { - // Debug.LogError("Error getting subscription lists: " + error.Message); - // } - // )); + StartCoroutine(Airship.Shared.contact.GetSubscriptionLists( + onComplete: (subscriptionLists) => { + Debug.Log("Contact Subscription lists:"); + foreach (var subscription in subscriptionLists) { + Debug.Log($"List: {subscription.Key}, Scopes: {string.Join(", ", subscription.Value)}"); + } + }, + onError: (error) => { + Debug.LogError("Error getting subscription lists: " + error.Message); + } + )); // InApp Airship.Shared.inApp.SetPaused(true); @@ -164,34 +167,34 @@ void Start () { // Debug.Log("Locale: " + Airship.Shared.locale.GetLocale()); // Message Center - // StartCoroutine(Airship.Shared.messageCenter.RefreshInbox( - // onComplete: () => { - // Debug.Log("Refresh inbox complete"); - // }, - // onError: (error) => { - // Debug.LogError("Error refreshing inbox: " + error.Message); - // } - // )); + StartCoroutine(Airship.Shared.messageCenter.RefreshInbox( + onComplete: () => { + Debug.Log("Refresh inbox complete"); + }, + onError: (error) => { + Debug.LogError("Error refreshing inbox: " + error.Message); + } + )); Airship.Shared.messageCenter.SetAutoLaunchDefaultMessageCenter(true); - // StartCoroutine(Airship.Shared.messageCenter.GetUnReadCount( - // onComplete: (unreadCount) => { - // Debug.Log("Unread count: " + unreadCount); - // }, - // onError: (error) => { - // Debug.LogError("Error getting unread count: " + error.Message); - // } - // )); + StartCoroutine(Airship.Shared.messageCenter.GetUnReadCount( + onComplete: (unreadCount) => { + Debug.Log("Unread count: " + unreadCount); + }, + onError: (error) => { + Debug.LogError("Error getting unread count: " + error.Message); + } + )); - // StartCoroutine(Airship.Shared.messageCenter.GetMessages( - // onComplete: (messages) => { - // Debug.Log("Messages: " + string.Join(", ", messages)); - // }, - // onError: (error) => { - // Debug.LogError("Error getting messages: " + error.Message); - // } - // )); + StartCoroutine(Airship.Shared.messageCenter.GetMessages( + onComplete: (messages) => { + Debug.Log("Messages: " + string.Join(", ", messages)); + }, + onError: (error) => { + Debug.LogError("Error getting messages: " + error.Message); + } + )); Airship.Shared.messageCenter.Display(null); // Airship.Shared.messageCenter.ShowMessageCenter(null); @@ -213,8 +216,6 @@ void Start () { Airship.Shared.push.SetUserNotificationsEnabled(false); Debug.Log("User notifications enabled after set to false: " + Airship.Shared.push.IsUserNotificationEnabled()); - // Airship.Shared.push.SetUserNotificationsEnabled(true); - // Debug.Log("User notifications enabled after set to true: " + Airship.Shared.push.IsUserNotificationEnabled()); StartCoroutine(Airship.Shared.push.EnableUserNotifications( new EnabledUserPushNotificationsArgs() { @@ -253,78 +254,91 @@ void Start () { accentColor = "#FF0000", }); + Airship.Shared.push.android.SetForegroundNotificationsEnabled(false); + Debug.Log("Foreground notifications enabled: " + Airship.Shared.push.android.IsForegroundNotificationsEnabled()); Airship.Shared.push.android.SetForegroundNotificationsEnabled(true); - // Debug.Log("Foreground notifications enabled: " + Airship.Shared.push.android.IsForegroundNotificationsEnabled()); - // Airship.Shared.push.android.SetForegroundNotificationsEnabled(false); - // Debug.Log("Foreground notifications enabled after false: " + Airship.Shared.push.android.IsForegroundNotificationsEnabled()); + Debug.Log("Foreground notifications enabled after true: " + Airship.Shared.push.android.IsForegroundNotificationsEnabled()); + } + + void OnDestroy () { + Airship.Shared.OnPushReceived -= OnPushReceived; + Airship.Shared.OnPushOpened -= OnPushOpened; + Airship.Shared.OnChannelCreated -= OnChannelCreated; + Airship.Shared.OnDeepLinkReceived -= OnDeepLinkReceived; + Airship.Shared.OnInboxUpdated -= OnInboxUpdated; + Airship.Shared.OnShowInbox -= OnShowInbox; + Airship.Shared.OnPreferenceCenterDisplay -= OnPreferenceCenterDisplay; + } + + void OnPushReceived (PushMessage message) { + Debug.Log ("Received push! " + message.Alert); + + if (message.Extras != null) { + foreach (KeyValuePair kvp in message.Extras) { + Debug.Log (string.Format ("Extras Key = {0}, Value = {1}", kvp.Key, kvp.Value)); + } + } } - // void OnDestroy () { - // UAirship.Shared.OnPushReceived -= OnPushReceived; - // UAirship.Shared.OnChannelUpdated -= OnChannelUpdated; - // UAirship.Shared.OnDeepLinkReceived -= OnDeepLinkReceived; - // UAirship.Shared.OnPushOpened -= OnPushOpened; - // } - - // void OnPushReceived (PushMessage message) { - // Debug.Log ("Received push! " + message.Alert); - - // if (message.Extras != null) { - // foreach (KeyValuePair kvp in message.Extras) { - // Debug.Log (string.Format ("Extras Key = {0}, Value = {1}", kvp.Key, kvp.Value)); - // } - // } - // } - - // void OnPushOpened (PushMessage message) { - // Debug.Log ("Opened Push! " + message.Alert); - - // if (message.Extras != null) { - // foreach (KeyValuePair kvp in message.Extras) { - // Debug.Log (string.Format ("Extras Key = {0}, Value = {1}", kvp.Key, kvp.Value)); - // } - // } - // } - - // void OnChannelUpdated (string channelId) { - // Debug.Log ("Channel updated: " + channelId); - // } - - // void OnDeepLinkReceived (string deeplink) { - // Debug.Log ("Received deep link: " + deeplink); - // } - - // void OnInboxUpdated (uint messageUnreadCount, uint messageCount) - // { - // Debug.Log("Inbox updated - unread messages: " + messageUnreadCount + " total messages: " + messageCount); - - // IEnumerableinboxMessages = UAirship.Shared.InboxMessages(); - // foreach (InboxMessage inboxMessage in inboxMessages) - // { - // Debug.Log("Message id: " + inboxMessage.id + ", title: " + inboxMessage.title + ", sentDate: " + inboxMessage.sentDate + ", isRead: " + inboxMessage.isRead + ", isDeleted: " + inboxMessage.isDeleted); - // if (inboxMessage.extras == null) - // { - // Debug.Log("Extras is null"); - // } - // else - // { - // foreach (KeyValuePair entry in inboxMessage.extras) - // { - // Debug.Log("Message extras [" + entry.Key + "] = " + entry.Value); - // } - // } - // } - // } - - // void OnShowInbox (string messageId) - // { - // if (messageId == null) - // { - // Debug.Log("OnShowInbox - show inbox"); - // } - // else - // { - // Debug.Log("OnShowInbox - show message: messageId = " + messageId); - // } - // } + void OnPushOpened (PushMessage message) { + Debug.Log ("Opened Push! " + message.Alert); + + if (message.Extras != null) { + foreach (KeyValuePair kvp in message.Extras) { + Debug.Log (string.Format ("Extras Key = {0}, Value = {1}", kvp.Key, kvp.Value)); + } + } + } + + void OnChannelCreated (string channelId) { + Debug.Log ("Channel created: " + channelId); + } + + void OnDeepLinkReceived (string deeplink) { + Debug.Log ("Received deep link: " + deeplink); + } + + void OnInboxUpdated (uint messageUnreadCount, uint messageCount) + { + Debug.Log("Inbox updated - unread messages: " + messageUnreadCount + " total messages: " + messageCount); + + StartCoroutine(Airship.Shared.messageCenter.GetMessages( + onComplete: (messages) => { + foreach (InboxMessage inboxMessage in messages) + { + Debug.Log("Message id: " + inboxMessage.id + ", title: " + inboxMessage.title + ", sentDate: " + inboxMessage.sentDate + ", isRead: " + inboxMessage.isRead + ", isDeleted: " + inboxMessage.isDeleted); + if (inboxMessage.extras == null) + { + Debug.Log("Extras is null"); + } + else + { + foreach (KeyValuePair entry in inboxMessage.extras) + { + Debug.Log("Message extras [" + entry.Key + "] = " + entry.Value); + } + } + } + }, + onError: (error) => { + Debug.LogError("Error getting messages: " + error.Message); + } + )); + } + + void OnShowInbox (string messageId) + { + if (messageId == null) + { + Debug.Log("OnShowInbox - show inbox"); + } + else + { + Debug.Log("OnShowInbox - show message: messageId = " + messageId); + } + } + + void OnPreferenceCenterDisplay (string preferenceCenterId) { + Debug.Log ("Preference Center display - preferenceCenterId: " + preferenceCenterId); + } } diff --git a/Assets/UrbanAirship/Platforms/Airship.cs b/Assets/UrbanAirship/Platforms/Airship.cs index 593c8f9c..58310819 100644 --- a/Assets/UrbanAirship/Platforms/Airship.cs +++ b/Assets/UrbanAirship/Platforms/Airship.cs @@ -234,7 +234,7 @@ void OnPushOpened(string payload) void OnDeepLinkReceived(string deeplink) { - DeepLinkReceivedEventHandler handler = Shared.OnDeepLinkReceived; + DeepLinkReceivedEventHandler handler = Airship.Shared.OnDeepLinkReceived; if (handler != null) { @@ -244,7 +244,7 @@ void OnDeepLinkReceived(string deeplink) void OnChannelCreated(string channelId) { - ChannelCreateEventHandler handler = Shared.OnChannelCreated; + ChannelCreateEventHandler handler = Airship.Shared.OnChannelCreated; if (handler != null) { @@ -254,7 +254,7 @@ void OnChannelCreated(string channelId) void OnInboxUpdated(string counts) { - InboxUpdatedEventHandler handler = Shared.OnInboxUpdated; + InboxUpdatedEventHandler handler = Airship.Shared.OnInboxUpdated; MessageCounts messageCounts = JsonUtility.FromJson(counts); @@ -267,7 +267,7 @@ void OnInboxUpdated(string counts) void OnShowInbox(string messageId) { - ShowInboxEventHandler handler = Shared.OnShowInbox; + ShowInboxEventHandler handler = Airship.Shared.OnShowInbox; if (handler != null) { @@ -284,7 +284,7 @@ void OnShowInbox(string messageId) void OnPreferenceCenterDisplay(string preferenceCenterId) { - PreferenceCenterDisplayEventHandler handler = Shared.OnPreferenceCenterDisplay; + PreferenceCenterDisplayEventHandler handler = Airship.Shared.OnPreferenceCenterDisplay; if (handler != null) { @@ -294,7 +294,7 @@ void OnPreferenceCenterDisplay(string preferenceCenterId) void OnAuthorizedSettingsChanged(AuthorizedNotificationSetting[] authorizedSettings) { - AuthorizedSettingsChangedEventHandler handler = Shared.OnAuthorizedSettingsChanged; + AuthorizedSettingsChangedEventHandler handler = Airship.Shared.OnAuthorizedSettingsChanged; if (handler != null) { diff --git a/Assets/UrbanAirship/Platforms/AirshipContact.cs b/Assets/UrbanAirship/Platforms/AirshipContact.cs index e4c95645..606f5f96 100644 --- a/Assets/UrbanAirship/Platforms/AirshipContact.cs +++ b/Assets/UrbanAirship/Platforms/AirshipContact.cs @@ -72,22 +72,31 @@ public AttributeEditor EditAttributes() } /// - /// Gets the contact's subscription lists. + /// Gets the contact's subscription lists asynchronously using a coroutine. + /// This method does not block Unity's main thread. /// - /// The subscription lists. - public Dictionary> GetSubscriptionLists() + /// Callback invoked with the subscription lists when the operation completes. + /// Optional callback invoked if an error occurs. + /// A coroutine that can be started with StartCoroutine. + public IEnumerator GetSubscriptionLists(Action>> onComplete, Action onError = null) { - Dictionary> scopedSubscriptionLists = new Dictionary>(); + yield return AirshipCoroutineHelper.RunAsync( + () => { + Dictionary> scopedSubscriptionLists = new Dictionary>(); - string subscriptionListsAsJson = plugin.Call("getContactSubscriptionLists"); - ScopedSubscriptionList[] _scopedSubscriptionLists = JsonArray.FromJson(subscriptionListsAsJson).values; + string subscriptionListsAsJson = plugin.Call("getContactSubscriptionLists"); + ScopedSubscriptionList[] _scopedSubscriptionLists = JsonArray.FromJson(subscriptionListsAsJson).values; - foreach (ScopedSubscriptionList subscriptionList in _scopedSubscriptionLists) - { - scopedSubscriptionLists.Add(subscriptionList.listId, subscriptionList.scopes.AsEnumerable()); - } + foreach (ScopedSubscriptionList subscriptionList in _scopedSubscriptionLists) + { + scopedSubscriptionLists.Add(subscriptionList.listId, subscriptionList.scopes.AsEnumerable()); + } - return scopedSubscriptionLists; + return scopedSubscriptionLists; + }, + onComplete, + onError + ); } /// diff --git a/Assets/UrbanAirship/Platforms/AirshipCoroutineHelper.cs b/Assets/UrbanAirship/Platforms/AirshipCoroutineHelper.cs index 93ae97b1..86a1f3ca 100644 --- a/Assets/UrbanAirship/Platforms/AirshipCoroutineHelper.cs +++ b/Assets/UrbanAirship/Platforms/AirshipCoroutineHelper.cs @@ -2,23 +2,22 @@ using System; using System.Collections; +using System.Threading.Tasks; using UnityEngine; namespace UrbanAirship { /// - /// Helper class to run blocking operations in coroutines. - /// AndroidJavaObject.Call() must run on Unity's main thread, so we call it directly - /// from the coroutine (which runs on the main thread). We yield first to let Unity - /// process a frame, then execute the blocking call. The blocking happens on the main - /// thread, but Unity has had a chance to process input/rendering first. + /// Helper class to run blocking operations asynchronously without blocking Unity's main thread. + /// On Android, JNI calls that use runBlocking in Kotlin will block the calling thread. + /// This helper runs those operations on a background thread with proper JNI thread attachment, + /// then returns results to the main thread via callbacks. /// internal static class AirshipCoroutineHelper { /// - /// Runs a blocking operation on the main thread. Yields first to let Unity process, - /// then executes the blocking call. Since coroutines run on the main thread, this - /// ensures AndroidJavaObject.Call() works correctly. + /// Runs a blocking operation on a background thread to avoid ANRs. + /// On Android, attaches/detaches the thread to/from the JVM. /// /// The return type /// The blocking operation to run @@ -26,21 +25,35 @@ internal static class AirshipCoroutineHelper { /// Optional callback invoked if an error occurs /// A coroutine public static IEnumerator RunAsync(Func operation, Action onComplete, Action onError = null) { - // Yield first to let Unity process a frame - yield return null; - T result = default(T); Exception exception = null; + bool completed = false; - try { - result = operation(); - } catch (Exception e) { - exception = e; - Debug.LogError($"[AirshipCoroutineHelper] Exception: {e.Message}\n{e.StackTrace}"); - } + // Run the blocking operation on a background thread + Task.Run(() => { + try { +#if UNITY_ANDROID && !UNITY_EDITOR + // Attach this thread to the JVM for JNI calls + AndroidJNI.AttachCurrentThread(); + try { + result = operation(); + } finally { + AndroidJNI.DetachCurrentThread(); + } +#else + result = operation(); +#endif + } catch (Exception e) { + exception = e; + } finally { + completed = true; + } + }); - // Yield again before invoking callbacks to ensure we're still on main thread - yield return null; + // Wait for completion without blocking the main thread + while (!completed) { + yield return null; + } // Handle result or error on main thread if (exception != null) { @@ -51,29 +64,42 @@ public static IEnumerator RunAsync(Func operation, Action onComplete, A } /// - /// Runs a blocking operation on the main thread. Yields first to let Unity process, - /// then executes the blocking call. Since coroutines run on the main thread, this - /// ensures AndroidJavaObject.Call() works correctly. + /// Runs a blocking operation on a background thread to avoid ANRs. + /// On Android, attaches/detaches the thread to/from the JVM. /// /// The blocking operation to run /// Callback invoked when the operation completes /// Optional callback invoked if an error occurs /// A coroutine public static IEnumerator RunAsync(Action operation, Action onComplete = null, Action onError = null) { - // Yield first to let Unity process a frame - yield return null; - Exception exception = null; + bool completed = false; - try { - operation(); - } catch (Exception e) { - exception = e; - Debug.LogError($"[AirshipCoroutineHelper] Exception: {e.Message}\n{e.StackTrace}"); - } + // Run the blocking operation on a background thread + Task.Run(() => { + try { +#if UNITY_ANDROID && !UNITY_EDITOR + // Attach this thread to the JVM for JNI calls + AndroidJNI.AttachCurrentThread(); + try { + operation(); + } finally { + AndroidJNI.DetachCurrentThread(); + } +#else + operation(); +#endif + } catch (Exception e) { + exception = e; + } finally { + completed = true; + } + }); - // Yield again before invoking callbacks to ensure we're still on main thread - yield return null; + // Wait for completion without blocking the main thread + while (!completed) { + yield return null; + } // Handle result or error on main thread if (exception != null) { @@ -84,4 +110,3 @@ public static IEnumerator RunAsync(Action operation, Action onComplete = null, A } } } - diff --git a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt index be6cbdde..fe1dd5ca 100644 --- a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt +++ b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt @@ -19,6 +19,7 @@ import com.urbanairship.util.UAStringUtil import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import org.json.JSONArray +import org.json.JSONObject class UnityPlugin { @@ -97,10 +98,10 @@ class UnityPlugin { fun getChannelSubscriptionLists(): String { ProxyLogger.debug("UnityPlugin getChannelSubscriptionLists method call") return runBlocking(Dispatchers.IO) { - val jsonArray = JSONArray() - for (tag in airshipProxyInstance.channel.getSubscriptionLists()) { - jsonArray.put(tag) - } + val jsonArray = JSONArray() + for (tag in airshipProxyInstance.channel.getSubscriptionLists()) { + jsonArray.put(tag) + } jsonArray.toString() } } @@ -156,13 +157,20 @@ class UnityPlugin { fun getContactSubscriptionLists(): String { ProxyLogger.debug("UnityPlugin getContactSubscriptionLists method call") - - // TODO finish this - val jsonArray = JSONArray() -// for (tag in airshipProxyInstance.contact.getSubscriptionLists()) { -// jsonArray.put(tag) -// } - return jsonArray.toString() + return runBlocking(Dispatchers.IO) { + val resultArray = JSONArray() + airshipProxyInstance.contact.getSubscriptionLists().forEach { subscription -> + val scopesArray = JSONArray() + for (scope in subscription.value) { + scopesArray.put(scope) + } + val item = JSONObject() + item.put("listId", subscription.key) + item.put("scopes", scopesArray) + resultArray.put(item) + } + resultArray.toString() + } } fun editContactSubscriptionLists(payload: String) { @@ -468,28 +476,34 @@ class UnityPlugin { } } - // TODO Implement the rest of the listeners (PreferenceCenter) - fun onInboxUpdated() { runBlocking(Dispatchers.IO) { val unreadCount = airshipProxyInstance.messageCenter.getUnreadMessagesCount() val messages = airshipProxyInstance.messageCenter.getMessages() val totalCount = messages.size - val counts: JsonMap = JsonMap.newBuilder() - .put("unread", unreadCount) - .put("total", totalCount) - .build() - ProxyLogger.debug( - "UnityPlugin inboxUpdated (unread = %s, total = %s)", - unreadCount, totalCount - ) + val counts: JsonMap = JsonMap.newBuilder() + .put("unread", unreadCount) + .put("total", totalCount) + .build() + ProxyLogger.debug( + "UnityPlugin inboxUpdated (unread = %s, total = %s)", + unreadCount, totalCount + ) - if (listener != null) { - UnityPlayer.UnitySendMessage(listener, "OnInboxUpdated", counts.toString()) + if (listener != null) { + UnityPlayer.UnitySendMessage(listener, "OnInboxUpdated", counts.toString()) } } } + fun onPreferenceCenterDisplay(preferenceCenterId: String) { + ProxyLogger.debug("UnityPlugin preference center display: $preferenceCenterId") + + if (listener != null) { + UnityPlayer.UnitySendMessage(listener, "OnPreferenceCenterDisplay", preferenceCenterId) + } + } + private fun getPushPayload(message: PushMessage?): String? { if (message == null) { return null From 1c3eb9edc3ee061a4b5d2e681789bed31cbd3270 Mon Sep 17 00:00:00 2001 From: Ulrich GIBERNE Date: Wed, 4 Feb 2026 11:01:42 -0400 Subject: [PATCH 15/27] fix listeners, add actions and feature flags --- Assets/Plugins/iOS/UnityPlugin.swift | 21 +++ Assets/Scripts/UrbanAirshipBehaviour.cs | 21 +++ Assets/UrbanAirship/Platforms/Airship.cs | 6 +- .../UrbanAirship/Platforms/AirshipAction.cs | 42 ++++++ .../Platforms/AirshipFeatureFlagManager.cs | 107 ++++++++++++++ .../unityplugin/UnityAutopilot.kt | 2 - .../urbanairship/unityplugin/UnityPlugin.kt | 134 ++++++++++++++++-- 7 files changed, 320 insertions(+), 13 deletions(-) create mode 100644 Assets/UrbanAirship/Platforms/AirshipAction.cs create mode 100644 Assets/UrbanAirship/Platforms/AirshipFeatureFlagManager.cs diff --git a/Assets/Plugins/iOS/UnityPlugin.swift b/Assets/Plugins/iOS/UnityPlugin.swift index 932324b1..edbab432 100644 --- a/Assets/Plugins/iOS/UnityPlugin.swift +++ b/Assets/Plugins/iOS/UnityPlugin.swift @@ -413,6 +413,27 @@ class UnityPlugin: NSObject { case "getQuietTime": return try AirshipJSON.wrap(try AirshipProxy.shared.push.getQuietTime()) + + case "runAction": + return AirshipJSON.wrap( + try await AirshipProxy.shared.actions.runAction( + try requireStringArg(args.first), + try? AirshipJSON.wrap(try requireStringArg(args.second)) + ) + ).toString() + + case "flag": + return AirshipJSON.wrap( + try await AirshipProxy.shared.featureFlagManager.flag( + try requireStringArg(args.first) + ) + ).toString() + + case "trackInteraction": + try AirshipProxy.shared.featureFlagManager.trackInteraction( + try requireCodableArg(args.first) + ) + return nil default: return nil diff --git a/Assets/Scripts/UrbanAirshipBehaviour.cs b/Assets/Scripts/UrbanAirshipBehaviour.cs index dcde1273..44a08f74 100644 --- a/Assets/Scripts/UrbanAirshipBehaviour.cs +++ b/Assets/Scripts/UrbanAirshipBehaviour.cs @@ -258,6 +258,27 @@ void Start () { Debug.Log("Foreground notifications enabled: " + Airship.Shared.push.android.IsForegroundNotificationsEnabled()); Airship.Shared.push.android.SetForegroundNotificationsEnabled(true); Debug.Log("Foreground notifications enabled after true: " + Airship.Shared.push.android.IsForegroundNotificationsEnabled()); + + StartCoroutine(Airship.Shared.actions.RunAction("test_action", "test_value", + onComplete: (result) => { + Debug.Log("Action result: " + result); + }, + onError: (error) => { + Debug.LogError("Error running action: " + error.Message); + } + )); + + StartCoroutine(Airship.Shared.featureFlagManager.Flag("ulrich_feature_flag", + onComplete: (flag) => { + Debug.Log("Feature flag: " + flag); + + Airship.Shared.featureFlagManager.TrackInteraction(flag); + }, + onError: (error) => { + Debug.LogError("Error getting feature flag: " + error.Message); + } + )); + } void OnDestroy () { diff --git a/Assets/UrbanAirship/Platforms/Airship.cs b/Assets/UrbanAirship/Platforms/Airship.cs index 58310819..3a72dd02 100644 --- a/Assets/UrbanAirship/Platforms/Airship.cs +++ b/Assets/UrbanAirship/Platforms/Airship.cs @@ -99,6 +99,8 @@ public class Airship public AirshipPreferenceCenter preferenceCenter; public AirshipPrivacyManager privacyManager; public AirshipLocale locale; + public AirshipAction actions; + public AirshipFeatureFlagManager featureFlagManager; private IAirshipPlugin plugin; internal GameObject gameObject; @@ -166,7 +168,9 @@ private void Init() preferenceCenter = new AirshipPreferenceCenter(plugin); privacyManager = new AirshipPrivacyManager(plugin); locale = new AirshipLocale(plugin); - // TODO finish the rest of the modules + actions = new AirshipAction(plugin); + featureFlagManager = new AirshipFeatureFlagManager(plugin); + // TODO finish the rest of the modules (live activity and live update) gameObject = new GameObject("[AirshipListener]"); gameObject.AddComponent(); diff --git a/Assets/UrbanAirship/Platforms/AirshipAction.cs b/Assets/UrbanAirship/Platforms/AirshipAction.cs new file mode 100644 index 00000000..d26e027e --- /dev/null +++ b/Assets/UrbanAirship/Platforms/AirshipAction.cs @@ -0,0 +1,42 @@ +/* Copyright Airship and Contributors */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UrbanAirship { + + /// + /// Airship action. + /// + public class AirshipAction + { + + private IAirshipPlugin plugin; + + internal AirshipAction(IAirshipPlugin plugin) + { + this.plugin = plugin; + } + + /// + /// Runs an Airship action asynchronously using a coroutine. + /// This method does not block Unity's main thread. + /// + /// The name of the action to run. + /// The action's value. + /// Callback invoked with the action result when the operation completes. + /// Optional callback invoked if an error occurs. + /// A coroutine that can be started with StartCoroutine. + public IEnumerator RunAction(string name, string? value, Action onComplete, Action onError = null) + { + yield return AirshipCoroutineHelper.RunAsync( + () => plugin.Call("runAction", name, value), + onComplete, + onError + ); + } + } +} \ No newline at end of file diff --git a/Assets/UrbanAirship/Platforms/AirshipFeatureFlagManager.cs b/Assets/UrbanAirship/Platforms/AirshipFeatureFlagManager.cs new file mode 100644 index 00000000..f8951505 --- /dev/null +++ b/Assets/UrbanAirship/Platforms/AirshipFeatureFlagManager.cs @@ -0,0 +1,107 @@ +/* Copyright Airship and Contributors */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UrbanAirship { + + /// + /// Airship feature flag manager. + /// + public class AirshipFeatureFlagManager + { + + private IAirshipPlugin plugin; + + internal AirshipFeatureFlagManager(IAirshipPlugin plugin) + { + this.plugin = plugin; + } + + /// + /// Retrieve a given flag's status and associated data by its name asynchronously using a coroutine. + /// This method does not block Unity's main thread. + /// + /// The name of the flag. + /// Callback invoked with the feature flag when the operation completes. + /// Optional callback invoked if an error occurs. + /// A coroutine that can be started with StartCoroutine. + public IEnumerator Flag(string name, Action onComplete, Action onError = null) + { + yield return AirshipCoroutineHelper.RunAsync( + () => { + string flagJson = plugin.Call("flag", name); + FeatureFlag flag = JsonUtility.FromJson(flagJson); + return flag; + }, + onComplete, + onError + ); + } + + /// + /// Tracks a feature flag interaction event. + /// + /// The flag. + public void TrackInteraction(FeatureFlag flag) + { + plugin.Call("trackInteraction", flag.ToJson()); + } + } + + [Serializable] + public record FeatureFlag + { + public bool isEligible; + public bool exists; + + // Stored as JSON strings (serialized on Kotlin/Swift side) + public string? variables; + public string _internal; + + public string ToJson() + { + return $"{{ \"isEligible\":{isEligible}, \"exists\":{exists}, \"variables\":{(variables == null ? "\"\"" : variables)}, \"_internal\":{_internal} }}"; + } + + // public FeatureFlag(InternalFeatureFlag internalFeatureFlag) + // { + // isEligible = internalFeatureFlag.isEligible; + // exists = internalFeatureFlag.exists; + + // if (internalFeatureFlag.variableKeys != null && internalFeatureFlag.variableKeys.Count > 0) + // { + // // Unity's JsonUtility doesn't support embedded dictionaries - create the extras dictionary manually + // variables = new Dictionary(); + // for (int index = 0; index < internalFeatureFlag.variableKeys.Count; index++) + // { + // variables[internalFeatureFlag.variableKeys[index]] = internalFeatureFlag.variableValues[index]; + // } + // } + + // if (internalFeatureFlag._internalKeys != null && internalFeatureFlag._internalKeys.Count > 0) + // { + // // Unity's JsonUtility doesn't support embedded dictionaries - create the extras dictionary manually + // _internal = new Dictionary(); + // for (int index = 0; index < internalFeatureFlag._internalKeys.Count; index++) + // { + // _internal[internalFeatureFlag._internalKeys[index]] = internalFeatureFlag._internalValues[index]; + // } + // } + // } + } + + // [Serializable] + // public class InternalFeatureFlag + // { + // public bool isEligible; + // public bool exists; + // public List? variableKeys; + // public List? variableValues; + // public List _internalKeys; + // public List _internalValues; + // } +} \ No newline at end of file diff --git a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityAutopilot.kt b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityAutopilot.kt index 9a5fb163..e75c0f0e 100644 --- a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityAutopilot.kt +++ b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityAutopilot.kt @@ -18,8 +18,6 @@ class UnityAutopilot : BaseAutopilot() { airship.analytics.registerSDKExtension(Extension.UNITY, BuildConfig.PLUGIN_VERSION) } - // TODO add the listeners - override fun createConfigBuilder(context: Context): AirshipConfigOptions.Builder { val resourceId = context.resources.getIdentifier("airship_config", "xml", context.packageName) if (resourceId <= 0) { diff --git a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt index fe1dd5ca..5d5006ef 100644 --- a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt +++ b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt @@ -1,33 +1,77 @@ /* Copyright Airship and Contributors */ package com.urbanairship.unityplugin -import android.R.attr.enabled -import android.R.attr.identifier +import android.R.id.message import android.os.Build +import android.os.Bundle import androidx.annotation.RequiresApi -import com.google.android.datatransport.runtime.scheduling.SchedulingConfigModule_ConfigFactory.config import com.unity3d.player.UnityPlayer +import com.urbanairship.Autopilot import com.urbanairship.PrivacyManager +import com.urbanairship.android.framework.proxy.EventType +import com.urbanairship.android.framework.proxy.MessageCenterMessage import com.urbanairship.android.framework.proxy.ProxyLogger +import com.urbanairship.android.framework.proxy.events.EventEmitter import com.urbanairship.android.framework.proxy.proxies.AirshipProxy import com.urbanairship.android.framework.proxy.proxies.EnableUserNotificationsArgs +import com.urbanairship.android.framework.proxy.proxies.FeatureFlagProxy import com.urbanairship.json.JsonException import com.urbanairship.json.JsonMap import com.urbanairship.json.JsonValue +import com.urbanairship.json.optionalField +import com.urbanairship.json.requireMap +import com.urbanairship.messagecenter.MessageCenter import com.urbanairship.push.PushMessage import com.urbanairship.util.UAStringUtil +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch +import kotlinx.coroutines.plus import kotlinx.coroutines.runBlocking import org.json.JSONArray import org.json.JSONObject class UnityPlugin { + private val scope: CoroutineScope = CoroutineScope(Dispatchers.Main) + SupervisorJob() private val airshipProxyInstance = AirshipProxy.shared(UnityPlayer.currentActivity.applicationContext) private var listener: String? = null + init { + Autopilot.automaticTakeOff(UnityPlayer.currentActivity.applicationContext) + + scope.launch { + EventEmitter.shared().pendingEventListener.collect { + notifyPendingEvents() + } + } + } + + private fun notifyPendingEvents() { + EventType.entries.forEach { eventType -> + EventEmitter.shared().processPending(listOf(eventType)) { event -> + when (event.type) { + EventType.CHANNEL_CREATED -> onChannelCreated(event.body.optionalField("channelId")) + EventType.DEEP_LINK_RECEIVED -> onDeepLinkReceived(event.body.optionalField("deepLink")) + EventType.DISPLAY_MESSAGE_CENTER -> onShowInbox(event.body.optionalField("messageId")) + EventType.DISPLAY_PREFERENCE_CENTER -> onPreferenceCenterDisplay(event.body.get("preferenceCenterId").toString()) + EventType.MESSAGE_CENTER_UPDATED -> onInboxUpdated() + EventType.PUSH_TOKEN_RECEIVED -> {} + EventType.FOREGROUND_NOTIFICATION_RESPONSE_RECEIVED -> onPushOpened(event.body.optionalField("pushPayload")) + EventType.BACKGROUND_NOTIFICATION_RESPONSE_RECEIVED -> onPushOpened(event.body.optionalField("pushPayload")) + EventType.FOREGROUND_PUSH_RECEIVED -> onPushReceived(event.body.optionalField("pushPayload")) + EventType.BACKGROUND_PUSH_RECEIVED -> onPushReceived(event.body.optionalField("pushPayload")) + EventType.NOTIFICATION_STATUS_CHANGED -> {} + EventType.PENDING_EMBEDDED_UPDATED -> {} + } + true + } + } + } + fun setListener(listener: String) { ProxyLogger.debug("UnityPlugin setListener method call with: $listener") this.listener = listener @@ -263,7 +307,7 @@ class UnityPlugin { fun getMessages(): String { ProxyLogger.debug("UnityPlugin getMessages method call") return runBlocking(Dispatchers.IO) { - JsonValue.wrapOpt(airshipProxyInstance.messageCenter.getMessages()).toString() + getInboxMessagesAsJSON(airshipProxyInstance.messageCenter.getMessages()) } } @@ -424,21 +468,60 @@ class UnityPlugin { return airshipProxyInstance.push.isForegroundNotificationsEnabled } - // TODO finish the implementation + fun runAction(name: String, value: String?): String { + ProxyLogger.debug("UnityPlugin runAction method call with: $name, $value") + return runBlocking(Dispatchers.IO) { + val actionResult = airshipProxyInstance.actions.runAction(name, JsonValue.parseString(value)) + JsonValue.wrapOpt(actionResult).toString() + } + } + + fun flag(name: String): String { + ProxyLogger.debug("UnityPlugin flag method call with: $name") + return runBlocking(Dispatchers.IO) { + val flagProxy = airshipProxyInstance.featureFlagManager.flag(name) + val flagJson = flagProxy.toJsonValue().optMap() + + // Build a new JSON with _internal and variables as strings + val result = JSONObject() + result.put("isEligible", flagJson.opt("isEligible").getBoolean(false)) + result.put("exists", flagJson.opt("exists").getBoolean(false)) + + // Stringify the nested objects so Unity's JsonUtility can deserialize them + val internal = flagJson.opt("_internal").toJsonValue() + if (!internal.isNull) { + result.put("_internal", internal.toString()) + } - fun onPushReceived(message: PushMessage?) { + val variables = flagJson.opt("variables").toJsonValue() + if (!variables.isNull) { + result.put("variables", variables.toString()) + } + + result.toString() + } + } + + fun trackInteraction(flag: String) { + ProxyLogger.debug("UnityPlugin trackInteraction method call with: $flag") + airshipProxyInstance.featureFlagManager.trackInteraction(FeatureFlagProxy(JsonValue.parseString(flag))) + } + + // TODO finish the implementation (live activity and live update) + + fun onPushReceived(message: JsonValue?) { ProxyLogger.debug("UnityPlugin push received: $message") if (listener != null) { - UnityPlayer.UnitySendMessage(listener, "OnPushReceived", getPushPayload(message)) + UnityPlayer.UnitySendMessage(listener, "OnPushReceived", message.toString()) } } - fun onPushOpened(message: PushMessage?) { + fun onPushOpened(message: JsonValue?) { ProxyLogger.debug("UnityPlugin push opened: $message") if (listener != null) { - UnityPlayer.UnitySendMessage(listener, "OnPushOpened", getPushPayload(message)) + UnityPlayer.UnitySendMessage(listener, "OnPushOpened", message.toString()) } } @@ -504,6 +587,7 @@ class UnityPlugin { } } + // TODO Probably remove that, I don't think we'll need it anymore private fun getPushPayload(message: PushMessage?): String? { if (message == null) { return null @@ -544,10 +628,40 @@ class UnityPlugin { return JsonValue.wrapOpt(payloadMap).toString() } + fun getInboxMessagesAsJSON(messageList: List): String { + val messages: MutableList?> = ArrayList() + for (message in messageList) { + val messageMap: MutableMap = HashMap() + messageMap["id"] = message.id + messageMap["title"] = message.title + messageMap["sentDate"] = message.sentDate + val listIconUrl: String? = message.listIconUrl + if (listIconUrl != null) { + messageMap["listIconUrl"] = listIconUrl + } + messageMap["isRead"] = message.isRead + + if (message.extras.entries.isNotEmpty()) { + val extrasKeys: MutableList = ArrayList() + val extrasValues: MutableList = ArrayList() + + for (entry in message.extras.entries.iterator()) { + extrasKeys.add(entry.key) + extrasValues.add(entry.value) + } + + messageMap["extrasKeys"] = extrasKeys + messageMap["extrasValues"] = extrasValues + } + messages.add(messageMap) + } + return JsonValue.wrapOpt(messages).toString() + } + companion object { private val instance = UnityPlugin() - private val featuresMap = mapOf( + private val FEATURE_MAP = mapOf( "FEATURE_NONE" to PrivacyManager.Feature.NONE, "FEATURE_IN_APP_AUTOMATION" to PrivacyManager.Feature.IN_APP_AUTOMATION, "FEATURE_MESSAGE_CENTER" to PrivacyManager.Feature.MESSAGE_CENTER, From d9a0e06bd3b51fca20fb1491d2eb7d95ad08d09f Mon Sep 17 00:00:00 2001 From: Ulrich GIBERNE Date: Thu, 5 Feb 2026 11:13:35 -0400 Subject: [PATCH 16/27] wip --- .../Platforms/AirshipFeatureFlagManager.cs | 36 ------------------- .../Platforms/AirshipMessageCenter.cs | 1 - 2 files changed, 37 deletions(-) diff --git a/Assets/UrbanAirship/Platforms/AirshipFeatureFlagManager.cs b/Assets/UrbanAirship/Platforms/AirshipFeatureFlagManager.cs index f8951505..aed8f404 100644 --- a/Assets/UrbanAirship/Platforms/AirshipFeatureFlagManager.cs +++ b/Assets/UrbanAirship/Platforms/AirshipFeatureFlagManager.cs @@ -67,41 +67,5 @@ public string ToJson() return $"{{ \"isEligible\":{isEligible}, \"exists\":{exists}, \"variables\":{(variables == null ? "\"\"" : variables)}, \"_internal\":{_internal} }}"; } - // public FeatureFlag(InternalFeatureFlag internalFeatureFlag) - // { - // isEligible = internalFeatureFlag.isEligible; - // exists = internalFeatureFlag.exists; - - // if (internalFeatureFlag.variableKeys != null && internalFeatureFlag.variableKeys.Count > 0) - // { - // // Unity's JsonUtility doesn't support embedded dictionaries - create the extras dictionary manually - // variables = new Dictionary(); - // for (int index = 0; index < internalFeatureFlag.variableKeys.Count; index++) - // { - // variables[internalFeatureFlag.variableKeys[index]] = internalFeatureFlag.variableValues[index]; - // } - // } - - // if (internalFeatureFlag._internalKeys != null && internalFeatureFlag._internalKeys.Count > 0) - // { - // // Unity's JsonUtility doesn't support embedded dictionaries - create the extras dictionary manually - // _internal = new Dictionary(); - // for (int index = 0; index < internalFeatureFlag._internalKeys.Count; index++) - // { - // _internal[internalFeatureFlag._internalKeys[index]] = internalFeatureFlag._internalValues[index]; - // } - // } - // } } - - // [Serializable] - // public class InternalFeatureFlag - // { - // public bool isEligible; - // public bool exists; - // public List? variableKeys; - // public List? variableValues; - // public List _internalKeys; - // public List _internalValues; - // } } \ No newline at end of file diff --git a/Assets/UrbanAirship/Platforms/AirshipMessageCenter.cs b/Assets/UrbanAirship/Platforms/AirshipMessageCenter.cs index 219b18b4..7075697c 100644 --- a/Assets/UrbanAirship/Platforms/AirshipMessageCenter.cs +++ b/Assets/UrbanAirship/Platforms/AirshipMessageCenter.cs @@ -52,7 +52,6 @@ public IEnumerator GetMessages(Action> onComplete, Act var inboxMessages = new List(); string inboxMessagesAsJson = plugin.Call("getMessages"); InternalInboxMessage[] internalInboxMessages = JsonArray.FromJson(inboxMessagesAsJson).values; - // TODO verify this as the proxy provide a the extras into a map // Unity's JsonUtility doesn't support embedded dictionaries - constructor will create the extras dictionary foreach (InternalInboxMessage internalInboxMessage in internalInboxMessages) { From 0b9c5384e22c8e339e359f31edcc274d4a6f262d Mon Sep 17 00:00:00 2001 From: Ulrich GIBERNE Date: Tue, 17 Mar 2026 17:27:04 -0400 Subject: [PATCH 17/27] fix android dep conflict issue --- Assets/UrbanAirship/Editor/UAConfig.cs | 1 - airship.properties | 2 +- .../unityplugin/CustomMessageActivity.java | 11 ----------- .../unityplugin/CustomMessageCenterActivity.java | 9 --------- 4 files changed, 1 insertion(+), 22 deletions(-) delete mode 100644 unity-plugin/src/main/java/com/urbanairship/unityplugin/CustomMessageActivity.java delete mode 100644 unity-plugin/src/main/java/com/urbanairship/unityplugin/CustomMessageCenterActivity.java diff --git a/Assets/UrbanAirship/Editor/UAConfig.cs b/Assets/UrbanAirship/Editor/UAConfig.cs index 5757f387..69e62686 100644 --- a/Assets/UrbanAirship/Editor/UAConfig.cs +++ b/Assets/UrbanAirship/Editor/UAConfig.cs @@ -336,7 +336,6 @@ private void GenerateAndroidLib () { xmlWriter.WriteStartDocument (); xmlWriter.WriteStartElement ("manifest"); xmlWriter.WriteAttributeString ("xmlns", "android", null, "http://schemas.android.com/apk/res/android"); - xmlWriter.WriteAttributeString ("package", "com.urbanairship.unityresources"); xmlWriter.WriteEndElement (); xmlWriter.WriteEndDocument (); } diff --git a/airship.properties b/airship.properties index 11b9fac2..18962094 100644 --- a/airship.properties +++ b/airship.properties @@ -23,6 +23,6 @@ androidTargetSdkVersion = 34 androidMinSdkVersion = 23 # Google Play Services Resolver tag -playServicesResolver = v1.2.178 +playServicesResolver = v1.2.187 unityVersion = 6000.2.8f1 diff --git a/unity-plugin/src/main/java/com/urbanairship/unityplugin/CustomMessageActivity.java b/unity-plugin/src/main/java/com/urbanairship/unityplugin/CustomMessageActivity.java deleted file mode 100644 index 6315415d..00000000 --- a/unity-plugin/src/main/java/com/urbanairship/unityplugin/CustomMessageActivity.java +++ /dev/null @@ -1,11 +0,0 @@ -/* Copyright Airship and Contributors */ - - -package com.urbanairship.unityplugin; - -//import com.urbanairship.messagecenter.ui.MessageActivity; - -// Copy of the MessageActivity so we can set a different theme -public class CustomMessageActivity /*extends MessageActivity*/ { - -} diff --git a/unity-plugin/src/main/java/com/urbanairship/unityplugin/CustomMessageCenterActivity.java b/unity-plugin/src/main/java/com/urbanairship/unityplugin/CustomMessageCenterActivity.java deleted file mode 100644 index 2b2d0ebe..00000000 --- a/unity-plugin/src/main/java/com/urbanairship/unityplugin/CustomMessageCenterActivity.java +++ /dev/null @@ -1,9 +0,0 @@ -/* Copyright Airship and Contributors */ - -package com.urbanairship.unityplugin; - -//import com.urbanairship.messagecenter.ui.MessageCenterActivity; - -// Copy of the MessageCenterActivity so we can set a different theme -public class CustomMessageCenterActivity /*extends MessageCenterActivity*/ { -} From 305b651fe771ffbb02a5c4c91f9f67896899332d Mon Sep 17 00:00:00 2001 From: Ulrich GIBERNE Date: Tue, 17 Mar 2026 17:28:21 -0400 Subject: [PATCH 18/27] fix iOS serialization and build --- Assets/Plugins/iOS/UnityPlugin.swift | 399 +++++++++++------- Assets/UrbanAirship/Platforms/AirshipUtils.cs | 2 +- .../UrbanAirship/Platforms/IAirshipPlugin.cs | 28 +- 3 files changed, 272 insertions(+), 157 deletions(-) diff --git a/Assets/Plugins/iOS/UnityPlugin.swift b/Assets/Plugins/iOS/UnityPlugin.swift index edbab432..af0cf543 100644 --- a/Assets/Plugins/iOS/UnityPlugin.swift +++ b/Assets/Plugins/iOS/UnityPlugin.swift @@ -11,23 +11,63 @@ import AirshipKit import AirshipCore #endif +private let _notHandled = "NOT_HANDLED" + @_cdecl("UnityPlugin_call") -public func UnityPlugin_call(_ method: String, argsJson: String) -> UnsafePointer? { - let args: [String: [Any]] +public func UnityPlugin_call(_ method: UnsafePointer, argsJson: UnsafePointer) -> UnsafePointer? { + let methodStr = String(cString: method) + let argsJsonStr = String(cString: argsJson) + + let args: [Any] do { - args = try AirshipJSON.wrap(argsJson).decode() as? [String: [Any]] ?? [:] + //args = try AirshipJSON.wrap(argsJson).decode() as? [String: [Any]] ?? [:] + let data = argsJsonStr.data(using: .utf8) ?? Data() + args = (try JSONSerialization.jsonObject(with: data) as? [Any]) ?? [] } catch { - AirshipLogger.error("Failed to deserialize arguments for method \(method): \(error)") + AirshipLogger.error("Failed to deserialize arguments for method \(methodStr): \(error)") return UnsafePointer(strdup("{}")) } - let result: Any? + var result: Any? - do { - result = try UnityPlugin.shared.handleCall(method: method, args: args) - } catch { - AirshipLogger.error("Error executing method \(method): \(error)") - return UnsafePointer(strdup("{}")) + // Try sync path + if Thread.isMainThread { + do { + result = try UnityPlugin.shared.handleCall(method: methodStr, args: args) + } catch { + AirshipLogger.error("Error executing method \(methodStr): \(error)") + return UnsafePointer(strdup("{}")) + } + } + + // Fall through to async path + if result == _notHandled { + let semaphore = DispatchSemaphore(value: 0) + var callError: Error? + result = nil + + Task { + do { + result = try await UnityPlugin.shared.handleCallAsync(method: methodStr, args: args) + } catch { + callError = error + } + semaphore.signal() + } + + if Thread.isMainThread { + // Spin the RunLoop instead of blocking, so @MainActor work can execute + while semaphore.wait(timeout: .now()) == .timedOut { + RunLoop.current.run(mode: .default, before: Date(timeIntervalSinceNow: 0.005)) + } + } else { + semaphore.wait() + } + + if let callError { + AirshipLogger.error("Error executing method \(methodStr): \(callError)") + return UnsafePointer(strdup("{}")) + } } do { @@ -50,8 +90,6 @@ class UnityPlugin: NSObject { } private static let initializeOnce: Void = { - AirshipLogger.debug("UnityPlugin class loaded") - // Add Notification Observer NotificationCenter.default.addObserver(forName: UIApplication.didFinishLaunchingNotification, object: nil, @@ -62,7 +100,7 @@ class UnityPlugin: NSObject { }() func handleCall(method: String, args: [Any]) throws -> Any? { - AirshipLogger.debug("UnityPlugin \(method): \(args.first?)") + AirshipLogger.debug("UnityPlugin \(method): \(args.first ?? "")") // TODO check how to handle the class attributes called in the static method // let instance = shared @@ -70,19 +108,25 @@ class UnityPlugin: NSObject { switch method { case "setListener": // shared.listener = requireAnyString(args.first) - listener = try requireStringArg(args.first) + listener = try requireStringArg(args.first) return nil - case "getDeepLink": - let deepLink = convertToJson(storedDeepLink) - if (requireBoolArg(args.first)) { - storedDeepLink = nil - } - return deepLink + // Check if we still need this +// case "getDeepLink": +// let deepLink = convertToJson(storedDeepLink) +// if (requireBoolArg(args.first)) { +// storedDeepLink = nil +// } +// return deepLink // Airship case "takeOff": - return try AirshipProxy.shared.takeOff(json: requireAnyArg(args.first)) + return try MainActor.assumeIsolated { + let config: String = try requireStringArg(args.first) + let data = config.data(using: .utf8) ?? Data() + let configJsonObject = try JSONSerialization.jsonObject(with: data) + return try AirshipProxy.shared.takeOff(json: configJsonObject) + } case "isFlying": return AirshipProxy.shared.isFlying() @@ -91,15 +135,12 @@ class UnityPlugin: NSObject { case "getChannelId": return try AirshipProxy.shared.channel.channelID - case "waitForChannelId": - return try await AirshipProxy.shared.channel.waitForChannelID() - case "addTag": - try AirshipProxy.shared.channel.addTags(requireStringArg(args.first)) + try AirshipProxy.shared.channel.addTags([requireStringArg(args.first)]) return nil case "removeTag": - try AirshipProxy.shared.channel.removeTags(requireStringArg(args.first)) + try AirshipProxy.shared.channel.removeTags([requireStringArg(args.first)]) return nil case "getTags": @@ -123,9 +164,6 @@ class UnityPlugin: NSObject { ) return nil - case "getChannelSubscriptionLists": - return try await AirshipProxy.shared.channel.fetchSubscriptionLists() - case "editChannelSubscriptionLists": try AirshipProxy.shared.channel.editSubscriptionLists( json: try requireAnyArg(args.first) @@ -141,9 +179,6 @@ class UnityPlugin: NSObject { try AirshipProxy.shared.contact.reset() return nil - case "getNamedUserId": - return try await AirshipProxy.shared.contact.namedUserID - case "notifyRemoteLogin": try AirshipProxy.shared.contact.notifyRemoteLogin() return nil @@ -160,9 +195,6 @@ class UnityPlugin: NSObject { ) return nil - case "getContactSubscriptionLists": - return try await AirshipProxy.shared.contact.getSubscriptionLists() - case "editContactSubscriptionLists": try AirshipProxy.shared.contact.editSubscriptionLists( operations: try requireCodableArg(args.first) @@ -175,15 +207,17 @@ class UnityPlugin: NSObject { throw AirshipErrors.error("associateIdentifier call requires 1 to 2 strings parameters.") } try AirshipProxy.shared.analytics.associateIdentifier( - identifier: args.count == 2 ? args[1] : nil, - key: args[0] + identifier: args.count == 2 ? requireStringArg(args[1]) : nil, + key: requireStringArg(args[0]) ) return nil case "trackScreen": - try AirshipProxy.shared.analytics.trackScreen( - try? requireStringArg(args.first) - ) + try MainActor.assumeIsolated { + try AirshipProxy.shared.analytics.trackScreen( + try? requireStringArg(args.first) + ) + } return nil case "addCustomEvent": @@ -193,24 +227,26 @@ class UnityPlugin: NSObject { return nil case "getSessionId": - return try AirshipProxy.shared.analytics.getSessionID() + return try MainActor.assumeIsolated { + try AirshipProxy.shared.analytics.getSessionID() + } // InApp case "setPaused": - try AirshipProxy.shared.inApp.setPaused(try requireBoolArg(args.first)) + try MainActor.assumeIsolated { + try AirshipProxy.shared.inApp.setPaused(try requireBoolArg(args.first)) + } return nil case "isPaused": - return try AirshipProxy.shared.inApp.isPaused() - - case "setDisplayInterval": - try AirshipProxy.shared.inApp.setDisplayInterval( - milliseconds: try requireIntArg(args.first) - ) - return nil + return try MainActor.assumeIsolated { + try AirshipProxy.shared.inApp.isPaused() + } case "getDisplayInterval": - return try AirshipProxy.shared.inApp.getDisplayInterval() + return try MainActor.assumeIsolated { + try AirshipProxy.shared.inApp.getDisplayInterval() + } // Locale case "setLocaleOverride": @@ -227,68 +263,53 @@ class UnityPlugin: NSObject { return try AirshipProxy.shared.locale.currentLocale // Message Center - case "getUnreadCount": - return try await AirshipProxy.shared.messageCenter.unreadCount - - case "getMessages": - return try await AirshipProxy.shared.messageCenter.messages - - case "markMessageRead": - try await AirshipProxy.shared.messageCenter.markMessageRead( - messageID: requireStringArg(args.first) - ) - return nil - - case "deleteMessage": - try await AirshipProxy.shared.messageCenter.deleteMessage( - messageID: requireStringArg(args.first) - ) - return nil - - case "refreshMessages": - try await AirshipProxy.shared.messageCenter.refresh() - return nil - case "setAutoLaunchDefaultMessageCenter": - AirshipProxy.shared.messageCenter.setAutoLaunchDefaultMessageCenter( - try requireBoolArg(args.first) - ) + try MainActor.assumeIsolated { + AirshipProxy.shared.messageCenter.setAutoLaunchDefaultMessageCenter( + try requireBoolArg(args.first) + ) + } return nil case "displayMessageCenter": - try AirshipProxy.shared.messageCenter.display( - messageID: try? requireStringArg(args.first) - ) + try MainActor.assumeIsolated { + try AirshipProxy.shared.messageCenter.display( + messageID: try? requireStringArg(args.first) + ) + } return nil case "dismissMessageCenter": - try AirshipProxy.shared.messageCenter.dismiss() + try MainActor.assumeIsolated { + try AirshipProxy.shared.messageCenter.dismiss() + } return nil case "showMessageView": - try AirshipProxy.shared.messageCenter.showMessageView( - messageID: try requireStringArg(args.first) - ) + try MainActor.assumeIsolated { + try AirshipProxy.shared.messageCenter.showMessageView( + messageID: try requireStringArg(args.first) + ) + } return nil case "showMessageCenter": - try AirshipProxy.shared.messageCenter.showMessageCenter( - messageID: try? requireStringArg(args.first) - ) + try MainActor.assumeIsolated { + try AirshipProxy.shared.messageCenter.showMessageCenter( + messageID: try? requireStringArg(args.first) + ) + } return nil // Preference Center case "displayPreferenceCenter": - try AirshipProxy.shared.preferenceCenter.displayPreferenceCenter( - preferenceCenterID: try requireStringArg(args.first) - ) + try MainActor.assumeIsolated { + try AirshipProxy.shared.preferenceCenter.displayPreferenceCenter( + preferenceCenterID: try requireStringArg(args.first) + ) + } return nil - case "getPreferenceCenterConfig": - return try await AirshipProxy.shared.preferenceCenter.getPreferenceCenterConfig( - preferenceCenterID: try requireStringArg(args.first) - ) - case "setAutoLaunchDefaultPreferenceCenter": guard args.count == 2, @@ -298,10 +319,12 @@ class UnityPlugin: NSObject { throw AirshipErrors.error("setAutoLaunchDefaultPreferenceCenter call requires [String, Bool]") } - AirshipProxy.shared.preferenceCenter.setAutoLaunchPreferenceCenter( - autoLaunch, - preferenceCenterID: identifier - ) + MainActor.assumeIsolated { + AirshipProxy.shared.preferenceCenter.setAutoLaunchPreferenceCenter( + autoLaunch, + preferenceCenterID: identifier + ) + } return nil // Privacy Manager @@ -341,19 +364,10 @@ class UnityPlugin: NSObject { ) return nil - case "enableUserNotifications": - return try await AirshipProxy.shared.push.enableUserPushNotifications( - args: try optionalCodableArg(args.first) - ) - - case "getNotificationStatus": - return try await AirshipProxy.shared.push.notificationStatus - case "getPushToken": - return try AirshipProxy.shared.push.getRegistrationToken() - - case "getActiveNotifications": - return try await AirshipProxy.shared.push.getActiveNotifications() + return try MainActor.assumeIsolated { + try AirshipProxy.shared.push.getRegistrationToken() + } case "clearNotifications": AirshipProxy.shared.push.clearNotifications() @@ -367,15 +381,19 @@ class UnityPlugin: NSObject { // Push iOS case "setForegroundPresentationOptions": - try AirshipProxy.shared.push.setForegroundPresentationOptions( - names: try requireStringArrayArg(args.first) - ) + try MainActor.assumeIsolated { + try AirshipProxy.shared.push.setForegroundPresentationOptions( + names: try requireStringArrayArg(args.first) + ) + } return nil case "setNotificationOptions": - try AirshipProxy.shared.push.setNotificationOptions( - names: try requireStringArrayArg(args.first) - ) + try MainActor.assumeIsolated { + try AirshipProxy.shared.push.setNotificationOptions( + names: try requireStringArrayArg(args.first) + ) + } return nil case "isAutobadgeEnabled": @@ -387,14 +405,10 @@ class UnityPlugin: NSObject { ) return nil - case "setBadgeNumber": - try await AirshipProxy.shared.push.setBadgeNumber( - try requireIntArg(args.first) - ) - return nil - case "getBadgeNumber": - return try AirshipProxy.shared.push.getBadgeNumber() + return try MainActor.assumeIsolated { + try AirshipProxy.shared.push.getBadgeNumber() + } case "setQuietTimeEnabled": try AirshipProxy.shared.push.setQuietTimeEnabled( @@ -414,27 +428,99 @@ class UnityPlugin: NSObject { case "getQuietTime": return try AirshipJSON.wrap(try AirshipProxy.shared.push.getQuietTime()) + case "trackInteraction": + try AirshipProxy.shared.featureFlagManager.trackInteraction( + flag: try requireCodableArg(args.first) + ) + return nil + + default: + return _notHandled + } + } + + func handleCallAsync(method: String, args: [Any]) async throws -> Any? { + AirshipLogger.debug("UnityPlugin \(method): \(args.first ?? "")") + print("UnityPlugin \(method): \(args.first ?? "")") + + switch method { + case "waitForChannelId": + return try await AirshipProxy.shared.channel.waitForChannelID() + + case "getChannelSubscriptionLists": + return try await AirshipProxy.shared.channel.fetchSubscriptionLists() + + case "getContactSubscriptionLists": + return try await AirshipProxy.shared.contact.getSubscriptionLists() + + case "getNamedUserId": + return try await AirshipProxy.shared.contact.namedUserID + + case "setDisplayInterval": + try await AirshipProxy.shared.inApp.setDisplayInterval( + milliseconds: try requireIntArg(args.first) + ) + return nil + + case "getUnreadCount": + return try await AirshipProxy.shared.messageCenter.unreadCount + + case "getMessages": + return try await AirshipProxy.shared.messageCenter.messages + + case "markMessageRead": + try await AirshipProxy.shared.messageCenter.markMessageRead( + messageID: requireStringArg(args.first) + ) + return nil + + case "deleteMessage": + try await AirshipProxy.shared.messageCenter.deleteMessage( + messageID: requireStringArg(args.first) + ) + return nil + + case "refreshMessages": + try await AirshipProxy.shared.messageCenter.refresh() + return nil + + case "getPreferenceCenterConfig": + return try await AirshipProxy.shared.preferenceCenter.getPreferenceCenterConfig( + preferenceCenterID: try requireStringArg(args.first) + ) + + case "enableUserNotifications": + return try await AirshipProxy.shared.push.enableUserPushNotifications( + args: try optionalCodableArg(args.first) + ) + + case "getNotificationStatus": + return try await AirshipProxy.shared.push.notificationStatus + + case "getActiveNotifications": + return try await AirshipProxy.shared.push.getActiveNotifications() + + case "setBadgeNumber": + try await AirshipProxy.shared.push.setBadgeNumber( + try requireIntArg(args.first) + ) + return nil + case "runAction": - return AirshipJSON.wrap( - try await AirshipProxy.shared.actions.runAction( + return try AirshipJSON.wrap( + try await AirshipProxy.shared.action.runAction( try requireStringArg(args.first), - try? AirshipJSON.wrap(try requireStringArg(args.second)) + value: try? AirshipJSON.wrap(try requireStringArg(args[1])) ) ).toString() case "flag": - return AirshipJSON.wrap( + return try AirshipJSON.wrap( try await AirshipProxy.shared.featureFlagManager.flag( - try requireStringArg(args.first) + name: try requireStringArg(args.first) ) ).toString() - case "trackInteraction": - try AirshipProxy.shared.featureFlagManager.trackInteraction( - try requireCodableArg(args.first) - ) - return nil - default: return nil } @@ -488,14 +574,14 @@ class UnityPlugin: NSObject { // Channel Registration Events public func channelCreated(_ notification: Notification) { - guard let channelID = notification.userInfo?[AirshipChannel.channelIdentifierKey] as? String else { - return + guard let channelID = notification.userInfo?[AirshipNotifications.ChannelCreated.channelIDKey] as? String else { + return } AirshipLogger.debug("UnityPlugin channelCreated: \(channelID)") if let listener = self.listener { callUnitySendMessage(objectName: listener, - methodName: "OnChannelUpdated", + methodName: "OnChannelCreated", message: channelID ) } @@ -503,21 +589,26 @@ class UnityPlugin: NSObject { // Inbox Message List Updated Notification public func inboxUpdated() async { - let unreadCount = try await AirshipProxy.shared.messageCenter.unreadCount - let totalCount = try await AirshipProxy.shared.messageCenter.messages.count - - let counts : [String: Any] = [ - "unread": unreadCount, - "total": totalCount - ] + do { + let unreadCount = try await AirshipProxy.shared.messageCenter.unreadCount + let totalCount = try await AirshipProxy.shared.messageCenter.messages.count + + let counts : [String: Any] = [ + "unread": unreadCount, + "total": totalCount + ] - AirshipLogger.debug("UnityPlugin inboxUpdated(unread = \(unreadCount), total = \(totalCount))") + AirshipLogger.debug("UnityPlugin inboxUpdated(unread = \(unreadCount), total = \(totalCount))") - if let listener = self.listener { - callUnitySendMessage(objectName: listener, - methodName: "OnInboxUpdated", - message: convertToJson(counts) - ) + if let listener = self.listener { + callUnitySendMessage(objectName: listener, + methodName: "OnInboxUpdated", + message: convertToJson(counts) + ) + } + } catch { + AirshipLogger.error("Error executing method inboxUpdated: \(error)") + return } } @@ -532,21 +623,21 @@ class UnityPlugin: NSObject { return value } - private func requireStringArg(_ arg: Any) throws -> String { + private func requireStringArg(_ arg: Any?) throws -> String { guard let value: String = arg as? String else { throw AirshipErrors.error("Argument must be a string") } return value } - private func requireBoolArg(_ arg: Any) throws -> Bool { + private func requireBoolArg(_ arg: Any?) throws -> Bool { guard let value: Bool = arg as? Bool else { throw AirshipErrors.error("Argument must be a bool") } return value } - private func requireIntArg(_ arg: Any) throws -> Int { + private func requireIntArg(_ arg: Any?) throws -> Int { let value = try requireAnyArg(arg) if let int = value as? Int { @@ -564,7 +655,7 @@ class UnityPlugin: NSObject { throw AirshipErrors.error("Argument must be an int") } - private func requireDoubleArg(_ arg: Any) throws -> Double { + private func requireDoubleArg(_ arg: Any?) throws -> Double { let value = try requireAnyArg(arg) if let double = value as? Double { @@ -596,7 +687,7 @@ class UnityPlugin: NSObject { return try AirshipJSON.wrap(value).decode() } - private func requireStringArrayArg(_ arg: Any) throws -> [String] { + private func requireStringArrayArg(_ arg: Any?) throws -> [String] { guard let value: [String] = arg as? [String] else { throw AirshipErrors.error("Argument must be a string array") } diff --git a/Assets/UrbanAirship/Platforms/AirshipUtils.cs b/Assets/UrbanAirship/Platforms/AirshipUtils.cs index 9842275e..fbd3da78 100644 --- a/Assets/UrbanAirship/Platforms/AirshipUtils.cs +++ b/Assets/UrbanAirship/Platforms/AirshipUtils.cs @@ -72,7 +72,7 @@ public static string Serialize(object obj) /// /// The value to serialize. /// JSON string representation of the value. - private static string SerializeValue(object value) + public static string SerializeValue(object value) { if (value == null) { diff --git a/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs b/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs index 9f785a08..552f96f4 100644 --- a/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs +++ b/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs @@ -3,6 +3,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Text; using System.Runtime.InteropServices; using UnityEngine; @@ -107,6 +108,21 @@ public T Call (string method, params object[] args) { if (string.IsNullOrEmpty(resultJson) || resultJson == "{}") { return default(T); } + Debug.Log("resultJson: " + resultJson + " for method: " + method); + + Type type = typeof(T); + if (type == typeof(bool)) { + return (T)(object)bool.Parse(resultJson); + } else if (type == typeof(int)) { + return (T)(object)int.Parse(resultJson); + } else if (type == typeof(float)) { + return (T)(object)float.Parse(resultJson, System.Globalization.CultureInfo.InvariantCulture); + } else if (type == typeof(string)) { + if (resultJson.StartsWith("\"") && resultJson.EndsWith("\"")) { + return (T)(object)resultJson.Substring(1, resultJson.Length - 2); + } + return (T)(object)resultJson; + } return JsonUtility.FromJson(resultJson); } @@ -119,9 +135,17 @@ public GameObject Listener { public static string SerializeArgs(params object[] args) { if (args == null || args.Length == 0) { - return "{}"; // Return empty JSON object if no arguments + return "[]"; + } + + var sb = new StringBuilder(); + sb.Append("["); + for (int i = 0; i < args.Length; i++) { + if (i > 0) sb.Append(","); + sb.Append(AirshipUtils.SerializeValue(args[i])); } - return JsonUtility.ToJson(args); + sb.Append("]"); + return sb.ToString(); } } From 94c69e2f0b90ea60f5398fa06d8cef19532a8468 Mon Sep 17 00:00:00 2001 From: Ulrich GIBERNE Date: Wed, 1 Apr 2026 18:09:31 -0400 Subject: [PATCH 19/27] Fix iOS methods and clean a bit --- Assets/Plugins/iOS/UnityPlugin.swift | 135 +++++++++++++----- Assets/Scripts/UrbanAirshipBehaviour.cs | 93 ++++++++---- Assets/UrbanAirship/Editor/UADependencies.xml | 49 ------- Assets/UrbanAirship/Platforms/Airship.cs | 4 +- .../UrbanAirship/Platforms/AirshipContact.cs | 15 +- .../Platforms/AirshipFeatureFlagManager.cs | 20 ++- Assets/UrbanAirship/Platforms/AirshipInApp.cs | 14 +- .../Platforms/AirshipPrivacyManager.cs | 8 -- Assets/UrbanAirship/Platforms/AirshipPush.cs | 115 +++++++++++---- Assets/UrbanAirship/Platforms/AirshipUtils.cs | 56 ++++++++ .../UrbanAirship/Platforms/AttributeEditor.cs | 47 ++++-- .../UrbanAirship/Platforms/IAirshipPlugin.cs | 17 +-- 12 files changed, 396 insertions(+), 177 deletions(-) diff --git a/Assets/Plugins/iOS/UnityPlugin.swift b/Assets/Plugins/iOS/UnityPlugin.swift index af0cf543..b5137ba2 100644 --- a/Assets/Plugins/iOS/UnityPlugin.swift +++ b/Assets/Plugins/iOS/UnityPlugin.swift @@ -20,7 +20,6 @@ public func UnityPlugin_call(_ method: UnsafePointer, argsJson: UnsafePoi let args: [Any] do { - //args = try AirshipJSON.wrap(argsJson).decode() as? [String: [Any]] ?? [:] let data = argsJsonStr.data(using: .utf8) ?? Data() args = (try JSONSerialization.jsonObject(with: data) as? [Any]) ?? [] } catch { @@ -41,7 +40,7 @@ public func UnityPlugin_call(_ method: UnsafePointer, argsJson: UnsafePoi } // Fall through to async path - if result == _notHandled { + if !Thread.isMainThread || (result as? String) == _notHandled { let semaphore = DispatchSemaphore(value: 0) var callError: Error? result = nil @@ -100,7 +99,7 @@ class UnityPlugin: NSObject { }() func handleCall(method: String, args: [Any]) throws -> Any? { - AirshipLogger.debug("UnityPlugin \(method): \(args.first ?? "")") + AirshipLogger.debug("UnityPlugin \(method): \(args)") // TODO check how to handle the class attributes called in the static method // let instance = shared @@ -122,10 +121,7 @@ class UnityPlugin: NSObject { // Airship case "takeOff": return try MainActor.assumeIsolated { - let config: String = try requireStringArg(args.first) - let data = config.data(using: .utf8) ?? Data() - let configJsonObject = try JSONSerialization.jsonObject(with: data) - return try AirshipProxy.shared.takeOff(json: configJsonObject) + return try AirshipProxy.shared.takeOff(json: try requireParsedAnyArg(args.first)) } case "isFlying": @@ -148,25 +144,29 @@ class UnityPlugin: NSObject { case "editTags": try AirshipProxy.shared.channel.editTags( - operations: try requireCodableArg(args.first) + operations: try requireParsedArgWithValues(args.first) ) return nil case "editChannelTagGroups": try AirshipProxy.shared.channel.editTagGroups( - operations: try requireCodableArg(args.first) + operations: try requireParsedArgWithValues(args.first) ) return nil case "editChannelAttributes": try AirshipProxy.shared.channel.editAttributes( - operations: try requireCodableArg(args.first) + operations: try requireParsedArgWithValues(args.first) ) return nil case "editChannelSubscriptionLists": + let parsed = try requireParsedAnyArg(args.first) + guard let dict = parsed as? [String: Any], let values = dict["values"] else { + throw AirshipErrors.error("Missing 'values' key in JSON wrapper") + } try AirshipProxy.shared.channel.editSubscriptionLists( - json: try requireAnyArg(args.first) + json: values ) return nil @@ -185,19 +185,19 @@ class UnityPlugin: NSObject { case "editContactTagGroups": try AirshipProxy.shared.contact.editTagGroups( - operations: try requireCodableArg(args.first) + operations: try requireParsedArgWithValues(args.first) ) return nil case "editContactAttributes": try AirshipProxy.shared.contact.editAttributes( - operations: try requireCodableArg(args.first) + operations: try requireParsedArgWithValues(args.first) ) return nil case "editContactSubscriptionLists": try AirshipProxy.shared.contact.editSubscriptionLists( - operations: try requireCodableArg(args.first) + operations: try requireParsedArgWithValues(args.first) ) return nil @@ -222,7 +222,7 @@ class UnityPlugin: NSObject { case "addCustomEvent": try AirshipProxy.shared.analytics.addEvent( - requireAnyArg(args.first) + try requireParsedAnyArg(args.first) ) return nil @@ -330,7 +330,7 @@ class UnityPlugin: NSObject { // Privacy Manager case "setEnabledFeatures": try AirshipProxy.shared.privacyManager.setEnabled( - featureNames: try requireStringArrayArg(args.first) + featureNames: try requireStringArrayArg(args) ) return nil @@ -339,19 +339,19 @@ class UnityPlugin: NSObject { case "enableFeatures": try AirshipProxy.shared.privacyManager.enable( - featureNames: try requireStringArrayArg(args.first) + featureNames: try requireStringArrayArg(args) ) return nil case "disableFeatures": try AirshipProxy.shared.privacyManager.disable( - featureNames: try requireStringArrayArg(args.first) + featureNames: try requireStringArrayArg(args) ) return nil case "isFeaturesEnabled": return try AirshipProxy.shared.privacyManager.isEnabled( - featuresNames: try requireStringArrayArg(args.first) + featuresNames: try requireStringArrayArg(args) ) // Push @@ -426,11 +426,11 @@ class UnityPlugin: NSObject { return nil case "getQuietTime": - return try AirshipJSON.wrap(try AirshipProxy.shared.push.getQuietTime()) + return try AirshipProxy.shared.push.getQuietTime() case "trackInteraction": try AirshipProxy.shared.featureFlagManager.trackInteraction( - flag: try requireCodableArg(args.first) + flag: try AirshipJSON.wrap(requireParsedAnyArg(args.first)).decode() ) return nil @@ -440,8 +440,7 @@ class UnityPlugin: NSObject { } func handleCallAsync(method: String, args: [Any]) async throws -> Any? { - AirshipLogger.debug("UnityPlugin \(method): \(args.first ?? "")") - print("UnityPlugin \(method): \(args.first ?? "")") + AirshipLogger.debug("UnityPlugin async \(method): \(args)") switch method { case "waitForChannelId": @@ -451,7 +450,14 @@ class UnityPlugin: NSObject { return try await AirshipProxy.shared.channel.fetchSubscriptionLists() case "getContactSubscriptionLists": - return try await AirshipProxy.shared.contact.getSubscriptionLists() + let subscriptionLists = try await AirshipProxy.shared.contact.getSubscriptionLists() + let subscriptionListsData = try AirshipJSON.wrap(subscriptionLists).toString().data(using: .utf8) ?? Data() + let subscriptionListsDict = try JSONSerialization.jsonObject(with: subscriptionListsData) as? [String: [String]] ?? [:] + var resultArray: [[String: Any]] = [] + for (listId, scopes) in subscriptionListsDict { + resultArray.append(["listId": listId, "scopes": scopes]) + } + return resultArray case "getNamedUserId": return try await AirshipProxy.shared.contact.namedUserID @@ -507,19 +513,34 @@ class UnityPlugin: NSObject { return nil case "runAction": - return try AirshipJSON.wrap( - try await AirshipProxy.shared.action.runAction( - try requireStringArg(args.first), - value: try? AirshipJSON.wrap(try requireStringArg(args[1])) - ) - ).toString() + return try await AirshipProxy.shared.action.runAction( + try requireStringArg(args.first), + value: try? AirshipJSON.wrap(try requireStringArg(args[1])) + ) case "flag": - return try AirshipJSON.wrap( - try await AirshipProxy.shared.featureFlagManager.flag( - name: try requireStringArg(args.first) - ) - ).toString() + let flag = try await AirshipProxy.shared.featureFlagManager.flag( + name: try requireStringArg(args.first) + ) + let flagJsonData = try AirshipJSON.wrap(flag).toString().data(using: .utf8) ?? Data() + let flagDict = try JSONSerialization.jsonObject(with: flagJsonData) as? [String: Any] ?? [:] + + var result: [String: Any] = [:] + result["isEligible"] = flagDict["isEligible"] as? Bool ?? false + result["exists"] = flagDict["exists"] as? Bool ?? false + + // Stringify the nested objects so Unity's JsonUtility can deserialize them + if let internalObj = flagDict["_internal"], + let internalData = try? JSONSerialization.data(withJSONObject: internalObj) { + result["_internal"] = String(data: internalData, encoding: .utf8) ?? "" + } + + if let variablesObj = flagDict["variables"], + let variablesData = try? JSONSerialization.data(withJSONObject: variablesObj) { + result["variables"] = String(data: variablesData, encoding: .utf8) ?? "" + } + + return result default: return nil @@ -623,6 +644,17 @@ class UnityPlugin: NSObject { return value } + private func requireParsedAnyArg(_ arg: Any?) throws -> Any { + guard let value = arg else { + throw AirshipErrors.error("Missing argument") + } + if let jsonString = value as? String, + let data = jsonString.data(using: .utf8) { + return try JSONSerialization.jsonObject(with: data) + } + return value + } + private func requireStringArg(_ arg: Any?) throws -> String { guard let value: String = arg as? String else { throw AirshipErrors.error("Argument must be a string") @@ -684,9 +716,42 @@ class UnityPlugin: NSObject { guard let value: Any = arg else { return nil } + if let stringValue = value as? String, + let data = stringValue.data(using: .utf8), + let parsed = try? JSONSerialization.jsonObject(with: data) { + return try AirshipJSON.wrap(parsed).decode() + } return try AirshipJSON.wrap(value).decode() } + private func requireParsedArgWithValues(_ arg: Any?) throws -> T { + guard let value = arg else { + throw AirshipErrors.error("Missing argument") + } + guard let jsonString = value as? String else { + throw AirshipErrors.error("Argument must be a JSON string") + } + guard let data = jsonString.data(using: .utf8) else { + throw AirshipErrors.error("Failed to encode JSON string to UTF-8") + } + let parsed = try JSONSerialization.jsonObject(with: data) + if let dict = parsed as? [String: Any] { + guard let values = dict["values"] else { + throw AirshipErrors.error("Missing 'values' key in JSON wrapper") + } + return try AirshipJSON.wrap(values).decode() + } + return try AirshipJSON.wrap(parsed).decode() + } + + // TODO Delete this if not needed + private func optionalParsedArg(_ arg: Any?) throws -> T? { + guard let value = arg else { + return nil + } + return try requireParsedArgWithValues(value) + } + private func requireStringArrayArg(_ arg: Any?) throws -> [String] { guard let value: [String] = arg as? [String] else { throw AirshipErrors.error("Argument must be a string array") diff --git a/Assets/Scripts/UrbanAirshipBehaviour.cs b/Assets/Scripts/UrbanAirshipBehaviour.cs index 44a08f74..454d4ca7 100644 --- a/Assets/Scripts/UrbanAirshipBehaviour.cs +++ b/Assets/Scripts/UrbanAirshipBehaviour.cs @@ -7,13 +7,12 @@ using UrbanAirship; public class UrbanAirshipBehaviour : MonoBehaviour { - public string addTagOnStart; void Awake () { Airship.Shared.TakeOff(new AirshipConfig() { defaultEnvironment = new ConfigEnvironment() { - appKey = "APP_KEY", - appSecret = "APP_SECRET", + appKey = "VWDwdOFjRTKLRxCeXTVP6g", + appSecret = "5Ifi5rYgTm2QHey9JkP0WA", logLevel = LogLevel.Verbose, }, site = Site.US, @@ -26,10 +25,6 @@ void Start () { Debug.Log("Airship is flying: " + Airship.Shared.IsFlying()); Airship.Shared.push.SetUserNotificationsEnabled(true); - - // if (!string.IsNullOrEmpty (addTagOnStart)) { - // UAirship.Shared.AddTag (addTagOnStart); - // } Airship.Shared.OnPushReceived += OnPushReceived; Airship.Shared.OnPushOpened += OnPushOpened; @@ -102,7 +97,7 @@ void Start () { Debug.Log("Channel Subscription lists: " + string.Join(", ", subscriptionLists)); }, onError: (error) => { - Debug.LogError("Error getting subscription lists: " + error.Message); + Debug.LogError("Error getting channel subscription lists: " + error.Message); } )); Airship.Shared.channel.EditSubscriptionLists().Unsubscribe("unity_subscription_list_to_remove").Apply(); @@ -119,9 +114,23 @@ void Start () { // Contact Airship.Shared.contact.Identify("my_named_user"); - Debug.Log("Named user ID: " + Airship.Shared.contact.GetNamedUserId()); - Airship.Shared.contact.Reset(); - Debug.Log("Named user ID after reset: " + Airship.Shared.contact.GetNamedUserId()); + StartCoroutine(Airship.Shared.contact.GetNamedUserId( + onComplete: (namedUserId) => { + Debug.Log("Named user ID: " + namedUserId); + }, + onError: (error) => { + Debug.LogError("Error getting named user ID: " + error.Message); + } + )); + // Airship.Shared.contact.Reset(); + // StartCoroutine(Airship.Shared.contact.GetNamedUserId( + // onComplete: (namedUserId) => { + // Debug.Log("Named user ID after reset: " + namedUserId); + // }, + // onError: (error) => { + // Debug.LogError("Error getting named user ID: " + error.Message); + // } + // )); Airship.Shared.contact.EditTagGroups().AddTag("unity_tag_group", "tag_1").Apply(); Airship.Shared.contact.EditTagGroups().AddTags("unity_tag_group", new string[] { "tag_2", "tag_3" }).Apply(); @@ -148,7 +157,7 @@ void Start () { } }, onError: (error) => { - Debug.LogError("Error getting subscription lists: " + error.Message); + Debug.LogError("Error getting contact subscription lists: " + error.Message); } )); @@ -158,13 +167,20 @@ void Start () { Airship.Shared.inApp.SetPaused(false); Debug.Log("InApp paused after false: " + Airship.Shared.inApp.IsPaused()); - Airship.Shared.inApp.SetDisplayInterval(TimeSpan.FromSeconds(10)); - Debug.Log("InApp display interval: " + Airship.Shared.inApp.GetDisplayInterval()); + StartCoroutine(Airship.Shared.inApp.SetDisplayInterval(TimeSpan.FromSeconds(10), + onComplete: () => { + Debug.Log("InApp display interval: " + Airship.Shared.inApp.GetDisplayInterval()); + }, + onError: (error) => { + Debug.LogError("Error setting display interval: " + error.Message); + } + )); // Locale Airship.Shared.locale.SetLocaleOverride("en_US"); + Debug.Log("Locale: " + Airship.Shared.locale.GetLocale()); Airship.Shared.locale.ClearLocaleOverride(); - // Debug.Log("Locale: " + Airship.Shared.locale.GetLocale()); + Debug.Log("Locale: " + Airship.Shared.locale.GetLocale()); // Message Center StartCoroutine(Airship.Shared.messageCenter.RefreshInbox( @@ -190,6 +206,9 @@ void Start () { StartCoroutine(Airship.Shared.messageCenter.GetMessages( onComplete: (messages) => { Debug.Log("Messages: " + string.Join(", ", messages)); + foreach (var message in messages) { + Debug.Log(message.title); + } }, onError: (error) => { Debug.LogError("Error getting messages: " + error.Message); @@ -202,15 +221,15 @@ void Start () { // Preference Center Airship.Shared.preferenceCenter.SetAutoLaunchDefaultPreferenceCenter("neat", true); + StartCoroutine(Airship.Shared.preferenceCenter.GetConfig("neat", + onComplete: (config) => { + Debug.Log("Config: " + JsonUtility.ToJson(config)); + }, + onError: (error) => { + Debug.LogError("Error getting config: " + error.Message); + } + )); // Airship.Shared.preferenceCenter.Display("neat"); - // StartCoroutine(Airship.Shared.preferenceCenter.GetConfig("neat", - // onComplete: (config) => { - // Debug.Log("Config: " + JsonUtility.ToJson(config)); - // }, - // onError: (error) => { - // Debug.LogError("Error getting config: " + error.Message); - // } - // )); // Push @@ -240,11 +259,20 @@ void Start () { Debug.Log("Push token: " + Airship.Shared.push.GetPushToken()); - Debug.Log("Active notifications: " + string.Join(", ", Airship.Shared.push.GetActiveNotifications())); + StartCoroutine(Airship.Shared.push.GetActiveNotifications( + onComplete: (notifications) => { + Debug.Log("Active notifications: " + string.Join(", ", notifications)); + }, + onError: (error) => { + Debug.LogError("Error getting active notifications: " + error.Message); + } + )); + + Airship.Shared.push.ClearNotifications(); + Debug.Log("Notifications cleared"); - // Airship.Shared.push.ClearNotifications(); - // Debug.Log("Notifications cleared"); + // Android Push methods Debug.Log("Is notification channel enabled: " + Airship.Shared.push.android.IsNotificationChannelEnabled("test_channel")); Airship.Shared.push.android.SetNotificationConfig(new AndroidNotificationConfig() { @@ -259,6 +287,18 @@ void Start () { Airship.Shared.push.android.SetForegroundNotificationsEnabled(true); Debug.Log("Foreground notifications enabled after true: " + Airship.Shared.push.android.IsForegroundNotificationsEnabled()); + // iOS Push methods + Airship.Shared.push.iOS.SetBadgeNumber(1); + Debug.Log("Badge number: " + Airship.Shared.push.iOS.GetBadgeNumber()); + Airship.Shared.push.iOS.SetBadgeNumber(0); + Debug.Log("Badge number: " + Airship.Shared.push.iOS.GetBadgeNumber()); + + Airship.Shared.push.iOS.SetQuietTimeEnabled(true); + Debug.Log("Quiet time enabled: " + Airship.Shared.push.iOS.IsQuietTimeEnabled()); + Airship.Shared.push.iOS.SetQuietTimeEnabled(false); + Debug.Log("Quiet time enabled: " + Airship.Shared.push.iOS.IsQuietTimeEnabled()); + + StartCoroutine(Airship.Shared.actions.RunAction("test_action", "test_value", onComplete: (result) => { Debug.Log("Action result: " + result); @@ -279,6 +319,7 @@ void Start () { } )); + // TODO fix contact subscription list, and listeners. Then check the TODOs in the code } void OnDestroy () { diff --git a/Assets/UrbanAirship/Editor/UADependencies.xml b/Assets/UrbanAirship/Editor/UADependencies.xml index 0d3bd5a8..6f3c41b5 100644 --- a/Assets/UrbanAirship/Editor/UADependencies.xml +++ b/Assets/UrbanAirship/Editor/UADependencies.xml @@ -8,49 +8,6 @@ https://repo.maven.apache.org/maven2 - - - - - - - - - - - - - - @@ -61,12 +18,6 @@ - - diff --git a/Assets/UrbanAirship/Platforms/Airship.cs b/Assets/UrbanAirship/Platforms/Airship.cs index 3a72dd02..b301ab3e 100644 --- a/Assets/UrbanAirship/Platforms/Airship.cs +++ b/Assets/UrbanAirship/Platforms/Airship.cs @@ -138,7 +138,7 @@ private Airship() { if (Application.isEditor) { - plugin = new StubbedAirshipPlugin(); + plugin = new StubbedAirshipPlugin (); } else { @@ -147,7 +147,7 @@ private Airship() #elif UNITY_IOS plugin = new AirshipPluginiOS (); #else - plugin = new StubbedAirshipPlugin(); + plugin = new StubbedAirshipPlugin (); #endif } diff --git a/Assets/UrbanAirship/Platforms/AirshipContact.cs b/Assets/UrbanAirship/Platforms/AirshipContact.cs index 606f5f96..3f82f012 100644 --- a/Assets/UrbanAirship/Platforms/AirshipContact.cs +++ b/Assets/UrbanAirship/Platforms/AirshipContact.cs @@ -39,12 +39,19 @@ public void Reset() } /// - /// Gets the named user Id. + /// Gets the named user Id asynchronously using a coroutine. + /// This method does not block Unity's main thread. /// - /// The named user Id. - public string? GetNamedUserId() + /// Callback invoked with the named user Id when the operation completes. + /// Optional callback invoked if an error occurs. + /// A coroutine that can be started with StartCoroutine. + public IEnumerator GetNamedUserId(Action onComplete, Action onError = null) { - return plugin.Call("getNamedUserId"); + yield return AirshipCoroutineHelper.RunAsync( + () => plugin.Call("getNamedUserId"), + onComplete, + onError + ); } /// diff --git a/Assets/UrbanAirship/Platforms/AirshipFeatureFlagManager.cs b/Assets/UrbanAirship/Platforms/AirshipFeatureFlagManager.cs index aed8f404..c728c5e9 100644 --- a/Assets/UrbanAirship/Platforms/AirshipFeatureFlagManager.cs +++ b/Assets/UrbanAirship/Platforms/AirshipFeatureFlagManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Text; using System.Linq; using UnityEngine; @@ -64,7 +65,24 @@ public record FeatureFlag public string ToJson() { - return $"{{ \"isEligible\":{isEligible}, \"exists\":{exists}, \"variables\":{(variables == null ? "\"\"" : variables)}, \"_internal\":{_internal} }}"; + //return $"{{ \"isEligible\":{isEligible}, \"exists\":{exists}, \"variables\":{(variables == null ? "\"\"" : variables)}, \"_internal\":{_internal} }}"; + var sb = new StringBuilder(); + sb.Append("{"); + + sb.Append($"\"isEligible\":{(isEligible ? "true" : "false")}"); + sb.Append($",\"exists\":{(exists ? "true" : "false")}"); + + if (!string.IsNullOrEmpty(variables)) + { + sb.Append($",\"variables\":{variables}"); + } + if (!string.IsNullOrEmpty(_internal)) + { + sb.Append($",\"_internal\":{_internal}"); + } + + sb.Append("}"); + return sb.ToString(); } } diff --git a/Assets/UrbanAirship/Platforms/AirshipInApp.cs b/Assets/UrbanAirship/Platforms/AirshipInApp.cs index 48fdd7a6..65439360 100644 --- a/Assets/UrbanAirship/Platforms/AirshipInApp.cs +++ b/Assets/UrbanAirship/Platforms/AirshipInApp.cs @@ -36,11 +36,19 @@ public bool IsPaused () { } /// - /// Sets the display interval for messages. + /// Sets the display interval for messages asynchronously using a coroutine. + /// This method does not block Unity's main thread. /// /// The display interval. - public void SetDisplayInterval (TimeSpan displayInterval) { - plugin.Call ("setDisplayInterval", (long)displayInterval.TotalMilliseconds); + /// Optional callback invoked when the operation completes. + /// Optional callback invoked if an error occurs. + /// A coroutine that can be started with StartCoroutine. + public IEnumerator SetDisplayInterval (TimeSpan displayInterval, Action onComplete = null, Action onError = null) { + yield return AirshipCoroutineHelper.RunAsync( + () => plugin.Call ("setDisplayInterval", (long)displayInterval.TotalMilliseconds), + onComplete, + onError + ); } /// diff --git a/Assets/UrbanAirship/Platforms/AirshipPrivacyManager.cs b/Assets/UrbanAirship/Platforms/AirshipPrivacyManager.cs index 371d9151..9c911d7b 100644 --- a/Assets/UrbanAirship/Platforms/AirshipPrivacyManager.cs +++ b/Assets/UrbanAirship/Platforms/AirshipPrivacyManager.cs @@ -29,8 +29,6 @@ public void SetEnabledFeatures(string[] enabledFeatures) { #if UNITY_ANDROID plugin.Call("setEnabledFeatures", ((AirshipPluginAndroid)plugin).MakeJavaArray(enabledFeatures)); - #elif UNITY_IOS - plugin.Call("setEnabledFeatures", string.Join(",", enabledFeatures)); #else plugin.Call("setEnabledFeatures", enabledFeatures); #endif @@ -53,8 +51,6 @@ public void EnableFeatures(string[] features) { #if UNITY_ANDROID plugin.Call("enableFeatures", ((AirshipPluginAndroid)plugin).MakeJavaArray(features)); - #elif UNITY_IOS - plugin.Call("enableFeatures", string.Join(",", features)); #else plugin.Call("enableFeatures", features); #endif @@ -68,8 +64,6 @@ public void DisableFeatures(string[] features) { #if UNITY_ANDROID plugin.Call("disableFeatures", ((AirshipPluginAndroid)plugin).MakeJavaArray(features)); - #elif UNITY_IOS - plugin.Call("disableFeatures", string.Join(",", features)); #else plugin.Call("disableFeatures", features); #endif @@ -84,8 +78,6 @@ public bool IsFeaturesEnabled(string[] features) { #if UNITY_ANDROID return plugin.Call("isFeaturesEnabled", ((AirshipPluginAndroid)plugin).MakeJavaArray(features)); - #elif UNITY_IOS - return plugin.Call("isFeaturesEnabled", string.Join(",", features)); #else return plugin.Call("isFeaturesEnabled", features); #endif diff --git a/Assets/UrbanAirship/Platforms/AirshipPush.cs b/Assets/UrbanAirship/Platforms/AirshipPush.cs index 8c19357e..28e2dae0 100644 --- a/Assets/UrbanAirship/Platforms/AirshipPush.cs +++ b/Assets/UrbanAirship/Platforms/AirshipPush.cs @@ -19,18 +19,26 @@ public class AirshipPush /// /// iOS only push methods. /// - public readonly AirshipPushIOS iOS; + public readonly IAirshipPushIOS iOS; /// /// Android only push methods. /// - public readonly AirshipPushAndroid android; + public readonly IAirshipPushAndroid android; internal AirshipPush(IAirshipPlugin plugin) { this.plugin = plugin; - iOS = new AirshipPushIOS(plugin); - android = new AirshipPushAndroid(plugin); +#if UNITY_IOS + iOS = new AirshipPushIOS (plugin); +#else + iOS = new StubbedAirshipPushIOS (); +#endif +#if UNITY_ANDROID + android = new AirshipPushAndroid (plugin); +#else + android = new StubbedAirshipPushAndroid (); +#endif } /// @@ -101,26 +109,35 @@ public IEnumerator GetNotificationStatus(Action onComple } /// - /// Gets the list of active notifications. + /// Gets the list of active notifications asynchronously using a coroutine. + /// This method does not block Unity's main thread. /// /// On Android, this list only includes notifications sent through Airship. /// - /// The list of active notifications. - public IEnumerable GetActiveNotifications() + /// Callback invoked with the list of active notifications when the operation completes. + /// Optional callback invoked if an error occurs. + /// A coroutine that can be started with StartCoroutine. + public IEnumerator GetActiveNotifications(Action> onComplete, Action onError = null) { - string jsonPushMessages = plugin.Call("getActiveNotifications"); - if (String.IsNullOrEmpty(jsonPushMessages)) - { - return null; - } - - var pushMessages = new List(); - foreach (string pushMessageAsJson in JsonArray.FromJson(jsonPushMessages).values) - { - pushMessages.Add(PushMessage.FromJson(pushMessageAsJson)); - } - - return pushMessages; + yield return AirshipCoroutineHelper.RunAsync( + () => { + string jsonPushMessages = plugin.Call("getActiveNotifications"); + if (String.IsNullOrEmpty(jsonPushMessages)) + { + return (IEnumerable)new List(); + } + + var pushMessages = new List(); + foreach (string pushMessageAsJson in JsonArray.FromJson(jsonPushMessages).values) + { + pushMessages.Add(PushMessage.FromJson(pushMessageAsJson)); + } + + return (IEnumerable)pushMessages; + }, + onComplete, + onError + ); } /// @@ -144,10 +161,36 @@ public void ClearNotification(string identifier) } } + public interface IAirshipPushIOS { + void SetForegroundPresentationOptions(ForegroundPresentationOption options); + void SetNotificationOptions(NotificationOption[] options); + bool IsAutobadgeEnabled(); + void SetAutobadgeEnabled(bool enabled); + IEnumerator SetBadgeNumber(int badge, Action onComplete = null, Action onError = null); + int GetBadgeNumber(); + void SetQuietTimeEnabled(bool enabled); + bool IsQuietTimeEnabled(); + void SetQuietTime(QuietTime quietTime); + QuietTime? GetQuietTime(); + } + + internal class StubbedAirshipPushIOS : IAirshipPushIOS { + public void SetForegroundPresentationOptions(ForegroundPresentationOption options) {} + public void SetNotificationOptions(NotificationOption[] options) {} + public bool IsAutobadgeEnabled() { return false; } + public void SetAutobadgeEnabled(bool enabled) {} + public IEnumerator SetBadgeNumber(int badge, Action onComplete = null, Action onError = null) { yield break; } + public int GetBadgeNumber() { return 0; } + public void SetQuietTimeEnabled(bool enabled) {} + public bool IsQuietTimeEnabled() { return false; } + public void SetQuietTime(QuietTime quietTime) {} + public QuietTime? GetQuietTime() { return null; } + } + /// /// IOS Push. /// - public class AirshipPushIOS + public class AirshipPushIOS : IAirshipPushIOS { private IAirshipPlugin plugin; @@ -193,12 +236,20 @@ public void SetAutobadgeEnabled(bool enabled) } /// - /// Set the badge number. + /// Sets the badge number asynchronously using a coroutine. + /// This method does not block Unity's main thread. /// /// The badge number. - public void SetBadgeNumber(int badge) + /// Optional callback invoked when the operation completes. + /// Optional callback invoked if an error occurs. + /// A coroutine that can be started with StartCoroutine. + public IEnumerator SetBadgeNumber(int badge, Action onComplete = null, Action onError = null) { - plugin.Call("setBadgeNumber", badge); + yield return AirshipCoroutineHelper.RunAsync( + () => plugin.Call("setBadgeNumber", badge), + onComplete, + onError + ); } /// @@ -249,10 +300,24 @@ public void SetQuietTime(QuietTime quietTime) // TODO Just noticed I forgot some methods, I need to add that } + public interface IAirshipPushAndroid { + bool IsNotificationChannelEnabled(string channel); + void SetNotificationConfig(AndroidNotificationConfig config); + void SetForegroundNotificationsEnabled(bool enabled); + bool IsForegroundNotificationsEnabled(); + } + + internal class StubbedAirshipPushAndroid : IAirshipPushAndroid { + public bool IsNotificationChannelEnabled(string channel) { return false; } + public void SetNotificationConfig(AndroidNotificationConfig config) {} + public void SetForegroundNotificationsEnabled(bool enabled) {} + public bool IsForegroundNotificationsEnabled() { return false; } + } + /// /// Android Push. /// - public class AirshipPushAndroid + public class AirshipPushAndroid : IAirshipPushAndroid { private IAirshipPlugin plugin; diff --git a/Assets/UrbanAirship/Platforms/AirshipUtils.cs b/Assets/UrbanAirship/Platforms/AirshipUtils.cs index fbd3da78..9e686106 100644 --- a/Assets/UrbanAirship/Platforms/AirshipUtils.cs +++ b/Assets/UrbanAirship/Platforms/AirshipUtils.cs @@ -4,6 +4,7 @@ using System.Collections; using System.Reflection; using System.Text; +using UnityEngine; namespace UrbanAirship { @@ -178,5 +179,60 @@ private static string EscapeJsonString(string str) .Replace("\r", "\\r") .Replace("\t", "\\t"); } + + public static T Deserialize(string json) + { + if (string.IsNullOrEmpty(json) || json == "{}") + { + return default(T); + } + Type type = typeof(T); + Type underlyingType = Nullable.GetUnderlyingType(type); + Type actualType = underlyingType ?? type; + // Primitives + if (actualType == typeof(bool)) + return (T)(object)bool.Parse(json); + if (actualType == typeof(int)) + return (T)(object)int.Parse(json); + if (actualType == typeof(long)) + return (T)(object)long.Parse(json); + if (actualType == typeof(float)) + return (T)(object)float.Parse(json, System.Globalization.CultureInfo.InvariantCulture); + if (actualType == typeof(double)) + return (T)(object)double.Parse(json, System.Globalization.CultureInfo.InvariantCulture); + // String + if (actualType == typeof(string)) + { + if (json.StartsWith("\"") && json.EndsWith("\"")) + return (T)(object)json.Substring(1, json.Length - 2); + return (T)(object)json; + } + // string[] — top-level JSON array of strings like ["a","b"] + if (actualType == typeof(string[])) + { + return (T)(object)JsonArray.FromJson(json).values; + } + // For other arrays of serializable objects, use the JsonArray wrapper + if (actualType.IsArray) + { + Type elementType = actualType.GetElementType(); + Type jsonArrayType = typeof(JsonArray<>).MakeGenericType(elementType); + var method = jsonArrayType.GetMethod("FromJson", BindingFlags.Public | BindingFlags.Static); + var wrapper = method.Invoke(null, new object[] { json }); + var valuesField = jsonArrayType.GetField("values"); + return (T)valuesField.GetValue(wrapper); + } + + // Let's check if I can fix QuietTime? result differently + // if (underlyingType != null) + // { + // var method = typeof(JsonUtility).GetMethod("FromJson", new[] { typeof(string) }) + // .MakeGenericMethod(underlyingType); + // return (T)method.Invoke(null, new object[] { json }); + // } + + // Serializable objects — unwrap nullable and use JsonUtility + return JsonUtility.FromJson(json); + } } } diff --git a/Assets/UrbanAirship/Platforms/AttributeEditor.cs b/Assets/UrbanAirship/Platforms/AttributeEditor.cs index 37fe6aca..966020f9 100644 --- a/Assets/UrbanAirship/Platforms/AttributeEditor.cs +++ b/Assets/UrbanAirship/Platforms/AttributeEditor.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Collections.Generic; using System.Linq; +using System.Text; using System.Text.RegularExpressions; using UnityEngine; @@ -147,13 +148,22 @@ private bool IsInvalidField (string key) { /// public void Apply () { if (onApply != null) { - JsonArray jsonArray = new JsonArray (); - jsonArray.values = operations.ToArray (); - string json = jsonArray.ToJson (); - // Remove empty type fields from JSON (Unity's JsonUtility serializes null strings as empty strings) - json = Regex.Replace(json, @",\s*""type""\s*:\s*""""", ""); - json = Regex.Replace(json, @"""type""\s*:\s*""""\s*,", ""); - onApply (json); + // JsonArray jsonArray = new JsonArray (); + // jsonArray.values = operations.ToArray (); + // string json = jsonArray.ToJson (); + // // Remove empty type fields from JSON (Unity's JsonUtility serializes null strings as empty strings) + // json = Regex.Replace(json, @",\s*""type""\s*:\s*""""", ""); + // json = Regex.Replace(json, @"""type""\s*:\s*""""\s*,", ""); + // onApply (json); + + var sb = new System.Text.StringBuilder(); + sb.Append("["); + for (int i = 0; i < operations.Count; i++) { + if (i > 0) sb.Append(","); + sb.Append(operations[i].ToJson()); + } + sb.Append("]"); + onApply(sb.ToString()); } } @@ -193,10 +203,29 @@ internal class AttributeMutation { #pragma warning restore public AttributeMutation (AttributeAction action, string key, string value, AttributeType? type) { - this.action = action.ToString(); + this.action = action.ToString().ToLower(); this.key = key; this.value = value; - this.type = type?.ToString(); + this.type = type?.ToString().ToLower(); + } + + public string ToJson() { + var sb = new System.Text.StringBuilder(); + sb.Append("{"); + sb.Append($"\"action\":\"{action}\",\"key\":\"{key}\""); + if (value != null) { + bool isNumericType = type == "number" || type == "date"; + if (isNumericType) { + sb.Append($",\"value\":{value}"); + } else { + sb.Append($",\"value\":\"{value}\""); + } + } + if (type != null) { + sb.Append($",\"type\":\"{type}\""); + } + sb.Append("}"); + return sb.ToString(); } } } diff --git a/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs b/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs index 552f96f4..fb0da7f6 100644 --- a/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs +++ b/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs @@ -103,6 +103,7 @@ public void Call (string method, params object[] args) { public T Call (string method, params object[] args) { string argsJson = SerializeArgs(args); + Debug.Log("argsJson: " + argsJson + " for method: " + method); string resultJson = UnityPlugin_call(method, argsJson); if (string.IsNullOrEmpty(resultJson) || resultJson == "{}") { @@ -110,21 +111,7 @@ public T Call (string method, params object[] args) { } Debug.Log("resultJson: " + resultJson + " for method: " + method); - Type type = typeof(T); - if (type == typeof(bool)) { - return (T)(object)bool.Parse(resultJson); - } else if (type == typeof(int)) { - return (T)(object)int.Parse(resultJson); - } else if (type == typeof(float)) { - return (T)(object)float.Parse(resultJson, System.Globalization.CultureInfo.InvariantCulture); - } else if (type == typeof(string)) { - if (resultJson.StartsWith("\"") && resultJson.EndsWith("\"")) { - return (T)(object)resultJson.Substring(1, resultJson.Length - 2); - } - return (T)(object)resultJson; - } - - return JsonUtility.FromJson(resultJson); + return AirshipUtils.Deserialize(resultJson); } public GameObject Listener { From 7cb24d1a08e63c37a5da2446bf5db1871f333010 Mon Sep 17 00:00:00 2001 From: Ulrich GIBERNE Date: Wed, 1 Apr 2026 18:10:35 -0400 Subject: [PATCH 20/27] clean up --- unity-plugin/build.gradle | 63 +-------------------------------------- 1 file changed, 1 insertion(+), 62 deletions(-) diff --git a/unity-plugin/build.gradle b/unity-plugin/build.gradle index 8eddc235..69d408f2 100644 --- a/unity-plugin/build.gradle +++ b/unity-plugin/build.gradle @@ -32,77 +32,16 @@ android { kotlinOptions { jvmTarget = '1.8' } - -// kotlinOptions { -// jvmTarget = '1.8' -// } } dependencies { implementation 'androidx.core:core-ktx:1.15.0' def lifecycle = '2.7.0' -// implementation 'androidx.core:core-ktx:1.15.0' compileOnly files('libs/unity-classes.jar') implementation "androidx.annotation:annotation:$airshipProperties.androidxAnnotationVersion" -// implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle" -// implementation "androidx.lifecycle:lifecycle-process:$lifecycle" -// implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle" -// implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle" -// implementation "androidx.lifecycle:lifecycle-runtime-compose:$lifecycle" -// implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle" -// implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle" - // implementation("androidx.lifecycle:lifecycle-common-java8") { - // version { - // strictly("$lifecycle") - // } - // } - // implementation("androidx.lifecycle:lifecycle-process") { - // version { - // strictly("$lifecycle") - // } - // } - // implementation("androidx.lifecycle:lifecycle-livedata-ktx") { - // version { - // strictly("$lifecycle") - // } - // } - // implementation("androidx.lifecycle:lifecycle-runtime-ktx") { - // version { - // strictly("$lifecycle") - // } - // } - // implementation("androidx.lifecycle:lifecycle-runtime-compose") { - // version { - // strictly("$lifecycle") - // } - // } - // implementation("androidx.lifecycle:lifecycle-viewmodel") { - // version { - // strictly("$lifecycle") - // } - // } - // implementation("androidx.lifecycle:lifecycle-viewmodel-ktx") { - // version { - // strictly("$lifecycle") - // } - // } - // implementation 'androidx.startup:startup-runtime:1.2.0' - - // implementation "com.urbanairship.android:urbanairship-fcm:${airshipProperties.androidAirshipVersion}" - // implementation "com.urbanairship.android:urbanairship-message-center:${airshipProperties.androidAirshipVersion}" - // implementation "com.urbanairship.android:urbanairship-automation:${airshipProperties.androidAirshipVersion}" - // implementation "com.urbanairship.android:urbanairship-preference-center:${airshipProperties.androidAirshipVersion}" + implementation "com.urbanairship.android:airship-framework-proxy:${airshipProperties.airshipFrameworkProxyVersion}" - // implementation("com.urbanairship.android:airship-framework-proxy:${airshipProperties.airshipFrameworkProxyVersion}") { - // exclude group: 'androidx.lifecycle', module: 'lifecycle-common-java8' - // exclude group: 'androidx.lifecycle', module: 'lifecycle-process' - // exclude group: 'androidx.lifecycle', module: 'lifecycle-livedata-ktx' - // exclude group: 'androidx.lifecycle', module: 'lifecycle-runtime-ktx' - // exclude group: 'androidx.lifecycle', module: 'lifecycle-runtime-compose' - // exclude group: 'androidx.lifecycle', module: 'lifecycle-viewmodel' - // exclude group: 'androidx.lifecycle', module: 'lifecycle-viewmodel-ktx' - // } } task copyUnityClassesJar(type: Copy) { From 53ed9c615cb91f52ab58a287f01332da0945b8d4 Mon Sep 17 00:00:00 2001 From: Ulrich GIBERNE Date: Wed, 8 Apr 2026 16:50:14 -0400 Subject: [PATCH 21/27] fix iOS listeners --- Assets/Plugins/iOS/UnityPlugin.swift | 240 +++++++++++++++++++----- Assets/Scripts/UrbanAirshipBehaviour.cs | 6 +- 2 files changed, 194 insertions(+), 52 deletions(-) diff --git a/Assets/Plugins/iOS/UnityPlugin.swift b/Assets/Plugins/iOS/UnityPlugin.swift index b5137ba2..2e8e8a02 100644 --- a/Assets/Plugins/iOS/UnityPlugin.swift +++ b/Assets/Plugins/iOS/UnityPlugin.swift @@ -83,9 +83,25 @@ class UnityPlugin: NSObject { public var listener: String? = nil public var storedDeepLink: String? = nil + + private static let eventNames: [AirshipProxyEventType: String] = [ + .authorizedNotificationSettingsChanged: "ios_authorized_notification_settings_changed", + .pushTokenReceived: "push_token_received", + .deepLinkReceived: "OnDeepLinkReceived", + .channelCreated: "OnChannelCreated", + .messageCenterUpdated: "OnInboxUpdated", + .displayMessageCenter: "display_message_center", + .displayPreferenceCenter: "display_preference_center", + .notificationResponseReceived: "OnPushOpened", + .pushReceived: "OnPushReceived", + .notificationStatusChanged: "notification_status_changed", + .liveActivitiesUpdated: "ios_live_activities_updated" + ] private override init() { super.init() + + startEventProcessing() } private static let initializeOnce: Void = { @@ -98,6 +114,14 @@ class UnityPlugin: NSObject { } }() + func startEventProcessing() { + Task { @MainActor in + for await _ in AirshipProxyEventEmitter.shared.pendingEventAdded { + self.notifyPendingEvents() + } + } + } + func handleCall(method: String, args: [Any]) throws -> Any? { AirshipLogger.debug("UnityPlugin \(method): \(args)") @@ -547,95 +571,213 @@ class UnityPlugin: NSObject { } } + @MainActor + private func notifyPendingEvents() { + for eventType in AirshipProxyEventType.allCases { + AirshipProxyEventEmitter.shared.processPendingEvents(type: eventType) { event in + if let data = try? JSONEncoder().encode(event.body), + let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] { + switch event.type { + case .channelCreated: + if let channelId = json["channelId"] as? String { + channelCreated(channelId) + } + case .deepLinkReceived: + if let deepLink = json["deepLink"] as? String { + receivedDeepLink(deepLink) + } + case .pushReceived: + if let pushPayloadRaw = json["pushPayload"], + let pushPayload: ProxyPushPayload = try? AirshipJSON.wrap(pushPayloadRaw).decode(), + let isForeground = json["isForeground"] as? Bool { + receivedNotification(pushPayload, isForeground: isForeground) + } + case .notificationResponseReceived: + if let pushPayloadRaw = json["pushPayload"], + let pushPayload: ProxyPushPayload = try? AirshipJSON.wrap(pushPayloadRaw).decode(), + let isForeground = json["isForeground"] as? Bool, + let actionId = json["actionId"] as? String? { + receivedNotificationResponse(pushPayload, isForeground: isForeground, actionId: actionId) + } + case .messageCenterUpdated: + if let messageCount = json["messageCount"] as? Int, + let messageUnreadCount = json["messageUnreadCount"] as? Int { + inboxUpdated(messageCount: messageCount, messageUnreadCount: messageUnreadCount) + } + case .displayMessageCenter: + if let messageId = json["messageId"] as? String? { + messageCenterDisplayed(messageId) + } + case .displayPreferenceCenter: + if let preferenceCenterId = json["preferenceCenterId"] as? String? { + preferenceCenterDisplayed(preferenceCenterId) + } + case .authorizedNotificationSettingsChanged: + if let authorizedSettings = json["authorizedSettings"] as? [String] { + authorizedNotificationSettingsChanged(authorizedSettings) + } + case .pushTokenReceived: + if let pushToken = json["pushToken"] as? String { + pushTokenReceived(pushToken) + } + case .notificationStatusChanged: + if let statusRaw = json["status"], + let status: NotificationStatus = try? AirshipJSON.wrap(statusRaw).decode() { + notificationStatusChanged(status) + } + case .pendingEmbeddedUpdated, .liveActivitiesUpdated: + break + } + } + return true + } + } + } + // Push Notification Delegates + + public func receivedNotification(_ pushPayload: ProxyPushPayload, isForeground: Bool) { + AirshipLogger.debug("UnityPlugin receivedNotification \(pushPayload)") + + do { + let jsonPush = try AirshipJSON.wrap(pushPayload).toString() + + if let listener = self.listener { + callUnitySendMessage(objectName: listener, + methodName: "OnPushReceived", + message: jsonPush + ) + } + } catch { + AirshipLogger.debug("UnityPlugin failed to serialize push") + } + } + + public func receivedNotificationResponse(_ pushPayload: ProxyPushPayload, isForeground: Bool, actionId: String?) { + AirshipLogger.debug("UnityPlugin receivedNotificationResponse \(pushPayload)") + + do { + let jsonPush = try AirshipJSON.wrap(pushPayload).toString() + + if let listener = self.listener { + callUnitySendMessage(objectName: listener, + methodName: "OnPushOpened", + message: jsonPush + ) + } + } catch { + AirshipLogger.debug("UnityPlugin failed to serialize push") + } + } - public func receivedForegroundNotification(_ userInfo: [AnyHashable: Any], completionHandler: @escaping () -> Void) { - AirshipLogger.debug("UnityPlugin receivedForegroundNotification \(userInfo)") + // Airship DeepLink Delegate + public func receivedDeepLink(_ deepLink: String) { + AirshipLogger.debug("UnityPlugin receivedDeepLink \(deepLink)") + + self.storedDeepLink = deepLink if let listener = self.listener { callUnitySendMessage(objectName: listener, - methodName: "OnPushReceived", - message: convertPushToJson(push: userInfo) + methodName: "OnDeepLinkReceived", + message: deepLink ) - completionHandler() } } - public func receivedNotificationResponse(_ notificationResponse: UNNotificationResponse, completionHandler: @escaping () -> Void) { - AirshipLogger.debug("UnityPlugin receivedNotificationResponse \(notificationResponse)") - + // Channel Creation Event + public func channelCreated(_ channelId: String) { + AirshipLogger.debug("UnityPlugin channelCreated: \(channelId)") + if let listener = self.listener { callUnitySendMessage(objectName: listener, - methodName: "OnPushOpened", - message: convertPushToJson( - push: notificationResponse.notification.request.content.userInfo - ) + methodName: "OnChannelCreated", + message: channelId ) - completionHandler() } } - // Airship DeepLink Delegate - - public func receivedDeepLink(_ url: URL, completionHandler: @escaping () -> Void) { - AirshipLogger.debug("UnityPlugin receivedDeepLink \(url)") + // Inbox Message List Updated Notification + public func inboxUpdated(messageCount: Int, messageUnreadCount: Int) { + let counts : [String: Any] = [ + "unread": messageUnreadCount, + "total": messageCount + ] - let deepLinkString = url.absoluteString - self.storedDeepLink = deepLinkString + AirshipLogger.debug("UnityPlugin inboxUpdated(unread = \(messageUnreadCount), total = \(messageCount))") if let listener = self.listener { callUnitySendMessage(objectName: listener, - methodName: "OnDeepLinkReceived", - message: deepLinkString + methodName: "OnInboxUpdated", + message: convertToJson(counts) ) } - completionHandler() } - // Channel Registration Events - - public func channelCreated(_ notification: Notification) { - guard let channelID = notification.userInfo?[AirshipNotifications.ChannelCreated.channelIDKey] as? String else { - return + // Message Center Display Delegate + + public func messageCenterDisplayed(_ messageId: String? = nil) { + AirshipLogger.debug("UnityPlugin messageCenterDisplayed \(String(describing: messageId))") + + if let listener = self.listener { + callUnitySendMessage(objectName: listener, + methodName: "OnShowInbox", + message: messageId ?? "" + ) } - AirshipLogger.debug("UnityPlugin channelCreated: \(channelID)") + } + + // Preference Center Display Delegate + public func preferenceCenterDisplayed(_ preferenceCenterId: String? = nil) { + AirshipLogger.debug("UnityPlugin preferenceCenterDisplayed \(String(describing: preferenceCenterId))") if let listener = self.listener { callUnitySendMessage(objectName: listener, - methodName: "OnChannelCreated", - message: channelID + methodName: "OnPreferenceCenterDisplay", + message: preferenceCenterId ?? "" + ) + } + } + + public func authorizedNotificationSettingsChanged(_ authorizedSettings: [String]) { + AirshipLogger.debug("UnityPlugin authorizedNotificationSettingsChanged \(String(describing: authorizedSettings))") + + if let listener = self.listener { + callUnitySendMessage(objectName: listener, + methodName: "OnAuthorizedNotificationSettingsChanged", + message: convertToJson(authorizedSettings) ) } } - // Inbox Message List Updated Notification - public func inboxUpdated() async { + public func pushTokenReceived(_ pushToken: String) { + AirshipLogger.debug("UnityPlugin pushTokenReceived \(pushToken)") + + if let listener = self.listener { + callUnitySendMessage(objectName: listener, + methodName: "OnPushTokenReceived", + message: pushToken + ) + } + } + + public func notificationStatusChanged(_ status: NotificationStatus) { + AirshipLogger.debug("UnityPlugin notificationStatusChanged \(status)") + do { - let unreadCount = try await AirshipProxy.shared.messageCenter.unreadCount - let totalCount = try await AirshipProxy.shared.messageCenter.messages.count + let jsonStatus = try AirshipJSON.wrap(status).toString() - let counts : [String: Any] = [ - "unread": unreadCount, - "total": totalCount - ] - - AirshipLogger.debug("UnityPlugin inboxUpdated(unread = \(unreadCount), total = \(totalCount))") - if let listener = self.listener { callUnitySendMessage(objectName: listener, - methodName: "OnInboxUpdated", - message: convertToJson(counts) + methodName: "OnNotificationStatusChanged", + message: jsonStatus ) } } catch { - AirshipLogger.error("Error executing method inboxUpdated: \(error)") - return + AirshipLogger.debug("UnityPlugin failed to serialize notification status") } } - - // TODO Message Center Display Delegates - - // TODO Implement the rest of the delegates (PC and AuthorizedSettings) + + // TODO Implement the rest of the delegates private func requireAnyArg(_ arg: Any? = nil) throws -> Any { guard let value: Any = arg else { diff --git a/Assets/Scripts/UrbanAirshipBehaviour.cs b/Assets/Scripts/UrbanAirshipBehaviour.cs index 454d4ca7..e4fdf14f 100644 --- a/Assets/Scripts/UrbanAirshipBehaviour.cs +++ b/Assets/Scripts/UrbanAirshipBehaviour.cs @@ -11,8 +11,8 @@ public class UrbanAirshipBehaviour : MonoBehaviour { void Awake () { Airship.Shared.TakeOff(new AirshipConfig() { defaultEnvironment = new ConfigEnvironment() { - appKey = "VWDwdOFjRTKLRxCeXTVP6g", - appSecret = "5Ifi5rYgTm2QHey9JkP0WA", + appKey = "APP_KEY", + appSecret = "APP_SECRET", logLevel = LogLevel.Verbose, }, site = Site.US, @@ -319,7 +319,7 @@ void Start () { } )); - // TODO fix contact subscription list, and listeners. Then check the TODOs in the code + // TODO fix listeners. Then check the TODOs in the code } void OnDestroy () { From e563c8bd7dfa69bbae8d95d386be3c1852c543ba Mon Sep 17 00:00:00 2001 From: Ulrich GIBERNE Date: Wed, 8 Apr 2026 18:08:51 -0400 Subject: [PATCH 22/27] missing listeners on Android --- Assets/Plugins/iOS/UnityPlugin.swift | 2 +- Assets/UrbanAirship/Platforms/Airship.cs | 52 +++++++++++++++- .../urbanairship/unityplugin/UnityPlugin.kt | 59 ++++++++++++------- 3 files changed, 89 insertions(+), 24 deletions(-) diff --git a/Assets/Plugins/iOS/UnityPlugin.swift b/Assets/Plugins/iOS/UnityPlugin.swift index 2e8e8a02..14f925b6 100644 --- a/Assets/Plugins/iOS/UnityPlugin.swift +++ b/Assets/Plugins/iOS/UnityPlugin.swift @@ -134,7 +134,7 @@ class UnityPlugin: NSObject { listener = try requireStringArg(args.first) return nil - // Check if we still need this + // TODO Check if we still need this // case "getDeepLink": // let deepLink = convertToJson(storedDeepLink) // if (requireBoolArg(args.first)) { diff --git a/Assets/UrbanAirship/Platforms/Airship.cs b/Assets/UrbanAirship/Platforms/Airship.cs index b301ab3e..62a23509 100644 --- a/Assets/UrbanAirship/Platforms/Airship.cs +++ b/Assets/UrbanAirship/Platforms/Airship.cs @@ -90,6 +90,26 @@ public class Airship /// public event AuthorizedSettingsChangedEventHandler OnAuthorizedSettingsChanged; + /// + /// Push token received event handler. + /// + public delegate void PushTokenReceivedEventHandler(string pushToken); + + /// + /// Occurs when the push token is received. + /// + public event PushTokenReceivedEventHandler OnPushTokenReceived; + + /// + /// Notification status changed event handler. + /// + public delegate void NotificationStatusChangedEventHandler(PushNotificationStatus status); + + /// + /// Occurs when the notification status changed. + /// + public event NotificationStatusChangedEventHandler OnNotificationStatusChanged; + public AirshipChannel channel; public AirshipContact contact; public AirshipAnalytics analytics; @@ -296,13 +316,41 @@ void OnPreferenceCenterDisplay(string preferenceCenterId) } } - void OnAuthorizedSettingsChanged(AuthorizedNotificationSetting[] authorizedSettings) + void OnAuthorizedSettingsChanged(string authorizedSettings) { AuthorizedSettingsChangedEventHandler handler = Airship.Shared.OnAuthorizedSettingsChanged; if (handler != null) { - handler(authorizedSettings); + AuthorizedNotificationSetting[] authorizedSettingsArray = JsonUtility.FromJson(authorizedSettings); + if (authorizedSettingsArray != null) + { + handler(authorizedSettingsArray); + } + } + } + + void OnPushTokenReceived(string pushToken) + { + PushTokenReceivedEventHandler handler = Airship.Shared.OnPushTokenReceived; + + if (handler != null) + { + handler(pushToken); + } + } + + void OnNotificationStatusChanged(string status) + { + NotificationStatusChangedEventHandler handler = Airship.Shared.OnNotificationStatusChanged; + + if (handler != null) + { + PushNotificationStatus pushStatus = JsonUtility.FromJson(status); + if (pushStatus != null) + { + handler(pushStatus); + } } } } diff --git a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt index 5d5006ef..63c3209a 100644 --- a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt +++ b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt @@ -15,6 +15,7 @@ import com.urbanairship.android.framework.proxy.events.EventEmitter import com.urbanairship.android.framework.proxy.proxies.AirshipProxy import com.urbanairship.android.framework.proxy.proxies.EnableUserNotificationsArgs import com.urbanairship.android.framework.proxy.proxies.FeatureFlagProxy +import com.urbanairship.android.framework.proxy.NotificationStatus import com.urbanairship.json.JsonException import com.urbanairship.json.JsonMap import com.urbanairship.json.JsonValue @@ -57,14 +58,14 @@ class UnityPlugin { EventType.CHANNEL_CREATED -> onChannelCreated(event.body.optionalField("channelId")) EventType.DEEP_LINK_RECEIVED -> onDeepLinkReceived(event.body.optionalField("deepLink")) EventType.DISPLAY_MESSAGE_CENTER -> onShowInbox(event.body.optionalField("messageId")) - EventType.DISPLAY_PREFERENCE_CENTER -> onPreferenceCenterDisplay(event.body.get("preferenceCenterId").toString()) - EventType.MESSAGE_CENTER_UPDATED -> onInboxUpdated() - EventType.PUSH_TOKEN_RECEIVED -> {} + EventType.DISPLAY_PREFERENCE_CENTER -> onPreferenceCenterDisplay(event.body.optionalField("preferenceCenterId")) + EventType.MESSAGE_CENTER_UPDATED -> onInboxUpdated(event.body.optionalField("messageUnreadCount"), event.body.optionalField("messageCount")) + EventType.PUSH_TOKEN_RECEIVED -> onPushTokenReceived(event.body.optionalField("pushToken")) EventType.FOREGROUND_NOTIFICATION_RESPONSE_RECEIVED -> onPushOpened(event.body.optionalField("pushPayload")) EventType.BACKGROUND_NOTIFICATION_RESPONSE_RECEIVED -> onPushOpened(event.body.optionalField("pushPayload")) EventType.FOREGROUND_PUSH_RECEIVED -> onPushReceived(event.body.optionalField("pushPayload")) EventType.BACKGROUND_PUSH_RECEIVED -> onPushReceived(event.body.optionalField("pushPayload")) - EventType.NOTIFICATION_STATUS_CHANGED -> {} + EventType.NOTIFICATION_STATUS_CHANGED -> onNotificationStatusChanged(event.body.optionalField("status")) EventType.PENDING_EMBEDDED_UPDATED -> {} } true @@ -559,27 +560,27 @@ class UnityPlugin { } } - fun onInboxUpdated() { - runBlocking(Dispatchers.IO) { - val unreadCount = airshipProxyInstance.messageCenter.getUnreadMessagesCount() - val messages = airshipProxyInstance.messageCenter.getMessages() - val totalCount = messages.size - val counts: JsonMap = JsonMap.newBuilder() - .put("unread", unreadCount) - .put("total", totalCount) - .build() - ProxyLogger.debug( - "UnityPlugin inboxUpdated (unread = %s, total = %s)", - unreadCount, totalCount - ) + fun onInboxUpdated(messageUnreadCount: Int?, messageCount: Int?) { + ProxyLogger.debug("UnityPlugin inboxUpdated (unread = $messageUnreadCount, total = $messageCount)") - if (listener != null) { - UnityPlayer.UnitySendMessage(listener, "OnInboxUpdated", counts.toString()) - } + if (messageUnreadCount == null) { + ProxyLogger.error("UnityPlugin failed to retrieve message unread count") + } + if (messageCount == null) { + ProxyLogger.error("UnityPlugin failed to retrieve message count") + } + + val counts: JsonMap = JsonMap.newBuilder() + .put("unread", messageUnreadCount?.toInt() ?: 0) + .put("total", messageCount?.toInt() ?: 0) + .build() + + if (listener != null) { + UnityPlayer.UnitySendMessage(listener, "OnInboxUpdated", counts.toString()) } } - fun onPreferenceCenterDisplay(preferenceCenterId: String) { + fun onPreferenceCenterDisplay(preferenceCenterId: String?) { ProxyLogger.debug("UnityPlugin preference center display: $preferenceCenterId") if (listener != null) { @@ -587,6 +588,22 @@ class UnityPlugin { } } + fun onPushTokenReceived(pushToken: String?) { + ProxyLogger.debug("UnityPlugin push token received: $pushToken") + + if (listener != null) { + UnityPlayer.UnitySendMessage(listener, "OnPushTokenReceived", pushToken) + } + } + + fun onNotificationStatusChanged(status: NotificationStatus?) { + ProxyLogger.debug("UnityPlugin notification status changed: ${status?.toJsonValue().toString()}") + + if (listener != null) { + UnityPlayer.UnitySendMessage(listener, "OnNotificationStatusChanged", status?.toJsonValue().toString()) + } + } + // TODO Probably remove that, I don't think we'll need it anymore private fun getPushPayload(message: PushMessage?): String? { if (message == null) { From dc01e83bd749e3f966b75eb2569ccd2427490528 Mon Sep 17 00:00:00 2001 From: Ulrich GIBERNE Date: Tue, 14 Apr 2026 11:41:45 -0400 Subject: [PATCH 23/27] wip --- Assets/Plugins/iOS/UnityPlugin.swift | 58 +++-------------- Assets/Scripts/UrbanAirshipBehaviour.cs | 65 +++++++++---------- Assets/UrbanAirship/Editor/UAConfig.cs | 21 ++++++ Assets/UrbanAirship/Editor/UAConfigEditor.cs | 15 ++++- Assets/UrbanAirship/Editor/UAPostBuild.cs | 23 ++++--- Assets/UrbanAirship/Editor/UAPreBuild.cs | 7 ++ Assets/UrbanAirship/Platforms/Airship.cs | 2 +- Assets/UrbanAirship/Platforms/AirshipPush.cs | 3 + .../UrbanAirship/Platforms/IAirshipPlugin.cs | 9 --- .../urbanairship/unityplugin/UnityPlugin.kt | 8 +-- 10 files changed, 101 insertions(+), 110 deletions(-) diff --git a/Assets/Plugins/iOS/UnityPlugin.swift b/Assets/Plugins/iOS/UnityPlugin.swift index 14f925b6..861e5e5c 100644 --- a/Assets/Plugins/iOS/UnityPlugin.swift +++ b/Assets/Plugins/iOS/UnityPlugin.swift @@ -82,37 +82,21 @@ class UnityPlugin: NSObject { static let shared = UnityPlugin() public var listener: String? = nil - public var storedDeepLink: String? = nil - - private static let eventNames: [AirshipProxyEventType: String] = [ - .authorizedNotificationSettingsChanged: "ios_authorized_notification_settings_changed", - .pushTokenReceived: "push_token_received", - .deepLinkReceived: "OnDeepLinkReceived", - .channelCreated: "OnChannelCreated", - .messageCenterUpdated: "OnInboxUpdated", - .displayMessageCenter: "display_message_center", - .displayPreferenceCenter: "display_preference_center", - .notificationResponseReceived: "OnPushOpened", - .pushReceived: "OnPushReceived", - .notificationStatusChanged: "notification_status_changed", - .liveActivitiesUpdated: "ios_live_activities_updated" - ] private override init() { super.init() - startEventProcessing() } - private static let initializeOnce: Void = { - // Add Notification Observer - NotificationCenter.default.addObserver(forName: UIApplication.didFinishLaunchingNotification, - object: nil, - queue: nil) { notification in - // TODO let's see how we handle take off - // UnityPlugin.performTakeOff(withLaunchOptions: notification.userInfo) - } - }() + // private static let initializeOnce: Void = { + // // Add Notification Observer + // NotificationCenter.default.addObserver(forName: UIApplication.didFinishLaunchingNotification, + // object: nil, + // queue: nil) { notification in + // // TODO let's see how we handle take off + // // UnityPlugin.performTakeOff(withLaunchOptions: notification.userInfo) + // } + // }() func startEventProcessing() { Task { @MainActor in @@ -125,23 +109,11 @@ class UnityPlugin: NSObject { func handleCall(method: String, args: [Any]) throws -> Any? { AirshipLogger.debug("UnityPlugin \(method): \(args)") - // TODO check how to handle the class attributes called in the static method - // let instance = shared - switch method { case "setListener": - // shared.listener = requireAnyString(args.first) listener = try requireStringArg(args.first) return nil - // TODO Check if we still need this -// case "getDeepLink": -// let deepLink = convertToJson(storedDeepLink) -// if (requireBoolArg(args.first)) { -// storedDeepLink = nil -// } -// return deepLink - // Airship case "takeOff": return try MainActor.assumeIsolated { @@ -674,8 +646,6 @@ class UnityPlugin: NSObject { public func receivedDeepLink(_ deepLink: String) { AirshipLogger.debug("UnityPlugin receivedDeepLink \(deepLink)") - self.storedDeepLink = deepLink - if let listener = self.listener { callUnitySendMessage(objectName: listener, methodName: "OnDeepLinkReceived", @@ -777,8 +747,6 @@ class UnityPlugin: NSObject { } } - // TODO Implement the rest of the delegates - private func requireAnyArg(_ arg: Any? = nil) throws -> Any { guard let value: Any = arg else { throw AirshipErrors.error("Argument must not be null") @@ -886,14 +854,6 @@ class UnityPlugin: NSObject { return try AirshipJSON.wrap(parsed).decode() } - // TODO Delete this if not needed - private func optionalParsedArg(_ arg: Any?) throws -> T? { - guard let value = arg else { - return nil - } - return try requireParsedArgWithValues(value) - } - private func requireStringArrayArg(_ arg: Any?) throws -> [String] { guard let value: [String] = arg as? [String] else { throw AirshipErrors.error("Argument must be a string array") diff --git a/Assets/Scripts/UrbanAirshipBehaviour.cs b/Assets/Scripts/UrbanAirshipBehaviour.cs index e4fdf14f..87a80f73 100644 --- a/Assets/Scripts/UrbanAirshipBehaviour.cs +++ b/Assets/Scripts/UrbanAirshipBehaviour.cs @@ -9,8 +9,9 @@ public class UrbanAirshipBehaviour : MonoBehaviour { void Awake () { - Airship.Shared.TakeOff(new AirshipConfig() { - defaultEnvironment = new ConfigEnvironment() { + Debug.Log("Taking off"); + bool takeOffResult = Airship.Shared.TakeOff(new AirshipConfig() { + @default = new ConfigEnvironment() { appKey = "APP_KEY", appSecret = "APP_SECRET", logLevel = LogLevel.Verbose, @@ -19,6 +20,7 @@ void Awake () { inProduction = false, urlAllowList = new string[] { "*" }, }); + Debug.Log("TakeOff returned: " + takeOffResult); } void Start () { @@ -33,6 +35,9 @@ void Start () { Airship.Shared.OnInboxUpdated += OnInboxUpdated; Airship.Shared.OnShowInbox += OnShowInbox; Airship.Shared.OnPreferenceCenterDisplay += OnPreferenceCenterDisplay; + Airship.Shared.OnPushTokenReceived += OnPushTokenReceived; + Airship.Shared.OnNotificationStatusChanged += OnNotificationStatusChanged; + Airship.Shared.OnAuthorizedSettingsChanged += OnAuthorizedSettingsChanged; // PrivacyManager Debug.Log("Set Enabled features to none"); @@ -318,8 +323,6 @@ void Start () { Debug.LogError("Error getting feature flag: " + error.Message); } )); - - // TODO fix listeners. Then check the TODOs in the code } void OnDestroy () { @@ -330,10 +333,13 @@ void OnDestroy () { Airship.Shared.OnInboxUpdated -= OnInboxUpdated; Airship.Shared.OnShowInbox -= OnShowInbox; Airship.Shared.OnPreferenceCenterDisplay -= OnPreferenceCenterDisplay; + Airship.Shared.OnPushTokenReceived -= OnPushTokenReceived; + Airship.Shared.OnNotificationStatusChanged -= OnNotificationStatusChanged; + Airship.Shared.OnAuthorizedSettingsChanged -= OnAuthorizedSettingsChanged; } void OnPushReceived (PushMessage message) { - Debug.Log ("Received push! " + message.Alert); + Debug.Log ("Listener: Received push! " + message.Alert); if (message.Extras != null) { foreach (KeyValuePair kvp in message.Extras) { @@ -343,7 +349,7 @@ void OnPushReceived (PushMessage message) { } void OnPushOpened (PushMessage message) { - Debug.Log ("Opened Push! " + message.Alert); + Debug.Log ("Listener: Opened Push! " + message.Alert); if (message.Extras != null) { foreach (KeyValuePair kvp in message.Extras) { @@ -353,54 +359,43 @@ void OnPushOpened (PushMessage message) { } void OnChannelCreated (string channelId) { - Debug.Log ("Channel created: " + channelId); + Debug.Log ("Listener: Channel created: " + channelId); } void OnDeepLinkReceived (string deeplink) { - Debug.Log ("Received deep link: " + deeplink); + Debug.Log ("Listener: Received deep link: " + deeplink); } void OnInboxUpdated (uint messageUnreadCount, uint messageCount) { - Debug.Log("Inbox updated - unread messages: " + messageUnreadCount + " total messages: " + messageCount); - - StartCoroutine(Airship.Shared.messageCenter.GetMessages( - onComplete: (messages) => { - foreach (InboxMessage inboxMessage in messages) - { - Debug.Log("Message id: " + inboxMessage.id + ", title: " + inboxMessage.title + ", sentDate: " + inboxMessage.sentDate + ", isRead: " + inboxMessage.isRead + ", isDeleted: " + inboxMessage.isDeleted); - if (inboxMessage.extras == null) - { - Debug.Log("Extras is null"); - } - else - { - foreach (KeyValuePair entry in inboxMessage.extras) - { - Debug.Log("Message extras [" + entry.Key + "] = " + entry.Value); - } - } - } - }, - onError: (error) => { - Debug.LogError("Error getting messages: " + error.Message); - } - )); + Debug.Log("Listener: Inbox updated - unread messages: " + messageUnreadCount + " total messages: " + messageCount); } void OnShowInbox (string messageId) { if (messageId == null) { - Debug.Log("OnShowInbox - show inbox"); + Debug.Log("Listener: OnShowInbox - show inbox"); } else { - Debug.Log("OnShowInbox - show message: messageId = " + messageId); + Debug.Log("Listener: OnShowInbox - show message: messageId = " + messageId); } } void OnPreferenceCenterDisplay (string preferenceCenterId) { - Debug.Log ("Preference Center display - preferenceCenterId: " + preferenceCenterId); + Debug.Log ("Listener: Preference Center display - preferenceCenterId: " + preferenceCenterId); + } + + void OnPushTokenReceived (string pushToken) { + Debug.Log ("Listener: Push token received: " + pushToken); + } + + void OnNotificationStatusChanged (PushNotificationStatus status) { + Debug.Log ("Listener: Notification status changed: " + status); + } + + void OnAuthorizedSettingsChanged (AuthorizedNotificationSetting[] authorizedSettings) { + Debug.Log ("Listener: Authorized settings changed: " + JsonUtility.ToJson(authorizedSettings)); } } diff --git a/Assets/UrbanAirship/Editor/UAConfig.cs b/Assets/UrbanAirship/Editor/UAConfig.cs index 69e62686..ce70cbeb 100644 --- a/Assets/UrbanAirship/Editor/UAConfig.cs +++ b/Assets/UrbanAirship/Editor/UAConfig.cs @@ -92,6 +92,19 @@ public enum CloudSite { [SerializeField] public CloudSite Site { get; set; } + /// + /// Whether any app credentials have been configured. + /// Returns false when all key/secret fields are empty, meaning the user + /// needs to configure Airship via TakeOff instead of the editor config files. + /// + public bool IsConfigured { + get { + bool hasProd = !string.IsNullOrEmpty (ProductionAppKey) || !string.IsNullOrEmpty (ProductionAppSecret); + bool hasDev = !string.IsNullOrEmpty (DevelopmentAppKey) || !string.IsNullOrEmpty (DevelopmentAppSecret); + return hasProd || hasDev; + } + } + public bool IsValid { get { try { @@ -183,6 +196,10 @@ public static void SaveConfig (UAConfig config) { } public bool Apply () { + if (!IsConfigured) { + return false; + } + if (IsValid) { #if UNITY_IOS GenerateIOSAirshipConfig (); @@ -200,6 +217,10 @@ public bool Apply () { } public void Validate () { + if (!IsConfigured) { + return; + } + if (InProduction) { if (string.IsNullOrEmpty (ProductionAppKey)) { throw new Exception ("Production App Key missing."); diff --git a/Assets/UrbanAirship/Editor/UAConfigEditor.cs b/Assets/UrbanAirship/Editor/UAConfigEditor.cs index 007ec67c..c5114438 100644 --- a/Assets/UrbanAirship/Editor/UAConfigEditor.cs +++ b/Assets/UrbanAirship/Editor/UAConfigEditor.cs @@ -14,7 +14,9 @@ public class UAConfigEditor : EditorWindow { void OnEnable () { config = UAConfig.LoadConfig (); - config.Apply (); + if (config.IsConfigured) { + config.Apply (); + } } void OnGUI () { @@ -66,6 +68,13 @@ void OnGUI () { config.NotificationPresentationOptionSound = EditorGUILayout.Toggle ("Sound", config.NotificationPresentationOptionSound); }); + if (!config.IsConfigured) { + EditorGUILayout.HelpBox ( + "No app credentials configured. Config files will not be generated. " + + "You can configure Airship at runtime via Airship.Shared.TakeOff() instead.", + MessageType.Info); + } + GUILayout.FlexibleSpace (); GUILayout.BeginHorizontal (); @@ -82,7 +91,9 @@ void OnGUI () { UnityEngine.Debug.Log ("Saving Urban Airship config."); - config.Apply (); + if (config.IsConfigured) { + config.Apply (); + } UAConfig.SaveConfig (config); AssetDatabase.Refresh (); diff --git a/Assets/UrbanAirship/Editor/UAPostBuild.cs b/Assets/UrbanAirship/Editor/UAPostBuild.cs index 7ce99e72..e5cdb699 100644 --- a/Assets/UrbanAirship/Editor/UAPostBuild.cs +++ b/Assets/UrbanAirship/Editor/UAPostBuild.cs @@ -45,16 +45,19 @@ private static void UpdatePbxProject (string projectPath, string buildPath) { }; #endif - string airshipConfig = Path.Combine (buildPath, "AirshipConfig.plist"); - if (File.Exists (airshipConfig)) { - File.Delete (airshipConfig); - } - - File.Copy (Path.Combine (Application.dataPath, "Plugins/iOS/AirshipConfig.plist"), airshipConfig); - string airshipGUID = proj.AddFile ("AirshipConfig.plist", "AirshipConfig.plist", PBXSourceTree.Source); - - foreach (string target in targets) { - proj.AddFileToBuild (target, airshipGUID); + string airshipConfigSource = Path.Combine (Application.dataPath, "Plugins/iOS/AirshipConfig.plist"); + if (File.Exists (airshipConfigSource)) { + string airshipConfig = Path.Combine (buildPath, "AirshipConfig.plist"); + if (File.Exists (airshipConfig)) { + File.Delete (airshipConfig); + } + + File.Copy (airshipConfigSource, airshipConfig); + string airshipGUID = proj.AddFile ("AirshipConfig.plist", "AirshipConfig.plist", PBXSourceTree.Source); + + foreach (string target in targets) { + proj.AddFileToBuild (target, airshipGUID); + } } // Update the Header Search Paths diff --git a/Assets/UrbanAirship/Editor/UAPreBuild.cs b/Assets/UrbanAirship/Editor/UAPreBuild.cs index 366a0924..6a27720a 100644 --- a/Assets/UrbanAirship/Editor/UAPreBuild.cs +++ b/Assets/UrbanAirship/Editor/UAPreBuild.cs @@ -34,6 +34,13 @@ class GenerateConfig { public static void Apply (BuildTarget target) { if (target == BuildTarget.iOS || target == BuildTarget.Android) { UAConfig config = UAConfig.LoadConfig (); + + if (!config.IsConfigured) { + UnityEngine.Debug.Log ("Urban Airship editor config is empty. " + + "Skipping config file generation. Make sure to call Airship.Shared.TakeOff() at runtime."); + return; + } + if (!config.IsValid) { EditorUtility.DisplayDialog ("Urban Airship", "Urban Airship not configured. Set the app credentials in Window -> Urban Airship -> Settings", "OK"); return; diff --git a/Assets/UrbanAirship/Platforms/Airship.cs b/Assets/UrbanAirship/Platforms/Airship.cs index 62a23509..c11c5d9b 100644 --- a/Assets/UrbanAirship/Platforms/Airship.cs +++ b/Assets/UrbanAirship/Platforms/Airship.cs @@ -478,7 +478,7 @@ public record AndroidNotificationConfig public record AirshipConfig { // Default environment. - public ConfigEnvironment? defaultEnvironment; + public ConfigEnvironment? @default; // Development environment. Overrides default environment if inProduction is false. public ConfigEnvironment? development; diff --git a/Assets/UrbanAirship/Platforms/AirshipPush.cs b/Assets/UrbanAirship/Platforms/AirshipPush.cs index 28e2dae0..41c911ad 100644 --- a/Assets/UrbanAirship/Platforms/AirshipPush.cs +++ b/Assets/UrbanAirship/Platforms/AirshipPush.cs @@ -185,6 +185,9 @@ public void SetQuietTimeEnabled(bool enabled) {} public bool IsQuietTimeEnabled() { return false; } public void SetQuietTime(QuietTime quietTime) {} public QuietTime? GetQuietTime() { return null; } + // TODO: Add these methods and figure out how to set the AuthorizedNotificationSettings and AuthorizedNotificationStatus + // public String[] GetAuthorizedNotificationSettings() { return []; } + // public String GetAuthroizedNotificationStatus() { return "" } } /// diff --git a/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs b/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs index fb0da7f6..18b2af3a 100644 --- a/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs +++ b/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs @@ -86,13 +86,6 @@ public GameObject Listener { internal class AirshipPluginiOS : IAirshipPlugin{ - // [DllImport ("__Internal")] - // private static extern void UnityPlugin_shared (); - - // public AirshipPluginiOS() { - // UnityPlugin_shared(); - // } - [DllImport ("__Internal")] private static extern string UnityPlugin_call (string method, string argsJson); @@ -103,13 +96,11 @@ public void Call (string method, params object[] args) { public T Call (string method, params object[] args) { string argsJson = SerializeArgs(args); - Debug.Log("argsJson: " + argsJson + " for method: " + method); string resultJson = UnityPlugin_call(method, argsJson); if (string.IsNullOrEmpty(resultJson) || resultJson == "{}") { return default(T); } - Debug.Log("resultJson: " + resultJson + " for method: " + method); return AirshipUtils.Deserialize(resultJson); } diff --git a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt index 63c3209a..0530e031 100644 --- a/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt +++ b/unity-plugin/src/main/java/com/urbanairship/unityplugin/UnityPlugin.kt @@ -65,7 +65,7 @@ class UnityPlugin { EventType.BACKGROUND_NOTIFICATION_RESPONSE_RECEIVED -> onPushOpened(event.body.optionalField("pushPayload")) EventType.FOREGROUND_PUSH_RECEIVED -> onPushReceived(event.body.optionalField("pushPayload")) EventType.BACKGROUND_PUSH_RECEIVED -> onPushReceived(event.body.optionalField("pushPayload")) - EventType.NOTIFICATION_STATUS_CHANGED -> onNotificationStatusChanged(event.body.optionalField("status")) + EventType.NOTIFICATION_STATUS_CHANGED -> onNotificationStatusChanged(event.body.optionalField("status")) EventType.PENDING_EMBEDDED_UPDATED -> {} } true @@ -596,11 +596,11 @@ class UnityPlugin { } } - fun onNotificationStatusChanged(status: NotificationStatus?) { - ProxyLogger.debug("UnityPlugin notification status changed: ${status?.toJsonValue().toString()}") + fun onNotificationStatusChanged(status: JsonValue?) { + ProxyLogger.debug("UnityPlugin notification status changed: ${status?.toString()}") if (listener != null) { - UnityPlayer.UnitySendMessage(listener, "OnNotificationStatusChanged", status?.toJsonValue().toString()) + UnityPlayer.UnitySendMessage(listener, "OnNotificationStatusChanged", status?.toString()) } } From b6c630a9ed13dd843b61d90bd998259afa99dba0 Mon Sep 17 00:00:00 2001 From: Ulrich GIBERNE Date: Tue, 14 Apr 2026 12:25:27 -0400 Subject: [PATCH 24/27] fix memory leak --- Assets/Plugins/iOS/UnityPlugin.swift | 12 ++++++------ .../UrbanAirship/Platforms/IAirshipPlugin.cs | 19 ++++++++++++++++--- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/Assets/Plugins/iOS/UnityPlugin.swift b/Assets/Plugins/iOS/UnityPlugin.swift index 861e5e5c..e22cd573 100644 --- a/Assets/Plugins/iOS/UnityPlugin.swift +++ b/Assets/Plugins/iOS/UnityPlugin.swift @@ -14,7 +14,7 @@ import AirshipCore private let _notHandled = "NOT_HANDLED" @_cdecl("UnityPlugin_call") -public func UnityPlugin_call(_ method: UnsafePointer, argsJson: UnsafePointer) -> UnsafePointer? { +public func UnityPlugin_call(_ method: UnsafePointer, argsJson: UnsafePointer) -> UnsafeMutablePointer? { let methodStr = String(cString: method) let argsJsonStr = String(cString: argsJson) @@ -24,7 +24,7 @@ public func UnityPlugin_call(_ method: UnsafePointer, argsJson: UnsafePoi args = (try JSONSerialization.jsonObject(with: data) as? [Any]) ?? [] } catch { AirshipLogger.error("Failed to deserialize arguments for method \(methodStr): \(error)") - return UnsafePointer(strdup("{}")) + return strdup("{}") } var result: Any? @@ -35,7 +35,7 @@ public func UnityPlugin_call(_ method: UnsafePointer, argsJson: UnsafePoi result = try UnityPlugin.shared.handleCall(method: methodStr, args: args) } catch { AirshipLogger.error("Error executing method \(methodStr): \(error)") - return UnsafePointer(strdup("{}")) + return strdup("{}") } } @@ -65,15 +65,15 @@ public func UnityPlugin_call(_ method: UnsafePointer, argsJson: UnsafePoi if let callError { AirshipLogger.error("Error executing method \(methodStr): \(callError)") - return UnsafePointer(strdup("{}")) + return strdup("{}") } } do { let jsonResult = try AirshipJSON.wrap(result).toString() - return UnsafePointer(strdup(jsonResult)) + return strdup(jsonResult) } catch { - return UnsafePointer(strdup("{}")) + return strdup("{}") } } diff --git a/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs b/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs index 18b2af3a..5a17d965 100644 --- a/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs +++ b/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs @@ -87,16 +87,29 @@ public GameObject Listener { internal class AirshipPluginiOS : IAirshipPlugin{ [DllImport ("__Internal")] - private static extern string UnityPlugin_call (string method, string argsJson); + private static extern IntPtr UnityPlugin_call (string method, string argsJson); + + [DllImport ("libc")] + private static extern void free (IntPtr ptr); + + private static string CallNative (string method, string argsJson) { + IntPtr ptr = UnityPlugin_call(method, argsJson); + if (ptr == IntPtr.Zero) { + return null; + } + string result = Marshal.PtrToStringUTF8(ptr); + free(ptr); + return result; + } public void Call (string method, params object[] args) { string argsJson = SerializeArgs(args); - UnityPlugin_call(method, argsJson); + CallNative(method, argsJson); } public T Call (string method, params object[] args) { string argsJson = SerializeArgs(args); - string resultJson = UnityPlugin_call(method, argsJson); + string resultJson = CallNative(method, argsJson); if (string.IsNullOrEmpty(resultJson) || resultJson == "{}") { return default(T); From 2516f8a73beaad0baac7c31f6af2ee771ea7835d Mon Sep 17 00:00:00 2001 From: Ulrich GIBERNE Date: Tue, 14 Apr 2026 13:06:06 -0400 Subject: [PATCH 25/27] cleaning --- Assets/Plugins/iOS/UnityPlugin.swift | 259 +++++++++++++++------------ 1 file changed, 141 insertions(+), 118 deletions(-) diff --git a/Assets/Plugins/iOS/UnityPlugin.swift b/Assets/Plugins/iOS/UnityPlugin.swift index e22cd573..14649c87 100644 --- a/Assets/Plugins/iOS/UnityPlugin.swift +++ b/Assets/Plugins/iOS/UnityPlugin.swift @@ -11,7 +11,11 @@ import AirshipKit import AirshipCore #endif -private let _notHandled = "NOT_HANDLED" +private enum HandleResult { + case handledSync(Any?) + case handledAsync(Any?) + case notHandled +} @_cdecl("UnityPlugin_call") public func UnityPlugin_call(_ method: UnsafePointer, argsJson: UnsafePointer) -> UnsafeMutablePointer? { @@ -27,46 +31,34 @@ public func UnityPlugin_call(_ method: UnsafePointer, argsJson: UnsafePoi return strdup("{}") } - var result: Any? + let result: Any? - // Try sync path - if Thread.isMainThread { - do { - result = try UnityPlugin.shared.handleCall(method: methodStr, args: args) - } catch { - AirshipLogger.error("Error executing method \(methodStr): \(error)") - return strdup("{}") - } - } - - // Fall through to async path - if !Thread.isMainThread || (result as? String) == _notHandled { - let semaphore = DispatchSemaphore(value: 0) - var callError: Error? - result = nil - - Task { - do { - result = try await UnityPlugin.shared.handleCallAsync(method: methodStr, args: args) - } catch { - callError = error - } - semaphore.signal() - } - - if Thread.isMainThread { - // Spin the RunLoop instead of blocking, so @MainActor work can execute - while semaphore.wait(timeout: .now()) == .timedOut { - RunLoop.current.run(mode: .default, before: Date(timeIntervalSinceNow: 0.005)) + do { + // Sync path + let syncResult = try UnityPlugin.shared.handleCall(method: methodStr, args: args) + switch syncResult { + case .handledSync(let value): + result = value + case .notHandled: + // Async path + let asyncResult = try runAsync { try await UnityPlugin.shared.handleCallAsync(method: methodStr, args: args) } + switch asyncResult { + case .handledAsync(let value): + result = value + case .notHandled: + AirshipLogger.error("Unknown method: \(methodStr)") + return strdup("{}") + default: + AirshipLogger.error("Unexpected result type for async handler \(methodStr)") + return strdup("{}") } - } else { - semaphore.wait() - } - - if let callError { - AirshipLogger.error("Error executing method \(methodStr): \(callError)") + default: + AirshipLogger.error("Unexpected result type for sync handler \(methodStr)") return strdup("{}") } + } catch { + AirshipLogger.error("Error executing method \(methodStr): \(error)") + return strdup("{}") } do { @@ -77,6 +69,32 @@ public func UnityPlugin_call(_ method: UnsafePointer, argsJson: UnsafePoi } } +private func runAsync(_ block: @escaping () async throws -> T) throws -> T { + let semaphore = DispatchSemaphore(value: 0) + var result: T? + var callError: Error? + + Task { + do { + result = try await block() + } catch { + callError = error + } + semaphore.signal() + } + + if Thread.isMainThread { + while semaphore.wait(timeout: .now()) == .timedOut { + RunLoop.current.run(mode: .default, before: Date(timeIntervalSinceNow: 0.005)) + } + } else { + semaphore.wait() + } + + if let callError { throw callError } + return result! +} + class UnityPlugin: NSObject { static let shared = UnityPlugin() @@ -106,55 +124,56 @@ class UnityPlugin: NSObject { } } - func handleCall(method: String, args: [Any]) throws -> Any? { + func handleCall(method: String, args: [Any]) throws -> HandleResult { AirshipLogger.debug("UnityPlugin \(method): \(args)") switch method { case "setListener": listener = try requireStringArg(args.first) - return nil + return .handledSync(nil) // Airship case "takeOff": - return try MainActor.assumeIsolated { - return try AirshipProxy.shared.takeOff(json: try requireParsedAnyArg(args.first)) + let value = try MainActor.assumeIsolated { + try AirshipProxy.shared.takeOff(json: try requireParsedAnyArg(args.first)) } + return .handledSync(value) case "isFlying": - return AirshipProxy.shared.isFlying() + return .handledSync(AirshipProxy.shared.isFlying()) // Channel case "getChannelId": - return try AirshipProxy.shared.channel.channelID + return .handledSync(try AirshipProxy.shared.channel.channelID) case "addTag": try AirshipProxy.shared.channel.addTags([requireStringArg(args.first)]) - return nil + return .handledSync(nil) case "removeTag": try AirshipProxy.shared.channel.removeTags([requireStringArg(args.first)]) - return nil + return .handledSync(nil) case "getTags": - return try AirshipProxy.shared.channel.tags + return .handledSync(try AirshipProxy.shared.channel.tags) case "editTags": try AirshipProxy.shared.channel.editTags( operations: try requireParsedArgWithValues(args.first) ) - return nil + return .handledSync(nil) case "editChannelTagGroups": try AirshipProxy.shared.channel.editTagGroups( operations: try requireParsedArgWithValues(args.first) ) - return nil + return .handledSync(nil) case "editChannelAttributes": try AirshipProxy.shared.channel.editAttributes( operations: try requireParsedArgWithValues(args.first) ) - return nil + return .handledSync(nil) case "editChannelSubscriptionLists": let parsed = try requireParsedAnyArg(args.first) @@ -164,38 +183,38 @@ class UnityPlugin: NSObject { try AirshipProxy.shared.channel.editSubscriptionLists( json: values ) - return nil + return .handledSync(nil) // Contact case "identify": try AirshipProxy.shared.contact.identify(try requireStringArg(args.first)) - return nil + return .handledSync(nil) case "reset": try AirshipProxy.shared.contact.reset() - return nil + return .handledSync(nil) case "notifyRemoteLogin": try AirshipProxy.shared.contact.notifyRemoteLogin() - return nil + return .handledSync(nil) case "editContactTagGroups": try AirshipProxy.shared.contact.editTagGroups( operations: try requireParsedArgWithValues(args.first) ) - return nil + return .handledSync(nil) case "editContactAttributes": try AirshipProxy.shared.contact.editAttributes( operations: try requireParsedArgWithValues(args.first) ) - return nil + return .handledSync(nil) case "editContactSubscriptionLists": try AirshipProxy.shared.contact.editSubscriptionLists( operations: try requireParsedArgWithValues(args.first) ) - return nil + return .handledSync(nil) // Analytics case "associateIdentifier": @@ -206,7 +225,7 @@ class UnityPlugin: NSObject { identifier: args.count == 2 ? requireStringArg(args[1]) : nil, key: requireStringArg(args[0]) ) - return nil + return .handledSync(nil) case "trackScreen": try MainActor.assumeIsolated { @@ -214,49 +233,52 @@ class UnityPlugin: NSObject { try? requireStringArg(args.first) ) } - return nil + return .handledSync(nil) case "addCustomEvent": try AirshipProxy.shared.analytics.addEvent( try requireParsedAnyArg(args.first) ) - return nil + return .handledSync(nil) case "getSessionId": - return try MainActor.assumeIsolated { + let value = try MainActor.assumeIsolated { try AirshipProxy.shared.analytics.getSessionID() } + return .handledSync(value) // InApp case "setPaused": try MainActor.assumeIsolated { try AirshipProxy.shared.inApp.setPaused(try requireBoolArg(args.first)) } - return nil + return .handledSync(nil) case "isPaused": - return try MainActor.assumeIsolated { + let value = try MainActor.assumeIsolated { try AirshipProxy.shared.inApp.isPaused() } + return .handledSync(value) case "getDisplayInterval": - return try MainActor.assumeIsolated { + let value = try MainActor.assumeIsolated { try AirshipProxy.shared.inApp.getDisplayInterval() } + return .handledSync(value) // Locale case "setLocaleOverride": try AirshipProxy.shared.locale.setCurrentLocale( try requireStringArg(args.first) ) - return nil + return .handledSync(nil) case "clearLocaleOverride": try AirshipProxy.shared.locale.clearLocale() - return nil + return .handledSync(nil) case "getLocale": - return try AirshipProxy.shared.locale.currentLocale + return .handledSync(try AirshipProxy.shared.locale.currentLocale) // Message Center case "setAutoLaunchDefaultMessageCenter": @@ -265,7 +287,7 @@ class UnityPlugin: NSObject { try requireBoolArg(args.first) ) } - return nil + return .handledSync(nil) case "displayMessageCenter": try MainActor.assumeIsolated { @@ -273,13 +295,13 @@ class UnityPlugin: NSObject { messageID: try? requireStringArg(args.first) ) } - return nil + return .handledSync(nil) case "dismissMessageCenter": try MainActor.assumeIsolated { try AirshipProxy.shared.messageCenter.dismiss() } - return nil + return .handledSync(nil) case "showMessageView": try MainActor.assumeIsolated { @@ -287,7 +309,7 @@ class UnityPlugin: NSObject { messageID: try requireStringArg(args.first) ) } - return nil + return .handledSync(nil) case "showMessageCenter": try MainActor.assumeIsolated { @@ -295,7 +317,7 @@ class UnityPlugin: NSObject { messageID: try? requireStringArg(args.first) ) } - return nil + return .handledSync(nil) // Preference Center case "displayPreferenceCenter": @@ -304,7 +326,7 @@ class UnityPlugin: NSObject { preferenceCenterID: try requireStringArg(args.first) ) } - return nil + return .handledSync(nil) case "setAutoLaunchDefaultPreferenceCenter": guard @@ -321,59 +343,60 @@ class UnityPlugin: NSObject { preferenceCenterID: identifier ) } - return nil + return .handledSync(nil) // Privacy Manager case "setEnabledFeatures": try AirshipProxy.shared.privacyManager.setEnabled( featureNames: try requireStringArrayArg(args) ) - return nil + return .handledSync(nil) case "getEnabledFeatures": - return try AirshipProxy.shared.privacyManager.getEnabledNames() + return .handledSync(try AirshipProxy.shared.privacyManager.getEnabledNames()) case "enableFeatures": try AirshipProxy.shared.privacyManager.enable( featureNames: try requireStringArrayArg(args) ) - return nil + return .handledSync(nil) case "disableFeatures": try AirshipProxy.shared.privacyManager.disable( featureNames: try requireStringArrayArg(args) ) - return nil + return .handledSync(nil) case "isFeaturesEnabled": - return try AirshipProxy.shared.privacyManager.isEnabled( + return .handledSync(try AirshipProxy.shared.privacyManager.isEnabled( featuresNames: try requireStringArrayArg(args) - ) + )) // Push case "isUserNotificationsEnabled": - return try AirshipProxy.shared.push.isUserNotificationsEnabled() + return .handledSync(try AirshipProxy.shared.push.isUserNotificationsEnabled()) case "setUserNotificationsEnabled": try AirshipProxy.shared.push.setUserNotificationsEnabled( try requireBoolArg(args.first) ) - return nil + return .handledSync(nil) case "getPushToken": - return try MainActor.assumeIsolated { + let value = try MainActor.assumeIsolated { try AirshipProxy.shared.push.getRegistrationToken() } + return .handledSync(value) case "clearNotifications": AirshipProxy.shared.push.clearNotifications() - return nil + return .handledSync(nil) case "clearNotification": AirshipProxy.shared.push.clearNotification( try requireStringArg(args.first) ) - return nil + return .handledSync(nil) // Push iOS case "setForegroundPresentationOptions": @@ -382,7 +405,7 @@ class UnityPlugin: NSObject { names: try requireStringArrayArg(args.first) ) } - return nil + return .handledSync(nil) case "setNotificationOptions": try MainActor.assumeIsolated { @@ -390,60 +413,61 @@ class UnityPlugin: NSObject { names: try requireStringArrayArg(args.first) ) } - return nil + return .handledSync(nil) case "isAutobadgeEnabled": - return try AirshipProxy.shared.push.isAutobadgeEnabled() + return .handledSync(try AirshipProxy.shared.push.isAutobadgeEnabled()) case "setAutobadgeEnabled": try AirshipProxy.shared.push.setAutobadgeEnabled( try requireBoolArg(args.first) ) - return nil + return .handledSync(nil) case "getBadgeNumber": - return try MainActor.assumeIsolated { + let value = try MainActor.assumeIsolated { try AirshipProxy.shared.push.getBadgeNumber() } + return .handledSync(value) case "setQuietTimeEnabled": try AirshipProxy.shared.push.setQuietTimeEnabled( try requireBoolArg(args.first) ) - return nil + return .handledSync(nil) case "isQuietTimeEnabled": - return try AirshipProxy.shared.push.isQuietTimeEnabled() + return .handledSync(try AirshipProxy.shared.push.isQuietTimeEnabled()) case "setQuietTime": try AirshipProxy.shared.push.setQuietTime( try requireCodableArg(args.first) ) - return nil + return .handledSync(nil) case "getQuietTime": - return try AirshipProxy.shared.push.getQuietTime() + return .handledSync(try AirshipProxy.shared.push.getQuietTime()) case "trackInteraction": try AirshipProxy.shared.featureFlagManager.trackInteraction( flag: try AirshipJSON.wrap(requireParsedAnyArg(args.first)).decode() ) - return nil + return .handledSync(nil) default: - return _notHandled + return .notHandled } } - func handleCallAsync(method: String, args: [Any]) async throws -> Any? { + func handleCallAsync(method: String, args: [Any]) async throws -> HandleResult { AirshipLogger.debug("UnityPlugin async \(method): \(args)") switch method { case "waitForChannelId": - return try await AirshipProxy.shared.channel.waitForChannelID() + return .handledAsync(try await AirshipProxy.shared.channel.waitForChannelID()) case "getChannelSubscriptionLists": - return try await AirshipProxy.shared.channel.fetchSubscriptionLists() + return .handledAsync(try await AirshipProxy.shared.channel.fetchSubscriptionLists()) case "getContactSubscriptionLists": let subscriptionLists = try await AirshipProxy.shared.contact.getSubscriptionLists() @@ -453,66 +477,66 @@ class UnityPlugin: NSObject { for (listId, scopes) in subscriptionListsDict { resultArray.append(["listId": listId, "scopes": scopes]) } - return resultArray + return .handledAsync(resultArray) case "getNamedUserId": - return try await AirshipProxy.shared.contact.namedUserID + return .handledAsync(try await AirshipProxy.shared.contact.namedUserID) case "setDisplayInterval": try await AirshipProxy.shared.inApp.setDisplayInterval( milliseconds: try requireIntArg(args.first) ) - return nil + return .handledAsync(nil) case "getUnreadCount": - return try await AirshipProxy.shared.messageCenter.unreadCount + return .handledAsync(try await AirshipProxy.shared.messageCenter.unreadCount) case "getMessages": - return try await AirshipProxy.shared.messageCenter.messages + return .handledAsync(try await AirshipProxy.shared.messageCenter.messages) case "markMessageRead": try await AirshipProxy.shared.messageCenter.markMessageRead( messageID: requireStringArg(args.first) ) - return nil + return .handledAsync(nil) case "deleteMessage": try await AirshipProxy.shared.messageCenter.deleteMessage( messageID: requireStringArg(args.first) ) - return nil + return .handledAsync(nil) case "refreshMessages": try await AirshipProxy.shared.messageCenter.refresh() - return nil + return .handledAsync(nil) case "getPreferenceCenterConfig": - return try await AirshipProxy.shared.preferenceCenter.getPreferenceCenterConfig( + return .handledAsync(try await AirshipProxy.shared.preferenceCenter.getPreferenceCenterConfig( preferenceCenterID: try requireStringArg(args.first) - ) + )) case "enableUserNotifications": - return try await AirshipProxy.shared.push.enableUserPushNotifications( + return .handledAsync(try await AirshipProxy.shared.push.enableUserPushNotifications( args: try optionalCodableArg(args.first) - ) + )) case "getNotificationStatus": - return try await AirshipProxy.shared.push.notificationStatus + return .handledAsync(try await AirshipProxy.shared.push.notificationStatus) case "getActiveNotifications": - return try await AirshipProxy.shared.push.getActiveNotifications() + return .handledAsync(try await AirshipProxy.shared.push.getActiveNotifications()) case "setBadgeNumber": try await AirshipProxy.shared.push.setBadgeNumber( try requireIntArg(args.first) ) - return nil + return .handledAsync(nil) case "runAction": - return try await AirshipProxy.shared.action.runAction( + return .handledAsync(try await AirshipProxy.shared.action.runAction( try requireStringArg(args.first), value: try? AirshipJSON.wrap(try requireStringArg(args[1])) - ) + )) case "flag": let flag = try await AirshipProxy.shared.featureFlagManager.flag( @@ -525,7 +549,6 @@ class UnityPlugin: NSObject { result["isEligible"] = flagDict["isEligible"] as? Bool ?? false result["exists"] = flagDict["exists"] as? Bool ?? false - // Stringify the nested objects so Unity's JsonUtility can deserialize them if let internalObj = flagDict["_internal"], let internalData = try? JSONSerialization.data(withJSONObject: internalObj) { result["_internal"] = String(data: internalData, encoding: .utf8) ?? "" @@ -536,10 +559,10 @@ class UnityPlugin: NSObject { result["variables"] = String(data: variablesData, encoding: .utf8) ?? "" } - return result + return .handledAsync(result) default: - return nil + return .notHandled } } From 2dfcbc9945fe0f3a825d031bb03d9b047f14aebc Mon Sep 17 00:00:00 2001 From: Ulrich GIBERNE Date: Tue, 14 Apr 2026 17:43:44 -0400 Subject: [PATCH 26/27] cleaning iOS bridge response --- Assets/Plugins/iOS/UnityPlugin.swift | 35 ++++++++++++------- .../UrbanAirship/Platforms/IAirshipPlugin.cs | 23 +++++++++--- 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/Assets/Plugins/iOS/UnityPlugin.swift b/Assets/Plugins/iOS/UnityPlugin.swift index 14649c87..a670e581 100644 --- a/Assets/Plugins/iOS/UnityPlugin.swift +++ b/Assets/Plugins/iOS/UnityPlugin.swift @@ -17,6 +17,22 @@ private enum HandleResult { case notHandled } +private func makeSuccessResponse(_ jsonResult: String) -> UnsafeMutablePointer? { + if let data = try? JSONSerialization.data(withJSONObject: ["result": jsonResult]), + let str = String(data: data, encoding: .utf8) { + return strdup(str) + } + return strdup("{\"error\":\"Failed to create response envelope\"}") +} + +private func makeErrorResponse(_ message: String) -> UnsafeMutablePointer? { + if let data = try? JSONSerialization.data(withJSONObject: ["error": message]), + let str = String(data: data, encoding: .utf8) { + return strdup(str) + } + return strdup("{\"error\":\"Unknown error\"}") +} + @_cdecl("UnityPlugin_call") public func UnityPlugin_call(_ method: UnsafePointer, argsJson: UnsafePointer) -> UnsafeMutablePointer? { let methodStr = String(cString: method) @@ -27,8 +43,7 @@ public func UnityPlugin_call(_ method: UnsafePointer, argsJson: UnsafePoi let data = argsJsonStr.data(using: .utf8) ?? Data() args = (try JSONSerialization.jsonObject(with: data) as? [Any]) ?? [] } catch { - AirshipLogger.error("Failed to deserialize arguments for method \(methodStr): \(error)") - return strdup("{}") + return makeErrorResponse("Failed to deserialize arguments for method \(methodStr): \(error)") } let result: Any? @@ -46,26 +61,22 @@ public func UnityPlugin_call(_ method: UnsafePointer, argsJson: UnsafePoi case .handledAsync(let value): result = value case .notHandled: - AirshipLogger.error("Unknown method: \(methodStr)") - return strdup("{}") + return makeErrorResponse("Unknown method: \(methodStr)") default: - AirshipLogger.error("Unexpected result type for async handler \(methodStr)") - return strdup("{}") + return makeErrorResponse("Unexpected result type for async handler \(methodStr)") } default: - AirshipLogger.error("Unexpected result type for sync handler \(methodStr)") - return strdup("{}") + return makeErrorResponse("Unexpected result type for sync handler \(methodStr)") } } catch { - AirshipLogger.error("Error executing method \(methodStr): \(error)") - return strdup("{}") + return makeErrorResponse("Error executing method \(methodStr): \(error)") } do { let jsonResult = try AirshipJSON.wrap(result).toString() - return strdup(jsonResult) + return makeSuccessResponse(jsonResult) } catch { - return strdup("{}") + return makeErrorResponse("Failed to serialize result for \(methodStr): \(error)") } } diff --git a/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs b/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs index 5a17d965..ef5d3964 100644 --- a/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs +++ b/Assets/UrbanAirship/Platforms/IAirshipPlugin.cs @@ -86,20 +86,35 @@ public GameObject Listener { internal class AirshipPluginiOS : IAirshipPlugin{ + [Serializable] + private class CallResponse { + public string result; + public string error; + } + [DllImport ("__Internal")] private static extern IntPtr UnityPlugin_call (string method, string argsJson); [DllImport ("libc")] private static extern void free (IntPtr ptr); + /// + /// Calls the native plugin, parses the response envelope, and returns + /// the inner result JSON string. Throws on native errors. + /// private static string CallNative (string method, string argsJson) { IntPtr ptr = UnityPlugin_call(method, argsJson); if (ptr == IntPtr.Zero) { - return null; + throw new Exception("Airship: null response from native for method " + method); } - string result = Marshal.PtrToStringUTF8(ptr); + string responseJson = Marshal.PtrToStringUTF8(ptr); free(ptr); - return result; + + CallResponse response = JsonUtility.FromJson(responseJson); + if (!string.IsNullOrEmpty(response.error)) { + throw new Exception(response.error); + } + return response.result; } public void Call (string method, params object[] args) { @@ -111,7 +126,7 @@ public T Call (string method, params object[] args) { string argsJson = SerializeArgs(args); string resultJson = CallNative(method, argsJson); - if (string.IsNullOrEmpty(resultJson) || resultJson == "{}") { + if (string.IsNullOrEmpty(resultJson) || resultJson == "null" || resultJson == "{}") { return default(T); } From 484c8e66e38bad9492d74b8facdb708286052422 Mon Sep 17 00:00:00 2001 From: Ulrich GIBERNE Date: Tue, 14 Apr 2026 17:44:36 -0400 Subject: [PATCH 27/27] fix subscription list when empty --- Assets/UrbanAirship/Platforms/AirshipChannel.cs | 3 +++ Assets/UrbanAirship/Platforms/AirshipContact.cs | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/Assets/UrbanAirship/Platforms/AirshipChannel.cs b/Assets/UrbanAirship/Platforms/AirshipChannel.cs index 44423422..b3a1ab42 100644 --- a/Assets/UrbanAirship/Platforms/AirshipChannel.cs +++ b/Assets/UrbanAirship/Platforms/AirshipChannel.cs @@ -107,6 +107,9 @@ public IEnumerator GetSubscriptionLists(Action> onComplete, yield return AirshipCoroutineHelper.RunAsync( () => { string subscriptionListsAsJson = plugin.Call("getChannelSubscriptionLists"); + if (string.IsNullOrEmpty(subscriptionListsAsJson)) { + return Enumerable.Empty(); + } JsonArray jsonArray = JsonArray.FromJson(subscriptionListsAsJson); return jsonArray.AsEnumerable(); }, diff --git a/Assets/UrbanAirship/Platforms/AirshipContact.cs b/Assets/UrbanAirship/Platforms/AirshipContact.cs index 3f82f012..5c2b1577 100644 --- a/Assets/UrbanAirship/Platforms/AirshipContact.cs +++ b/Assets/UrbanAirship/Platforms/AirshipContact.cs @@ -92,7 +92,14 @@ public IEnumerator GetSubscriptionLists(Action> scopedSubscriptionLists = new Dictionary>(); string subscriptionListsAsJson = plugin.Call("getContactSubscriptionLists"); + if (string.IsNullOrEmpty(subscriptionListsAsJson)) { + return scopedSubscriptionLists; + } + ScopedSubscriptionList[] _scopedSubscriptionLists = JsonArray.FromJson(subscriptionListsAsJson).values; + if (_scopedSubscriptionLists == null) { + return scopedSubscriptionLists; + } foreach (ScopedSubscriptionList subscriptionList in _scopedSubscriptionLists) {