Skip to content

Commit 5973c56

Browse files
committed
Fixed crash on iOS 7 devices when the device tries to register for push
* This crash was introduced in 2.3.6. * Added test to prevent regression of this issue.
1 parent 48e75b3 commit 5973c56

File tree

4 files changed

+95
-40
lines changed

4 files changed

+95
-40
lines changed

iOS_SDK/OneSignalSDK/Source/OneSignal.m

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -232,10 +232,12 @@ + (id)initWithLaunchOptions:(NSDictionary*)launchOptions appId:(NSString*)appId
232232
else
233233
[self enableInAppLaunchURL:@YES];
234234

235-
// Register this device with Apple's APNS server if enabled auto-prompt or not passed a NO
235+
236236
BOOL autoPrompt = YES;
237237
if (settings[kOSSettingsKeyAutoPrompt] && [settings[kOSSettingsKeyAutoPrompt] isKindOfClass:[NSNumber class]])
238238
autoPrompt = [settings[kOSSettingsKeyAutoPrompt] boolValue];
239+
240+
// Register with Apple's APNS server if we registed once before or if auto-prompt hasn't been disabled.
239241
if (autoPrompt || registeredWithApple)
240242
[self registerForPushNotifications];
241243
else
@@ -348,8 +350,11 @@ void onesignal_Log(ONE_S_LOG_LEVEL logLevel, NSString* message) {
348350
}
349351

350352

