Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TIMOB-20272] (6_2_X) Attempt to fix notifications in cold app-start #9295

Merged
merged 6 commits into from
Aug 10, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 12 additions & 11 deletions iphone/Classes/TiApp.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,34 +37,35 @@ TI_INLINE void waitForMemoryPanicCleared() //WARNING: This must never be run o

TiContextGroupRef contextGroup;
KrollBridge *kjsBridge;

#ifdef USE_TI_UIWEBVIEW
XHRBridge *xhrBridge;
#endif

NSMutableDictionary *launchOptions;
NSTimeInterval started;

int32_t networkActivityCount;

TiRootViewController *controller;
NSString *userAgent;
NSString *remoteDeviceUUID;

id remoteNotificationDelegate;
NSDictionary* remoteNotification;
NSMutableDictionary* pendingCompletionHandlers;
NSMutableDictionary* pendingReplyHandlers;
NSMutableDictionary* backgroundTransferCompletionHandlers;
BOOL appBooted;

NSDictionary *remoteNotification;
NSMutableDictionary *pendingCompletionHandlers;
NSMutableDictionary *pendingReplyHandlers;
NSMutableDictionary *backgroundTransferCompletionHandlers;
NSMutableDictionary *queuedBootEvents;
BOOL appBooted;

NSString *sessionId;

UIBackgroundTaskIdentifier bgTask;
NSMutableArray *backgroundServices;
NSMutableArray *runningServices;
NSDictionary *localNotification;
UIApplicationShortcutItem *launchedShortcutItem;
UIApplicationShortcutItem *launchedShortcutItem;
}

@property (nonatomic) BOOL forceSplashAsSnapshot;
Expand Down
121 changes: 62 additions & 59 deletions iphone/Classes/TiApp.m
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,19 @@ -(TiContextGroupRef)contextGroup
return contextGroup;
}


+(TiContextGroupRef)contextGroup
{
return [sharedApp contextGroup];
}

- (NSMutableDictionary *)queuedBootEvents
{
if (queuedBootEvents == nil) {
queuedBootEvents = [[[NSMutableDictionary alloc] init] retain];
}

return queuedBootEvents;
}

-(void)startNetwork
{
Expand Down Expand Up @@ -296,13 +303,17 @@ - (void)booted:(id)bridge
appBooted = YES;

if(launchedShortcutItem != nil) {
[self handleShortcutItem:launchedShortcutItem waitForBootIfNotLaunched:YES];
[self handleShortcutItem:launchedShortcutItem queueToBootIfNotLaunched:YES];
RELEASE_TO_NIL(launchedShortcutItem);
}

if (localNotification != nil) {
[[NSNotificationCenter defaultCenter] postNotificationName:kTiLocalNotification object:localNotification userInfo:nil];
if (queuedBootEvents != nil) {
for (NSString *notificationName in queuedBootEvents) {
[[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:self userInfo:[queuedBootEvents objectForKey:notificationName]];
}
RELEASE_TO_NIL(queuedBootEvents);
}

TiThreadPerformOnMainThread(^{[self validator];}, YES);
}
}
Expand Down Expand Up @@ -495,11 +506,9 @@ -(void)application:(UIApplication*)application performFetchWithCompletionHandler

[pendingCompletionHandlers setObject:[[completionHandler copy] autorelease ]forKey:key];

// Handling the case, where the app is not running and backgroundfetch launches the app into background. In this case, the delegate gets called
// the bridge completes processing of app.js (adding the event into notification center).

[self postNotificationwithKey:[NSMutableDictionary dictionaryWithObjectsAndKeys:key, @"handlerId", nil] withNotificationName:kTiBackgroundFetchNotification] ;

[self tryToPostBackgroundModeNotification:[NSMutableDictionary dictionaryWithObjectsAndKeys:key, @"handlerId", nil]
withNotificationName:kTiBackgroundFetchNotification];

// We will go ahead and keeper a timer just in case the user returns the value too late - this is the worst case scenario.
NSTimer* flushTimer = [NSTimer timerWithTimeInterval:TI_BACKGROUNDFETCH_MAX_INTERVAL target:self selector:@selector(fireCompletionHandler:) userInfo:key repeats:NO] ;
[[NSRunLoop mainRunLoop] addTimer:flushTimer forMode:NSDefaultRunLoopMode];
Expand All @@ -512,7 +521,7 @@ -(void)application:(UIApplication*)application performFetchWithCompletionHandler

- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
[[NSNotificationCenter defaultCenter] postNotificationName:kTiUserNotificationSettingsNotification object:notificationSettings userInfo:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:kTiUserNotificationSettingsNotification object:self userInfo:notificationSettings];
}

