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": [
{