From 2326ba92fffb96f929e68f99ea70fd984fe4953e Mon Sep 17 00:00:00 2001 From: Brad Hesse Date: Mon, 14 Jan 2019 18:05:45 -0800 Subject: [PATCH 1/3] Persist Category ID's MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • We recently discovered an issue where if an app sends two notifications that have their own unique buttons, both notifications will display whatever the more recent notifications buttons were supposed to be. • For example, if an app sends a notification with "Like" as the button title, and then later on it sends another notification with "Dislike" as the title, BOTH notifications will now show "Dislike" as the button title. • This was caused by the fact that the SDK uses a single UNNotificationCategory ID called "__dynamic__" to register notification actions, meaning the most recently received notification defines the buttons. • Fixes the issue by now registering unique UNNotificationCategory's to each specific notification. • To prevent the steady buildup of registered categories over time, we define a limit (MAX_CATEGORIES_SIZE, which is currently 128) and delete/de-register any previous UNNotificationCategory's than the 128 most recent. • Adds a test to make sure that the SDK is correctly registering, deleting, and associating categories with notifications --- .../OneSignal.xcodeproj/project.pbxproj | 12 ++ .../Source/OneSignalCommonDefines.h | 10 ++ iOS_SDK/OneSignalSDK/Source/OneSignalHelper.m | 11 +- .../OneSignalNotificationCategoryController.h | 53 ++++++++ .../OneSignalNotificationCategoryController.m | 120 ++++++++++++++++++ .../UnitTests/UnitTestCommonMethods.h | 7 + iOS_SDK/OneSignalSDK/UnitTests/UnitTests.m | 66 ++++++++-- 7 files changed, 261 insertions(+), 18 deletions(-) create mode 100644 iOS_SDK/OneSignalSDK/Source/OneSignalNotificationCategoryController.h create mode 100644 iOS_SDK/OneSignalSDK/Source/OneSignalNotificationCategoryController.m diff --git a/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj b/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj index ab9e47b00..5ba74e46a 100644 --- a/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj +++ b/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj @@ -188,6 +188,10 @@ CAABF34B205B15780042F8E5 /* OneSignalExtensionBadgeHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = CAABF34A205B15780042F8E5 /* OneSignalExtensionBadgeHandler.m */; }; CAABF34C205B157B0042F8E5 /* OneSignalExtensionBadgeHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = CAABF34A205B15780042F8E5 /* OneSignalExtensionBadgeHandler.m */; }; CAABF34D205B157B0042F8E5 /* OneSignalExtensionBadgeHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = CAABF34A205B15780042F8E5 /* OneSignalExtensionBadgeHandler.m */; }; + CAAEA68721ED68A40049CF15 /* OneSignalNotificationCategoryController.m in Sources */ = {isa = PBXBuildFile; fileRef = CAAEA68521ED68A30049CF15 /* OneSignalNotificationCategoryController.m */; }; + CAAEA68821ED68A40049CF15 /* OneSignalNotificationCategoryController.m in Sources */ = {isa = PBXBuildFile; fileRef = CAAEA68521ED68A30049CF15 /* OneSignalNotificationCategoryController.m */; }; + CAAEA68921ED68A40049CF15 /* OneSignalNotificationCategoryController.m in Sources */ = {isa = PBXBuildFile; fileRef = CAAEA68521ED68A30049CF15 /* OneSignalNotificationCategoryController.m */; }; + CAAEA68A21ED68A40049CF15 /* OneSignalNotificationCategoryController.h in Headers */ = {isa = PBXBuildFile; fileRef = CAAEA68621ED68A40049CF15 /* OneSignalNotificationCategoryController.h */; }; CAB4112920852E48005A70D1 /* DelayedInitializationParameters.m in Sources */ = {isa = PBXBuildFile; fileRef = CAB4112820852E48005A70D1 /* DelayedInitializationParameters.m */; }; CAB4112A20852E4C005A70D1 /* DelayedInitializationParameters.m in Sources */ = {isa = PBXBuildFile; fileRef = CAB4112820852E48005A70D1 /* DelayedInitializationParameters.m */; }; CAB4112B20852E4C005A70D1 /* DelayedInitializationParameters.m in Sources */ = {isa = PBXBuildFile; fileRef = CAB4112820852E48005A70D1 /* DelayedInitializationParameters.m */; }; @@ -331,6 +335,8 @@ CAA4ED0020646762005BD59B /* BadgeTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BadgeTests.m; sourceTree = ""; }; CAABF349205B15780042F8E5 /* OneSignalExtensionBadgeHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OneSignalExtensionBadgeHandler.h; sourceTree = ""; }; CAABF34A205B15780042F8E5 /* OneSignalExtensionBadgeHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OneSignalExtensionBadgeHandler.m; sourceTree = ""; }; + CAAEA68521ED68A30049CF15 /* OneSignalNotificationCategoryController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OneSignalNotificationCategoryController.m; sourceTree = ""; }; + CAAEA68621ED68A40049CF15 /* OneSignalNotificationCategoryController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OneSignalNotificationCategoryController.h; sourceTree = ""; }; CAB4112720852E48005A70D1 /* DelayedInitializationParameters.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DelayedInitializationParameters.h; sourceTree = ""; }; CAB4112820852E48005A70D1 /* DelayedInitializationParameters.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DelayedInitializationParameters.m; sourceTree = ""; }; CAB411AC208931EE005A70D1 /* DummyNotificationCenterDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DummyNotificationCenterDelegate.h; sourceTree = ""; }; @@ -514,6 +520,8 @@ 454F94F11FAD218000D74CCF /* OneSignalNotificationServiceExtensionHandler.m */, CAABF349205B15780042F8E5 /* OneSignalExtensionBadgeHandler.h */, CAABF34A205B15780042F8E5 /* OneSignalExtensionBadgeHandler.m */, + CAAEA68621ED68A40049CF15 /* OneSignalNotificationCategoryController.h */, + CAAEA68521ED68A30049CF15 /* OneSignalNotificationCategoryController.m */, ); path = Source; sourceTree = ""; @@ -627,6 +635,7 @@ 9124121D1E73342200E41FD7 /* OneSignalJailbreakDetection.h in Headers */, 9129C6B71E89E59B009CB6A0 /* OSPermission.h in Headers */, 912412151E73342200E41FD7 /* OneSignalHelper.h in Headers */, + CAAEA68A21ED68A40049CF15 /* OneSignalNotificationCategoryController.h in Headers */, 91F58D7F1E7C7F5F0017D24D /* OneSignalNotificationSettingsIOS10.h in Headers */, 912412391E73342200E41FD7 /* OneSignalWebView.h in Headers */, 91C7725E1E7CCE1000D612D0 /* OneSignalInternal.h in Headers */, @@ -796,6 +805,7 @@ 9124120E1E73342200E41FD7 /* OneSignal.m in Sources */, CA08FC731FE99AFD004C445F /* OneSignalClient.m in Sources */, 91F58D831E7C80DA0017D24D /* OneSignalNotificationSettingsIOS8.m in Sources */, + CAAEA68721ED68A40049CF15 /* OneSignalNotificationCategoryController.m in Sources */, 9124121E1E73342200E41FD7 /* OneSignalJailbreakDetection.m in Sources */, CA08FC791FE99B13004C445F /* OneSignalRequest.m in Sources */, 912412471E73369600E41FD7 /* OneSignalHelper.m in Sources */, @@ -837,6 +847,7 @@ 9124120F1E73342200E41FD7 /* OneSignal.m in Sources */, CA08FC741FE99AFF004C445F /* OneSignalClient.m in Sources */, 91F58D861E7C88250017D24D /* OneSignalNotificationSettingsIOS8.m in Sources */, + CAAEA68821ED68A40049CF15 /* OneSignalNotificationCategoryController.m in Sources */, 9124121F1E73342200E41FD7 /* OneSignalJailbreakDetection.m in Sources */, CA08FC7A1FE99B13004C445F /* OneSignalRequest.m in Sources */, 912412481E73369700E41FD7 /* OneSignalHelper.m in Sources */, @@ -879,6 +890,7 @@ 91F58D8B1E7C9A240017D24D /* OneSignalNotificationSettingsIOS7.m in Sources */, 91F60F7D1E80E4E400706E60 /* UncaughtExceptionHandler.m in Sources */, 912412201E73342200E41FD7 /* OneSignalJailbreakDetection.m in Sources */, + CAAEA68921ED68A40049CF15 /* OneSignalNotificationCategoryController.m in Sources */, CA85C15320604AEA003AB529 /* RequestTests.m in Sources */, CAE2E5A8215D80010036FD32 /* OneSignalTrackFirebaseAnalytics.m in Sources */, 912412381E73342200E41FD7 /* OneSignalTrackIAP.m in Sources */, diff --git a/iOS_SDK/OneSignalSDK/Source/OneSignalCommonDefines.h b/iOS_SDK/OneSignalSDK/Source/OneSignalCommonDefines.h index 9b7d50941..24ae4ba96 100644 --- a/iOS_SDK/OneSignalSDK/Source/OneSignalCommonDefines.h +++ b/iOS_SDK/OneSignalSDK/Source/OneSignalCommonDefines.h @@ -114,6 +114,10 @@ typedef enum {GET, POST, HEAD, PUT, DELETE, OPTIONS, CONNECT, TRACE} HTTPMethod; // before registering the user anyways #define APNS_TIMEOUT 25.0 +// The SDK saves a list of category ID's allowing multiple notifications +// to have their own unique buttons/etc. +#define SHARED_CATEGORY_LIST @"com.onesignal.shared_registered_categories" + #ifndef OS_TEST // OneSignal API Client Defines #define REATTEMPT_DELAY 30.0 @@ -123,6 +127,9 @@ typedef enum {GET, POST, HEAD, PUT, DELETE, OPTIONS, CONNECT, TRACE} HTTPMethod; // Send tags batch delay #define SEND_TAGS_DELAY 5.0 + + // the max number of UNNotificationCategory ID's the SDK will register + #define MAX_CATEGORIES_SIZE 128 #else // Test defines for API Client #define REATTEMPT_DELAY 0.004 @@ -132,6 +139,9 @@ typedef enum {GET, POST, HEAD, PUT, DELETE, OPTIONS, CONNECT, TRACE} HTTPMethod; // Send tags batch delay #define SEND_TAGS_DELAY 0.005 + + // the max number of UNNotificationCategory ID's the SDK will register + #define MAX_CATEGORIES_SIZE 5 #endif // A max timeout for a request, which might include multiple reattempts diff --git a/iOS_SDK/OneSignalSDK/Source/OneSignalHelper.m b/iOS_SDK/OneSignalSDK/Source/OneSignalHelper.m index 0b0bf3847..9943cb34a 100644 --- a/iOS_SDK/OneSignalSDK/Source/OneSignalHelper.m +++ b/iOS_SDK/OneSignalSDK/Source/OneSignalHelper.m @@ -38,6 +38,7 @@ #import "NSURL+OneSignal.h" #import "OneSignalCommonDefines.h" #import "OneSignalDialogController.h" +#import "OneSignalNotificationCategoryController.h" #define NOTIFICATION_TYPE_ALL 7 #pragma clang diagnostic push @@ -596,9 +597,11 @@ + (void)addActionButtons:(OSNotificationPayload*)payload finalActionArray = actionArray; // Get a full list of categories so we don't replace any exisiting ones. - var allCategories = [self existingCategories]; + var allCategories = OneSignalNotificationCategoryController.sharedInstance.existingCategories; - let category = [UNNotificationCategory categoryWithIdentifier:@"__dynamic__" + let newCategoryIdentifier = [OneSignalNotificationCategoryController.sharedInstance registerNotificationCategoryForNotificationId:payload.notificationID]; + + let category = [UNNotificationCategory categoryWithIdentifier:newCategoryIdentifier actions:finalActionArray intentIdentifiers:@[] options:UNNotificationCategoryOptionCustomDismissAction]; @@ -606,7 +609,7 @@ + (void)addActionButtons:(OSNotificationPayload*)payload if (allCategories) { let newCategorySet = [NSMutableSet new]; for(UNNotificationCategory *existingCategory in allCategories) { - if (![existingCategory.identifier isEqualToString:@"__dynamic__"]) + if (![existingCategory.identifier isEqualToString:newCategoryIdentifier]) [newCategorySet addObject:existingCategory]; } @@ -618,7 +621,7 @@ + (void)addActionButtons:(OSNotificationPayload*)payload [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:allCategories]; - content.categoryIdentifier = @"__dynamic__"; + content.categoryIdentifier = newCategoryIdentifier; } + (NSMutableSet*)existingCategories { diff --git a/iOS_SDK/OneSignalSDK/Source/OneSignalNotificationCategoryController.h b/iOS_SDK/OneSignalSDK/Source/OneSignalNotificationCategoryController.h new file mode 100644 index 000000000..069d88dc5 --- /dev/null +++ b/iOS_SDK/OneSignalSDK/Source/OneSignalNotificationCategoryController.h @@ -0,0 +1,53 @@ +/** + * Modified MIT License + * + * Copyright 2017 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + + +/** + This class maintains a saved list of UNNotificationCategory ID + strings. Allows the SDK to store unique UNNotificationCategory + objects for each notification. + + The SDK automatically prunes notification categories once more + than MAX_CATEGORIES_SIZE categories have been registered. + */ + +@interface OneSignalNotificationCategoryController : NSObject + ++ (OneSignalNotificationCategoryController *)sharedInstance; + +- (NSString *)registerNotificationCategoryForNotificationId:(NSString *)notificationId; + +- (NSMutableSet*)existingCategories; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_SDK/OneSignalSDK/Source/OneSignalNotificationCategoryController.m b/iOS_SDK/OneSignalSDK/Source/OneSignalNotificationCategoryController.m new file mode 100644 index 000000000..d6f2779c2 --- /dev/null +++ b/iOS_SDK/OneSignalSDK/Source/OneSignalNotificationCategoryController.m @@ -0,0 +1,120 @@ +/** + * Modified MIT License + * + * Copyright 2017 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#import "OneSignalNotificationCategoryController.h" +#import "OneSignalExtensionBadgeHandler.h" +#import "OneSignalHelper.h" +#import "OneSignalCommonDefines.h" + +#define CATEGORY_FORMAT_STRING(notificationId) [NSString stringWithFormat:@"__onesignal__dynamic__%@", notificationId] + +@implementation OneSignalNotificationCategoryController + ++ (OneSignalNotificationCategoryController *)sharedInstance { + static OneSignalNotificationCategoryController *sharedInstance = nil; + static dispatch_once_t once; + dispatch_once(&once, ^{ + sharedInstance = [OneSignalNotificationCategoryController new]; + }); + return sharedInstance; +} + +// appends the new category ID to the current saved array of category ID's +// The array is then inherently sorted in ascending order (the ID at index 0 is the oldest) +// we want to run this on the main thread so that the extension service doesn't stop before it finishes +// To prevent the SDK from registering too many categories as time goes by, we will prune the categories +// when more than MAX_CATEGORIES_SIZE have been registered +- (void)saveCategoryId:(NSString *)categoryId { + let defaults = [[NSUserDefaults alloc] initWithSuiteName:OneSignalExtensionBadgeHandler.appGroupName]; + + NSMutableArray *mutableExisting = [self.existingRegisteredCategoryIds mutableCopy]; + + [mutableExisting addObject:categoryId]; + + // prune array if > max size + if (mutableExisting.count > MAX_CATEGORIES_SIZE) { + + // removes these categories from UNUserNotificationCenter + [self pruneCategories:mutableExisting]; + + [mutableExisting removeObjectsInRange:NSMakeRange(0, mutableExisting.count - MAX_CATEGORIES_SIZE)]; + } + + + [defaults setObject:mutableExisting forKey:SHARED_CATEGORY_LIST]; + + [defaults synchronize]; +} + +- (NSArray *)existingRegisteredCategoryIds { + let defaults = [[NSUserDefaults alloc] initWithSuiteName:OneSignalExtensionBadgeHandler.appGroupName]; + + NSArray *existing = [defaults arrayForKey:SHARED_CATEGORY_LIST] ?: [NSArray new]; + + return existing; +} + +- (void)pruneCategories:(NSMutableArray *)currentCategories { + NSMutableSet *categoriesToRemove = [NSMutableSet new]; + + for (int i = (int)currentCategories.count - MAX_CATEGORIES_SIZE; i >= 0; i--) + [categoriesToRemove addObject:currentCategories[i]]; + + let existingCategories = self.existingCategories; + + NSMutableSet *newCategories = [NSMutableSet new]; + + for (UNNotificationCategory *category in existingCategories) + if (![categoriesToRemove containsObject:category.identifier]) + [newCategories addObject:category]; + + [UNUserNotificationCenter.currentNotificationCenter setNotificationCategories:newCategories]; +} + +- (NSString *)registerNotificationCategoryForNotificationId:(NSString *)notificationId { + // if the notificationID is null/empty, just generate a random new UUID + let categoryId = CATEGORY_FORMAT_STRING(notificationId ?: NSUUID.UUID.UUIDString); + + [self saveCategoryId:categoryId]; + + return categoryId; +} + +- (NSMutableSet*)existingCategories { + __block NSMutableSet* allCategories; + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + let notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; + [notificationCenter getNotificationCategoriesWithCompletionHandler:^(NSSet *categories) { + allCategories = [categories mutableCopy]; + dispatch_semaphore_signal(semaphore); + }]; + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); + + return allCategories; +} + +@end diff --git a/iOS_SDK/OneSignalSDK/UnitTests/UnitTestCommonMethods.h b/iOS_SDK/OneSignalSDK/UnitTests/UnitTestCommonMethods.h index dd35dce7f..08e04c315 100644 --- a/iOS_SDK/OneSignalSDK/UnitTests/UnitTestCommonMethods.h +++ b/iOS_SDK/OneSignalSDK/UnitTests/UnitTestCommonMethods.h @@ -28,6 +28,7 @@ #import #import #import "OneSignal.h" +#import "OneSignalNotificationCategoryController.h" #define TEST_EXTERNAL_USER_ID @"i_am_a_test_external_user_id" @@ -53,6 +54,12 @@ NSString * serverUrlWithPath(NSString *path); + (void)setDelayIntervals:(NSTimeInterval)apnsMaxWait withRegistrationDelay:(NSTimeInterval)registrationDelay; @end +// Expose methods on OneSignalNotificationCategoryController +@interface OneSignalNotificationCategoryController () +- (void)pruneCategories:(NSMutableArray *)currentCategories; +- (NSArray *)existingRegisteredCategoryIds; +@end + // START - Start Observers @interface OSPermissionStateTestObserver : NSObject { diff --git a/iOS_SDK/OneSignalSDK/UnitTests/UnitTests.m b/iOS_SDK/OneSignalSDK/UnitTests/UnitTests.m index 8f751af16..8fd077a76 100644 --- a/iOS_SDK/OneSignalSDK/UnitTests/UnitTests.m +++ b/iOS_SDK/OneSignalSDK/UnitTests/UnitTests.m @@ -26,21 +26,13 @@ */ #import - #import "UnitTestCommonMethods.h" - #import - #import #import #import - - #import "UncaughtExceptionHandler.h" - - #import "OneSignal.h" - #import "OneSignalHelper.h" #import "OneSignalTracker.h" #import "OneSignalSelectorHelpers.h" @@ -50,15 +42,14 @@ #import "OneSignalNotificationSettingsIOS10.h" #import "OSPermission.h" #import "OSNotificationPayload+Internal.h" - #include #include - #include "TestHelperFunctions.h" - #import "UnitTestAppDelegate.h" - #import "OneSignalExtensionBadgeHandler.h" +#import "DummyNotificationCenterDelegate.h" +#import "OneSignalDialogControllerOverrider.h" +#import "OneSignalNotificationCategoryController.h" // Shadows #import "NSObjectOverrider.h" @@ -79,9 +70,7 @@ #import "OneSignalClientOverrider.h" #import "OneSignalCommonDefines.h" -#import "DummyNotificationCenterDelegate.h" -#import "OneSignalDialogControllerOverrider.h" @interface OneSignalHelper (TestHelper) + (NSString*)downloadMediaAndSaveInBundle:(NSString*)urlString; @@ -2237,4 +2226,53 @@ - (void)testDoesntSendExistingExternalUserIdBeforeRegistration { XCTAssertNil(OneSignalClientOverrider.lastHTTPRequest[@"external_user_id"]); } +// Tests to make sure that the SDK clears out registered categories when it has saved +// more than MAX_CATEGORIES_SIZE number of UNNotificationCategory objects. Also tests +// to make sure that the SDK generates correct +- (void)testCategoryControllerClearsNotificationCategories { + let controller = [OneSignalNotificationCategoryController new]; + + NSMutableArray *generatedIds = [NSMutableArray new]; + + for (int i = 0; i < MAX_CATEGORIES_SIZE + 3; i++) { + let testId = NSUUID.UUID.UUIDString; + + let newId = [controller registerNotificationCategoryForNotificationId:testId]; + + let expected = [NSString stringWithFormat:@"__onesignal__dynamic__%@", testId]; + + XCTAssertEqualObjects(newId, expected); + + [generatedIds addObject:newId]; + } + + let currentlySavedCategoryIds = [controller existingRegisteredCategoryIds]; + + XCTAssertEqual(currentlySavedCategoryIds.count, MAX_CATEGORIES_SIZE); + + for (int i = 0; i < MAX_CATEGORIES_SIZE; i++) + XCTAssertEqualObjects(generatedIds[generatedIds.count - 1 - i], currentlySavedCategoryIds[currentlySavedCategoryIds.count - 1 - i]); +} + +- (void)testNotificationWithButtonsRegistersUniqueCategory { + + [OneSignal initWithLaunchOptions:nil appId:@"b2f7f966-d8cc-11e4-bed1-df8f05be55ba" handleNotificationAction:nil]; + + [UnitTestCommonMethods runBackgroundThreads]; + + let notification = (NSDictionary *)[self exampleNotificationJSONWithMediaURL:@"https://www.onesignal.com"]; + + let notifResponse = [UnitTestCommonMethods createBasiciOSNotificationResponseWithPayload:notification]; + + let content = [OneSignal didReceiveNotificationExtensionRequest:[notifResponse notification].request withMutableNotificationContent:nil]; + + let ids = OneSignalNotificationCategoryController.sharedInstance.existingRegisteredCategoryIds; + + XCTAssertEqual(ids.count, 1); + + XCTAssertEqualObjects(ids.firstObject, @"__onesignal__dynamic__b2f7f966-d8cc-11e4-bed1-df8f05be55ba"); + + XCTAssertEqualObjects(content.categoryIdentifier, @"__onesignal__dynamic__b2f7f966-d8cc-11e4-bed1-df8f05be55ba"); +} + @end From 120f0bfb1e0a3e4fc7467e0ffa7dbdf075b2432a Mon Sep 17 00:00:00 2001 From: Brad Hesse Date: Mon, 14 Jan 2019 18:08:42 -0800 Subject: [PATCH 2/3] Fix Old Test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • Fixes an old test that depended on the old statically defined UNNotificationCategory identifier --- iOS_SDK/OneSignalSDK/UnitTests/UnitTests.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS_SDK/OneSignalSDK/UnitTests/UnitTests.m b/iOS_SDK/OneSignalSDK/UnitTests/UnitTests.m index 8fd077a76..7326146af 100644 --- a/iOS_SDK/OneSignalSDK/UnitTests/UnitTests.m +++ b/iOS_SDK/OneSignalSDK/UnitTests/UnitTests.m @@ -1736,7 +1736,7 @@ - (void) testServiceExtensionTimeWillExpireRequest { UNMutableNotificationContent* content = [OneSignal serviceExtensionTimeWillExpireRequest:[notifResponse notification].request withMutableNotificationContent:nil]; // Make sure butons were added. - XCTAssertEqualObjects(content.categoryIdentifier, @"__dynamic__"); + XCTAssertEqualObjects(content.categoryIdentifier, @"__onesignal__dynamic__b2f7f966-d8cc-11e4-bed1-df8f05be55ba"); // Make sure attachments were NOT added. // We should not try to download attachemts as iOS is about to kill the extension and this will take to much time. XCTAssertNil(content.attachments); From 8fb26b9d337296a20d13dceabd4aefbb346bbb01 Mon Sep 17 00:00:00 2001 From: Brad Hesse Date: Tue, 15 Jan 2019 11:25:02 -0800 Subject: [PATCH 3/3] Move Method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • Moves the existingCategories() method from OneSignalHelper to OneSignalNotificationCategoryController --- iOS_SDK/OneSignalSDK/Source/OneSignalHelper.m | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/Source/OneSignalHelper.m b/iOS_SDK/OneSignalSDK/Source/OneSignalHelper.m index 9943cb34a..0c167f74a 100644 --- a/iOS_SDK/OneSignalSDK/Source/OneSignalHelper.m +++ b/iOS_SDK/OneSignalSDK/Source/OneSignalHelper.m @@ -624,19 +624,6 @@ + (void)addActionButtons:(OSNotificationPayload*)payload content.categoryIdentifier = newCategoryIdentifier; } -+ (NSMutableSet*)existingCategories { - __block NSMutableSet* allCategories; - dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - let notificationCenter = [UNUserNotificationCenter currentNotificationCenter]; - [notificationCenter getNotificationCategoriesWithCompletionHandler:^(NSSet *categories) { - allCategories = [categories mutableCopy]; - dispatch_semaphore_signal(semaphore); - }]; - dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); - - return allCategories; -} - + (void)addAttachments:(OSNotificationPayload*)payload toNotificationContent:(UNMutableNotificationContent*)content { if (!payload.attachments)