351-
// iOS 8+, only tries to register for an APNs token if one is true:
353+
// iOS 8+, only tries to register for an APNs token
352354
+ (BOOL)registerForAPNsToken {
355+
if (![OneSignalHelper isIOSVersionGreaterOrEqual:8])
356+
return false;
357+
353358
if (waitingForApnsResponse)
354359
return true;
355360

@@ -358,11 +363,11 @@ + (BOOL)registerForAPNsToken {
358363

359364
// Only try to register for a pushToken if:
360365
// - The user accepted notifications
361-
// - Background Modes > Remote Notifications are enabled in Xcode
366+
// - "Background Modes" > "Remote Notifications" are enabled in Xcode
362367
if (![self accpetedNotificationPermission] && !backgroundModesEnabled)
363368
return false;
364369

365-
// Don't attempt to register again if the was non-recoverable error.
370+
// Don't attempt to register again if there was a non-recoverable error.
366371
if (mSubscriptionStatus < -9)
367372
return false;
368373

@@ -383,19 +388,17 @@ + (void)registerForPushNotifications {
383388
// BOOL granted, NSError * _Nullable error) {
384389
// }];
385390

386-
// For iOS 8 devices
387-
if ([[UIApplication sharedApplication] respondsToSelector:@selector(registerUserNotificationSettings:)]) {
388-
// ClassFromString to work around pre Xcode 6 link errors when building an app using the OneSignal framework.
389-
Class uiUserNotificationSettings = NSClassFromString(@"UIUserNotificationSettings");
390-
NSUInteger notificationTypes = NOTIFICATION_TYPE_ALL;
391-
392-
NSSet* categories = [[[UIApplication sharedApplication] currentUserNotificationSettings] categories];
391+
UIApplication* shardApp = [UIApplication sharedApplication];
392+
393+
if ([OneSignalHelper isIOSVersionGreaterOrEqual:8]) {
394+
// Get all current Categories so we don't remove any of the app developer's.
395+
NSSet* categories = [[shardApp currentUserNotificationSettings] categories];
393396

394-
[[UIApplication sharedApplication] registerUserNotificationSettings:[uiUserNotificationSettings settingsForTypes:notificationTypes categories:categories]];
397+
[shardApp registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:NOTIFICATION_TYPE_ALL categories:categories]];
395398
[self registerForAPNsToken];
396399
}
397400
else { // For iOS 6 & 7 devices
398-
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert];
401+
[shardApp registerForRemoteNotificationTypes:UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert];
399402
if (!registeredWithApple) {
400403
waitingForApnsResponse = true;
401404
[[NSUserDefaults standardUserDefaults] setObject:@YES forKey:@"GT_REGISTERED_WITH_APPLE"];
@@ -641,7 +644,7 @@ + (void)setNotificationDisplayOptions:(NSNumber*)option {
641644

642645
// Special Case: If iOS version < 10 && Option passed is 2, default to inAppAlerts.
643646
NSInteger op = option.integerValue;
644-
if (![OneSignalHelper isiOS10Plus] && OSNotificationDisplayTypeNotification == op)
647+
if (![OneSignalHelper isIOSVersionGreaterOrEqual:10] && OSNotificationDisplayTypeNotification == op)
645648
op = OSNotificationDisplayTypeInAppAlert;
646649

647650
[[NSUserDefaults standardUserDefaults] setObject:@(op) forKey:@"ONESIGNAL_ALERT_OPTION"];
@@ -1218,7 +1221,8 @@ + (void)setSubscriptionErrorStatus:(int)errorType {
12181221
}
12191222

12201223
+ (void)userAnsweredNotificationPrompt:(void (^)(OSSubcscriptionStatus *anwsered))completionHandler {
1221-
if ([OneSignalHelper isiOS10Plus]) {
1224+
1225+
if ([OneSignalHelper isIOSVersionGreaterOrEqual:10]) {
12221226
Class unUserNotifClass = NSClassFromString(@"UNUserNotificationCenter");
12231227
[[unUserNotifClass currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(id settings) {
12241228
// Trigger callback on the main thread.
@@ -1232,27 +1236,33 @@ + (void)userAnsweredNotificationPrompt:(void (^)(OSSubcscriptionStatus *anwsered
12321236
});
12331237
}];
12341238
}
1235-
else {
1239+
else { // Pre-iOS 10
12361240
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
12371241
OSSubcscriptionStatus *status = [OSSubcscriptionStatus alloc];
12381242
status.anwseredPrompt = [userDefaults boolForKey:@"OS_NOTIFICATION_PROMPT_ANSWERED"];
12391243

1240-
if ([OneSignalHelper canGetNotificationTypes]) {
1244+
// iOS 8+
1245+
if ([OneSignalHelper canGetNotificationTypes])
12411246
status.accepted = [[UIApplication sharedApplication] currentUserNotificationSettings].types > 0;
1242-
completionHandler(status);
1243-
}
1244-
else {
1247+
else { // iOS 7
12451248
status.accepted = mDeviceToken != nil;
1246-
completionHandler(status);
1249+
1250+
// No other iOS 7 event will trigger an awnsered event so do so here.
1251+
if (!status.anwseredPrompt && status.accepted) {
1252+
[userDefaults setBool:true forKey:@"OS_NOTIFICATION_PROMPT_ANSWERED"];
1253+
[userDefaults synchronize];
1254+
}
12471255
}
1256+
1257+
completionHandler(status);
12481258
}
12491259
}
12501260

12511261
// iOS 8.0+ only
12521262
// User just responed to the iOS native notification permission prompt.
12531263
// Also extra calls to registerUserNotificationSettings will fire this without prompting again.
12541264
+ (void) updateNotificationTypes:(int)notificationTypes {
1255-
if (![OneSignalHelper isiOS10Plus]) {
1265+
if (![OneSignalHelper isIOSVersionGreaterOrEqual:10]) {
12561266
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
12571267
[userDefaults setBool:true forKey:@"OS_NOTIFICATION_PROMPT_ANSWERED"];
12581268
[userDefaults synchronize];

iOS_SDK/OneSignalSDK/Source/OneSignalHelper.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
+ (void)handleNotificationAction:(OSNotificationActionType)actionType actionID:(NSString*)actionID displayType:(OSNotificationDisplayType)displayType;
4848

4949
// - iOS 10
50-
+ (BOOL)isiOS10Plus;
50+
+ (BOOL)isIOSVersionGreaterOrEqual:(float)version;
5151
#if XC8_AVAILABLE
5252
+ (void)registerAsUNNotificationCenterDelegate;
5353
+ (void)clearCachedMedia;

iOS_SDK/OneSignalSDK/Source/OneSignalHelper.m

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -532,21 +532,20 @@ +(NSNumber*)getNetType {
532532
return @1;
533533
}
534534

535+
// Can call currentUserNotificationSettings
535536
+ (BOOL) canGetNotificationTypes {
536-
return [[UIApplication sharedApplication] respondsToSelector:@selector(currentUserNotificationSettings)];
537+
return [OneSignalHelper isIOSVersionGreaterOrEqual:8];
537538
}
538539

539540
+ (UILocalNotification*)createUILocalNotification:(NSDictionary*)data {
541+
UILocalNotification* notification = [[UILocalNotification alloc] init];
540542

541-
UILocalNotification * notification = [[UILocalNotification alloc] init];
542-
543-
id category = [[NSClassFromString(@"UIMutableUserNotificationCategory") alloc] init];
543+
UIMutableUserNotificationCategory* category = [[UIMutableUserNotificationCategory alloc] init];
544544
[category setIdentifier:@"__dynamic__"];
545545

546-
Class UIMutableUserNotificationActionClass = NSClassFromString(@"UIMutableUserNotificationAction");
547546
NSMutableArray* actionArray = [[NSMutableArray alloc] init];
548547
for (NSDictionary* button in [OneSignalHelper getActionButtons:data]) {
549-
id action = [[UIMutableUserNotificationActionClass alloc] init];
548+
id action = [[UIMutableUserNotificationAction alloc] init];
550549
[action setTitle:button[@"n"]];
551550
[action setIdentifier:button[@"i"] ? button[@"i"] : [action title]];
552551
[action setActivationMode:UIUserNotificationActivationModeForeground];
@@ -561,16 +560,13 @@ + (UILocalNotification*)createUILocalNotification:(NSDictionary*)data {
561560

562561
[category setActions:actionArray forContext:UIUserNotificationActionContextDefault];
563562

564-
Class uiUserNotificationSettings = NSClassFromString(@"UIUserNotificationSettings");
565-
NSUInteger notificationTypes = NOTIFICATION_TYPE_ALL;
566-
567563
NSSet* currentCategories = [[[UIApplication sharedApplication] currentUserNotificationSettings] categories];
568564
if (currentCategories)
569565
currentCategories = [currentCategories setByAddingObject:category];
570566
else
571567
currentCategories = [NSSet setWithObject:category];
572568

573-
[[UIApplication sharedApplication] registerUserNotificationSettings:[uiUserNotificationSettings settingsForTypes:notificationTypes categories:currentCategories]];
569+
[[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:NOTIFICATION_TYPE_ALL categories:currentCategories]];
574570
notification.category = [category identifier];
575571
return notification;
576572
}
@@ -580,6 +576,7 @@ + (UILocalNotification*)prepareUILocalNotification:(NSDictionary*)data :(NSDicti
580576
UILocalNotification *notification = [self createUILocalNotification:data];
581577

582578
if ([data[@"m"] isKindOfClass:[NSDictionary class]]) {
579+
// alertTitle was added in iOS 8.2
583580
if ([notification respondsToSelector:NSSelectorFromString(@"alertTitle")])
584581
notification.alertTitle = data[@"m"][@"title"];
585582
notification.alertBody = data[@"m"][@"body"];
@@ -609,8 +606,8 @@ +(OneSignal*) sharedInstance {
609606
return singleInstance;
610607
}
611608

612-
+ (BOOL)isiOS10Plus {
613-
return [[[UIDevice currentDevice] systemVersion] floatValue] >= 10.0 ;
609+
+ (BOOL)isIOSVersionGreaterOrEqual:(float)version {
610+
return [[[UIDevice currentDevice] systemVersion] floatValue] >= version;
614611
}
615612

616613
+(NSString*)randomStringWithLength:(int)length {

iOS_SDK/OneSignalSDK/UnitTests/UnitTests.m

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -281,19 +281,21 @@ @interface UIApplicationOverrider : NSObject
281281
@end
282282
@implementation UIApplicationOverrider
283283

284+
static BOOL calledRegisterForRemoteNotifications;
284285
static NSInteger didFailRegistarationErrorCode;
285286
static BOOL shouldFireDeviceToken;
286287

287288
+ (void)load {
288289
injectToProperClass(@selector(overrideRegisterForRemoteNotifications), @selector(registerForRemoteNotifications), @[], [UIApplicationOverrider class], [UIApplication class]);
289290
injectToProperClass(@selector(override_run), @selector(_run), @[], [UIApplicationOverrider class], [UIApplication class]);
290291
injectToProperClass(@selector(overrideCurrentUserNotificationSettings), @selector(currentUserNotificationSettings), @[], [UIApplicationOverrider class], [UIApplication class]);
292+
injectToProperClass(@selector(overrideRegisterForRemoteNotificationTypes:), @selector(registerForRemoteNotificationTypes:), @[], [UIApplicationOverrider class], [UIApplication class]);
291293
}
292294

293295
// Keeps UIApplicationMain(...) from looping to continue to the next line.
294296
- (void) override_run {}
295297

296-
- (void) overrideRegisterForRemoteNotifications {
298+
+ (void) helperCallDidRegisterForRemoteNotificationsWithDeviceToken {
297299
id app = [UIApplication sharedApplication];
298300
id appDelegate = [[UIApplication sharedApplication] delegate];
299301

@@ -305,13 +307,25 @@ - (void) overrideRegisterForRemoteNotifications {
305307

306308
if (!shouldFireDeviceToken)
307309
return;
308-
310+
309311

310312
char bytes[32];
311313
memset(bytes, 0, 32);
312314

313315
id deviceToken = [NSData dataWithBytes:bytes length:32];
314316
[appDelegate application:app didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
317+
318+
}
319+
320+
// Called on iOS 8+
321+
- (void) overrideRegisterForRemoteNotifications {
322+
calledRegisterForRemoteNotifications = true;
323+
[UIApplicationOverrider helperCallDidRegisterForRemoteNotificationsWithDeviceToken];
324+
}
325+
326+
// iOS 7
327+
- (void)overrideRegisterForRemoteNotificationTypes:(UIRemoteNotificationType)types {
328+
[UIApplicationOverrider helperCallDidRegisterForRemoteNotificationsWithDeviceToken];
315329
}
316330

317331

@@ -329,9 +343,12 @@ @implementation OneSignalHelperOverrider
329343
static NSDictionary* lastHTTPRequset;
330344
static int networkRequestCount;
331345

346+
static float mockIOSVersion;
347+
332348
+ (void)load {
333349
injectStaticSelector([OneSignalHelperOverrider class], @selector(overrideEnqueueRequest:onSuccess:onFailure:isSynchronous:), [OneSignalHelper class], @selector(enqueueRequest:onSuccess:onFailure:isSynchronous:));
334350
injectStaticSelector([OneSignalHelperOverrider class], @selector(overrideGetAppName), [OneSignalHelper class], @selector(getAppName));
351+
injectStaticSelector([OneSignalHelperOverrider class], @selector(overrideIsIOSVersionGreaterOrEqual:), [OneSignalHelper class], @selector(isIOSVersionGreaterOrEqual:));
335352
}
336353

337354
+ (NSString*) overrideGetAppName {
@@ -354,6 +371,10 @@ + (void)overrideEnqueueRequest:(NSURLRequest*)request onSuccess:(OSResultSuccess
354371
successBlock(@{@"id": @"1234"});
355372
}
356373

374+
+ (BOOL)overrideIsIOSVersionGreaterOrEqual:(float)version {
375+
return mockIOSVersion >= version;
376+
}
377+
357378
@end
358379

359380

@@ -405,6 +426,8 @@ @implementation UnitTests
405426
- (void)setUp {
406427
[super setUp];
407428

429+
mockIOSVersion = 10;
430+
408431
timeOffset = 0;
409432
networkRequestCount = 0;
410433
lastUrl = nil;
@@ -426,6 +449,7 @@ - (void)setUp {
426449
authorizationStatus = [NSNumber numberWithInteger:UNAuthorizationStatusAuthorized];
427450

428451
shouldFireDeviceToken = true;
452+
calledRegisterForRemoteNotifications = false;
429453
didFailRegistarationErrorCode = 0;
430454
nsbundleDictionary = @{@"UIBackgroundModes": @[@"remote-notification"]};
431455

@@ -521,13 +545,14 @@ - (UNNotificationResponse*)createBasiciOSNotificationResponseWithPayload:(NSDict
521545
}
522546

523547
- (UNNotificationResponse*)createBasiciOSNotificationResponse {
524-
id userInfo = @{@"custom": @{
525-
@"i": @"b2f7f966-d8cc-11e4-bed1-df8f05be55bb"
526-
}};
548+
id userInfo = @{@"custom":
549+
@{@"i": @"b2f7f966-d8cc-11e4-bed1-df8f05be55bb"}
550+
};
527551

528552
return [self createBasiciOSNotificationResponseWithPayload:userInfo];
529553
}
530554

555+
// Helper used to simpify tests below.
531556
- (void)initOneSignal {
532557
[OneSignal initWithLaunchOptions:nil appId:@"b2f7f966-d8cc-11e4-bed1-df8f05be55ba"];
533558

@@ -556,6 +581,29 @@ - (void)testBasicInitTest {
556581
XCTAssertEqual(networkRequestCount, 1);
557582
}
558583

584+
- (void)testRegisterationOniOS7 {
585+
mockIOSVersion = 7;
586+
587+
[self initOneSignal];
588+
[self runBackgroundThreads];
589+
590+
XCTAssertEqualObjects(lastHTTPRequset[@"app_id"], @"b2f7f966-d8cc-11e4-bed1-df8f05be55ba");
591+
XCTAssertEqualObjects(lastHTTPRequset[@"identifier"], @"0000000000000000000000000000000000000000000000000000000000000000");
592+
XCTAssertEqualObjects(lastHTTPRequset[@"notification_types"], @7);
593+
XCTAssertEqualObjects(lastHTTPRequset[@"device_model"], @"x86_64");
594+
XCTAssertEqualObjects(lastHTTPRequset[@"device_type"], @0);
595+
XCTAssertEqualObjects(lastHTTPRequset[@"language"], @"en-US");
596+
597+
// 2nd init call should not fire another on_session call.
598+
lastHTTPRequset = nil;
599+
[OneSignal initWithLaunchOptions:nil appId:@"b2f7f966-d8cc-11e4-bed1-df8f05be55ba"];
600+
XCTAssertNil(lastHTTPRequset);
601+
602+
XCTAssertEqual(networkRequestCount, 1);
603+
// Make sure calledRegisterForRemoteNotifications didn't fire as this isn't available on iOS 7
604+
XCTAssertFalse(calledRegisterForRemoteNotifications);
605+
}
606+
559607
// Seen a few rare crash reports where [NSLocale preferredLanguages] resturns an empty array
560608
- (void)testInitWithEmptyPreferredLanguages {
561609
preferredLanguagesArray = @[];

0 commit comments

Comments
 (0)