diff options
author | Alex Zolotarev <alex@maps.me> | 2015-08-06 21:54:16 +0300 |
---|---|---|
committer | Alex Zolotarev <alex@maps.me> | 2015-09-23 03:00:15 +0300 |
commit | 914515733940c16d98412adba4981484fdd567b5 (patch) | |
tree | 53f278fe837de9ac58db5a610652cb716d16984d /3party/Alohalytics/src/apple/alohalytics_objc.mm | |
parent | 0cd24f656e69356e35354558e39ca9bcda4d7ce3 (diff) |
[alohalytics][ios] Measure total time spent in the app, get install/update/build/first launch date.
Diffstat (limited to '3party/Alohalytics/src/apple/alohalytics_objc.mm')
-rw-r--r-- | 3party/Alohalytics/src/apple/alohalytics_objc.mm | 115 |
1 files changed, 96 insertions, 19 deletions
diff --git a/3party/Alohalytics/src/apple/alohalytics_objc.mm b/3party/Alohalytics/src/apple/alohalytics_objc.mm index 6adc88f34a..b9820d7b7b 100644 --- a/3party/Alohalytics/src/apple/alohalytics_objc.mm +++ b/3party/Alohalytics/src/apple/alohalytics_objc.mm @@ -123,11 +123,9 @@ static Location ExtractLocation(CLLocation * l) { return extracted; } -// Returns string representing uint64_t timestamp of given file or directory (modification date in millis from 1970). -static std::string PathTimestampMillis(NSString * path) { - NSDictionary * attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil]; - if (attributes) { - NSDate * date = [attributes objectForKey:NSFileModificationDate]; +// Internal helper. +static std::string NSDateToMillisFrom1970(NSDate * date) { + if (date) { return std::to_string(static_cast<uint64_t>([date timeIntervalSince1970] * 1000.)); } return std::string("0"); @@ -328,6 +326,14 @@ bool IsConnectionActive() { // Keys for NSUserDefaults. static NSString * const kInstalledVersionKey = @"AlohalyticsInstalledVersion"; +static NSString * const kFirstLaunchDateKey = @"AlohalyticsFirstLaunchDate"; +static NSString * const kTotalSecondsInTheApp = @"AlohalyticsTotalSecondsInTheApp"; + +// Used to calculate session length and total time spent in the app. +// setup should be called to activate counting. +static NSDate * gSessionStartTime = nil; +static BOOL gIsFirstSession = NO; + @implementation Alohalytics + (void)setDebugMode:(BOOL)enable { @@ -335,10 +341,6 @@ static NSString * const kInstalledVersionKey = @"AlohalyticsInstalledVersion"; } + (void)setup:(NSString *)serverUrl withLaunchOptions:(NSDictionary *)options { - [Alohalytics setup:serverUrl andFirstLaunch:YES withLaunchOptions:options]; -} - -+ (void)setup:(NSString *)serverUrl andFirstLaunch:(BOOL)isFirstLaunch withLaunchOptions:(NSDictionary *)options { const NSBundle * bundle = [NSBundle mainBundle]; NSString * bundleIdentifier = [bundle bundleIdentifier]; NSString * version = [[bundle infoDictionary] objectForKey:@"CFBundleShortVersionString"]; @@ -372,21 +374,26 @@ static NSString * const kInstalledVersionKey = @"AlohalyticsInstalledVersion"; NSUserDefaults * userDataBase = [NSUserDefaults standardUserDefaults]; NSString * installedVersion = [userDataBase objectForKey:kInstalledVersionKey]; BOOL shouldSendUpdatedSystemInformation = NO; - if (installationId.second && isFirstLaunch && installedVersion == nil) { - // Documents folder modification time can be interpreted as a "first app launch time" or an approx. "app install time". - // App bundle modification time can be interpreted as an "app update time". - instance.LogEvent("$install", {{"CFBundleShortVersionString", [version UTF8String]}, - {"documentsTimestampMillis", PathTimestampMillis([NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject])}, - {"bundleTimestampMillis", PathTimestampMillis([bundle executablePath])}}); + // Do not generate $install event for old users who did not have Alohalytics installed but already used the app. + const BOOL appWasNeverInstalledAndLaunchedBefore = (NSOrderedAscending == [[Alohalytics buildDate] compare:[Alohalytics installDate]]); + if (installationId.second && appWasNeverInstalledAndLaunchedBefore && installedVersion == nil) { + gIsFirstSession = YES; + instance.LogEvent("$install", [Alohalytics bundleInformation:version]); [userDataBase setValue:version forKey:kInstalledVersionKey]; + // Also store first launch date for future use. + if (nil == [userDataBase objectForKey:kFirstLaunchDateKey]) { + [userDataBase setObject:[NSDate date] forKey:kFirstLaunchDateKey]; + } [userDataBase synchronize]; shouldSendUpdatedSystemInformation = YES; } else { if (installedVersion == nil || ![installedVersion isEqualToString:version]) { - instance.LogEvent("$update", {{"CFBundleShortVersionString", [version UTF8String]}, - {"documentsTimestampMillis", PathTimestampMillis([NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject])}, - {"bundleTimestampMillis", PathTimestampMillis([bundle executablePath])}}); + instance.LogEvent("$update", [Alohalytics bundleInformation:version]); [userDataBase setValue:version forKey:kInstalledVersionKey]; + // Also store first launch date for future use, if Alohalytics was integrated only in this update. + if (nil == [userDataBase objectForKey:kFirstLaunchDateKey]) { + [userDataBase setObject:[NSDate date] forKey:kFirstLaunchDateKey]; + } [userDataBase synchronize]; shouldSendUpdatedSystemInformation = YES; } @@ -409,6 +416,14 @@ static NSString * const kInstalledVersionKey = @"AlohalyticsInstalledVersion"; #endif // TARGET_OS_IPHONE } ++(alohalytics::TStringMap)bundleInformation:(NSString *)version { + return {{"CFBundleShortVersionString", [version UTF8String]}, + {"installTimestampMillis", NSDateToMillisFrom1970([Alohalytics installDate])}, + {"updateTimestampMillis", NSDateToMillisFrom1970([Alohalytics updateDate])}, + {"buildTimestampMillis", NSDateToMillisFrom1970([Alohalytics buildDate])}, + }; +} + + (void)forceUpload { Stats::Instance().Upload(); } @@ -448,11 +463,24 @@ static NSString * const kInstalledVersionKey = @"AlohalyticsInstalledVersion"; #pragma mark App lifecycle notifications used to calculate basic metrics. #if (TARGET_OS_IPHONE > 0) + (void)applicationDidBecomeActive:(NSNotification *)notification { + gSessionStartTime = [NSDate date]; Stats::Instance().LogEvent("$applicationDidBecomeActive"); } + (void)applicationWillResignActive:(NSNotification *)notification { - Stats::Instance().LogEvent("$applicationWillResignActive"); + // Calculate session length. + NSInteger seconds = static_cast<NSInteger>(-gSessionStartTime.timeIntervalSinceNow); + // nil it to filter time when the app is in the background, but totalSecondsSpentInTheApp is called. + gSessionStartTime = nil; + Stats & instance = Stats::Instance(); + instance.LogEvent("$applicationWillResignActive", std::to_string(seconds)); + NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults]; + seconds += [defaults integerForKey:kTotalSecondsInTheApp]; + [defaults setInteger:seconds forKey:kTotalSecondsInTheApp]; + [defaults synchronize]; + if (instance.DebugMode()) { + ALOG("Total seconds spent in the app:", seconds); + } } + (void)applicationWillEnterForeground:(NSNotificationCenter *)notification { @@ -479,10 +507,59 @@ static NSString * const kInstalledVersionKey = @"AlohalyticsInstalledVersion"; ALOG("Skipped statistics uploading as connection is not active."); } } + if (gIsFirstSession) { + gIsFirstSession = NO; + } } + (void)applicationWillTerminate:(NSNotification *)notification { Stats::Instance().LogEvent("$applicationWillTerminate"); } #endif // TARGET_OS_IPHONE + +#pragma mark Utility methods + ++ (BOOL)isFirstSession { + return gIsFirstSession; +} + ++ (NSDate *)firstLaunchDate { + NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults]; + NSDate * date = [defaults objectForKey:kFirstLaunchDateKey]; + if (!date) { + // Non-standard situation: this method is called before calling setup. Return current date. + date = [NSDate date]; + [defaults setObject:date forKey:kFirstLaunchDateKey]; + [defaults synchronize]; + } + return date; +} + ++ (NSInteger)totalSecondsSpentInTheApp { + NSInteger seconds = [[NSUserDefaults standardUserDefaults] integerForKey:kTotalSecondsInTheApp]; + // Take into an account currently active session. + if (gSessionStartTime) { + seconds += static_cast<NSInteger>(-gSessionStartTime.timeIntervalSinceNow); + } + return seconds; +} + +// Internal helper, returns nil for invalid paths. ++ (NSDate *)fileCreationDate:(NSString *)fullPath { + NSDictionary * attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:fullPath error:nil]; + return attributes ? [attributes objectForKey:NSFileCreationDate] : nil; +} + ++ (NSDate *)installDate { + return [Alohalytics fileCreationDate:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]]; +} + ++ (NSDate *)updateDate { + return [Alohalytics fileCreationDate:[[NSBundle mainBundle] resourcePath]]; +} + ++ (NSDate *)buildDate { + return [Alohalytics fileCreationDate:[[NSBundle mainBundle] executablePath]]; +} + @end |