2  * Copyright 2017 Google
 
   4  * Licensed under the Apache License, Version 2.0 (the "License");
 
   5  * you may not use this file except in compliance with the License.
 
   6  * You may obtain a copy of the License at
 
   8  *      http://www.apache.org/licenses/LICENSE-2.0
 
  10  * Unless required by applicable law or agreed to in writing, software
 
  11  * distributed under the License is distributed on an "AS IS" BASIS,
 
  12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
  13  * See the License for the specific language governing permissions and
 
  14  * limitations under the License.
 
  17 #import "FIRMessagingContextManagerService.h"
 
  19 #import <UIKit/UIKit.h>
 
  21 #import "FIRMessagingDefines.h"
 
  22 #import "FIRMessagingLogger.h"
 
  23 #import "FIRMessagingUtilities.h"
 
  25 #define kFIRMessagingContextManagerPrefixKey @"google.c.cm."
 
  26 #define kFIRMessagingContextManagerNotificationKeyPrefix @"gcm.notification."
 
  28 static NSString *const kLogTag = @"FIRMessagingAnalytics";
 
  30 static NSString *const kLocalTimeFormatString = @"yyyy-MM-dd HH:mm:ss";
 
  32 static NSString *const kContextManagerPrefixKey = kFIRMessagingContextManagerPrefixKey;
 
  34 // Local timed messages (format yyyy-mm-dd HH:mm:ss)
 
  35 NSString *const kFIRMessagingContextManagerLocalTimeStart = kFIRMessagingContextManagerPrefixKey @"lt_start";
 
  36 NSString *const kFIRMessagingContextManagerLocalTimeEnd = kFIRMessagingContextManagerPrefixKey @"lt_end";
 
  38 // Local Notification Params
 
  39 NSString *const kFIRMessagingContextManagerBodyKey = kFIRMessagingContextManagerNotificationKeyPrefix @"body";
 
  40 NSString *const kFIRMessagingContextManagerTitleKey = kFIRMessagingContextManagerNotificationKeyPrefix @"title";
 
  41 NSString *const kFIRMessagingContextManagerBadgeKey = kFIRMessagingContextManagerNotificationKeyPrefix @"badge";
 
  42 NSString *const kFIRMessagingContextManagerCategoryKey =
 
  43     kFIRMessagingContextManagerNotificationKeyPrefix @"click_action";
 
  44 NSString *const kFIRMessagingContextManagerSoundKey = kFIRMessagingContextManagerNotificationKeyPrefix @"sound";
 
  45 NSString *const kFIRMessagingContextManagerContentAvailableKey =
 
  46     kFIRMessagingContextManagerNotificationKeyPrefix @"content-available";
 
  48 typedef NS_ENUM(NSUInteger, FIRMessagingContextManagerMessageType) {
 
  49   FIRMessagingContextManagerMessageTypeNone,
 
  50   FIRMessagingContextManagerMessageTypeLocalTime,
 
  53 @implementation FIRMessagingContextManagerService
 
  55 + (BOOL)isContextManagerMessage:(NSDictionary *)message {
 
  56   // For now we only support local time in ContextManager.
 
  57   if (![message[kFIRMessagingContextManagerLocalTimeStart] length]) {
 
  58     FIRMessagingLoggerDebug(kFIRMessagingMessageCodeContextManagerService000,
 
  59                             @"Received message missing local start time, dropped.");
 
  66 + (BOOL)handleContextManagerMessage:(NSDictionary *)message {
 
  67   NSString *startTimeString = message[kFIRMessagingContextManagerLocalTimeStart];
 
  68   if (startTimeString.length) {
 
  69     FIRMessagingLoggerDebug(kFIRMessagingMessageCodeContextManagerService001,
 
  70                             @"%@ Received context manager message with local time %@", kLogTag,
 
  72     return [self handleContextManagerLocalTimeMessage:message];
 
  78 + (BOOL)handleContextManagerLocalTimeMessage:(NSDictionary *)message {
 
  79   NSString *startTimeString = message[kFIRMessagingContextManagerLocalTimeStart];
 
  80   NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
 
  81   [dateFormatter setDateFormat:kLocalTimeFormatString];
 
  82   NSDate *startDate = [dateFormatter dateFromString:startTimeString];
 
  84   _FIRMessagingDevAssert(startDate, @"Invalid local start date format %@", startTimeString);
 
  85   if (!startTimeString) {
 
  86     FIRMessagingLoggerError(kFIRMessagingMessageCodeContextManagerService002,
 
  87                             @"Invalid local start date format %@. Message dropped",
 
  92   NSDate *currentDate = [NSDate date];
 
  94   if ([currentDate compare:startDate] == NSOrderedAscending) {
 
  95     [self scheduleLocalNotificationForMessage:message
 
  98     // check end time has not passed
 
  99     NSString *endTimeString = message[kFIRMessagingContextManagerLocalTimeEnd];
 
 100     if (!endTimeString) {
 
 101       FIRMessagingLoggerInfo(
 
 102           kFIRMessagingMessageCodeContextManagerService003,
 
 103           @"No end date specified for message, start date elapsed. Message dropped.");
 
 107     NSDate *endDate = [dateFormatter dateFromString:endTimeString];
 
 109     _FIRMessagingDevAssert(endDate, @"Invalid local end date format %@", endTimeString);
 
 110     if (!endTimeString) {
 
 111       FIRMessagingLoggerError(kFIRMessagingMessageCodeContextManagerService004,
 
 112                               @"Invalid local end date format %@. Message dropped", endTimeString);
 
 116     if ([endDate compare:currentDate] == NSOrderedAscending) {
 
 117       // end date has already passed drop the message
 
 118       FIRMessagingLoggerInfo(kFIRMessagingMessageCodeContextManagerService005,
 
 119                              @"End date %@ has already passed. Message dropped.", endTimeString);
 
 123     // schedule message right now (buffer 10s)
 
 124     [self scheduleLocalNotificationForMessage:message
 
 125                                        atDate:[currentDate dateByAddingTimeInterval:10]];
 
 130 + (void)scheduleLocalNotificationForMessage:(NSDictionary *)message
 
 131                                      atDate:(NSDate *)date {
 
 132   NSDictionary *apsDictionary = message;
 
 133 #pragma clang diagnostic push
 
 134 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
 
 135   UILocalNotification *notification = [[UILocalNotification alloc] init];
 
 136 #pragma clang diagnostic pop
 
 138   // A great way to understand timezones and UILocalNotifications
 
 139   // http://stackoverflow.com/questions/18424569/understanding-uilocalnotification-timezone
 
 140   notification.timeZone = [NSTimeZone defaultTimeZone];
 
 141   notification.fireDate = date;
 
 143   // In the current solution all of the display stuff goes into a special "aps" dictionary
 
 144   // being sent in the message.
 
 145   if ([apsDictionary[kFIRMessagingContextManagerBodyKey] length]) {
 
 146     notification.alertBody = apsDictionary[kFIRMessagingContextManagerBodyKey];
 
 148   if ([apsDictionary[kFIRMessagingContextManagerTitleKey] length]) {
 
 149     // |alertTitle| is iOS 8.2+, so check if we can set it
 
 150       if ([notification respondsToSelector:@selector(setAlertTitle:)]) {
 
 151 #pragma clang diagnostic push
 
 152 #pragma clang diagnostic ignored "-Wunguarded-availability"
 
 153       notification.alertTitle = apsDictionary[kFIRMessagingContextManagerTitleKey];
 
 158   if (apsDictionary[kFIRMessagingContextManagerSoundKey]) {
 
 159     notification.soundName = apsDictionary[kFIRMessagingContextManagerSoundKey];
 
 161   if (apsDictionary[kFIRMessagingContextManagerBadgeKey]) {
 
 162     notification.applicationIconBadgeNumber =
 
 163         [apsDictionary[kFIRMessagingContextManagerBadgeKey] integerValue];
 
 165   if (apsDictionary[kFIRMessagingContextManagerCategoryKey]) {
 
 166     // |category| is iOS 8.0+, so check if we can set it
 
 167     if ([notification respondsToSelector:@selector(setCategory:)]) {
 
 168       notification.category = apsDictionary[kFIRMessagingContextManagerCategoryKey];
 
 172   NSDictionary *userInfo = [self parseDataFromMessage:message];
 
 173   if (userInfo.count) {
 
 174     notification.userInfo = userInfo;
 
 176 #pragma clang diagnostic push
 
 177 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
 
 178   UIApplication *application = FIRMessagingUIApplication();
 
 182   [application scheduleLocalNotification:notification];
 
 183 #pragma clang diagnostic pop
 
 186 + (NSDictionary *)parseDataFromMessage:(NSDictionary *)message {
 
 187   NSMutableDictionary *data = [NSMutableDictionary dictionary];
 
 188   for (NSObject<NSCopying> *key in message) {
 
 189     if ([key isKindOfClass:[NSString class]]) {
 
 190       NSString *keyString = (NSString *)key;
 
 191       if ([keyString isEqualToString:kFIRMessagingContextManagerContentAvailableKey]) {
 
 193       } else if ([keyString hasPrefix:kContextManagerPrefixKey]) {
 
 197     data[[key copy]] = message[key];