- (void) application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification withResponseInfo:(NSDictionary *)responseInfo completionHandler:(void (^)())completionHandler {
Expand All @@ -523,8 +532,7 @@ - (void) application:(UIApplication *)application handleActionWithIdentifier:(NS
[localNotification setValue:responseInfo[UIUserNotificationActionResponseTypedTextKey] forKey:@"typedText"];
}

[[NSNotificationCenter defaultCenter] postNotificationName:kTiLocalNotificationAction object:localNotification userInfo:nil];
completionHandler();
[self tryToPostNotification:localNotification withNotificationName:kTiLocalNotificationAction completionHandler:completionHandler];
}

// iOS 9+
Expand Down Expand Up @@ -589,8 +597,27 @@ -(void)watchKitExtensionRequestHandler:(id)key withUserInfo:(NSDictionary*)userI

#pragma mark Helper Methods

-(void)postNotificationwithKey:(NSMutableDictionary*)userInfo withNotificationName:(NSString*)notificationName{
- (void)tryToPostNotification:(NSDictionary *)_notification withNotificationName:(NSString *)_notificationName completionHandler:(void (^)())completionHandler
{
typedef void (^NotificationBlock)();

NotificationBlock myNotificationBlock = ^void() {
[[NSNotificationCenter defaultCenter] postNotificationName:_notificationName object:self userInfo:_notification];

if (completionHandler != nil) {
completionHandler();
}
};

if (appBooted) {
myNotificationBlock();
} else {
[[self queuedBootEvents] setObject:_notification forKey:_notificationName];
}
}

-(void)tryToPostBackgroundModeNotification:(NSMutableDictionary*)userInfo withNotificationName:(NSString*)notificationName
{
//Check to see if the app booted and we still have the completionhandler in the system
NSString* key = [userInfo objectForKey:@"handlerId"] ;
BOOL shouldContinue = NO;
Expand All @@ -607,17 +634,13 @@ -(void)postNotificationwithKey:(NSMutableDictionary*)userInfo withNotificationNa
if (appBooted ) {
[[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:self userInfo:userInfo];
} else {
//Try again in 2 sec. TODO: should we reduce this value ?
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self postNotificationwithKey:userInfo withNotificationName:notificationName];
});
[[self queuedBootEvents] setObject:userInfo forKey:notificationName];
}
}

