diff --git a/CHANGELOG.md b/CHANGELOG.md index dc73592..d059c68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,27 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [4.0.0-pre.1] - 2023-09-05 + +### Changed + +- You can now only subscribe to OnNotificationReceived before calling RegisterForPushNotificationsAsync + - Once RegisterForPushNotificationsAsync completes, OnNotificationReceived will be invoked if the app was launched from a remote notification +- Updated `com.unity.services.analytics` dependency to 5.0.0 +- Updated `com.unity.services.core` dependency to 1.10.1 +- Added `com.unity.mobile.notifications` version `2.2.0` as a dependency. + +### Fixed + +- Behaviour when the app is launched from a push notification is now consistent between iOS and Android (incoming push notification data is broadcast after RegisterForPushNotificationsAsync flow is complete) + +## [3.0.1-pre.2] - 2023-08-15 + +### Fixed + +- Bug fix for PushNotificationsService's OnNotificationReceived not being called on IOS +- Bug fix where RegisterForPushNotificationsAsync is stuck when the user disables notification settings. + ## [3.0.1-pre.1] - 2023-03-20 ### Fixed diff --git a/Editor/Unity.Services.PushNotifications.Editor.asmdef b/Editor/Unity.Services.PushNotifications.Editor.asmdef index f376cda..a5a8c10 100644 --- a/Editor/Unity.Services.PushNotifications.Editor.asmdef +++ b/Editor/Unity.Services.PushNotifications.Editor.asmdef @@ -4,7 +4,9 @@ "Unity.Services.PushNotifications", "Unity.Services.Core", "Unity.Services.Core.Internal", - "Unity.Services.Core.Editor" + "Unity.Services.Core.Editor", + "Unity.Notifications.iOS", + "Unity.Notifications.Android" ], "includePlatforms": [ "Editor" diff --git a/Plugins/Android/PushNotificationsAndroidLib-release.aar b/Plugins/Android/PushNotificationsAndroidLib-release.aar index e8d73b5..affa22f 100644 Binary files a/Plugins/Android/PushNotificationsAndroidLib-release.aar and b/Plugins/Android/PushNotificationsAndroidLib-release.aar differ diff --git a/Plugins/Android/PushNotificationsAndroidLib-release.aar.meta b/Plugins/Android/PushNotificationsAndroidLib-release.aar.meta index 0162bca..986735a 100644 --- a/Plugins/Android/PushNotificationsAndroidLib-release.aar.meta +++ b/Plugins/Android/PushNotificationsAndroidLib-release.aar.meta @@ -1,3 +1,32 @@ fileFormatVersion: 2 guid: ef86ab48496543d195b88fd51a57d171 -timeCreated: 1675220299 \ No newline at end of file +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Android: Android + second: + enabled: 1 + settings: {} + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + userData: + assetBundleName: + assetBundleVariant: diff --git a/Plugins/iOS/PushNotificationManager.h b/Plugins/iOS/PushNotificationManager.h deleted file mode 100644 index 10f3138..0000000 --- a/Plugins/iOS/PushNotificationManager.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef PushNotificationManager_h -#define PushNotificationManager_h - -#import "UnityAppController.h" -#import - -// Required for internal messaging constants. -#include "Classes/PluginBase/LifeCycleListener.h" -#include "Classes/PluginBase/AppDelegateListener.h" - -typedef void (REGISTRATION_CALLBACK)(char *token); -typedef void (NOTIFICATION_CALLBACK)(char *serialisedUserInfo); - -@interface PushNotificationManager : NSObject - -@property NSString *deviceToken; -@property (nonatomic) REGISTRATION_CALLBACK *registrationCallback; -@property (nonatomic) NOTIFICATION_CALLBACK *notificationCallback; - -+ (instancetype)sharedInstance; -- (void) registerForRemoteNotifications : (REGISTRATION_CALLBACK) callback; -- (char *) getLaunchedNotificationString; -- (void) resetLaunchedNotificationString; -- (void) flushNotificationBuffer; - -@end - -#endif - diff --git a/Plugins/iOS/PushNotificationManager.h.meta b/Plugins/iOS/PushNotificationManager.h.meta deleted file mode 100644 index 28eb743..0000000 --- a/Plugins/iOS/PushNotificationManager.h.meta +++ /dev/null @@ -1,80 +0,0 @@ -fileFormatVersion: 2 -guid: 46294242127bd47e493a651900fee6cb -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 1 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - : Any - second: - enabled: 0 - settings: - Exclude Android: 1 - Exclude Editor: 1 - Exclude Linux64: 1 - Exclude OSXUniversal: 1 - Exclude Win: 1 - Exclude Win64: 1 - Exclude iOS: 0 - - first: - Android: Android - second: - enabled: 0 - settings: - CPU: ARMv7 - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - CPU: AnyCPU - DefaultValueInitialized: true - OS: AnyOS - - first: - Standalone: Linux64 - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: OSXUniversal - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: Win - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: Win64 - second: - enabled: 0 - settings: - CPU: None - - first: - iPhone: iOS - second: - enabled: 1 - settings: - AddToEmbeddedBinaries: false - CPU: AnyCPU - CompileFlags: - FrameworkDependencies: UserNotifications; - userData: - assetBundleName: - assetBundleVariant: diff --git a/Plugins/iOS/PushNotificationManager.mm b/Plugins/iOS/PushNotificationManager.mm deleted file mode 100644 index 3efac9f..0000000 --- a/Plugins/iOS/PushNotificationManager.mm +++ /dev/null @@ -1,212 +0,0 @@ -#import -#import -#import - -#import "PushNotificationManager.h" - -//MARK: Internal Class. Not publicly supported. Do not use directly. -@implementation PushNotificationManager : NSObject - -NSArray *bufferedNotifications; - -NSString *launchedNotificationString; - -+ (void)load { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - [PushNotificationManager sharedInstance]; - - UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; - center.delegate = [PushNotificationManager sharedInstance]; - }); -} - -// MARK: Interface implementations - -+ (instancetype)sharedInstance -{ - static PushNotificationManager *sharedInstance = nil; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sharedInstance = [[PushNotificationManager alloc] init]; - bufferedNotifications = [[NSArray alloc] init]; - launchedNotificationString = nil; - - NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; - - [nc addObserver:sharedInstance - selector:@selector(didRegisterForRemoteNotifications:) - name:kUnityDidRegisterForRemoteNotificationsWithDeviceToken - object:nil]; - - [nc addObserver:sharedInstance - selector:@selector(didFailToRegisterForRemoteNotifications:) - name:kUnityDidFailToRegisterForRemoteNotificationsWithError - object:nil]; - - [nc addObserver:sharedInstance - selector:@selector(didReceiveNotification:) - name:kUnityDidReceiveRemoteNotification - object:nil]; - - [nc addObserver:sharedInstance - selector:@selector(createLaunchedNotificationChecker:) - name:@"UIApplicationDidFinishLaunchingNotification" - object:nil]; - }); - - return sharedInstance; -} - -/// Called externally to register for push notifications. Initally requests notification permissions, -/// then registers to get a device token, which will be handled by one of the notification handlers. -- (void) registerForRemoteNotifications : (REGISTRATION_CALLBACK) callback -{ - // If we've already registered for a token previously, and have one ready, then just return it without - // requesting authorisation again. - if (_deviceToken != NULL && _deviceToken.length > 0) { - callback((char *)_deviceToken.UTF8String); - return; - } - - _registrationCallback = callback; - - // Register for the permissions to display notifications from the iOS system. Without this, we'd receive - // the pushes but wouldn't be able to display them to a user. - UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; - NSInteger authOptions = (UNAuthorizationOptionSound + UNAuthorizationOptionAlert + UNAuthorizationOptionBadge); - [center requestAuthorizationWithOptions:authOptions completionHandler:^(BOOL granted, NSError *error) { - if (granted) { - dispatch_async(dispatch_get_main_queue(), ^{ - [[UIApplication sharedApplication] registerForRemoteNotifications]; - }); - } - }]; -} - -// MARK: Delegate methods - -- (void) userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler -{ - NSDictionary *userInfo = notification.request.content.userInfo; - [self handleReceivedNotificationUserInfo:userInfo]; - completionHandler((UNAuthorizationOptionSound + UNAuthorizationOptionAlert + UNAuthorizationOptionBadge)); -} - -// MARK: Internal methods - -- (void) didRegisterForRemoteNotifications: (NSNotification *) notification -{ - if ([notification.userInfo isKindOfClass:[NSData class]]) - { - NSString *token = [[PushNotificationManager sharedInstance] getDeviceTokenFromNSData: (NSData*)notification.userInfo]; - [PushNotificationManager sharedInstance].deviceToken = token; - [PushNotificationManager sharedInstance].registrationCallback((char *)token.UTF8String); - } -} - -- (void) didFailToRegisterForRemoteNotifications: (NSNotification *) notification -{ - NSLog(@"Failed to register for push notifications"); -} - -- (void) didReceiveNotification: (NSNotification *) notification -{ - NSDictionary *userInfoDictionary = notification.userInfo; - [self handleReceivedNotificationUserInfo:userInfoDictionary]; -} - -- (void) handleReceivedNotificationUserInfo: (NSDictionary *)userInfoDictionary -{ - if ([PushNotificationManager sharedInstance].notificationCallback == nil) { - // Unity hasn't had time to register the callback yet, so let's store the notification data until we're ready for it. - bufferedNotifications = [bufferedNotifications arrayByAddingObject:userInfoDictionary]; - return; - } - - char *jsonString = [self serializeRemoteNotification:userInfoDictionary]; - - [PushNotificationManager sharedInstance].notificationCallback(jsonString); -} - -- (void) flushNotificationBuffer -{ - NSMutableArray *tempBuffer = [NSMutableArray arrayWithArray:bufferedNotifications]; - NSMutableArray *emptyBuffer = [NSMutableArray arrayWithArray:bufferedNotifications]; - - [emptyBuffer removeAllObjects]; - bufferedNotifications = [NSArray arrayWithArray:emptyBuffer]; - - for (NSDictionary *bufferedData in tempBuffer) { - [self handleReceivedNotificationUserInfo:bufferedData]; - } -} - -- (char *) serializeRemoteNotification: (NSDictionary *)notificationDictionary -{ - NSError *error = nil; - NSData *data = [NSJSONSerialization dataWithJSONObject:notificationDictionary options:kNilOptions error:&error]; - - if (error != nil) - { - NSLog(@"Failed to serialize notification user info to string. User info was: %@", notificationDictionary); - return nil; - } - - char *jsonString = (char *) [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] UTF8String]; - - return jsonString; -} - -/// Checks if app was launched from notification and sends to notification handler -- (void)createLaunchedNotificationChecker:(NSNotification *)notification -{ - NSDictionary *launchOptions = [notification userInfo]; - NSDictionary *launchedNotification = [launchOptions objectForKey: @"UIApplicationLaunchOptionsRemoteNotificationKey"]; - - if (launchedNotification) - { - char *serializedString = [self serializeRemoteNotification:launchedNotification]; - - launchedNotificationString = [NSString stringWithFormat:@"%s", serializedString]; - } -} - -- (char *) getLaunchedNotificationString -{ - return [[PushNotificationManager sharedInstance] convertNSStringToCString: launchedNotificationString]; -} - -- (void) resetLaunchedNotificationString -{ - launchedNotificationString = nil; -} - -// MARK: Helper methods - -- (NSString *)getDeviceTokenFromNSData:(NSData *)deviceTokenData { - const char *data = (const char *)[deviceTokenData bytes]; - NSMutableString *token = [NSMutableString string]; - - for (NSUInteger i = 0; i < [deviceTokenData length]; i++) { - [token appendFormat:@"%02.2hhX", data[i]]; - } - - return [token copy]; -} - -- (char *) convertNSStringToCString: (NSString*) nsString -{ - if (nsString == nil) - return nil; - - const char* nsStringUtf8 = [nsString UTF8String]; - //create a null terminated C string on the heap so that our string's memory isn't wiped out right after method's return - char* cString = (char*)malloc(strlen(nsStringUtf8) + 1); - strcpy(cString, nsStringUtf8); - - return cString; -} - -@end diff --git a/Plugins/iOS/PushNotificationManager.mm.meta b/Plugins/iOS/PushNotificationManager.mm.meta deleted file mode 100644 index a9c5d72..0000000 --- a/Plugins/iOS/PushNotificationManager.mm.meta +++ /dev/null @@ -1,85 +0,0 @@ -fileFormatVersion: 2 -guid: da538eb90ba2d4ee5b13504644879380 -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 1 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - : Any - second: - enabled: 0 - settings: - Exclude Android: 1 - Exclude Editor: 1 - Exclude Linux64: 1 - Exclude OSXUniversal: 1 - Exclude Win: 1 - Exclude Win64: 1 - Exclude iOS: 0 - - first: - Android: Android - second: - enabled: 0 - settings: - CPU: ARMv7 - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - CPU: AnyCPU - DefaultValueInitialized: true - OS: AnyOS - - first: - Standalone: Linux64 - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: OSXUniversal - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: Win - second: - enabled: 0 - settings: - CPU: x86 - - first: - Standalone: Win64 - second: - enabled: 0 - settings: - CPU: x86_64 - - first: - iPhone: iOS - second: - enabled: 1 - settings: - AddToEmbeddedBinaries: false - CPU: AnyCPU - CompileFlags: - FrameworkDependencies: UserNotifications; - - first: - tvOS: tvOS - second: - enabled: 1 - settings: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Plugins/iOS/PushNotificationManagerWrapper.h b/Plugins/iOS/PushNotificationManagerWrapper.h deleted file mode 100644 index 85fe376..0000000 --- a/Plugins/iOS/PushNotificationManagerWrapper.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef PushNotificationManagerWrapper_h -#define PushNotificationManagerWrapper_h - -#import "PushNotificationManager.h" - -extern "C" void NativeRegisterForPushNotifications(REGISTRATION_CALLBACK callback); -extern "C" void RegisterUnityCallbackForNotificationReceived(NOTIFICATION_CALLBACK callback); -extern "C" char * GetLaunchedNotificationString(); -extern "C" void ResetLaunchedNotificationString(); - -#endif diff --git a/Plugins/iOS/PushNotificationManagerWrapper.h.meta b/Plugins/iOS/PushNotificationManagerWrapper.h.meta deleted file mode 100644 index a389527..0000000 --- a/Plugins/iOS/PushNotificationManagerWrapper.h.meta +++ /dev/null @@ -1,80 +0,0 @@ -fileFormatVersion: 2 -guid: 8ff38dbd0933b433abe19d794a877c63 -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 1 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - : Any - second: - enabled: 0 - settings: - Exclude Android: 1 - Exclude Editor: 1 - Exclude Linux64: 1 - Exclude OSXUniversal: 1 - Exclude Win: 1 - Exclude Win64: 1 - Exclude iOS: 0 - - first: - Android: Android - second: - enabled: 0 - settings: - CPU: ARMv7 - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - CPU: AnyCPU - DefaultValueInitialized: true - OS: AnyOS - - first: - Standalone: Linux64 - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: OSXUniversal - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: Win - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: Win64 - second: - enabled: 0 - settings: - CPU: None - - first: - iPhone: iOS - second: - enabled: 1 - settings: - AddToEmbeddedBinaries: false - CPU: AnyCPU - CompileFlags: - FrameworkDependencies: UserNotifications; - userData: - assetBundleName: - assetBundleVariant: diff --git a/Plugins/iOS/PushNotificationManagerWrapper.mm b/Plugins/iOS/PushNotificationManagerWrapper.mm deleted file mode 100644 index 48bd747..0000000 --- a/Plugins/iOS/PushNotificationManagerWrapper.mm +++ /dev/null @@ -1,22 +0,0 @@ -#import "PushNotificationManagerWrapper.h" - -extern "C" { - void NativeRegisterForPushNotifications(REGISTRATION_CALLBACK callback) { - [[PushNotificationManager sharedInstance] registerForRemoteNotifications:callback]; - } - - void RegisterUnityCallbackForNotificationReceived(NOTIFICATION_CALLBACK callback) { - [[PushNotificationManager sharedInstance] setNotificationCallback:callback]; - [[PushNotificationManager sharedInstance] flushNotificationBuffer]; - } - - char * GetLaunchedNotificationString() { - return [[PushNotificationManager sharedInstance] getLaunchedNotificationString]; - } - - void ResetLaunchedNotificationString() { - [[PushNotificationManager sharedInstance] resetLaunchedNotificationString]; - } -} - - diff --git a/Plugins/iOS/PushNotificationManagerWrapper.mm.meta b/Plugins/iOS/PushNotificationManagerWrapper.mm.meta deleted file mode 100644 index 64838ba..0000000 --- a/Plugins/iOS/PushNotificationManagerWrapper.mm.meta +++ /dev/null @@ -1,85 +0,0 @@ -fileFormatVersion: 2 -guid: ed9aa836168124bf1920a23f63d6935d -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 1 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - : Any - second: - enabled: 0 - settings: - Exclude Android: 1 - Exclude Editor: 1 - Exclude Linux64: 1 - Exclude OSXUniversal: 1 - Exclude Win: 1 - Exclude Win64: 1 - Exclude iOS: 0 - - first: - Android: Android - second: - enabled: 0 - settings: - CPU: ARMv7 - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - CPU: AnyCPU - DefaultValueInitialized: true - OS: AnyOS - - first: - Standalone: Linux64 - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: OSXUniversal - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: Win - second: - enabled: 0 - settings: - CPU: x86 - - first: - Standalone: Win64 - second: - enabled: 0 - settings: - CPU: x86_64 - - first: - iPhone: iOS - second: - enabled: 1 - settings: - AddToEmbeddedBinaries: false - CPU: AnyCPU - CompileFlags: - FrameworkDependencies: UserNotifications; - - first: - tvOS: tvOS - second: - enabled: 1 - settings: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/Analytics/EventsWrapper.cs b/Runtime/Analytics/EventsWrapper.cs deleted file mode 100644 index b8c1ce5..0000000 --- a/Runtime/Analytics/EventsWrapper.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Collections.Generic; -using Unity.Services.Analytics; -using Unity.Services.Analytics.Internal; - -namespace Unity.Services.PushNotifications -{ - interface IPushNotificationEventsWrapper - { - void RecordCustomEvent(string eventName, Dictionary parameters, int version); - } - - class EventsWrapper: IPushNotificationEventsWrapper - { - public void RecordCustomEvent(string eventName, Dictionary parameters, int version) - { - Event evt = new Event(eventName, version); - - foreach (KeyValuePair parameter in parameters) - { - evt.Parameters.Set(parameter.Key, parameter.Value); - } - - evt.Parameters.AddUserCountry(); - - - - AnalyticsService.Instance.RecordInternalEvent(evt); - } - } -} diff --git a/Runtime/Analytics/IPushNotificationsAnalytics.cs b/Runtime/Analytics/IPushNotificationsAnalytics.cs index 03ca6dd..ae1ebb3 100644 --- a/Runtime/Analytics/IPushNotificationsAnalytics.cs +++ b/Runtime/Analytics/IPushNotificationsAnalytics.cs @@ -1,12 +1,33 @@ using System; using System.Collections.Generic; -using UnityEngine; namespace Unity.Services.PushNotifications { + [Obsolete("This interface should not be used. It will be deleted in the future version. Notification events are recorded for you automatically.")] public interface IPushNotificationsAnalytics { void RecordPushTokenUpdated(string pushToken); void RecordNotificationOpened(Dictionary payload, bool didLaunch); } + + [Obsolete("Stub to help transition away from IPushNotificationsAnalytics. This should ensure deprecation warnings appear only for the correct (exposed) elements, not for any internal parts.")] + internal class PushNotificationsAnalyticsStub : IPushNotificationsAnalytics + { + readonly IPushAnalytics m_Actual; + + public PushNotificationsAnalyticsStub(IPushAnalytics actual) + { + m_Actual = actual; + } + + public void RecordPushTokenUpdated(string pushToken) + { + m_Actual.RecordPushTokenUpdated(pushToken); + } + + public void RecordNotificationOpened(Dictionary payload, bool didLaunch) + { + m_Actual.RecordNotificationOpened(payload, didLaunch); + } + } } diff --git a/Runtime/Analytics/PushAnalytics.cs b/Runtime/Analytics/PushAnalytics.cs new file mode 100644 index 0000000..34c57ce --- /dev/null +++ b/Runtime/Analytics/PushAnalytics.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using Unity.Services.Core.Analytics.Internal; +using UnityEngine; + +namespace Unity.Services.PushNotifications +{ + interface IPushAnalytics + { + void RecordPushTokenUpdated(string pushToken); + void RecordNotificationOpened(Dictionary payload, bool didLaunch); + } + + internal class PushAnalytics : IPushAnalytics + { + const string k_PackageName = "com.unity.services.push-notifications"; + + readonly IAnalyticsStandardEventComponent m_Analytics; + readonly ISystemWrapper m_Platform; + + internal PushAnalytics(IAnalyticsStandardEventComponent analytics, ISystemWrapper platform) + { + m_Analytics = analytics; + m_Platform = platform; + } + + public void RecordPushTokenUpdated(string pushToken) + { + var eventParams = new Dictionary(); + + switch (m_Platform.RuntimePlatform()) + { + case RuntimePlatform.Android: + eventParams.Add("androidRegistrationID", pushToken); + break; + case RuntimePlatform.IPhonePlayer: + case RuntimePlatform.tvOS: + eventParams.Add("pushNotificationToken", pushToken); + break; + } + + m_Analytics.Record("notificationServices", eventParams, 1, k_PackageName); + } + + public void RecordNotificationOpened(Dictionary payload, bool didLaunch) + { + var eventParams = new Dictionary(); + + bool insertCommunicationAttrs = false; + + if (payload.ContainsKey("_ddCampaign")) + { + eventParams.Add("campaignId", Convert.ToInt64(payload["_ddCampaign"])); + insertCommunicationAttrs = true; + } + + if (payload.ContainsKey("_ddCohort")) + { + eventParams.Add("cohortId", Convert.ToInt64(payload["_ddCohort"])); + insertCommunicationAttrs = true; + } + + if (insertCommunicationAttrs && + payload.ContainsKey("_ddCommunicationSender")) + { + eventParams.Add("communicationSender", payload["_ddCommunicationSender"]); + } + + if (didLaunch) + { + eventParams.Add("notificationLaunch", true); + } + + if (payload.ContainsKey("_ddId")) + { + eventParams.Add("notificationId", Convert.ToInt64(payload["_ddId"])); + } + + if (payload.ContainsKey("_ddName")) + { + eventParams.Add("notificationName", payload["_ddName"]); + } + + eventParams.Add("communicationState", "OPEN"); + + m_Analytics.Record("notificationOpened", eventParams, 1, k_PackageName); + } + } +} diff --git a/Runtime/Analytics/PushNotificationAnalytics.cs.meta b/Runtime/Analytics/PushAnalytics.cs.meta similarity index 100% rename from Runtime/Analytics/PushNotificationAnalytics.cs.meta rename to Runtime/Analytics/PushAnalytics.cs.meta diff --git a/Runtime/Analytics/PushNotificationAnalytics.cs b/Runtime/Analytics/PushNotificationAnalytics.cs deleted file mode 100644 index 635c890..0000000 --- a/Runtime/Analytics/PushNotificationAnalytics.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using System.Collections.Generic; -using UnityEngine; - -namespace Unity.Services.PushNotifications -{ - static class SdkVersion - { - // This value should always match what is in the package.json for this package - public static readonly string SDK_VERSION = "3.0.1-pre.1"; - } - - class PushNotificationAnalytics : IPushNotificationsAnalytics - { - IPushNotificationEventsWrapper m_EventsWrapper; - IPushNotificationAnalyticsPlatformWrapper m_AnalyticsPlatformWrapper; - - internal PushNotificationAnalytics(IPushNotificationEventsWrapper eventsWrapper, IPushNotificationAnalyticsPlatformWrapper analyticsPlatformWrapper) - { - m_EventsWrapper = eventsWrapper; - m_AnalyticsPlatformWrapper = analyticsPlatformWrapper; - } - - /// - /// This method should be called when the user's push notification token is updated. Most commonly this is - /// when first registering for notifications, but can also occur at other points during the app lifecycle. - /// If using one of this SDK's platform specific implementations this method will be called for you. - /// - /// The push token relating to this user's device. - /// - public void RecordPushTokenUpdated(string pushToken) - { - Dictionary eventData = new Dictionary - { - { "clientVersion", m_AnalyticsPlatformWrapper.ApplicationVersion() }, - { "sdkVersion", SdkVersion.SDK_VERSION }, - { "sdkMethod", "com.unity.services.pushNotifications.PushNotificationsAnalytics.RecordPushTokenUpdated" }, - { "platform", m_AnalyticsPlatformWrapper.AnalyticsPlatform() } - }; - - RuntimePlatform runtimePlatform = m_AnalyticsPlatformWrapper.RuntimePlatform(); - if (runtimePlatform == RuntimePlatform.Android) - { - eventData.Add("androidRegistrationID", pushToken); - } - else if (runtimePlatform == RuntimePlatform.IPhonePlayer || runtimePlatform == RuntimePlatform.tvOS) - { - eventData.Add("pushNotificationToken", pushToken); - } - - m_EventsWrapper.RecordCustomEvent("notificationServices", eventData, 1); - } - - /// - /// This method should be called when the user opens the app from a push notification. - /// If using one of this SDK's platform specific implementations this method will be called for you. - /// - /// The dictionary containing required the event data. - /// Was the app launched from opening the notification? - /// - public void RecordNotificationOpened(Dictionary payload, bool didLaunch) - { - Dictionary eventParams = new Dictionary - { - { "clientVersion", m_AnalyticsPlatformWrapper.ApplicationVersion() }, - { "sdkVersion", SdkVersion.SDK_VERSION }, - { "sdkMethod", "com.unity.services.pushNotifications.PushNotificationsAnalytics.RecordNotificationOpened" }, - { "platform", m_AnalyticsPlatformWrapper.AnalyticsPlatform() } - }; - - bool insertCommunicationAttrs = false; - - if (payload.ContainsKey("_ddCampaign")) - { - eventParams["campaignId"] = Convert.ToInt64(payload["_ddCampaign"]); - insertCommunicationAttrs = true; - } - if (payload.ContainsKey("_ddCohort")) - { - eventParams["cohortId"] = Convert.ToInt64(payload["_ddCohort"]); - insertCommunicationAttrs = true; - } - if (insertCommunicationAttrs && payload.ContainsKey("_ddCommunicationSender")) - { - eventParams["communicationSender"] = payload["_ddCommunicationSender"]; - eventParams["communicationState"] = "OPEN"; - } - - if (didLaunch) - { - eventParams["notificationLaunch"] = true; - } - if (payload.ContainsKey("_ddId")) - { - eventParams["notificationId"] = Convert.ToInt64(payload["_ddId"]); - } - if (payload.ContainsKey("_ddName")) - { - eventParams["notificationName"] = payload["_ddName"]; - } - eventParams["communicationState"] = "OPEN"; - - m_EventsWrapper.RecordCustomEvent("notificationOpened", eventParams, 1); - } - } -} diff --git a/Runtime/Android.meta b/Runtime/Android.meta deleted file mode 100644 index afe33c9..0000000 --- a/Runtime/Android.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: e6b00f86b74f34c6788b04515f4eb4b7 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/Android/AndroidPushNotifications.cs b/Runtime/Android/AndroidPushNotifications.cs deleted file mode 100644 index e462bff..0000000 --- a/Runtime/Android/AndroidPushNotifications.cs +++ /dev/null @@ -1,183 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using UnityEngine; -using UnityEngine.Android; - -namespace Unity.Services.PushNotifications -{ - class AndroidPushNotifications : AndroidJavaProxy - { - static object s_RegistrationLock = new object(); - static TaskCompletionSource s_DeviceRegistrationTcs; - static string s_DeviceToken; - - PushNotificationReceivedHandler m_NotificationReceivedHandler; - PushNotificationAnalytics m_NotificationAnalytics; - - AndroidJavaObject m_IntentExtras; - - const string k_AndroidNotificationPermissionName = "android.permission.POST_NOTIFICATIONS"; - const string k_PlayerPrefPermissionDenied = "NotificationPermissionRequested"; - const int k_TiramisuVersionNumber = 33; - - public AndroidPushNotifications(PushNotificationReceivedHandler notificationReceivedHandler, PushNotificationAnalytics analytics) - : base("com.unity.services.pushnotifications.android.UnityCallbackClass") - { - m_NotificationReceivedHandler = notificationReceivedHandler; - m_NotificationAnalytics = analytics; - - try - { - AndroidJavaObject intent = GetCurrentActivity().Call("getIntent"); - string intentNotificationData = intent.Call("getStringExtra", "notificationData"); - - if (intentNotificationData != null) - { - Debug.Log("App launched from notification, sending relevant events"); - OnNotificationReceived(intentNotificationData); - - // remove opened notificationData so it cannot be re-processed - intent.Call("removeExtra", "notificationData"); - } - } - catch (Exception e) - { - Debug.LogError($"Failed to check intent for notification data: {e.Message}"); - } - } - - internal event Action> InternalNotificationWasReceived; - - public Task RegisterForPushNotificationsAsync(string firebaseApiKey, string firebaseSenderId, string firebaseApplicationId, string firebaseProjectId) - { - lock (s_RegistrationLock) - { - if (!string.IsNullOrEmpty(s_DeviceToken)) - { - return Task.FromResult(s_DeviceToken); - } - - if (s_DeviceRegistrationTcs != null) - { - return s_DeviceRegistrationTcs.Task; - } - - s_DeviceRegistrationTcs = new TaskCompletionSource(); - - void InitializePushNotifications() - { - try - { - AndroidJavaObject applicationContext = GetCurrentActivity().Call("getApplicationContext"); - - AndroidJavaObject instance = GetPluginInstance(); - instance.Call("setCallbackClass", this); - instance.Call("initialize", applicationContext, - firebaseApiKey, firebaseApplicationId, firebaseSenderId, firebaseProjectId); - } - catch (Exception e) - { - s_DeviceRegistrationTcs.TrySetException(new Exception($"Failed to initialize Push Notification plugin instance and register the device for remote notifications")); - } - } - - bool isPermissionAuthorized = Permission.HasUserAuthorizedPermission(k_AndroidNotificationPermissionName); - AndroidJavaClass version = new AndroidJavaClass("android.os.Build$VERSION"); - int sdkVersion = version.GetStatic("SDK_INT"); - - if (sdkVersion < k_TiramisuVersionNumber || isPermissionAuthorized) - { - InitializePushNotifications(); - } - else - { - bool hasPermissionBeenDenied = Convert.ToBoolean(PlayerPrefs.GetInt(k_PlayerPrefPermissionDenied)); - - if (!hasPermissionBeenDenied) - { - var callbacks = new PermissionCallbacks(); - callbacks.PermissionGranted += _ => InitializePushNotifications(); - callbacks.PermissionDenied += _ => - { - PlayerPrefs.SetInt(k_PlayerPrefPermissionDenied, 1); - PlayerPrefs.Save(); - }; - - Permission.RequestUserPermission(k_AndroidNotificationPermissionName, callbacks); - } - } - - return s_DeviceRegistrationTcs.Task; - } - } - - AndroidJavaObject GetPluginInstance() - { - AndroidJavaClass notificationPluginObject = new AndroidJavaClass("com.unity.services.pushnotifications.android.UnityNotifications"); - AndroidJavaObject instance = notificationPluginObject.GetStatic("INSTANCE"); - if (instance == null) - { - Debug.LogError("Unity Push Notification Android plugin is missing, android push notifications will not work as expected."); - return null; - } - - return instance; - } - - AndroidJavaObject GetCurrentActivity() - { - AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); - return unityPlayer.GetStatic("currentActivity"); - } - - // Called from the Kotlin code - internal void OnTokenReceived(string token) - { - lock (s_RegistrationLock) - { - if (s_DeviceRegistrationTcs != null) - { - if (String.IsNullOrEmpty(token)) - { - s_DeviceRegistrationTcs.TrySetException(new Exception("Failed to register the device for remote notifications.")); - } - else - { - s_DeviceRegistrationTcs.TrySetResult(token); - } - // Reset registration flow ready for next time. - s_DeviceRegistrationTcs = null; - } - - if (!String.IsNullOrEmpty(token)) - { - s_DeviceToken = token; - - MainThreadHelper.RunOnMainThread(() => - { - m_NotificationAnalytics.RecordPushTokenUpdated(token); - }); - - Debug.Log($"Successfully registered for remote push notifications with token: {token}"); - } - } - } - - // Called from the Kotlin code - internal void OnNotificationReceived(string notificationDataAsJson) - { - if (string.IsNullOrEmpty(notificationDataAsJson)) - { - Debug.Log("Notification received with no data, ignoring"); - return; - } - - MainThreadHelper.RunOnMainThread(() => - { - Dictionary notificationData = m_NotificationReceivedHandler.HandleReceivedNotification(notificationDataAsJson); - InternalNotificationWasReceived?.Invoke(notificationData); - }); - } - } -} diff --git a/Runtime/AssemblyInternals.cs b/Runtime/AssemblyInternals.cs index 76ebef9..0b6e9aa 100644 --- a/Runtime/AssemblyInternals.cs +++ b/Runtime/AssemblyInternals.cs @@ -1,4 +1,6 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Unity.Services.PushNotifications.Tests")] +[assembly: InternalsVisibleTo("Unity.Services.PushNotifications.EditorTests")] [assembly: InternalsVisibleTo("Unity.Services.PushNotifications.Editor")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] diff --git a/Runtime/IPushNotificationsService.cs b/Runtime/IPushNotificationsService.cs index 95a2378..dd40624 100644 --- a/Runtime/IPushNotificationsService.cs +++ b/Runtime/IPushNotificationsService.cs @@ -6,9 +6,26 @@ namespace Unity.Services.PushNotifications { public interface IPushNotificationsService { - public event Action> OnNotificationReceived; + /// + /// Subscribe to this event to be notified when a remote notification is used to launch or reopen the app. + /// + /// You must set up event subscriptions before calling RegisterForPushNotificationsAsync. + /// + /// If the application was started from a remote notification, this event will be invoked + /// once the RegisterForPushNotificationsAsync process has completed. + /// + public event Action> OnRemoteNotificationReceived; + + [Obsolete("Do not use this. It will be removed in a future version. Events are recorded for you automatically.")] public IPushNotificationsAnalytics Analytics { get; } - public Task RegisterForPushNotificationsAsync(); + /// + /// Registers for push notifications with the appropriate mechanism for the current platform. + /// + /// This method will automatically handle platform specific intricacies of getting a push notification token, and will + /// send the appropriate analytics events to Unity Analytics 2. + /// + /// (Asynchronously via a Task) The device token as a string. + public Task RegisterForPushNotificationsAsync(); } } diff --git a/Runtime/MainThreadHelper.cs b/Runtime/MainThreadHelper.cs index 88b1745..e3d94d0 100644 --- a/Runtime/MainThreadHelper.cs +++ b/Runtime/MainThreadHelper.cs @@ -6,27 +6,27 @@ namespace Unity.Services.PushNotifications { - static class MainThreadHelper + interface IMainThreadHelper + { + void RunOnMainThread(Action methodToRun); + } + + class MainThreadHelper : IMainThreadHelper { static SynchronizationContext s_UnitySynchronizationContext; static TaskScheduler s_TaskScheduler; static int s_MainThreadId; -#if UNITY_EDITOR - [InitializeOnLoadMethod] -#endif - [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] - public static void Init() + public MainThreadHelper() { s_UnitySynchronizationContext = SynchronizationContext.Current; s_TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext(); s_MainThreadId = Thread.CurrentThread.ManagedThreadId; } - internal static async void RunOnMainThread(Action methodToRun) + public void RunOnMainThread(Action methodToRun) { - await Task.Factory.StartNew(methodToRun, CancellationToken.None, TaskCreationOptions.None, - s_TaskScheduler); + Task.Factory.StartNew(methodToRun, CancellationToken.None, TaskCreationOptions.None, s_TaskScheduler); } } } diff --git a/Plugins/iOS.meta b/Runtime/Platforms.meta similarity index 77% rename from Plugins/iOS.meta rename to Runtime/Platforms.meta index 693352e..f245646 100644 --- a/Plugins/iOS.meta +++ b/Runtime/Platforms.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: cf662c0d28635416ea5a220d519b8de0 +guid: 0f0a0d24882a3ff4aa72b0b7dd497826 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Runtime/Platforms/AndroidPushNotifications.cs b/Runtime/Platforms/AndroidPushNotifications.cs new file mode 100644 index 0000000..bf8fcf3 --- /dev/null +++ b/Runtime/Platforms/AndroidPushNotifications.cs @@ -0,0 +1,166 @@ +#if UNITY_ANDROID || UNITY_EDITOR +using System; +using System.Collections; +using System.Collections.Generic; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Unity.Notifications.Android; +using UnityEngine; + +namespace Unity.Services.PushNotifications +{ + class AndroidPushNotifications : AndroidJavaProxy, IPushPlatform + { + TaskCompletionSource s_RegisterTcSource; + + IPushPlatformCallbacks m_PushNotificationsHandler; + readonly ICoroutineRunner m_Container; + + AndroidJavaObject s_CurrentActivity; + AndroidJavaObject s_CurrentContext; + AndroidJavaObject s_PushNotificationManager; + + public AndroidPushNotifications(ICoroutineRunner container) + : base("com.unity.services.pushnotifications.android.UnityPushNotificationsCallback") + { + m_Container = container; + } + + public void Initialize(IPushPlatformCallbacks callbacks) + { + m_PushNotificationsHandler = callbacks; + } + + AndroidJavaObject GetCurrentActivity() + { + AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); + return unityPlayer.GetStatic("currentActivity"); + } + + public void OnApplicationPause(bool isPaused) + { + // Android has no pause/unpause actions. + } + + /// + /// Registers for push notifications on Android. Returns the device token for the registered device. + /// This method checks if the user grants the notification permission, it will register for push notification and get the device token. + /// + /// The push notification token for this device + public Task RegisterForPushNotifications(PushNotificationSettings settings) + { + if (String.IsNullOrEmpty(settings.firebaseWebApiKey) || + String.IsNullOrEmpty(settings.firebaseAppID) || + String.IsNullOrEmpty(settings.firebaseProjectNumber) || + String.IsNullOrEmpty(settings.firebaseProjectID)) + { + throw new Exception("UGS Push Notifications is missing Android settings - make sure these are set in the editor Project Settings"); + } + + s_RegisterTcSource = new TaskCompletionSource(); + m_Container.StartCoroutine( + RequestAuthorization( + settings.firebaseWebApiKey, + settings.firebaseProjectNumber, + settings.firebaseAppID, + settings.firebaseProjectID)); + + return s_RegisterTcSource.Task; + } + + IEnumerator RequestAuthorization(string firebaseApiKey, string firebaseSenderId, string firebaseApplicationId, string firebaseProjectId) + { + var request = new PermissionRequest(); + while (request.Status == PermissionStatus.RequestPending) + { + yield return null; + } + + switch (request.Status) + { + case PermissionStatus.Allowed: + InitializePushNotifications(firebaseApiKey, firebaseSenderId, firebaseApplicationId, firebaseProjectId); + break; + case PermissionStatus.Denied: + case PermissionStatus.DeniedDontAskAgain: + s_RegisterTcSource.TrySetException(new Exception($"Authorization request was denied")); + break; + } + } + + void InitializePushNotifications(string firebaseApiKey, string firebaseSenderId, string firebaseApplicationId, string firebaseProjectId) + { + try + { + // Call Android Plugin code to initialise Push Notification with firebase + using (var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) + { + s_CurrentActivity = unityPlayer.GetStatic("currentActivity"); + } + s_CurrentContext = GetCurrentActivity().Call("getApplicationContext"); + + var unityPushNotificationManagerClass = new AndroidJavaClass("com.unity.services.pushnotifications.android.UnityPushNotifications"); + s_PushNotificationManager = unityPushNotificationManagerClass.CallStatic("getUnityPushNotificationImpl", s_CurrentContext); + s_PushNotificationManager.Call("initialize", s_CurrentActivity, this); + s_PushNotificationManager.Call("registerForPushNotifications", firebaseApiKey, firebaseApplicationId, firebaseSenderId, firebaseProjectId); + } + catch (Exception e) + { + s_RegisterTcSource.TrySetException(new Exception($"Failed to initialize Push Notification Plugin {e.Message}")); + } + } + + public Dictionary CheckForAppLaunchByNotification() + { + // This is to check if the notification is launched on cold boot and call OnRemoteNotificationReceived + try + { + AndroidJavaObject intent = GetCurrentActivity().Call("getIntent"); + string intentNotificationData = intent.Call("getStringExtra", "notificationData"); + if (intentNotificationData != null) + { + // remove opened notificationData so it cannot be re-processed + intent.Call("removeExtra", "notificationData"); + + return ParseNotification(intentNotificationData); + } + } + catch (Exception e) + { + Debug.LogError($"Push: GetIntent -> Failed to check intent for notification data: {e.Message}"); + } + + return null; + } + + // Implementing the UnityRemoteNotificationsCallback from the Plugin + // See: UnityPushNotificationsCallback.java + void OnTokenReceived(string token) + { + s_RegisterTcSource.TrySetResult(token); + } + + // Implementing the UnityRemoteNotificationsCallback from the Plugin + // See: UnityPushNotificationsCallback.java + void OnRemoteNotificationReceived(string notificationDataAsJson) + { + Dictionary notificationData = ParseNotification(notificationDataAsJson); + m_PushNotificationsHandler.RemoteNotificationReceived(notificationData); + } + + Dictionary ParseNotification(string notificationData) + { + try + { + return JsonConvert.DeserializeObject>(notificationData); + } + catch (Exception e) + { + Debug.Log($"Push: Failed handle notification: {e.Message}"); + } + + return null; + } + } +} +#endif diff --git a/Runtime/Android/AndroidPushNotifications.cs.meta b/Runtime/Platforms/AndroidPushNotifications.cs.meta similarity index 100% rename from Runtime/Android/AndroidPushNotifications.cs.meta rename to Runtime/Platforms/AndroidPushNotifications.cs.meta diff --git a/Runtime/Platforms/IPlatformLogic.cs b/Runtime/Platforms/IPlatformLogic.cs new file mode 100644 index 0000000..0dedacb --- /dev/null +++ b/Runtime/Platforms/IPlatformLogic.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using UnityEngine; + +namespace Unity.Services.PushNotifications +{ + internal interface IPushPlatform + { + void Initialize(IPushPlatformCallbacks callbacks); + + Task RegisterForPushNotifications(PushNotificationSettings settings); + + Dictionary CheckForAppLaunchByNotification(); + + void OnApplicationPause(bool isPaused); + } + + internal class UnsupportedPlatformLogic : IPushPlatform + { + public void Initialize(IPushPlatformCallbacks callbacks) + { + } + + public Dictionary CheckForAppLaunchByNotification() + { + return null; + } + + public Task RegisterForPushNotifications(PushNotificationSettings settings) + { +#if UNITY_EDITOR + Debug.Log("Push notifications are not available in the Unity Editor."); +#else + Debug.Log("Push notifications are not available on this platform."); +#endif + return Task.FromResult(""); + } + + public void OnApplicationPause(bool isPaused) + { + } + } +} diff --git a/Runtime/Analytics/EventsWrapper.cs.meta b/Runtime/Platforms/IPlatformLogic.cs.meta similarity index 83% rename from Runtime/Analytics/EventsWrapper.cs.meta rename to Runtime/Platforms/IPlatformLogic.cs.meta index 103c2f6..3a25594 100644 --- a/Runtime/Analytics/EventsWrapper.cs.meta +++ b/Runtime/Platforms/IPlatformLogic.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 0673bcedb71414fd388cdf2c8440b377 +guid: 43a8f9104c5fbdb4e9e28011beefc9e6 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Runtime/Platforms/iOSPushNotifications.cs b/Runtime/Platforms/iOSPushNotifications.cs new file mode 100644 index 0000000..b95eb20 --- /dev/null +++ b/Runtime/Platforms/iOSPushNotifications.cs @@ -0,0 +1,121 @@ +#if UNITY_IOS || UNITY_EDITOR +using System; +using System.Collections; +using System.Collections.Generic; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Unity.Notifications.iOS; + +namespace Unity.Services.PushNotifications +{ + class IOSPushNotifications : IPushPlatform + { + TaskCompletionSource s_RegisterTcSource; + + IPushPlatformCallbacks m_PushNotificationsHandler; + readonly ICoroutineRunner m_Container; + + public IOSPushNotifications(ICoroutineRunner container) + { + m_Container = container; + } + + public void Initialize(IPushPlatformCallbacks callbacks) + { + m_PushNotificationsHandler = callbacks; + iOSNotificationCenter.OnRemoteNotificationReceived += OnRemoteNotificationReceived; + } + + /// + /// Registers for push notifications on iOS. Returns the device token for the registered device. + /// This method checks if the user grants the notification permission, it will register for push notification and get the device token. + /// + /// The push notification token for this device + public Task RegisterForPushNotifications(PushNotificationSettings settings) + { + // NOTE: Settings is only required by Android, but is taken to ensure a consistent interface + + s_RegisterTcSource = new TaskCompletionSource(); + m_Container.StartCoroutine(RequestAuthorization(AuthorizationOption.Alert | AuthorizationOption.Badge | AuthorizationOption.Sound)); + return s_RegisterTcSource.Task; + } + + IEnumerator RequestAuthorization(AuthorizationOption options) + { + using (AuthorizationRequest request = new AuthorizationRequest(options, true)) + { + while (!request.IsFinished) + { + yield return null; + } + + if (request.Granted) + { + s_RegisterTcSource.TrySetResult(request.DeviceToken); + } + else + { + s_RegisterTcSource.TrySetException(new Exception("Authorization request was denied")); + } + } + } + + public Dictionary CheckForAppLaunchByNotification() + { + // in case a killed app was launched by clicking a notification + iOSNotification notification = iOSNotificationCenter.GetLastRespondedNotification(); + if (notification != null && + notification.Trigger.Type == iOSNotificationTriggerType.Push) + { + return ParseNotification(notification); + } + else + { + return null; + } + } + + public void OnApplicationPause(bool isPaused) + { + if (isPaused == false) + { + iOSNotification notification = iOSNotificationCenter.GetLastRespondedNotification(); + if (notification != null && + notification.Trigger.Type == iOSNotificationTriggerType.Push) + { + OnRemoteNotificationReceived(notification); + } + } + } + + void OnRemoteNotificationReceived(iOSNotification notification) + { + Dictionary notificationData = ParseNotification(notification); + m_PushNotificationsHandler.RemoteNotificationReceived(notificationData); + } + + Dictionary ParseNotification(iOSNotification notification) + { + Dictionary notificationData = new Dictionary(); + notificationData["title"] = notification.Title; + notificationData["alert"] = notification.Body; + + if (notification.UserInfo.ContainsKey("_ddCampaign")) + { + notificationData["_ddCampaign"] = notification.UserInfo["_ddCampaign"]; + } + if (notification.UserInfo.ContainsKey("_ddCohort")) + { + notificationData["_ddCohort"] = notification.UserInfo["_ddCohort"]; + } + Dictionary aps = JsonConvert.DeserializeObject>(notification.UserInfo["aps"]);; + if (aps.ContainsKey("imageUrl")) + { + notificationData["imageUrl"] = aps["imageUrl"]; + } + + return notificationData; + } + } +} +#endif diff --git a/Runtime/iOS/iOSPushNotifications.cs.meta b/Runtime/Platforms/iOSPushNotifications.cs.meta similarity index 100% rename from Runtime/iOS/iOSPushNotifications.cs.meta rename to Runtime/Platforms/iOSPushNotifications.cs.meta diff --git a/Runtime/PushNotificationCoreInitialization.cs b/Runtime/PushNotificationCoreInitialization.cs index 157359d..69aba66 100644 --- a/Runtime/PushNotificationCoreInitialization.cs +++ b/Runtime/PushNotificationCoreInitialization.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using Unity.Services.Core.Analytics.Internal; using Unity.Services.Core.Internal; using UnityEngine; @@ -9,12 +10,36 @@ class PushNotificationCoreInitialization : IInitializablePackage [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] static void Register() { - CoreRegistry.Instance.RegisterPackage(new PushNotificationCoreInitialization()); + CoreRegistry.Instance.RegisterPackage(new PushNotificationCoreInitialization()) + .DependsOn(); } public Task Initialize(CoreRegistry registry) { - PushNotificationsService.internalInstance = new PushNotificationsServiceInstance(); + var analyticsSdk = registry.GetServiceComponent(); + var platformWrapper = new SystemWrapper(); + + var containerObject = PushNotificationsContainer.CreateContainer(); + var mainThreadHelper = new MainThreadHelper(); + var analytics = new PushAnalytics(analyticsSdk, platformWrapper); + + IPushPlatform platformLogic; +#if UNITY_IOS && !UNITY_EDITOR + platformLogic = new IOSPushNotifications(containerObject); +#elif UNITY_ANDROID && !UNITY_EDITOR + platformLogic = new AndroidPushNotifications(containerObject); +#else + platformLogic = new UnsupportedPlatformLogic(); +#endif + + PushNotificationsService.internalInstance = new PushNotificationsServiceInstance( + analytics, + platformLogic, + platformWrapper, + mainThreadHelper); + + platformLogic.Initialize(PushNotificationsService.internalInstance); + containerObject.Initialize(PushNotificationsService.internalInstance); return Task.CompletedTask; } diff --git a/Runtime/PushNotificationReceivedHandler.cs b/Runtime/PushNotificationReceivedHandler.cs deleted file mode 100644 index cb5b572..0000000 --- a/Runtime/PushNotificationReceivedHandler.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Collections.Generic; -using Newtonsoft.Json; -using UnityEngine; - -namespace Unity.Services.PushNotifications -{ - class PushNotificationReceivedHandler - { - readonly IPushNotificationAnalyticsPlatformWrapper m_PlatformWrapper; - readonly IPushNotificationsAnalytics m_NotificationAnalytics; - - bool m_IsCleanStart = true; - - internal PushNotificationReceivedHandler(IPushNotificationsAnalytics analytics, IPushNotificationAnalyticsPlatformWrapper platformWrapper) - { - m_NotificationAnalytics = analytics; - m_PlatformWrapper = platformWrapper; - } - - internal Dictionary HandleReceivedNotification(string jsonNotificationData) - { - try - { - Dictionary notificationData = JsonConvert.DeserializeObject>(jsonNotificationData); - if (notificationData != null) - { - // If the application is in the background, or we're opening for the first time, then we want to send the relevant analytics. - // (Note: This doesn't 100% guarantee that we're launching from an event, but it matches the previous deltaDNA behavior). - if (!m_PlatformWrapper.IsApplicationFocused() || m_IsCleanStart) - { - m_NotificationAnalytics.RecordNotificationOpened(notificationData, true); - m_IsCleanStart = false; - } - else - { - m_NotificationAnalytics.RecordNotificationOpened(notificationData, false); - } - } - - return notificationData; - } - catch (Exception e) - { - Debug.Log($"Failed to parse notification user info dictionary data: {e.Message}"); - return null; - } - } - } -} diff --git a/Runtime/PushNotificationReceivedHandler.cs.meta b/Runtime/PushNotificationReceivedHandler.cs.meta deleted file mode 100644 index aa08ca1..0000000 --- a/Runtime/PushNotificationReceivedHandler.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 6c5362295dee40ca9b18c3eb8afef691 -timeCreated: 1627985420 \ No newline at end of file diff --git a/Runtime/PushNotificationsContainer.cs b/Runtime/PushNotificationsContainer.cs new file mode 100644 index 0000000..1864501 --- /dev/null +++ b/Runtime/PushNotificationsContainer.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections; +using UnityEngine; + +namespace Unity.Services.PushNotifications +{ + interface ICoroutineRunner + { + Coroutine StartCoroutine(IEnumerator method); + } + + class PushNotificationsContainer : MonoBehaviour, ICoroutineRunner + { + static bool s_Created; + static GameObject s_Container; + + PushNotificationsServiceInstance m_Service; + + /// + /// For the test harness only. + /// + internal static PushNotificationsContainer Instance { get; private set; } + + + internal static PushNotificationsContainer CreateContainer() + { + if (!s_Created) + { +#if UNITY_PUSH_NOTIFICATION_DEVELOPMENT + Debug.Log("Created Analytics Container"); +#endif + + s_Container = new GameObject("PushNotificationsContainer"); + Instance = s_Container.AddComponent(); + + s_Container.hideFlags = HideFlags.DontSaveInBuild | HideFlags.NotEditable; +#if !UNITY_PUSH_NOTIFICATION_DEVELOPMENT + s_Container.hideFlags |= HideFlags.HideInInspector; +#endif + + DontDestroyOnLoad(s_Container); + s_Created = true; + } + + return Instance; + } + + public void Initialize(PushNotificationsServiceInstance service) + { + m_Service = service; + } + + void OnApplicationPause(bool paused) + { + m_Service.OnApplicationPause(paused); + } + + void OnDestroy() + { + // NOTE: we use OnDestroy rather than OnApplicationQuit in case the game developer should + // deliberately/accidentally destroy the container object. This should ensure graceful shutdown + // of the SDK regardless of 'how' it actually got turned off. + s_Container = null; + s_Created = false; + } + } +} diff --git a/Runtime/PushNotificationsContainer.cs.meta b/Runtime/PushNotificationsContainer.cs.meta new file mode 100644 index 0000000..b64158a --- /dev/null +++ b/Runtime/PushNotificationsContainer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a9b779ada67049c9b03b23aca86c57d1 +timeCreated: 1688650166 \ No newline at end of file diff --git a/Runtime/PushNotificationsServiceInstance.cs b/Runtime/PushNotificationsServiceInstance.cs index bcd2de0..b976c49 100644 --- a/Runtime/PushNotificationsServiceInstance.cs +++ b/Runtime/PushNotificationsServiceInstance.cs @@ -5,79 +5,113 @@ namespace Unity.Services.PushNotifications { - class PushNotificationsServiceInstance : IPushNotificationsService + internal interface IPushPlatformCallbacks { - PushNotificationsAnalyticsPlatformWrapper m_AnalyticsPlatformWrapper; - PushNotificationAnalytics m_PushNotificationAnalyticsImpl; - internal PushNotificationReceivedHandler notificationReceivedHandler; + void RemoteNotificationReceived(Dictionary notificationData); + } + + internal class PushNotificationsServiceInstance : IPushNotificationsService, IPushPlatformCallbacks + { + string m_DeviceToken; -#if UNITY_IOS && !UNITY_EDITOR - IOSPushNotifications m_IOSPushNotifications; -#elif UNITY_ANDROID && !UNITY_EDITOR - AndroidPushNotifications m_AndroidPushNotifications; -#endif + readonly IPushAnalytics m_Analytics; + readonly IPushPlatform m_PlatformLogic; + readonly ISystemWrapper m_PlatformWrapper; + readonly IMainThreadHelper m_MainThreadHelper; - internal PushNotificationsServiceInstance() + [Obsolete("Do not use this. It will be deleted in a future version. Notification events are recorded for you automatically.")] + public IPushNotificationsAnalytics Analytics { get { return new PushNotificationsAnalyticsStub(m_Analytics); } } + + internal PushNotificationsServiceInstance( + IPushAnalytics analytics, + IPushPlatform platformSpecificLogic, + ISystemWrapper platformWrapper, + IMainThreadHelper mainThreadHelper) { - m_AnalyticsPlatformWrapper = new PushNotificationsAnalyticsPlatformWrapper(); - m_PushNotificationAnalyticsImpl = new PushNotificationAnalytics(new EventsWrapper(), m_AnalyticsPlatformWrapper); - notificationReceivedHandler = new PushNotificationReceivedHandler(m_PushNotificationAnalyticsImpl, m_AnalyticsPlatformWrapper); - -#if UNITY_IOS && !UNITY_EDITOR - m_IOSPushNotifications = new IOSPushNotifications(notificationReceivedHandler, m_PushNotificationAnalyticsImpl); -#elif UNITY_ANDROID && !UNITY_EDITOR - m_AndroidPushNotifications = new AndroidPushNotifications(notificationReceivedHandler, m_PushNotificationAnalyticsImpl); -#endif + m_Analytics = analytics; + m_PlatformLogic = platformSpecificLogic; + m_PlatformWrapper = platformWrapper; + m_MainThreadHelper = mainThreadHelper; } - public event Action> OnNotificationReceived + event Action> m_NotificationReceivedEvent; + public event Action> OnRemoteNotificationReceived { -#if UNITY_IOS && !UNITY_EDITOR - add => IOSPushNotifications.InternalNotificationWasReceived += value; - remove => IOSPushNotifications.InternalNotificationWasReceived -= value; -#elif UNITY_ANDROID && !UNITY_EDITOR - add => m_AndroidPushNotifications.InternalNotificationWasReceived += value; - remove => m_AndroidPushNotifications.InternalNotificationWasReceived -= value; -#else add - { /* No action on unsupported platforms */ } - remove - { /* No action on unsupported platforms */ } -#endif + { + if (String.IsNullOrEmpty(m_DeviceToken)) + { + m_NotificationReceivedEvent += value; + } + else + { + throw new InvalidOperationException("You may not subscribe to OnRemoteNotificationReceived after calling RegisterForPushNotificationsAsync."); + } + } + remove => m_NotificationReceivedEvent -= value; + } + + public async Task RegisterForPushNotificationsAsync() + { + if (String.IsNullOrEmpty(m_DeviceToken)) + { + PushNotificationSettings settings = m_PlatformWrapper.GetSettings(); + + m_DeviceToken = await m_PlatformLogic.RegisterForPushNotifications(settings); + if (String.IsNullOrEmpty(m_DeviceToken)) + { + throw new Exception("Failed to register the device for remote notifications."); + } + else + { + // The underlying platform logic includes interop callbacks, so there's no guarantee we're back where we started. + // Therefore: force this bit onto the main thread so that we can use Unity APIs again. + m_MainThreadHelper.RunOnMainThread(CompleteRegistration); + } + } + + return m_DeviceToken; + } + + void CompleteRegistration() + { + Debug.Log($"DeviceToken = {m_DeviceToken}"); + + m_Analytics.RecordPushTokenUpdated(m_DeviceToken); + + Dictionary launchNotification = m_PlatformLogic.CheckForAppLaunchByNotification(); + if (launchNotification != null) + { + m_Analytics.RecordNotificationOpened(launchNotification, true); + m_NotificationReceivedEvent?.Invoke(launchNotification); + } } - public IPushNotificationsAnalytics Analytics => m_PushNotificationAnalyticsImpl; + internal void OnApplicationPause(bool isPaused) + { + m_PlatformLogic.OnApplicationPause(isPaused); + } - /// - /// Registers for push notifications with the appropriate mechanism for the current platform. - /// - /// This method will automatically handle platform specific intricacies of getting a push notification token, and will - /// send the appropriate analytics events to Unity Analytics 2. - /// - /// (Asynchronously via a Task) The device token as a string. - public Task RegisterForPushNotificationsAsync() + public void RemoteNotificationReceived(Dictionary notificationData) { - PushNotificationSettings settings = PushNotificationSettings.GetAssetInstance(); - return RegisterForPushNotificationsInternal(settings); + // Once again, this is invoked by underlying platform logic so there's no guarantee it's on the main thread. + // Therefore: enforce it. + m_MainThreadHelper.RunOnMainThread(() => + { + ProcessRemoteNotification(notificationData); + }); } - Task RegisterForPushNotificationsInternal(PushNotificationSettings settings) + void ProcessRemoteNotification(Dictionary notificationData) { -#if UNITY_IOS && !UNITY_EDITOR - return m_IOSPushNotifications.RegisterForPushNotificationsAsync(); -#elif UNITY_ANDROID && !UNITY_EDITOR - if (string.IsNullOrEmpty(settings.firebaseWebApiKey) || string.IsNullOrEmpty(settings.firebaseAppID) || string.IsNullOrEmpty(settings.firebaseProjectNumber) || string.IsNullOrEmpty(settings.firebaseProjectID)) + if (notificationData != null) { - throw new Exception("UGS Push Notifications is missing Android settings - make sure these are set in the editor Project Settings"); + // If the application is in the background then we want to send the relevant analytics. + // (Note: This doesn't 100% guarantee that we're launching from an event, but it matches the previous deltaDNA behavior). + m_Analytics.RecordNotificationOpened(notificationData, !m_PlatformWrapper.IsApplicationFocused()); } - return m_AndroidPushNotifications.RegisterForPushNotificationsAsync(settings.firebaseWebApiKey, settings.firebaseProjectNumber, settings.firebaseAppID, settings.firebaseProjectID); -#elif UNITY_EDITOR - Debug.Log("Push Notifications are not supported in the Editor Play mode. Returning an empty push token."); - return Task.FromResult(""); -#else - Debug.Log("Push notifications are not supported on this platform at this time. Returning an empty push token."); - return Task.FromResult(""); -#endif + + m_NotificationReceivedEvent?.Invoke(notificationData); } } } diff --git a/Runtime/Analytics/PushNotificationsAnalyticsPlatformWrapper.cs b/Runtime/SystemWrapper.cs similarity index 52% rename from Runtime/Analytics/PushNotificationsAnalyticsPlatformWrapper.cs rename to Runtime/SystemWrapper.cs index 8657491..eaf23c0 100644 --- a/Runtime/Analytics/PushNotificationsAnalyticsPlatformWrapper.cs +++ b/Runtime/SystemWrapper.cs @@ -1,21 +1,18 @@ -using System; -using Unity.Services.Analytics.Internal; using UnityEngine; namespace Unity.Services.PushNotifications { - interface IPushNotificationAnalyticsPlatformWrapper + interface ISystemWrapper { string ApplicationVersion(); RuntimePlatform RuntimePlatform(); bool IsApplicationFocused(); - string AnalyticsPlatform(); + + PushNotificationSettings GetSettings(); } - - class PushNotificationsAnalyticsPlatformWrapper: IPushNotificationAnalyticsPlatformWrapper + + class SystemWrapper : ISystemWrapper { - const string k_UnknownCountryCode = "ZZ"; - public string ApplicationVersion() { return Application.version; @@ -31,15 +28,9 @@ public bool IsApplicationFocused() return Application.isFocused; } - public string AnalyticsPlatform() + public PushNotificationSettings GetSettings() { -#if UNITY_IOS - return "IOS"; -#elif UNITY_ANDROID - return "ANDROID"; -#else - return "UNKNOWN"; -#endif + return PushNotificationSettings.GetAssetInstance(); } } } diff --git a/Runtime/Analytics/PushNotificationsAnalyticsPlatformWrapper.cs.meta b/Runtime/SystemWrapper.cs.meta similarity index 100% rename from Runtime/Analytics/PushNotificationsAnalyticsPlatformWrapper.cs.meta rename to Runtime/SystemWrapper.cs.meta diff --git a/Runtime/Unity.Services.PushNotifications.api b/Runtime/Unity.Services.PushNotifications.api index 9ffa63e..44246d8 100644 --- a/Runtime/Unity.Services.PushNotifications.api +++ b/Runtime/Unity.Services.PushNotifications.api @@ -3,7 +3,7 @@ // make sure the XML doc file is present and located next to the scraped dll namespace Unity.Services.PushNotifications { - public interface IPushNotificationsAnalytics + [System.Obsolete(@"This interface should not be used. It will be deleted in the future version. Notification events are recorded for you automatically.")] public interface IPushNotificationsAnalytics { public void RecordNotificationOpened(System.Collections.Generic.Dictionary payload, bool didLaunch); public void RecordPushTokenUpdated(string pushToken); @@ -11,8 +11,8 @@ namespace Unity.Services.PushNotifications public interface IPushNotificationsService { - public event System.Action> OnNotificationReceived; - public IPushNotificationsAnalytics Analytics { get; } + public event System.Action> OnRemoteNotificationReceived; + [System.Obsolete(@"Do not use this. It will be removed in a future version. Events are recorded for you automatically.")] public IPushNotificationsAnalytics Analytics { get; } public System.Threading.Tasks.Task RegisterForPushNotificationsAsync(); } diff --git a/Runtime/Unity.Services.PushNotifications.asmdef b/Runtime/Unity.Services.PushNotifications.asmdef index 4f51717..93c3ffc 100644 --- a/Runtime/Unity.Services.PushNotifications.asmdef +++ b/Runtime/Unity.Services.PushNotifications.asmdef @@ -4,7 +4,9 @@ "Unity.Services.Analytics", "Unity.Services.AnalyticsRuntime", "Unity.Services.Core", - "Unity.Services.Core.Internal" + "Unity.Services.Core.Internal", + "Unity.Notifications.iOS", + "Unity.Notifications.Android" ], "includePlatforms": [], "excludePlatforms": [], @@ -15,4 +17,4 @@ "defineConstraints": [], "versionDefines": [], "noEngineReferences": false -} \ No newline at end of file +} diff --git a/Runtime/iOS.meta b/Runtime/iOS.meta deleted file mode 100644 index 4e55f7f..0000000 --- a/Runtime/iOS.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 85c5045330d5a4e7c99b4fbc6e73ca06 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/iOS/iOSPushNotifications.cs b/Runtime/iOS/iOSPushNotifications.cs deleted file mode 100644 index 5f82e36..0000000 --- a/Runtime/iOS/iOSPushNotifications.cs +++ /dev/null @@ -1,128 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using UnityEngine; - -namespace Unity.Services.PushNotifications -{ - class IOSPushNotifications - { - static object s_RegistrationLock = new object(); - static TaskCompletionSource s_DeviceRegistrationTcs; - static string s_DeviceToken; - - static PushNotificationReceivedHandler s_NotificationReceivedHandler; - static PushNotificationAnalytics s_NotificationAnalytics; - - internal static event Action> InternalNotificationWasReceived; - - delegate void NotificationRegistrationCallback(string deviceToken); - - delegate void NotificationReceivedCallback(string serialisedNotificationData); - -#if UNITY_IOS && !UNITY_EDITOR - [DllImport("__Internal")] - static extern void NativeRegisterForPushNotifications(NotificationRegistrationCallback callback); - - [DllImport("__Internal")] - static extern void RegisterUnityCallbackForNotificationReceived(NotificationReceivedCallback callback); - - [DllImport("__Internal")] - static internal extern string GetLaunchedNotificationString(); - - [DllImport("__Internal")] - static internal extern void ResetLaunchedNotificationString(); - - [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] - internal static void PerformLaunchActions() - { - RegisterUnityCallbackForNotificationReceived(NotificationReceived); - } - -#endif - public IOSPushNotifications(PushNotificationReceivedHandler notificationReceivedHandler, PushNotificationAnalytics analytics) - { - s_NotificationReceivedHandler = notificationReceivedHandler; - s_NotificationAnalytics = analytics; - - #if UNITY_IOS && !UNITY_EDITOR - string launchedNotificationString = GetLaunchedNotificationString(); - - if (launchedNotificationString != null) - { - Debug.Log("App launched from notification, sending relevant events"); - NotificationReceived(launchedNotificationString); - - // remove launched notificationData so it cannot be re-processed - ResetLaunchedNotificationString(); - } - #endif - } - - /// - /// Registers for push notifications on iOS. Returns the device token for the registered device. - /// - /// The push notification token for this device - public Task RegisterForPushNotificationsAsync() - { - #if UNITY_IOS && !UNITY_EDITOR - lock (s_RegistrationLock) - { - if (!String.IsNullOrEmpty(s_DeviceToken)) - { - return Task.FromResult(s_DeviceToken); - } - - if (s_DeviceRegistrationTcs != null) - { - return s_DeviceRegistrationTcs.Task; - } - - s_DeviceRegistrationTcs = new TaskCompletionSource(); - - NativeRegisterForPushNotifications(NotificationRegistrationTokenReceived); - - return s_DeviceRegistrationTcs.Task; - } - #else - Debug.Log("iOS notification support is only available in iOS builds"); - return Task.FromResult(null); - #endif - } - - [AOT.MonoPInvokeCallback(typeof(NotificationRegistrationCallback))] - internal static void NotificationRegistrationTokenReceived(string token) - { - lock (s_RegistrationLock) - { - if (string.IsNullOrEmpty(token)) - { - s_DeviceRegistrationTcs.TrySetException(new Exception("Failed to register the device for remote notifications.")); - } - else - { - s_DeviceToken = token; - s_DeviceRegistrationTcs.TrySetResult(token); - s_NotificationAnalytics.RecordPushTokenUpdated(token); - Debug.Log($"Successfully registered for remote push notifications with token: {token}"); - } - - // Reset registration flow ready for next time. - s_DeviceRegistrationTcs = null; - } - } - - [AOT.MonoPInvokeCallback(typeof(NotificationReceivedCallback))] - internal static void NotificationReceived(string serialisedNotificationData) - { - if (string.IsNullOrEmpty(serialisedNotificationData)) - { - return; - } - - Dictionary userInfo = s_NotificationReceivedHandler.HandleReceivedNotification(serialisedNotificationData); - InternalNotificationWasReceived?.Invoke(userInfo); - } - } -} diff --git a/Samples~/Example/PushNotificationExample.cs b/Samples~/Example/PushNotificationExample.cs index 1ef5993..71abc7c 100644 --- a/Samples~/Example/PushNotificationExample.cs +++ b/Samples~/Example/PushNotificationExample.cs @@ -1,8 +1,8 @@ -using System; +using System.Collections.Generic; +using Unity.Services.Analytics; using Unity.Services.Core; using Unity.Services.PushNotifications; using UnityEngine; -using Unity.Services.Analytics; public class PushNotificationExample : MonoBehaviour { @@ -11,17 +11,24 @@ async void Start() { await UnityServices.InitializeAsync(); + PushNotificationsService.Instance.OnRemoteNotificationReceived += PushNotificationRecieved; + // Note: This is the minimum required to ensure the events with the push notification data are sent correctly through Analytics. - // In a real game you would need to handle privacy consent states here, see the Analytics documentation for more details. - await AnalyticsService.Instance.CheckForRequiredConsents(); + // In a real game you would need to handle privacy consent states here, see the Analytics documentation for more details: + // https://docs.unity.com/ugs/en-us/manual/analytics/manual/manage-data-privacy + AnalyticsService.Instance.StartDataCollection(); // Make sure to set the required settings in Project Settings before testing string token = await PushNotificationsService.Instance.RegisterForPushNotificationsAsync(); Debug.Log($"The push notification token is {token}"); + } - PushNotificationsService.Instance.OnNotificationReceived += notificationData => + void PushNotificationRecieved(Dictionary notificationData) + { + Debug.Log("Notification received!"); + foreach (KeyValuePair item in notificationData) { - Debug.Log("Data retrieved!"); - }; + Debug.Log($"Notification data item: {item.Key} - {item.Value}"); + } } } diff --git a/ValidationExceptions.json b/ValidationExceptions.json index 0962447..3d351eb 100644 --- a/ValidationExceptions.json +++ b/ValidationExceptions.json @@ -3,7 +3,7 @@ { "ValidationTest": "API Validation", "ExceptionMessage": "Breaking changes require a new major version.", - "PackageVersion": "3.0.1-pre.1" + "PackageVersion": "3.0.1-pre.2" } ], "WarningExceptions": [] diff --git a/package.json b/package.json index cfd9358..a958686 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "com.unity.services.push-notifications", "displayName": "Push Notifications", - "version": "3.0.1-pre.1", + "version": "4.0.0-pre.1", "unity": "2020.3", "_upm": { "gameService": { @@ -14,12 +14,13 @@ "Android", "iOS" ], - "changelog": "### Fixed\n\n- Android SDK Level 33 (Tiramisu) support now available.\n - Added necessary post notification permission to manifest.\n - Package will now ask for permission during initialization if required.\n- External Dependency Manager for Unity (EDM4U) and Mobile Dependency Resolver (MDR) support now available. When either is installed:\n - A new `PushSDKDependencies.xml` file is generated for them to use.\n - `InsertPushNotificationDependenciesIntoGradleScript` will not run to prevent duplication from gradle.\n- Fixed errors appearing in the Editor Play Mode due to platform specific classes instantiating." + "changelog": "### Changed\n\n- You can now only subscribe to OnNotificationReceived before calling RegisterForPushNotificationsAsync\n - Once RegisterForPushNotificationsAsync completes, OnNotificationReceived will be invoked if the app was launched from a remote notification\n- Updated `com.unity.services.analytics` dependency to 5.0.0\n- Updated `com.unity.services.core` dependency to 1.10.1\n- Added `com.unity.mobile.notifications` version `2.2.0` as a dependency.\n\n### Fixed\n\n- Behaviour when the app is launched from a push notification is now consistent between iOS and Android (incoming push notification data is broadcast after RegisterForPushNotificationsAsync flow is complete)" }, "description": "This package adds support for Push Notifications to your game. It allows sending rich push notifications with images, and provides analytics on the number of received push notifications.", "dependencies": { - "com.unity.services.analytics": "4.3.0", - "com.unity.services.core": "1.4.0" + "com.unity.services.analytics": "5.0.0", + "com.unity.services.core": "1.10.1", + "com.unity.mobile.notifications": "2.2.0" }, "samples": [ { @@ -29,15 +30,15 @@ } ], "relatedPackages": { - "com.unity.services.push-notifications.tests": "3.0.1-pre.1" + "com.unity.services.push-notifications.tests": "4.0.0-pre.1" }, "upmCi": { - "footprint": "927a49827610e91ab8c814bbc2e5b1574dd90449" + "footprint": "054068a89710c104e87a975345d09a133468fb0c" }, - "documentationUrl": "https://docs.unity3d.com/Packages/com.unity.services.push-notifications@3.0/manual/index.html", + "documentationUrl": "https://docs.unity3d.com/Packages/com.unity.services.push-notifications@4.0/manual/index.html", "repository": { "url": "https://github.cds.internal.unity3d.com/unity/operate-services-sdk.git", "type": "git", - "revision": "3805a41b5e29d7f30e70323b4bacd1a93335a8bb" + "revision": "90d4a9809f94049ed1b036b44dcdba044f530efd" } }