diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a86ac1..bc3474d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ All notable changes to this package will be documented in this file. +## [1.4.3] - 2022-04-15 + +### Fixes: +- [Android] [issue 101](https://github.com/Unity-Technologies/com.unity.mobile.notifications/pull/101) Fix GC allocation every frame. +- [1360115](https://issuetracker.unity3d.com/issues/mobile-notifications-project-settings-window-loses-focus-when-clicking-on-any-category-while-mobile-notifications-pane-is-open) Fixed focus loss when trying to switching from notifications settings. +- [Android] [case 1376849] Fixed receivers needs to be marked as exported in manifest file, disable exact alarms on Android 12 because they require special permissions. +- [iOS] [1375744](https://issuetracker.unity3d.com/issues/ios-app-freezes-slash-crashes-when-both-mobile-notifications-and-firebase-are-used) Fixed application startup hang when application uses Unity notifications or Firebase. +- [iOS] [case 1382960] Handle numeric data in UserInfo. +- [iOS] Correctly handle boolean values in UserInfo, previously boolean values were detected as numbers and get parsed as 1 or 0, now they will be correctly handled as 'true' and 'false'. + ## [1.4.2] - 2021-07-22 ### Fixes: diff --git a/Editor/AndroidNotificationPostProcessor.cs b/Editor/AndroidNotificationPostProcessor.cs index 7d2981c..200d265 100644 --- a/Editor/AndroidNotificationPostProcessor.cs +++ b/Editor/AndroidNotificationPostProcessor.cs @@ -112,7 +112,7 @@ internal static void InjectReceivers(string manifestPath, XmlDocument manifestXm applicationXmlNode.AppendChild(notificationManagerReceiver); } - notificationManagerReceiver.SetAttribute("exported", kAndroidNamespaceURI, "true"); + notificationManagerReceiver.SetAttribute("exported", kAndroidNamespaceURI, "false"); // Create notification restart-on-boot receiver if necessary. if (notificationRestartOnBootReceiver == null) @@ -130,6 +130,7 @@ internal static void InjectReceivers(string manifestPath, XmlDocument manifestXm applicationXmlNode.AppendChild(notificationRestartOnBootReceiver); } notificationRestartOnBootReceiver.SetAttribute("enabled", kAndroidNamespaceURI, "false"); + notificationRestartOnBootReceiver.SetAttribute("exported", kAndroidNamespaceURI, "false"); } internal static void AppendAndroidPermissionField(string manifestPath, XmlDocument xmlDoc, string name) diff --git a/Editor/NotificationSettingsManager.cs b/Editor/NotificationSettingsManager.cs index dae2a2e..12c8492 100644 --- a/Editor/NotificationSettingsManager.cs +++ b/Editor/NotificationSettingsManager.cs @@ -279,9 +279,9 @@ public void RemoveDrawableResource(int index) var scale = drawableResource.Type == NotificationIconType.Small ? 0.375f : 1; var textXhdpi = TextureAssetUtils.ScaleTexture(texture, (int)(128 * scale), (int)(128 * scale)); - var textHdpi = TextureAssetUtils.ScaleTexture(texture, (int)(96 * scale), (int)(96 * scale)); - var textMdpi = TextureAssetUtils.ScaleTexture(texture, (int)(64 * scale), (int)(64 * scale)); - var textLdpi = TextureAssetUtils.ScaleTexture(texture, (int)(48 * scale), (int)(48 * scale)); + var textHdpi = TextureAssetUtils.ScaleTexture(texture, (int)(96 * scale), (int)(96 * scale)); + var textMdpi = TextureAssetUtils.ScaleTexture(texture, (int)(64 * scale), (int)(64 * scale)); + var textLdpi = TextureAssetUtils.ScaleTexture(texture, (int)(48 * scale), (int)(48 * scale)); icons[string.Format("drawable-xhdpi-v11/{0}.png", drawableResource.Id)] = textXhdpi.EncodeToPNG(); icons[string.Format("drawable-hdpi-v11/{0}.png", drawableResource.Id)] = textHdpi.EncodeToPNG(); diff --git a/Editor/NotificationSettingsProvider.cs b/Editor/NotificationSettingsProvider.cs index e379461..83a67e6 100644 --- a/Editor/NotificationSettingsProvider.cs +++ b/Editor/NotificationSettingsProvider.cs @@ -5,6 +5,7 @@ using UnityEditorInternal; using Unity.Notifications.iOS; using UnityEngine.Assertions; +using UnityEngine.UIElements; namespace Unity.Notifications { @@ -20,7 +21,7 @@ internal class NotificationSettingsProvider : SettingsProvider private readonly GUIContent k_IdentifierLabelText = new GUIContent("Identifier"); private readonly GUIContent k_TypeLabelText = new GUIContent("Type"); - private readonly string[] k_ToolbarStrings = {"Android", "iOS"}; + private readonly string[] k_ToolbarStrings = { "Android", "iOS" }; private const string k_InfoStringAndroid = "Only icons added to this list or manually added to the 'res/drawable' folder can be used for notifications.\n" + "Note, that not all devices support colored icons.\n\n" + @@ -47,10 +48,18 @@ static SettingsProvider CreateMobileNotificationsSettingsProvider() return new NotificationSettingsProvider("Project/Mobile Notifications", SettingsScope.Project); } + public override void OnActivate(string searchContext, VisualElement rootElement) + { + base.OnActivate(searchContext, rootElement); + // in case of domain reload (enter-exit play mode, this gets lost) + if (m_SettingsManager == null) + Initialize(); + } + public override void OnDeactivate() { + base.OnDeactivate(); m_SettingsManager.SaveSettings(false); - SettingsService.NotifySettingsProviderChanged(); } private void Initialize() @@ -212,6 +221,9 @@ private static Rect AddPadding(Rect rect, float horizontal, float vertical) public override void OnGUI(string searchContext) { + if (m_SettingsManager == null) + Initialize(); + // This has to be called to sync all the changes between m_SettingsManager and m_SettingsManagerObject. if (m_SettingsManagerObject.targetObject != null) m_SettingsManagerObject.Update(); @@ -223,7 +235,12 @@ public override void OnGUI(string searchContext) // Draw the toolbar for Android/iOS. var toolBarRect = new Rect(totalRect.x, totalRect.y, totalRect.width, k_ToolbarHeight); - m_SettingsManager.ToolbarIndex = GUI.Toolbar(toolBarRect, m_SettingsManager.ToolbarIndex, k_ToolbarStrings); + var toolbarIndex = GUI.Toolbar(toolBarRect, m_SettingsManager.ToolbarIndex, k_ToolbarStrings); + if (toolbarIndex != m_SettingsManager.ToolbarIndex) + { + m_SettingsManager.ToolbarIndex = toolbarIndex; + m_SettingsManager.SaveSettings(); + } var notificationSettingsRect = new Rect(totalRect.x, k_ToolbarHeight + 2, totalRect.width, totalRect.height - k_ToolbarHeight - k_Padding); diff --git a/Editor/TextureAssetUtils.cs b/Editor/TextureAssetUtils.cs index 5e861a7..6227fbe 100644 --- a/Editor/TextureAssetUtils.cs +++ b/Editor/TextureAssetUtils.cs @@ -71,7 +71,7 @@ public static Texture2D ProcessTextureForType(Texture2D sourceTexture, Notificat if (type == NotificationIconType.Small) { texture = new Texture2D(sourceTexture.width, sourceTexture.height, TextureFormat.RGBA32, true, false); - for (var i = 0; i < sourceTexture.mipmapCount; i++) + for (var i = 0; i < sourceTexture.mipmapCount; i++) { var c_0 = sourceTexture.GetPixels(i); var c_1 = texture.GetPixels(i); diff --git a/Runtime/Android/AndroidNotification.cs b/Runtime/Android/AndroidNotification.cs index 7c5cf6e..e9eeb18 100644 --- a/Runtime/Android/AndroidNotification.cs +++ b/Runtime/Android/AndroidNotification.cs @@ -186,6 +186,9 @@ public DateTime CustomTimestamp /// /// Create a notification struct with all optional fields set to default values. /// + /// Notification title + /// Text to show on notification + /// Date and time when to show, can be DateTime.Now to show right away public AndroidNotification(string title, string text, DateTime fireTime) { Title = title; @@ -214,6 +217,10 @@ public AndroidNotification(string title, string text, DateTime fireTime) /// /// Create a repeatable notification struct with all optional fields set to default values. /// + /// Notification title + /// Text to show on notification + /// Date and time when to show, can be DateTime.Now to show right away + /// Makes notification repeatable with this time interval /// /// There is a minimum period of 1 minute for repeating notifications. /// @@ -226,6 +233,11 @@ public AndroidNotification(string title, string text, DateTime fireTime, TimeSpa /// /// Create a notification struct with a custom small icon and all optional fields set to default values. /// + /// Notification title + /// Text to show on notification + /// Date and time when to show, can be DateTime.Now to show right away + /// Makes notification repeatable with this time interval + /// Name of the small icon to be shown on notification public AndroidNotification(string title, string text, DateTime fireTime, TimeSpan repeatInterval, string smallIcon) : this(title, text, fireTime, repeatInterval) { diff --git a/Runtime/Android/AndroidNotificationCallback.cs b/Runtime/Android/AndroidNotificationCallback.cs index b2178f6..d032a58 100644 --- a/Runtime/Android/AndroidNotificationCallback.cs +++ b/Runtime/Android/AndroidNotificationCallback.cs @@ -10,7 +10,7 @@ public NotificationCallback() : base("com.unity.androidnotifications.Notificatio public void onSentNotification(AndroidJavaObject notificationIntent) { - AndroidReceivedNotificationMainThreadDispatcher.EnqueueReceivedNotification(notificationIntent); + AndroidReceivedNotificationMainThreadDispatcher.GetInstance().EnqueueReceivedNotification(notificationIntent); } } } diff --git a/Runtime/Android/AndroidNotificationCenter.cs b/Runtime/Android/AndroidNotificationCenter.cs index 59f8844..658df00 100644 --- a/Runtime/Android/AndroidNotificationCenter.cs +++ b/Runtime/Android/AndroidNotificationCenter.cs @@ -44,7 +44,7 @@ public class AndroidNotificationCenter /// /// Subscribe to this event to receive callbacks whenever a scheduled notification is shown to the user. /// - public static event NotificationReceivedCallback OnNotificationReceived = delegate {}; + public static event NotificationReceivedCallback OnNotificationReceived = delegate { }; private static AndroidJavaClass s_NotificationManagerClass; private static AndroidJavaObject s_NotificationManager; @@ -54,7 +54,9 @@ public class AndroidNotificationCenter /// /// Initialize the AndroidNotificationCenter class. + /// Can be safely called multiple times /// + /// True if has been successfully initialized public static bool Initialize() { if (s_Initialized) @@ -93,6 +95,7 @@ public static bool Initialize() /// On older Android versions settings set on the notification channel struct will still be applied to the notification /// if they are supported to by the Android version the app is running on. /// + /// Channel parameters /// /// When a channel is deleted and recreated, all of the previous settings are restored. In order to change any settings /// besides the name or description an entirely new channel (with a different channel ID) must be created. @@ -133,6 +136,8 @@ public static void RegisterNotificationChannel(AndroidNotificationChannel channe /// Returns the notification channel with the specified id. /// The notification channel struct fields might not be identical to the channel struct used to initially register the channel if they were changed by the user. /// + /// ID of the channel to retrieve + /// Channel with given ID or empty struct if such channel does not exist public static AndroidNotificationChannel GetNotificationChannel(string channelId) { return GetNotificationChannels().SingleOrDefault(channel => channel.Id == channelId); @@ -141,6 +146,7 @@ public static AndroidNotificationChannel GetNotificationChannel(string channelId /// /// Returns all notification channels that were created by the app. /// + /// All existing channels public static AndroidNotificationChannel[] GetNotificationChannels() { if (!Initialize()) @@ -173,6 +179,7 @@ public static AndroidNotificationChannel[] GetNotificationChannels() /// /// Delete the specified notification channel. /// + /// ID of the channel to delete public static void DeleteNotificationChannel(string channelId) { if (Initialize()) @@ -183,6 +190,9 @@ public static void DeleteNotificationChannel(string channelId) /// Schedule a notification which will be shown at the time specified in the notification struct. /// The returned id can later be used to update the notification before it's triggered, it's current status can be tracked using CheckScheduledNotificationStatus. /// + /// Data for the notification + /// ID of the channel to send notification to + /// The generated ID for the notification public static int SendNotification(AndroidNotification notification, string channelId) { if (!Initialize()) @@ -198,6 +208,9 @@ public static int SendNotification(AndroidNotification notification, string chan /// Schedule a notification which will be shown at the time specified in the notification struct. /// The specified id can later be used to update the notification before it's triggered, it's current status can be tracked using CheckScheduledNotificationStatus. /// + /// Data for the notification + /// ID of the channel to send notification to + /// A unique ID for the notification public static void SendNotificationWithExplicitID(AndroidNotification notification, string channelId, int id) { if (Initialize()) @@ -208,6 +221,9 @@ public static void SendNotificationWithExplicitID(AndroidNotification notificati /// Update an already scheduled notification. /// If a notification with the specified id was already scheduled it will be overridden with the information from the passed notification struct. /// + /// ID of the notification to update + /// Data for the notification + /// ID of the channel to send notification to public static void UpdateScheduledNotification(int id, AndroidNotification notification, string channelId) { if (!Initialize()) @@ -221,6 +237,7 @@ public static void UpdateScheduledNotification(int id, AndroidNotification notif /// Cancel a scheduled or previously shown notification. /// The notification will no longer be displayed on it's scheduled time. If it's already delivered it will be removed from the status bar. /// + /// ID of the notification to cancel public static void CancelNotification(int id) { if (!Initialize()) @@ -234,6 +251,7 @@ public static void CancelNotification(int id) /// Cancel a scheduled notification. /// The notification will no longer be displayed on it's scheduled time. It it will not be removed from the status bar if it's already delivered. /// + /// ID of the notification to cancel public static void CancelScheduledNotification(int id) { if (Initialize()) @@ -244,6 +262,7 @@ public static void CancelScheduledNotification(int id) /// Cancel a previously shown notification. /// The notification will be removed from the status bar. /// + /// ID of the notification to cancel public static void CancelDisplayedNotification(int id) { if (Initialize()) @@ -287,6 +306,8 @@ public static void CancelAllDisplayedNotifications() /// Return the status of a scheduled notification. /// Only available in API 23 and above. /// + /// ID of the notification to check + /// The status of the notification public static NotificationStatus CheckScheduledNotificationStatus(int id) { if (!Initialize()) diff --git a/Runtime/Android/AndroidNotificationChannel.cs b/Runtime/Android/AndroidNotificationChannel.cs index f1112d6..5a6c02e 100644 --- a/Runtime/Android/AndroidNotificationChannel.cs +++ b/Runtime/Android/AndroidNotificationChannel.cs @@ -132,6 +132,10 @@ public bool Enabled /// /// Create a notification channel struct with all optional fields set to default values. /// + /// ID for the channel + /// Channel name + /// Channel description + /// Importance of the channel public AndroidNotificationChannel(string id, string name, string description, Importance importance) { Id = id; diff --git a/Runtime/Android/AndroidNotificationIntentData.cs b/Runtime/Android/AndroidNotificationIntentData.cs index c949dc5..efd2abd 100644 --- a/Runtime/Android/AndroidNotificationIntentData.cs +++ b/Runtime/Android/AndroidNotificationIntentData.cs @@ -25,6 +25,9 @@ public class AndroidNotificationIntentData /// /// Create an AndroidNotificationIntentData with AndroidNotification, id, and channel id. /// + /// Notification id + /// ID of the notification channel + /// Data of the received notification public AndroidNotificationIntentData(int id, string channelId, AndroidNotification notification) { Id = id; diff --git a/Runtime/Android/AndroidReceivedNotificationMainThreadDispatcher.cs b/Runtime/Android/AndroidReceivedNotificationMainThreadDispatcher.cs index 3760a08..b1329f0 100644 --- a/Runtime/Android/AndroidReceivedNotificationMainThreadDispatcher.cs +++ b/Runtime/Android/AndroidReceivedNotificationMainThreadDispatcher.cs @@ -10,15 +10,15 @@ public class AndroidReceivedNotificationMainThreadDispatcher : MonoBehaviour { private static AndroidReceivedNotificationMainThreadDispatcher instance = null; - private static readonly Queue s_ReceivedNotificationQueue = new Queue(); + private List m_ReceivedNotificationQueue = new List(); - private static readonly List s_ReceivedNotificationList = new List(); + private List m_ReceivedNotificationList = new List(); - internal static void EnqueueReceivedNotification(AndroidJavaObject intent) + internal void EnqueueReceivedNotification(AndroidJavaObject intent) { - lock (s_ReceivedNotificationQueue) + lock (this) { - s_ReceivedNotificationQueue.Enqueue(intent); + m_ReceivedNotificationQueue.Add(intent); } } @@ -34,18 +34,21 @@ public void Update() { // Note: Don't call callbacks while locking receivedNotificationQueue, otherwise there's a risk // that callback might introduce an operations which would create a deadlock - lock (s_ReceivedNotificationQueue) + lock (this) { - s_ReceivedNotificationList.AddRange(s_ReceivedNotificationQueue); - s_ReceivedNotificationQueue.Clear(); + if (m_ReceivedNotificationQueue.Count == 0) + return; + var temp = m_ReceivedNotificationQueue; + m_ReceivedNotificationQueue = m_ReceivedNotificationList; + m_ReceivedNotificationList = temp; } - foreach (var notification in s_ReceivedNotificationList) + foreach (var notification in m_ReceivedNotificationList) { AndroidNotificationCenter.ReceivedNotificationCallback(notification); } - s_ReceivedNotificationList.Clear(); + m_ReceivedNotificationList.Clear(); } void Awake() diff --git a/Runtime/Android/Plugins/com/unity/androidnotifications/UnityNotificationManager.java b/Runtime/Android/Plugins/com/unity/androidnotifications/UnityNotificationManager.java index d39e128..30abc47 100644 --- a/Runtime/Android/Plugins/com/unity/androidnotifications/UnityNotificationManager.java +++ b/Runtime/Android/Plugins/com/unity/androidnotifications/UnityNotificationManager.java @@ -372,6 +372,17 @@ protected static List loadNotificationIntents(Context context) { return intent_data_list; } + private static boolean canScheduleExactAlarms(AlarmManager alarmManager) { + // The commented-out if below is the correct one and should replace the one further down + // However it requires compile SDK 31 to compile, cutting edge and not shipped with Unity at the moment of writing this + // It means exact timing for notifications is not supported on Android 12+ out of the box + //if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) + //return alarmManager.canScheduleExactAlarms(); + if (Build.VERSION.SDK_INT >= 31) + return false; + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; + } + // Call AlarmManager to set the broadcast intent with fire time and interval. protected static void scheduleNotificationIntentAlarm(Context context, Intent intent, PendingIntent broadcast) { long repeatInterval = intent.getLongExtra("repeatInterval", 0L); @@ -380,7 +391,7 @@ protected static void scheduleNotificationIntentAlarm(Context context, Intent in AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); if (repeatInterval <= 0) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && canScheduleExactAlarms(alarmManager)) { alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, fireTime, broadcast); } else { alarmManager.set(AlarmManager.RTC_WAKEUP, fireTime, broadcast); diff --git a/Runtime/iOS/Plugins/UnityAppController+Notifications.mm b/Runtime/iOS/Plugins/UnityAppController+Notifications.mm index c56e0a3..0e7f0e5 100644 --- a/Runtime/iOS/Plugins/UnityAppController+Notifications.mm +++ b/Runtime/iOS/Plugins/UnityAppController+Notifications.mm @@ -17,9 +17,6 @@ + (void)load static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [UnityNotificationLifeCycleManager sharedInstance]; - - UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; - center.delegate = [UnityNotificationManager sharedInstance]; }); } @@ -49,10 +46,12 @@ + (instancetype)sharedInstance; [UnityNotificationManager sharedInstance].lastReceivedNotification = NULL; }]; - [nc addObserverForName: UIApplicationDidFinishLaunchingNotification + [nc addObserverForName: kUnityWillFinishLaunchingWithOptions object: nil queue: [NSOperationQueue mainQueue] usingBlock:^(NSNotification *notification) { + [UNUserNotificationCenter currentNotificationCenter].delegate = [UnityNotificationManager sharedInstance]; + BOOL authorizeOnLaunch = [[[NSBundle mainBundle] objectForInfoDictionaryKey: @"UnityNotificationRequestAuthorizationOnAppLaunch"] boolValue]; BOOL supportsPushNotification = [[[NSBundle mainBundle] objectForInfoDictionaryKey: @"UnityAddRemoteNotificationCapability"] boolValue]; BOOL registerRemoteOnLaunch = supportsPushNotification == YES ? diff --git a/Runtime/iOS/Plugins/UnityNotificationData.m b/Runtime/iOS/Plugins/UnityNotificationData.m index e4a7063..4a06061 100644 --- a/Runtime/iOS/Plugins/UnityNotificationData.m +++ b/Runtime/iOS/Plugins/UnityNotificationData.m @@ -13,6 +13,39 @@ #import "UnityNotificationData.h" + +static NSString* ParseNotificationDataObject(id obj) +{ + if ([obj isKindOfClass: [NSString class]]) + return obj; + else if ([obj isKindOfClass: [NSNumber class]]) + { + NSNumber* numberVal = obj; + if (CFBooleanGetTypeID() == CFGetTypeID((__bridge CFTypeRef)(obj))) + return numberVal.boolValue ? @"true" : @"false"; + return numberVal.stringValue; + } + else if ([NSJSONSerialization isValidJSONObject: obj]) + { + NSError* error; + NSData* data = [NSJSONSerialization dataWithJSONObject: obj options: NSJSONWritingPrettyPrinted error: &error]; + if (data) + { + NSString* v = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding]; + return v; + } + else + { + NSLog(@"Failed parsing notification userInfo value: %@", error); + } + } + else + NSLog(@"Failed parsing notification userInfo value"); + + NSObject* o = obj; + return o.description; +} + NotificationSettingsData UNNotificationSettingsToNotificationSettingsData(UNNotificationSettings* settings) { NotificationSettingsData settingsData; @@ -49,7 +82,7 @@ void initiOSNotificationData(iOSNotificationData* notificationData) notificationData->userInfo = NULL; } -void parseCustomizedData(iOSNotificationData* notificationData, UNNotificationRequest* request) +static void parseCustomizedData(iOSNotificationData* notificationData, UNNotificationRequest* request) { NSDictionary* userInfo = request.content.userInfo; NSObject* customizedData = [userInfo objectForKey: @"data"]; @@ -62,32 +95,9 @@ void parseCustomizedData(iOSNotificationData* notificationData, UNNotificationRe } // For push notifications, we have to handle more cases. - NSString* strData; - if ([NSJSONSerialization isValidJSONObject: customizedData]) - { - NSError* error; - NSData* data = [NSJSONSerialization dataWithJSONObject: customizedData options: NSJSONWritingPrettyPrinted error: &error]; - if (!data) - { - NSLog(@"Failed parsing notification userInfo[\"data\"]: %@", error); - return; - } - - strData = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding]; - } - else - { - // Convert bool defined with true/false in payload to "true"/"false", otherwise it will be converted to 1/0. - if ([customizedData isKindOfClass: [NSNumber class]] && CFBooleanGetTypeID() == CFGetTypeID((__bridge CFTypeRef)(customizedData))) - { - NSNumber* number = (NSNumber*)customizedData; - strData = number.boolValue ? @"true" : @"false"; - } - else - { - strData = customizedData.description; - } - } + NSString* strData = ParseNotificationDataObject(customizedData); + if (strData == nil) + NSLog(@"Failed parsing notification userInfo[\"data\"]"); NSMutableDictionary* parsedUserInfo = [NSMutableDictionary dictionaryWithDictionary: userInfo]; [parsedUserInfo setValue: strData forKey: @"data"]; @@ -216,21 +226,11 @@ void _ReadNSDictionary(void* csDict, void* nsDict, void (*callback)(void* csDcit NSDictionary* dict = (__bridge NSDictionary*)nsDict; [dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { NSString* k = key; - if ([obj isKindOfClass: [NSString class]]) - { - NSString* v = obj; + NSString* v = ParseNotificationDataObject(obj); + if (v != nil) callback(csDict, k.UTF8String, v.UTF8String); - } else - { - NSError* error; - NSData* data = [NSJSONSerialization dataWithJSONObject: obj options: NSJSONWritingPrettyPrinted error: &error]; - if (data) - { - NSString* v = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding]; - callback(csDict, k.UTF8String, v.UTF8String); - } - } + NSLog(@"Failed to parse value for key '%@'", key); }]; } diff --git a/Runtime/iOS/iOSNotificationCenter.cs b/Runtime/iOS/iOSNotificationCenter.cs index 3bd9dfb..6759cab 100644 --- a/Runtime/iOS/iOSNotificationCenter.cs +++ b/Runtime/iOS/iOSNotificationCenter.cs @@ -36,7 +36,7 @@ public class iOSNotificationCenter } private static bool s_OnNotificationReceivedCallbackSet; - private static event NotificationReceivedCallback s_OnNotificationReceived = delegate {}; + private static event NotificationReceivedCallback s_OnNotificationReceived = delegate { }; /// /// Subscribe to this event to receive a callback whenever a remote notification is received while the app is in foreground, @@ -64,7 +64,7 @@ public class iOSNotificationCenter } private static bool s_OnRemoteNotificationReceivedCallbackSet; - private static event NotificationReceivedCallback s_OnRemoteNotificationReceived = delegate {}; + private static event NotificationReceivedCallback s_OnRemoteNotificationReceived = delegate { }; internal delegate void AuthorizationRequestCompletedCallback(iOSAuthorizationRequestData data); @@ -94,6 +94,7 @@ static bool Initialize() /// /// Schedules a local notification for delivery. /// + /// Notification to schedule public static void ScheduleNotification(iOSNotification notification) { if (!Initialize()) @@ -105,6 +106,7 @@ public static void ScheduleNotification(iOSNotification notification) /// /// Returns all notifications that are currently scheduled. /// + /// Array of scheduled notifications public static iOSNotification[] GetScheduledNotifications() { return NotificationDataToNotifications(iOSNotificationsWrapper.GetScheduledNotificationData()); @@ -113,6 +115,7 @@ public static iOSNotification[] GetScheduledNotifications() /// /// Returns all of the app's delivered notifications that are currently shown in the Notification Center. /// + /// Array of delivered notifications public static iOSNotification[] GetDeliveredNotifications() { return NotificationDataToNotifications(iOSNotificationsWrapper.GetDeliveredNotificationData()); @@ -147,6 +150,7 @@ public static iOSNotification GetLastRespondedNotification() /// /// Unschedules the specified notification. /// + /// Identifier for the notification to be removed public static void RemoveScheduledNotification(string identifier) { if (Initialize()) @@ -156,6 +160,7 @@ public static void RemoveScheduledNotification(string identifier) /// /// Removes the specified notification from Notification Center. /// + /// Identifier for the notification to be removed public static void RemoveDeliveredNotification(string identifier) { if (Initialize()) @@ -183,6 +188,7 @@ public static void RemoveAllDeliveredNotifications() /// /// Get the notification settings for this app. /// + /// Notification settings public static iOSNotificationSettings GetNotificationSettings() { return iOSNotificationsWrapper.GetNotificationSettings(); diff --git a/Runtime/iOS/iOSNotificationTriggers.cs b/Runtime/iOS/iOSNotificationTriggers.cs index ec26eab..8c8b915 100644 --- a/Runtime/iOS/iOSNotificationTriggers.cs +++ b/Runtime/iOS/iOSNotificationTriggers.cs @@ -14,7 +14,7 @@ internal enum NotificationTriggerType /// /// iOSNotificationTrigger interface is implemented by notification trigger types representing an event that triggers the delivery of a notification. /// - public interface iOSNotificationTrigger {} + public interface iOSNotificationTrigger { } /// /// A trigger condition that causes a notification to be delivered when the user's device enters or exits the specified geographic region. diff --git a/Tests/Runtime/Android/AndroidNotificationTests.cs b/Tests/Runtime/Android/AndroidNotificationTests.cs index a47abea..999869d 100644 --- a/Tests/Runtime/Android/AndroidNotificationTests.cs +++ b/Tests/Runtime/Android/AndroidNotificationTests.cs @@ -10,6 +10,7 @@ class AndroidNotificationTests private static int receivedNotificationCount = 0; [Test] + [UnityPlatform(RuntimePlatform.Android)] public void CreateNotificationChannel_NotificationChannelIsCreated() { var testChannelId = "default_test_channel_10"; @@ -29,6 +30,7 @@ public void CreateNotificationChannel_NotificationChannelIsCreated() } [Test] + [UnityPlatform(RuntimePlatform.Android)] public void DeleteNotificationChannels_NotificationChannelsAreDeleted() { if (AndroidNotificationCenter.GetNotificationChannels().Length < 1) @@ -49,6 +51,7 @@ public void DeleteNotificationChannels_NotificationChannelsAreDeleted() } [UnityTest] + [UnityPlatform(RuntimePlatform.Android)] public IEnumerator SendNotificationExplicitID_NotificationIsReceived() { AndroidNotificationCenter.CancelAllNotifications(); @@ -81,7 +84,7 @@ public IEnumerator SendNotificationExplicitID_NotificationIsReceived() AndroidNotificationCenter.NotificationReceivedCallback receivedNotificationHandler = - delegate(AndroidNotificationIntentData data) + delegate (AndroidNotificationIntentData data) { receivedNotificationCount += 1; Assert.AreEqual(originalId, data.Id); @@ -93,7 +96,7 @@ public IEnumerator SendNotificationExplicitID_NotificationIsReceived() yield return new WaitForSeconds(8.0f); - Debug.LogWarning("SendNotification_NotificationIsReceived::: Assert.AreEqual(1, receivedNotificationCount) receivedNotificationCount: " + receivedNotificationCount.ToString()); + Debug.LogWarning("SendNotification_NotificationIsReceived::: Assert.AreEqual(1, receivedNotificationCount) receivedNotificationCount: " + receivedNotificationCount.ToString()); Assert.AreEqual(1, receivedNotificationCount); @@ -104,6 +107,7 @@ public IEnumerator SendNotificationExplicitID_NotificationIsReceived() } [UnityTest] + [UnityPlatform(RuntimePlatform.Android)] public IEnumerator SendNotification_NotificationIsReceived() { AndroidNotificationCenter.CancelAllNotifications(); @@ -133,7 +137,7 @@ public IEnumerator SendNotification_NotificationIsReceived() AndroidNotificationCenter.NotificationReceivedCallback receivedNotificationHandler = - delegate(AndroidNotificationIntentData data) + delegate (AndroidNotificationIntentData data) { receivedNotificationCount += 1; Assert.AreEqual(originalId, data.Id); @@ -144,7 +148,7 @@ public IEnumerator SendNotification_NotificationIsReceived() yield return new WaitForSeconds(8.0f); - Debug.LogWarning("SendNotification_NotificationIsReceived::: Assert.AreEqual(1, receivedNotificationCount) receivedNotificationCount: " + receivedNotificationCount.ToString()); + Debug.LogWarning("SendNotification_NotificationIsReceived::: Assert.AreEqual(1, receivedNotificationCount) receivedNotificationCount: " + receivedNotificationCount.ToString()); Assert.AreEqual(1, receivedNotificationCount); @@ -155,6 +159,7 @@ public IEnumerator SendNotification_NotificationIsReceived() } [UnityTest] + [UnityPlatform(RuntimePlatform.Android)] public IEnumerator SendNotificationAndCancelNotification_NotificationIsNotReceived() { AndroidNotificationCenter.CancelAllNotifications(); @@ -184,7 +189,7 @@ public IEnumerator SendNotificationAndCancelNotification_NotificationIsNotReceiv AndroidNotificationCenter.CancelAllNotifications(); AndroidNotificationCenter.NotificationReceivedCallback receivedNotificationHandler = - delegate(AndroidNotificationIntentData data) + delegate (AndroidNotificationIntentData data) { receivedNotificationCount += 1; Assert.AreEqual(originalId, data.Id); @@ -194,7 +199,7 @@ public IEnumerator SendNotificationAndCancelNotification_NotificationIsNotReceiv yield return new WaitForSeconds(6.0f); - Debug.LogWarning("SendNotification_NotificationIsReceived::: Assert.AreEqual(1, receivedNotificationCount) receivedNotificationCount: " + receivedNotificationCount.ToString()); + Debug.LogWarning("SendNotification_NotificationIsReceived::: Assert.AreEqual(1, receivedNotificationCount) receivedNotificationCount: " + receivedNotificationCount.ToString()); Assert.AreEqual(0, receivedNotificationCount); @@ -203,7 +208,7 @@ public IEnumerator SendNotificationAndCancelNotification_NotificationIsNotReceiv receivedNotificationCount = 0; } -// [UnityTest] + // [UnityTest] public IEnumerator ScheduleRepeatableNotification_NotificationsAreReceived() { AndroidNotificationCenter.CancelAllNotifications(); @@ -226,7 +231,7 @@ public IEnumerator ScheduleRepeatableNotification_NotificationsAreReceived() int originalId = AndroidNotificationCenter.SendNotification(n, "default_test_channel_2"); AndroidNotificationCenter.NotificationReceivedCallback receivedNotificationHandler = - delegate(AndroidNotificationIntentData data) + delegate (AndroidNotificationIntentData data) { receivedNotificationCount += 1; Assert.AreEqual(originalId, data.Id); @@ -303,6 +308,7 @@ public IEnumerator NotificationIsScheduled_NotificationStatusIsCorrectlyReported } [Test] + [UnityPlatform(RuntimePlatform.Android)] public void CreateNotificationChannelWithInitializedSettings_ChannelSettingsAreSaved() { var chOrig = new AndroidNotificationChannel(); @@ -314,7 +320,8 @@ public void CreateNotificationChannelWithInitializedSettings_ChannelSettingsAreS chOrig.CanShowBadge = true; chOrig.EnableLights = true; chOrig.EnableVibration = false; - chOrig.LockScreenVisibility = LockScreenVisibility.Private; + // read-only + //chOrig.LockScreenVisibility = LockScreenVisibility.Private; AndroidNotificationCenter.RegisterNotificationChannel(chOrig); @@ -326,10 +333,11 @@ public void CreateNotificationChannelWithInitializedSettings_ChannelSettingsAreS Assert.AreEqual(chOrig.Importance, ch.Importance); Assert.AreEqual(chOrig.EnableLights, ch.EnableLights); Assert.AreEqual(chOrig.EnableVibration, ch.EnableVibration); - Assert.AreEqual(chOrig.LockScreenVisibility, ch.LockScreenVisibility); + //Assert.AreEqual(chOrig.LockScreenVisibility, ch.LockScreenVisibility); } [UnityTest] + [UnityPlatform(RuntimePlatform.Android)] public IEnumerator SendNotification_NotificationIsReceived_CallMainThread() { AndroidNotificationCenter.CancelAllNotifications(); @@ -359,7 +367,7 @@ public IEnumerator SendNotification_NotificationIsReceived_CallMainThread() AndroidNotificationCenter.NotificationReceivedCallback receivedNotificationHandler = - delegate(AndroidNotificationIntentData data) + delegate (AndroidNotificationIntentData data) { receivedNotificationCount += 1; @@ -375,7 +383,7 @@ public IEnumerator SendNotification_NotificationIsReceived_CallMainThread() yield return new WaitForSeconds(8.0f); - Debug.LogWarning("SendNotification_NotificationIsReceived::: Assert.AreEqual(1, receivedNotificationCount) receivedNotificationCount: " + receivedNotificationCount.ToString()); + Debug.LogWarning("SendNotification_NotificationIsReceived::: Assert.AreEqual(1, receivedNotificationCount) receivedNotificationCount: " + receivedNotificationCount.ToString()); Assert.AreEqual(1, receivedNotificationCount); diff --git a/Tests/Runtime/Android/Unity.Android.Notifications.Tests.asmdef b/Tests/Runtime/Android/Unity.Android.Notifications.Tests.asmdef index b62a144..176cc1c 100644 --- a/Tests/Runtime/Android/Unity.Android.Notifications.Tests.asmdef +++ b/Tests/Runtime/Android/Unity.Android.Notifications.Tests.asmdef @@ -7,7 +7,8 @@ "TestAssemblies" ], "includePlatforms": [ - "Android" + "Android", + "Editor" ], "excludePlatforms": [], "allowUnsafeCode": false, diff --git a/Tests/Runtime/iOS/Unity.iOS.Notifications.Tests.asmdef b/Tests/Runtime/iOS/Unity.iOS.Notifications.Tests.asmdef index d0da52a..9b873c6 100644 --- a/Tests/Runtime/iOS/Unity.iOS.Notifications.Tests.asmdef +++ b/Tests/Runtime/iOS/Unity.iOS.Notifications.Tests.asmdef @@ -1,12 +1,14 @@ { "name": "Unity.iOS.Notifications.Tests", "references": [ - "Unity.Notifications.iOS" + "Unity.Notifications.iOS", + "Unity.Notifications" ], "optionalUnityReferences": [ "TestAssemblies" ], "includePlatforms": [ + "Editor", "iOS" ], "excludePlatforms": [], diff --git a/Tests/Runtime/iOS/iOSNotificationTests.cs b/Tests/Runtime/iOS/iOSNotificationTests.cs index 13a4239..72e5a92 100644 --- a/Tests/Runtime/iOS/iOSNotificationTests.cs +++ b/Tests/Runtime/iOS/iOSNotificationTests.cs @@ -4,11 +4,51 @@ using NUnit.Framework; using System.Collections; using Unity.Notifications.iOS; +#if UNITY_EDITOR +using Unity.Notifications; +using UnityEditor; +#endif class iOSNotificationTests + : IPrebuildSetup, IPostBuildCleanup { private static int receivedNotificationCount = 0; private static iOSNotification lastReceivedNotification = null; +#if UNITY_EDITOR + private static iOSSdkVersion originaliOSSDK; + private static bool originalRequestAuthorizationOnAppLaunch; + private static PresentationOption originalAuthorizationOptions; + private static bool originalAddRemoteNotificationCapability; + private static bool originalRequestRemoteOnLaunch; +#endif + + public void Setup() + { +#if UNITY_EDITOR + originaliOSSDK = PlayerSettings.iOS.sdkVersion; + originalRequestAuthorizationOnAppLaunch = NotificationSettings.iOSSettings.RequestAuthorizationOnAppLaunch; + originalAuthorizationOptions = NotificationSettings.iOSSettings.DefaultAuthorizationOptions; + originalAddRemoteNotificationCapability = NotificationSettings.iOSSettings.AddRemoteNotificationCapability; + originalRequestRemoteOnLaunch = NotificationSettings.iOSSettings.NotificationRequestAuthorizationForRemoteNotificationsOnAppLaunch; + + PlayerSettings.iOS.sdkVersion = iOSSdkVersion.SimulatorSDK; + NotificationSettings.iOSSettings.RequestAuthorizationOnAppLaunch = true; + NotificationSettings.iOSSettings.DefaultAuthorizationOptions = originalAuthorizationOptions; + NotificationSettings.iOSSettings.AddRemoteNotificationCapability = false; + NotificationSettings.iOSSettings.NotificationRequestAuthorizationForRemoteNotificationsOnAppLaunch = false; +#endif + } + + public void Cleanup() + { +#if UNITY_EDITOR + PlayerSettings.iOS.sdkVersion = originaliOSSDK; + NotificationSettings.iOSSettings.RequestAuthorizationOnAppLaunch = originalRequestAuthorizationOnAppLaunch; + NotificationSettings.iOSSettings.DefaultAuthorizationOptions = originalAuthorizationOptions; + NotificationSettings.iOSSettings.AddRemoteNotificationCapability = originalAddRemoteNotificationCapability; + NotificationSettings.iOSSettings.NotificationRequestAuthorizationForRemoteNotificationsOnAppLaunch = originalRequestRemoteOnLaunch; +#endif + } [OneTimeSetUp] public void BeforeTests() @@ -36,6 +76,7 @@ public void AfterEachTest() } [UnityTest] + [UnityPlatform(RuntimePlatform.IPhonePlayer)] public IEnumerator SendSimpleNotification_NotificationIsReceived() { var timeTrigger = new iOSNotificationTimeIntervalTrigger() @@ -68,6 +109,7 @@ public IEnumerator SendSimpleNotification_NotificationIsReceived() } [UnityTest] + [UnityPlatform(RuntimePlatform.IPhonePlayer)] public IEnumerator SendNotificationWithUserInfo_NotificationIsReceivedWithSameUserInfo() { var timeTrigger = new iOSNotificationTimeIntervalTrigger() diff --git a/package.json b/package.json index 66c232e..b509a03 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "com.unity.mobile.notifications", "displayName": "Mobile Notifications", - "version": "1.4.2", + "version": "1.4.3", "unity": "2019.4", "description": "Mobile Notifications package adds support for scheduling local repeatable or one-time notifications on iOS and Android.\n\nRequires iOS 10 and Android 4.4 or above.", "keywords": [ @@ -13,13 +13,16 @@ "dependencies": { "com.unity.modules.androidjni": "1.0.0" }, + "upm": { + "changelog": "### Fixes:\n- [Android] [issue 101](https://github.com/Unity-Technologies/com.unity.mobile.notifications/pull/101) Fix GC allocation every frame.\n- [1360115](https://issuetracker.unity3d.com/issues/mobile-notifications-project-settings-window-loses-focus-when-clicking-on-any-category-while-mobile-notifications-pane-is-open) Fixed focus loss when trying to switching from notifications settings.\n- [Android] [case 1376849] Fixed receivers needs to be marked as exported in manifest file, disable exact alarms on Android 12 because they require special permissions.\n- [iOS] [1375744](https://issuetracker.unity3d.com/issues/ios-app-freezes-slash-crashes-when-both-mobile-notifications-and-firebase-are-used) Fixed application startup hang when application uses Unity notifications or Firebase.\n- [iOS] [case 1382960] Handle numeric data in UserInfo.\n- [iOS] Correctly handle boolean values in UserInfo, previously boolean values were detected as numbers and get parsed as 1 or 0, now they will be correctly handled as 'true' and 'false'." + }, "upmCi": { - "footprint": "f7be3609993912ed716a8d81ec562cc11dd70cf9" + "footprint": "6645e25c3f172ba199dc2371163db7d07a512848" }, "repository": { "url": "https://github.com/Unity-Technologies/com.unity.mobile.notifications.git", "type": "git", - "revision": "6e1b6e1acbe5d585c8d9557e82a9b2b274aecfa5" + "revision": "4f4fa1a2f51ccabc89a7a174065332f61ea8a25d" }, "samples": [ {