//Clear out the pendingCompletionHandlerQueue
-(void)flushCompletionHandlerQueue
{
//FunctionName();
if (pendingCompletionHandlers !=nil) {
for (id key in pendingCompletionHandlers) {
[self completionHandler:key withResult:2]; //UIBackgroundFetchResultFailed
Expand All @@ -629,18 +652,16 @@ -(void)flushCompletionHandlerQueue
// This method gets called when the wall clock runs out and the completionhandler is still there.
-(void)fireCompletionHandler:(NSTimer*)timer
{
//FunctionName();
id key = timer.userInfo;
if ([pendingCompletionHandlers objectForKey:key]) {
[self completionHandler:key withResult:UIBackgroundFetchResultFailed];
//Send a event signalling the that backgroundfetch ended.
}
}

// gets called when user ends finishes with backgrounding stuff. By default this would always be called with UIBackgroundFetchResultNoData.
// Gets called when user ends finishes with backgrounding stuff. By default this would always be called with UIBackgroundFetchResultNoData.
-(void)completionHandler:(id)key withResult:(int)result
{
//FunctionName();
if ([pendingCompletionHandlers objectForKey:key]) {
void (^completionHandler)(UIBackgroundFetchResult);
completionHandler = [pendingCompletionHandlers objectForKey:key];
Expand All @@ -651,7 +672,7 @@ -(void)completionHandler:(id)key withResult:(int)result
}
}

//Called to mark the end of background transfer while in the background.
// Called to mark the end of background transfer while in the background.
-(void)completionHandlerForBackgroundTransfer:(id)key
{
if ([backgroundTransferCompletionHandlers objectForKey:key] != nil) {
Expand All @@ -668,16 +689,15 @@ -(void)completionHandlerForBackgroundTransfer:(id)key
#pragma mark Remote Notifications iOS 7

#ifdef USE_TI_SILENTPUSH
//Delegate callback for Silent Remote Notification.
// Delegate callback for Silent Remote Notification.
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler
{
//FunctionName();
//Forward the callback
// Forward the callback
if ([self respondsToSelector:@selector(application:didReceiveRemoteNotification:)]) {
[self application:application didReceiveRemoteNotification:userInfo];
}

//This only here for Simulator builds.
// This only here for Simulator builds.

NSArray* backgroundModes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIBackgroundModes"];
if ([backgroundModes containsObject:@"remote-notification"]) {
Expand All @@ -692,12 +712,11 @@ - (void)application:(UIApplication *)application didReceiveRemoteNotification:(N

[pendingCompletionHandlers setObject:[[completionHandler copy] autorelease ]forKey:key];

// Handling the case, where the app is not running and backgroundfetch launches the app into background. In this case, the delegate gets called
// the bridge completes processing of app.js (adding the event into notification center).

NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithObjectsAndKeys:key, @"handlerId", nil];
[dict addEntriesFromDictionary:userInfo];
[self postNotificationwithKey:dict withNotificationName:kTiSilentPushNotification];

[self tryToPostBackgroundModeNotification:dict
withNotificationName:kTiSilentPushNotification];

// We will go ahead and keeper a timer just in case the user returns the value too late - this is the worst case scenario.
NSTimer* flushTimer = [NSTimer timerWithTimeInterval:TI_BACKGROUNDFETCH_MAX_INTERVAL target:self selector:@selector(fireCompletionHandler:) userInfo:key repeats:NO] ;
Expand All @@ -713,7 +732,6 @@ - (void)application:(UIApplication *)application didReceiveRemoteNotification:(N
//Delegate callback for Background Transfer completes.
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler
{
//FunctionName();
// Generate unique key with timestamp.
id key = [NSString stringWithFormat:@"Session-%f",[[NSDate date] timeIntervalSince1970]];

Expand All @@ -725,7 +743,7 @@ - (void)application:(UIApplication *)application handleEventsForBackgroundURLSes
[backgroundTransferCompletionHandlers setObject:[[completionHandler copy] autorelease ]forKey:key];
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObjectsAndKeys:identifier, @"sessionId",
key, @"handlerId", nil];
[self postNotificationwithKey:dict withNotificationName:kTiBackgroundTransfer];
[self tryToPostBackgroundModeNotification:dict withNotificationName:kTiBackgroundTransfer];

}

Expand All @@ -735,8 +753,7 @@ - (void)application:(UIApplication *)application handleEventsForBackgroundURLSes

-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
//FunctionName();
//copy downloaded file from location to tempFile (in NSTemporaryDirectory), because file in location will be removed after delegate completes
// Copy downloaded file from location to tempFile (in NSTemporaryDirectory), because file in location will be removed after delegate completes
NSError *error;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *destinationFilename = location.lastPathComponent;
Expand Down Expand Up @@ -798,8 +815,6 @@ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSend

-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
//FunctionName();

NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithUnsignedInteger:task.taskIdentifier], @"taskIdentifier",
nil];
Expand Down Expand Up @@ -974,7 +989,6 @@ - (void)applicationDidBecomeActive:(UIApplication *)application

-(void)applicationDidEnterBackground:(UIApplication *)application
{
//FunctionName();
[[NSNotificationCenter defaultCenter] postNotificationName:kTiPausedNotification object:self];

if (backgroundServices==nil)
Expand Down Expand Up @@ -1008,7 +1022,6 @@ -(void)applicationDidEnterBackground:(UIApplication *)application

-(void)applicationWillEnterForeground:(UIApplication *)application
{
//FunctionName(); Uncomment to see function name printed when it is called.
[self flushCompletionHandlerQueue];
[sessionId release];
sessionId = [[TiUtils createUUID] retain];
Expand Down Expand Up @@ -1063,9 +1076,7 @@ - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserAct
if (appBooted) {
[[NSNotificationCenter defaultCenter] postNotificationName:kTiContinueActivity object:self userInfo:dict];
} else {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:kTiContinueActivity object:self userInfo:dict];
});
[[self queuedBootEvents] setObject:dict forKey:kTiContinueActivity];
}

return YES;
Expand Down Expand Up @@ -1244,14 +1255,13 @@ - (void)application:(UIApplication *)application didReceiveLocalNotification:(UI
{
RELEASE_TO_NIL(localNotification);
localNotification = [[[self class] dictionaryWithLocalNotification:notification] retain];
[[NSNotificationCenter defaultCenter] postNotificationName:kTiLocalNotification object:localNotification userInfo:nil];

[self tryToPostNotification:localNotification withNotificationName:kTiLocalNotification completionHandler:nil];
}

-(BOOL)handleShortcutItem:(UIApplicationShortcutItem*) shortcutItem
waitForBootIfNotLaunched:(BOOL) bootWait {


if(shortcutItem.type == nil) {
-(BOOL)handleShortcutItem:(UIApplicationShortcutItem *) shortcutItem queueToBootIfNotLaunched:(BOOL)bootWait
{
if (shortcutItem.type == nil) {
NSLog(@"[ERROR] The shortcut type property is required");
return NO;
}
Expand All @@ -1268,7 +1278,7 @@ -(BOOL)handleShortcutItem:(UIApplicationShortcutItem*) shortcutItem
[dict setObject:shortcutItem.localizedSubtitle forKey:@"subtitle" ];
}

if(shortcutItem.userInfo !=nil) {
if (shortcutItem.userInfo !=nil) {
[dict setObject:shortcutItem.userInfo forKey:@"userInfo"];
}

Expand All @@ -1278,13 +1288,8 @@ -(BOOL)handleShortcutItem:(UIApplicationShortcutItem*) shortcutItem
if (appBooted) {
[[NSNotificationCenter defaultCenter] postNotificationName:kTiApplicationShortcut
object:self userInfo:dict];
} else {
if(bootWait) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:kTiApplicationShortcut
object:self userInfo:dict];
});
}
} else if (bootWait) {
[[self queuedBootEvents] setObject:dict forKey:kTiApplicationShortcut];
}

return YES;
Expand Down Expand Up @@ -1313,17 +1318,15 @@ - (void)handleRemoteNotificationWithIdentifier:(NSString *)identifier
event[@"category"] = category;
}

[[NSNotificationCenter defaultCenter] postNotificationName:kTiRemoteNotificationAction object:event userInfo:nil];
[event autorelease];
completionHandler();
[self tryToPostNotification:[event autorelease] withNotificationName:kTiRemoteNotificationAction completionHandler:completionHandler];
}

- (void)application:(UIApplication *)application
performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem
completionHandler:(void (^)(BOOL succeeded))completionHandler
{

BOOL handledShortCutItem = [self handleShortcutItem:shortcutItem waitForBootIfNotLaunched:NO];
BOOL handledShortCutItem = [self handleShortcutItem:shortcutItem queueToBootIfNotLaunched:NO];
completionHandler(handledShortCutItem);

}
Expand Down