diff --git a/CHANGES.rst b/CHANGES.rst index 619141a7c..c3260eef6 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,8 +4,10 @@ Changes in 0.6.18 () Improvements: * Upgrade MatrixKit version (). * RoomVC: Add a re-request keys button on message unable to decrypt (#1879). + * Analytics: Move code from AppDelegate to a dedicated class: Analytics. + * Analytics: Track Matrix SDK stats (time to startup the app). * Added the i18n localisation strings to the accessibility labels (#1842), thanks to @einMarco (PR#1906). - + Bug fix: * RoomVC: Read receipts processing dramatically slows down UI (#1899). @@ -1127,4 +1129,4 @@ Changes in Vector iOS in 0.1.0 (2016-01-29) Changes in Vector iOS in 0.0.1 (2015-11-16) =============================================== - * Creation : The first implementation of Vector application based on Matrix iOS Kit v0.2.7. \ No newline at end of file + * Creation : The first implementation of Vector application based on Matrix iOS Kit v0.2.7. diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index f3a963be3..097705c6a 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -59,6 +59,11 @@ 32471CE11F13AC1500BDF50A /* RoomMembershipExpandedWithPaginationTitleBubbleCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 32471CDF1F13AC1500BDF50A /* RoomMembershipExpandedWithPaginationTitleBubbleCell.m */; }; 32471CE21F13AC1500BDF50A /* RoomMembershipExpandedWithPaginationTitleBubbleCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 32471CE01F13AC1500BDF50A /* RoomMembershipExpandedWithPaginationTitleBubbleCell.xib */; }; 325E1C151E8D03950018D91E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 325E1C131E8D03950018D91E /* LaunchScreen.storyboard */; }; + 3267EFB220E2A04100FF1CAA /* Analytics.m in Sources */ = {isa = PBXBuildFile; fileRef = 3267EFB120E2A04100FF1CAA /* Analytics.m */; }; + 3267EFB720E379FE00FF1CAA /* CHANGES.rst in Resources */ = {isa = PBXBuildFile; fileRef = 3267EFB320E379FD00FF1CAA /* CHANGES.rst */; }; + 3267EFB820E379FE00FF1CAA /* Podfile in Resources */ = {isa = PBXBuildFile; fileRef = 3267EFB420E379FD00FF1CAA /* Podfile */; }; + 3267EFB920E379FE00FF1CAA /* AUTHORS.rst in Resources */ = {isa = PBXBuildFile; fileRef = 3267EFB520E379FD00FF1CAA /* AUTHORS.rst */; }; + 3267EFBA20E379FE00FF1CAA /* README.rst in Resources */ = {isa = PBXBuildFile; fileRef = 3267EFB620E379FD00FF1CAA /* README.rst */; }; 327382B51F276AD200356143 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 327382A81F276AD200356143 /* InfoPlist.strings */; }; 327382B61F276AD200356143 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 327382AA1F276AD200356143 /* Localizable.strings */; }; 327382B71F276AD200356143 /* Vector.strings in Resources */ = {isa = PBXBuildFile; fileRef = 327382AC1F276AD200356143 /* Vector.strings */; }; @@ -725,6 +730,12 @@ 32471CDF1F13AC1500BDF50A /* RoomMembershipExpandedWithPaginationTitleBubbleCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RoomMembershipExpandedWithPaginationTitleBubbleCell.m; sourceTree = ""; }; 32471CE01F13AC1500BDF50A /* RoomMembershipExpandedWithPaginationTitleBubbleCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = RoomMembershipExpandedWithPaginationTitleBubbleCell.xib; sourceTree = ""; }; 325E1C141E8D03950018D91E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 3267EFB020E2A04100FF1CAA /* Analytics.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Analytics.h; path = Riot/Analytics/Analytics.h; sourceTree = SOURCE_ROOT; }; + 3267EFB120E2A04100FF1CAA /* Analytics.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = Analytics.m; path = Riot/Analytics/Analytics.m; sourceTree = SOURCE_ROOT; }; + 3267EFB320E379FD00FF1CAA /* CHANGES.rst */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CHANGES.rst; sourceTree = ""; }; + 3267EFB420E379FD00FF1CAA /* Podfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Podfile; sourceTree = ""; }; + 3267EFB520E379FD00FF1CAA /* AUTHORS.rst */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = AUTHORS.rst; sourceTree = ""; }; + 3267EFB620E379FD00FF1CAA /* README.rst */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.rst; sourceTree = ""; }; 327382A91F276AD200356143 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = InfoPlist.strings; sourceTree = ""; }; 327382AB1F276AD200356143 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = Localizable.strings; sourceTree = ""; }; 327382AD1F276AD200356143 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = Vector.strings; sourceTree = ""; }; @@ -1597,6 +1608,16 @@ path = "jitsi-meet"; sourceTree = ""; }; + 3267EFAF20E2A00F00FF1CAA /* Analytics */ = { + isa = PBXGroup; + children = ( + 3267EFB020E2A04100FF1CAA /* Analytics.h */, + 3267EFB120E2A04100FF1CAA /* Analytics.m */, + ); + name = Analytics; + path = "New Group"; + sourceTree = ""; + }; 327382A71F276AD200356143 /* de.lproj */ = { isa = PBXGroup; children = ( @@ -1796,6 +1817,7 @@ F083BB081E7009EC00A9B29C /* Riot */ = { isa = PBXGroup; children = ( + 3267EFAF20E2A00F00FF1CAA /* Analytics */, F083BB091E7009EC00A9B29C /* API */, F083BB0E1E7009EC00A9B29C /* Assets */, F083BBE41E7009EC00A9B29C /* Categories */, @@ -2682,6 +2704,10 @@ isa = PBXGroup; children = ( F083BB081E7009EC00A9B29C /* Riot */, + 3267EFB520E379FD00FF1CAA /* AUTHORS.rst */, + 3267EFB320E379FD00FF1CAA /* CHANGES.rst */, + 3267EFB420E379FD00FF1CAA /* Podfile */, + 3267EFB620E379FD00FF1CAA /* README.rst */, F083BB021E7005FD00A9B29C /* RiotTests */, 24CBEC4F1F0EAD310093EABB /* RiotShareExtension */, 92726A441F58737A004AD26F /* SiriIntents */, @@ -2988,6 +3014,7 @@ F083BD641E7009ED00A9B29C /* direct_icon@3x.png in Resources */, F083BE261E7009ED00A9B29C /* SegmentedViewController.xib in Resources */, F083BE161E7009ED00A9B29C /* MediaAlbumContentViewController.xib in Resources */, + 3267EFB720E379FE00FF1CAA /* CHANGES.rst in Resources */, F083BD441E7009ED00A9B29C /* call_speaker_on_icon@3x.png in Resources */, F0614A0F1EDDCCE700F5DC9A /* jump_to_unread@3x.png in Resources */, F083BE511E7009ED00A9B29C /* RoomOutgoingEncryptedTextMsgBubbleCell.xib in Resources */, @@ -3040,6 +3067,7 @@ 32BB89F0204D86DA002F3AEC /* InfoPlist.strings in Resources */, F083BD3E1E7009ED00A9B29C /* call_hangup_icon@3x.png in Resources */, 32AE61F21F0D2183007255F4 /* InfoPlist.strings in Resources */, + 3267EFB920E379FE00FF1CAA /* AUTHORS.rst in Resources */, F083BDB91E7009ED00A9B29C /* remove_icon_pink.png in Resources */, F083BE531E7009ED00A9B29C /* RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.xib in Resources */, F083BD7F1E7009ED00A9B29C /* error@3x.png in Resources */, @@ -3070,6 +3098,7 @@ F083BD5F1E7009ED00A9B29C /* details_icon.png in Resources */, F083BE241E7009ED00A9B29C /* RoomViewController.xib in Resources */, F083BE7B1E7009ED00A9B29C /* RoomInputToolbarView.xib in Resources */, + 3267EFB820E379FE00FF1CAA /* Podfile in Resources */, F0E05A3D1EA0F9EB004B83FB /* tab_people_selected@2x.png in Resources */, F0E05A451EA0F9EB004B83FB /* tab_rooms.png in Resources */, F083BE751E7009ED00A9B29C /* RoomOutgoingTextMsgWithoutSenderNameBubbleCell.xib in Resources */, @@ -3319,6 +3348,7 @@ F0E05A3C1EA0F9EB004B83FB /* tab_people_selected.png in Resources */, F083BD221E7009ED00A9B29C /* add_participant.png in Resources */, F083BDC61E7009ED00A9B29C /* search_bg@2x.png in Resources */, + 3267EFBA20E379FE00FF1CAA /* README.rst in Resources */, F083BD421E7009ED00A9B29C /* call_speaker_on_icon.png in Resources */, F04AF26D1F83A4C100D20F4D /* Localizable.strings in Resources */, F083BD9A1E7009ED00A9B29C /* logo@3x.png in Resources */, @@ -3543,6 +3573,7 @@ 321082B21F0E9F40002E0091 /* RoomMembershipCollapsedBubbleCell.m in Sources */, 32C2356F1F7B871800E38FC5 /* WidgetPickerViewController.m in Sources */, F083BE661E7009ED00A9B29C /* RoomIncomingTextMsgWithPaginationTitleBubbleCell.m in Sources */, + 3267EFB220E2A04100FF1CAA /* Analytics.m in Sources */, F083BE141E7009ED00A9B29C /* HomeViewController.m in Sources */, F083BDFB1E7009ED00A9B29C /* RoomSearchDataSource.m in Sources */, 32EF474920B6EE990031695C /* StickerPickerViewController.m in Sources */, diff --git a/Riot/Analytics/Analytics.h b/Riot/Analytics/Analytics.h new file mode 100644 index 000000000..d6b29b595 --- /dev/null +++ b/Riot/Analytics/Analytics.h @@ -0,0 +1,62 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#import + +/** + `Analytics` sends analytics to an analytics tool. + */ +@interface Analytics : NSObject + +/** + Returns the shared Analytics manager. + + @return the shared Analytics manager. + */ ++ (instancetype)sharedInstance; + +/** + Start doing analytics if the settings `enableCrashReport` is enabled. + */ +- (void)start; + +/** + Stop doing analytics. + */ +- (void)stop; + +/** + Track a screen display. + + @param screenName the name of the displayed screen. + */ +- (void)trackScreen:(NSString*)screenName; + +/** + Flush analytics data. + */ +- (void)dispatch; + +/** + Track how long the launch screen has been displayed to the end user. + + @param seconds the duration in seconds. + */ +- (void)trackLaunchScreenDisplayDuration: (NSTimeInterval)seconds; + +@end diff --git a/Riot/Analytics/Analytics.m b/Riot/Analytics/Analytics.m new file mode 100644 index 000000000..ad8068266 --- /dev/null +++ b/Riot/Analytics/Analytics.m @@ -0,0 +1,169 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "Analytics.h" + +#import "AppDelegate.h" + +// All metrics are store under a Piwik category called "Metrics". +// Then, there are 2 Piwik actions: "iOS.startup" and "iOS.stats" (these actions +// are namespaced by plaform to have a nice rendering on the Piwik website). +// Then, we use constants defined by the Matrix SDK as Piwik Names (ex:"mountData") +NSString *const kAnalyticsPiwikMetricsCategory = @"Metrics"; +NSString *const kAnalyticsPiwikMetricsActionPattern = @"iOS.%@"; + +@import PiwikTracker; + +@implementation Analytics + ++ (instancetype)sharedInstance +{ + static Analytics *sharedInstance = nil; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + sharedInstance = [[Analytics alloc] init]; + }); + + return sharedInstance; +} + +- (void)start +{ + NSDictionary *piwikConfig = [[NSUserDefaults standardUserDefaults] objectForKey:@"piwik"]; + [PiwikTracker configureSharedInstanceWithSiteID:piwikConfig[@"siteId"] + baseURL:[NSURL URLWithString:piwikConfig[@"url"]] + userAgent:@"iOSPiwikTracker"]; + + // Check whether the user has enabled the sending of crash reports. + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"enableCrashReport"]) + { + [PiwikTracker shared].isOptedOut = NO; + + [[PiwikTracker shared] setCustomVariableWithIndex:1 name:@"App Platform" value:@"iOS Platform"]; + [[PiwikTracker shared] setCustomVariableWithIndex:2 name:@"App Version" value:[AppDelegate theDelegate].appVersion]; + + // The language is either the one selected by the user within the app + // or, else, the one configured by the OS + NSString *language = [NSBundle mxk_language] ? [NSBundle mxk_language] : [[NSBundle mainBundle] preferredLocalizations][0]; + [[PiwikTracker shared] setCustomVariableWithIndex:4 name:@"Chosen Language" value:language]; + + MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; + if (account) + { + [[PiwikTracker shared] setCustomVariableWithIndex:7 name:@"Homeserver URL" value:account.mxCredentials.homeServer]; + [[PiwikTracker shared] setCustomVariableWithIndex:8 name:@"Identity Server URL" value:account.identityServerURL]; + } + + // TODO: We should also track device and os version + // But that needs to be decided for all platforms + + // Catch and log crashes + [MXLogger logCrashes:YES]; + [MXLogger setBuildVersion:[AppDelegate theDelegate].build]; + +#ifdef DEBUG + // Disable analytics in debug as it pollutes stats + [PiwikTracker shared].isOptedOut = YES; +#endif + } + else + { + NSLog(@"[AppDelegate] The user decided to not send analytics"); + [PiwikTracker shared].isOptedOut = YES; + [MXLogger logCrashes:NO]; + } +} + +- (void)stop +{ + [PiwikTracker shared].isOptedOut = YES; + [MXLogger logCrashes:NO]; +} + +- (void)trackScreen:(NSString *)screenName +{ + // Use the same pattern as Android + NSString *appName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"]; + NSString *appVersion = [AppDelegate theDelegate].appVersion; + + [[PiwikTracker shared] trackWithView:@[@"ios", appName, appVersion, screenName] + url:nil]; +} + +- (void)dispatch +{ + [[PiwikTracker shared] dispatch]; +} + +- (void)trackLaunchScreenDisplayDuration:(NSTimeInterval)seconds +{ + NSString *action = [NSString stringWithFormat:kAnalyticsPiwikMetricsActionPattern, kMXAnalyticsStartupCategory]; + + [[PiwikTracker shared] trackWithEventWithCategory:kAnalyticsPiwikMetricsCategory + action:action + name:kMXAnalyticsStartupLaunchScreen + number:@(seconds * 1000) + url:nil]; +} + +#pragma mark - MXAnalyticsDelegate + +- (void)trackStartupStorePreloadDuration: (NSTimeInterval)seconds +{ + NSString *action = [NSString stringWithFormat:kAnalyticsPiwikMetricsActionPattern, kMXAnalyticsStartupCategory]; + + [[PiwikTracker shared] trackWithEventWithCategory:kAnalyticsPiwikMetricsCategory + action:action + name:kMXAnalyticsStartupStorePreload + number:@(seconds * 1000) + url:nil]; +} + +- (void)trackStartupMountDataDuration: (NSTimeInterval)seconds +{ + NSString *action = [NSString stringWithFormat:kAnalyticsPiwikMetricsActionPattern, kMXAnalyticsStartupCategory]; + + [[PiwikTracker shared] trackWithEventWithCategory:kAnalyticsPiwikMetricsCategory + action:action + name:kMXAnalyticsStartupMountData + number:@(seconds * 1000) + url:nil]; +} + +- (void)trackStartupSyncDuration: (NSTimeInterval)seconds isInitial: (BOOL)isInitial +{ + NSString *action = [NSString stringWithFormat:kAnalyticsPiwikMetricsActionPattern, kMXAnalyticsStartupCategory]; + + [[PiwikTracker shared] trackWithEventWithCategory:kAnalyticsPiwikMetricsCategory + action:action + name:isInitial ? kMXAnalyticsStartupInititialSync : kMXAnalyticsStartupIncrementalSync + number:@(seconds * 1000) + url:nil]; +} + +- (void)trackRoomCount: (NSUInteger)roomCount +{ + NSString *action = [NSString stringWithFormat:kAnalyticsPiwikMetricsActionPattern, kMXAnalyticsStatsCategory]; + + [[PiwikTracker shared] trackWithEventWithCategory:kAnalyticsPiwikMetricsCategory + action:action + name:kMXAnalyticsStatsRooms + number:@(roomCount) + url:nil]; +} + +@end diff --git a/Riot/AppDelegate.h b/Riot/AppDelegate.h index ede68dacd..305650eab 100644 --- a/Riot/AppDelegate.h +++ b/Riot/AppDelegate.h @@ -22,6 +22,7 @@ #import "JitsiViewController.h" #import "RageShakeManager.h" +#import "Analytics.h" #import "RiotDesignValues.h" @@ -128,12 +129,6 @@ extern NSString *const kAppDelegateNetworkStatusDidChangeNotification; - (void)selectMatrixAccount:(void (^)(MXKAccount *selectedAccount))onSelection; -#pragma mark - Analytics - -- (void)startAnalytics; -- (void)stopAnalytics; -- (void)trackScreen:(NSString*)screenName; - #pragma mark - Push notifications - (void)registerUserNotificationSettings; diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 5ced25b79..1ad79e6fc 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -48,8 +48,6 @@ #import "WebViewViewController.h" -@import PiwikTracker; - // Calls #import "CallViewController.h" @@ -235,10 +233,6 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN MXSDKOptions *sdkOptions = [MXSDKOptions sharedInstance]; sdkOptions.applicationGroupIdentifier = @"group.im.vector"; - // Track SDK performance on Google analytics - // TODO: needs the same tool - //sdkOptions.analyticsDelegate = [[MXGoogleAnalytics alloc] init]; - // Redirect NSLogs to files only if we are not debugging if (!isatty(STDERR_FILENO)) { @@ -468,8 +462,9 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN [[NSUserDefaults standardUserDefaults] registerDefaults:defaults]; [[NSUserDefaults standardUserDefaults] synchronize]; - // Configure Google Analytics here if the option is enabled - [self startAnalytics]; + // Configure our analytics. It will indeed start if the option is enabled + [MXSDKOptions sharedInstance].analyticsDelegate = [Analytics sharedInstance]; + [[Analytics sharedInstance] start]; // Prepare Pushkit handling _incomingPushEventIds = [NSMutableDictionary dictionary]; @@ -563,7 +558,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN _isAppForeground = NO; // Analytics: Force to send the pending actions - [[PiwikTracker shared] dispatch]; + [[Analytics sharedInstance] dispatch]; } - (void)applicationWillEnterForeground:(UIApplication *)application @@ -1026,70 +1021,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN } } -#pragma mark - Analytics - -- (void)startAnalytics -{ - NSDictionary *piwikConfig = [[NSUserDefaults standardUserDefaults] objectForKey:@"piwik"]; - [PiwikTracker configureSharedInstanceWithSiteID:piwikConfig[@"siteId"] - baseURL:[NSURL URLWithString:piwikConfig[@"url"]] - userAgent:@"iOSPiwikTracker"]; - - // Check whether the user has enabled the sending of crash reports. - if ([[NSUserDefaults standardUserDefaults] boolForKey:@"enableCrashReport"]) - { - [PiwikTracker shared].isOptedOut = NO; - - [[PiwikTracker shared] setCustomVariableWithIndex:1 name:@"App Platform" value:@"iOS Platform"]; - [[PiwikTracker shared] setCustomVariableWithIndex:2 name:@"App Version" value:[self appVersion]]; - - // The language is either the one selected by the user within the app - // or, else, the one configured by the OS - NSString *language = [NSBundle mxk_language] ? [NSBundle mxk_language] : [[NSBundle mainBundle] preferredLocalizations][0]; - [[PiwikTracker shared] setCustomVariableWithIndex:4 name:@"Chosen Language" value:language]; - - MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; - if (account) - { - [[PiwikTracker shared] setCustomVariableWithIndex:7 name:@"Homeserver URL" value:account.mxCredentials.homeServer]; - [[PiwikTracker shared] setCustomVariableWithIndex:8 name:@"Identity Server URL" value:account.identityServerURL]; - } - - // TODO: We should also track device and os version - // But that needs to be decided for all platforms - - // Catch and log crashes - [MXLogger logCrashes:YES]; - [MXLogger setBuildVersion:[AppDelegate theDelegate].build]; - -#ifdef DEBUG - // Disable analytics in debug as it pollutes stats - [PiwikTracker shared].isOptedOut = YES; -#endif - } - else if ([[NSUserDefaults standardUserDefaults] objectForKey:@"enableCrashReport"]) - { - NSLog(@"[AppDelegate] The user decided to not "); - [PiwikTracker shared].isOptedOut = YES; - [MXLogger logCrashes:NO]; - } -} - -- (void)stopAnalytics -{ - [PiwikTracker shared].isOptedOut = YES; - [MXLogger logCrashes:NO]; -} - -- (void)trackScreen:(NSString *)screenName -{ - // Use the same pattern as Android - NSString *appName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"]; - NSString *appVersion = [self appVersion]; - - [[PiwikTracker shared] trackWithView:@[@"ios", appName, appVersion, screenName] - url:nil]; -} +#pragma mark - Crash handling // Check if there is a crash log to send to server - (void)checkExceptionToReport @@ -2914,8 +2846,11 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN if (launchAnimationContainerView) { - NSTimeInterval durationMs = [[NSDate date] timeIntervalSinceDate:launchAnimationStart] * 1000; - NSLog(@"[AppDelegate] LaunchAnimation was shown for %.3fms", durationMs); + NSTimeInterval duration = [[NSDate date] timeIntervalSinceDate:launchAnimationStart]; + NSLog(@"[AppDelegate] LaunchAnimation was shown for %.3fms", duration * 1000); + + // Track it on our analytics + [[Analytics sharedInstance] trackLaunchScreenDisplayDuration:duration]; // TODO: Send durationMs to Piwik // Such information should be the same on all platforms diff --git a/Riot/ViewController/AttachmentsViewController.m b/Riot/ViewController/AttachmentsViewController.m index 77a3afdb9..72f2dd5b0 100644 --- a/Riot/ViewController/AttachmentsViewController.m +++ b/Riot/ViewController/AttachmentsViewController.m @@ -73,7 +73,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"AttachmentsViewer"]; + [[Analytics sharedInstance] trackScreen:@"AttachmentsViewer"]; } - (void)destroy diff --git a/Riot/ViewController/AuthenticationViewController.m b/Riot/ViewController/AuthenticationViewController.m index 889a922df..47d6ffd64 100644 --- a/Riot/ViewController/AuthenticationViewController.m +++ b/Riot/ViewController/AuthenticationViewController.m @@ -193,7 +193,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"Authentication"]; + [[Analytics sharedInstance] trackScreen:@"Authentication"]; } - (void)viewDidAppear:(BOOL)animated diff --git a/Riot/ViewController/Communities/GroupDetailsViewController.m b/Riot/ViewController/Communities/GroupDetailsViewController.m index 95c2635ac..08bf19492 100644 --- a/Riot/ViewController/Communities/GroupDetailsViewController.m +++ b/Riot/ViewController/Communities/GroupDetailsViewController.m @@ -135,7 +135,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"GroupDetails"]; + [[Analytics sharedInstance] trackScreen:@"GroupDetails"]; } - (void)viewWillDisappear:(BOOL)animated diff --git a/Riot/ViewController/Communities/GroupHomeViewController.m b/Riot/ViewController/Communities/GroupHomeViewController.m index 983710c56..88ab6533b 100644 --- a/Riot/ViewController/Communities/GroupHomeViewController.m +++ b/Riot/ViewController/Communities/GroupHomeViewController.m @@ -182,7 +182,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"GroupDetailsHome"]; + [[Analytics sharedInstance] trackScreen:@"GroupDetailsHome"]; // Release the potential pushed view controller [self releasePushedViewController]; diff --git a/Riot/ViewController/Communities/GroupParticipantsViewController.m b/Riot/ViewController/Communities/GroupParticipantsViewController.m index 8b20b8369..ef7ba2360 100644 --- a/Riot/ViewController/Communities/GroupParticipantsViewController.m +++ b/Riot/ViewController/Communities/GroupParticipantsViewController.m @@ -217,7 +217,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"GroupDetailsPeople"]; + [[Analytics sharedInstance] trackScreen:@"GroupDetailsPeople"]; // Release the potential pushed view controller [self releasePushedViewController]; diff --git a/Riot/ViewController/Communities/GroupRoomsViewController.m b/Riot/ViewController/Communities/GroupRoomsViewController.m index 0a11ef080..9d2e0bfe1 100644 --- a/Riot/ViewController/Communities/GroupRoomsViewController.m +++ b/Riot/ViewController/Communities/GroupRoomsViewController.m @@ -181,7 +181,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"GroupDetailsRooms"]; + [[Analytics sharedInstance] trackScreen:@"GroupDetailsRooms"]; // Release the potential pushed view controller [self releasePushedViewController]; diff --git a/Riot/ViewController/Communities/GroupsViewController.m b/Riot/ViewController/Communities/GroupsViewController.m index cde207edb..a96ead33f 100644 --- a/Riot/ViewController/Communities/GroupsViewController.m +++ b/Riot/ViewController/Communities/GroupsViewController.m @@ -186,7 +186,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"Groups"]; + [[Analytics sharedInstance] trackScreen:@"Groups"]; // Deselect the current selected row, it will be restored on viewDidAppear (if any) NSIndexPath *indexPath = [self.groupsTableView indexPathForSelectedRow]; diff --git a/Riot/ViewController/ContactDetailsViewController.m b/Riot/ViewController/ContactDetailsViewController.m index 0bf37eeed..ed477c040 100644 --- a/Riot/ViewController/ContactDetailsViewController.m +++ b/Riot/ViewController/ContactDetailsViewController.m @@ -263,7 +263,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"ContactDetails"]; + [[Analytics sharedInstance] trackScreen:@"ContactDetails"]; // Hide the bottom border of the navigation bar to display the expander header [self hideNavigationBarBorder:YES]; diff --git a/Riot/ViewController/ContactsTableViewController.h b/Riot/ViewController/ContactsTableViewController.h index 521f8be81..a196bd788 100644 --- a/Riot/ViewController/ContactsTableViewController.h +++ b/Riot/ViewController/ContactsTableViewController.h @@ -73,7 +73,7 @@ @property (nonatomic) BOOL shouldScrollToTopOnRefresh; /** - The Google Analytics Instance screen name (Default is "ContactsTable"). + The analytics instance screen name (Default is "ContactsTable"). */ @property (nonatomic) NSString *screenName; diff --git a/Riot/ViewController/ContactsTableViewController.m b/Riot/ViewController/ContactsTableViewController.m index 842a208e1..c5f199fd5 100644 --- a/Riot/ViewController/ContactsTableViewController.m +++ b/Riot/ViewController/ContactsTableViewController.m @@ -144,7 +144,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:_screenName]; + [[Analytics sharedInstance] trackScreen:_screenName]; // Check whether the access to the local contacts has not been already asked. if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined) diff --git a/Riot/ViewController/CountryPickerViewController.m b/Riot/ViewController/CountryPickerViewController.m index 881295893..1c0a2fa40 100644 --- a/Riot/ViewController/CountryPickerViewController.m +++ b/Riot/ViewController/CountryPickerViewController.m @@ -99,7 +99,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"CountryPicker"]; + [[Analytics sharedInstance] trackScreen:@"CountryPicker"]; } - (void)destroy diff --git a/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.m b/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.m index a8fe4b6ca..dfbd4f8ca 100644 --- a/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.m +++ b/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.m @@ -96,7 +96,7 @@ static CGFloat const kTextFontSize = 15.0; [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"DeactivateAccount"]; + [[Analytics sharedInstance] trackScreen:@"DeactivateAccount"]; } - (void)viewDidLayoutSubviews diff --git a/Riot/ViewController/DirectoryServerPickerViewController.m b/Riot/ViewController/DirectoryServerPickerViewController.m index 22f33a7f2..a7450b622 100644 --- a/Riot/ViewController/DirectoryServerPickerViewController.m +++ b/Riot/ViewController/DirectoryServerPickerViewController.m @@ -143,7 +143,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"DirectoryServerPicker"]; + [[Analytics sharedInstance] trackScreen:@"DirectoryServerPicker"]; // Observe kAppDelegateDidTapStatusBarNotificationObserver. kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { diff --git a/Riot/ViewController/DirectoryViewController.m b/Riot/ViewController/DirectoryViewController.m index 5bd12e113..90bb1bfd5 100644 --- a/Riot/ViewController/DirectoryViewController.m +++ b/Riot/ViewController/DirectoryViewController.m @@ -105,7 +105,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"Directory"]; + [[Analytics sharedInstance] trackScreen:@"Directory"]; // Observe kAppDelegateDidTapStatusBarNotificationObserver. kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { diff --git a/Riot/ViewController/HomeFilesSearchViewController.m b/Riot/ViewController/HomeFilesSearchViewController.m index 83235f5bb..6cf51a715 100644 --- a/Riot/ViewController/HomeFilesSearchViewController.m +++ b/Riot/ViewController/HomeFilesSearchViewController.m @@ -107,7 +107,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"FilesGlobalSearch"]; + [[Analytics sharedInstance] trackScreen:@"FilesGlobalSearch"]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshSearchResult:) name:kMXSessionDidLeaveRoomNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshSearchResult:) name:kMXSessionNewRoomNotification object:nil]; diff --git a/Riot/ViewController/HomeMessagesSearchViewController.m b/Riot/ViewController/HomeMessagesSearchViewController.m index ac1544db3..d239acbc6 100644 --- a/Riot/ViewController/HomeMessagesSearchViewController.m +++ b/Riot/ViewController/HomeMessagesSearchViewController.m @@ -114,7 +114,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"MessagesGlobalSearch"]; + [[Analytics sharedInstance] trackScreen:@"MessagesGlobalSearch"]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshSearchResult:) name:kMXSessionDidLeaveRoomNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshSearchResult:) name:kMXSessionNewRoomNotification object:nil]; diff --git a/Riot/ViewController/LanguagePickerViewController.m b/Riot/ViewController/LanguagePickerViewController.m index 0a64838e7..ef3ac56d8 100644 --- a/Riot/ViewController/LanguagePickerViewController.m +++ b/Riot/ViewController/LanguagePickerViewController.m @@ -109,7 +109,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"CountryPicker"]; + [[Analytics sharedInstance] trackScreen:@"CountryPicker"]; } - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath; diff --git a/Riot/ViewController/MasterTabBarController.m b/Riot/ViewController/MasterTabBarController.m index 3b2bd62fe..fb3458f33 100644 --- a/Riot/ViewController/MasterTabBarController.m +++ b/Riot/ViewController/MasterTabBarController.m @@ -148,7 +148,7 @@ // (Check whether 'enableCrashReport' flag has been set once) if (![[NSUserDefaults standardUserDefaults] objectForKey:@"enableCrashReport"]) { - [self promptUserBeforeUsingGoogleAnalytics]; + [self promptUserBeforeUsingAnalytics]; } [self refreshTabBarBadges]; @@ -771,7 +771,7 @@ #pragma mark - -- (void)promptUserBeforeUsingGoogleAnalytics +- (void)promptUserBeforeUsingAnalytics { NSLog(@"[MasterTabBarController]: Invite the user to send crash reports"); @@ -810,12 +810,12 @@ typeof(self) self = weakSelf; self->currentAlert = nil; } - - [[AppDelegate theDelegate] startAnalytics]; + + [[Analytics sharedInstance] start]; }]]; - [currentAlert mxk_setAccessibilityIdentifier: @"HomeVCUseGoogleAnalyticsAlert"]; + [currentAlert mxk_setAccessibilityIdentifier: @"HomeVCUseAnalyticsAlert"]; [self presentViewController:currentAlert animated:YES completion:nil]; } diff --git a/Riot/ViewController/MediaAlbumContentViewController.m b/Riot/ViewController/MediaAlbumContentViewController.m index 025a2e54f..4ed3f011f 100644 --- a/Riot/ViewController/MediaAlbumContentViewController.m +++ b/Riot/ViewController/MediaAlbumContentViewController.m @@ -157,7 +157,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"MediaAlbumContent"]; + [[Analytics sharedInstance] trackScreen:@"MediaAlbumContent"]; self.navigationItem.title = _assetsCollection.localizedTitle; diff --git a/Riot/ViewController/MediaPickerViewController.m b/Riot/ViewController/MediaPickerViewController.m index b81f5b34f..f0f30b847 100644 --- a/Riot/ViewController/MediaPickerViewController.m +++ b/Riot/ViewController/MediaPickerViewController.m @@ -236,7 +236,7 @@ static void *RecordingContext = &RecordingContext; [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"MediaPicker"]; + [[Analytics sharedInstance] trackScreen:@"MediaPicker"]; if (!userAlbumsQueue) { diff --git a/Riot/ViewController/RecentsViewController.h b/Riot/ViewController/RecentsViewController.h index 09117f31d..6fefeed34 100644 --- a/Riot/ViewController/RecentsViewController.h +++ b/Riot/ViewController/RecentsViewController.h @@ -79,7 +79,7 @@ @property (nonatomic) CGFloat stickyHeaderHeight; /** - The Google Analytics Instance screen name (Default is "RecentsScreen"). + The analytics instance screen name (Default is "RecentsScreen"). */ @property (nonatomic) NSString *screenName; diff --git a/Riot/ViewController/RecentsViewController.m b/Riot/ViewController/RecentsViewController.m index 24b8d97db..8bb715eaa 100644 --- a/Riot/ViewController/RecentsViewController.m +++ b/Riot/ViewController/RecentsViewController.m @@ -231,7 +231,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:_screenName]; + [[Analytics sharedInstance] trackScreen:_screenName]; // Deselect the current selected row, it will be restored on viewDidAppear (if any) NSIndexPath *indexPath = [self.recentsTableView indexPathForSelectedRow]; diff --git a/Riot/ViewController/RoomFilesSearchViewController.m b/Riot/ViewController/RoomFilesSearchViewController.m index 6973cde1c..a710dc8cb 100644 --- a/Riot/ViewController/RoomFilesSearchViewController.m +++ b/Riot/ViewController/RoomFilesSearchViewController.m @@ -108,7 +108,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"RoomFilesSearch"]; + [[Analytics sharedInstance] trackScreen:@"RoomFilesSearch"]; // Observe kAppDelegateDidTapStatusBarNotificationObserver. kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { diff --git a/Riot/ViewController/RoomMemberDetailsViewController.m b/Riot/ViewController/RoomMemberDetailsViewController.m index d7b9ecf94..f0c78e61f 100644 --- a/Riot/ViewController/RoomMemberDetailsViewController.m +++ b/Riot/ViewController/RoomMemberDetailsViewController.m @@ -251,7 +251,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"RoomMemberDetails"]; + [[Analytics sharedInstance] trackScreen:@"RoomMemberDetails"]; // Hide the bottom border of the navigation bar to display the expander header [self hideNavigationBarBorder:YES]; diff --git a/Riot/ViewController/RoomMessagesSearchViewController.m b/Riot/ViewController/RoomMessagesSearchViewController.m index 7ac1b8031..f152865d4 100644 --- a/Riot/ViewController/RoomMessagesSearchViewController.m +++ b/Riot/ViewController/RoomMessagesSearchViewController.m @@ -109,7 +109,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"RoomMessagesSearch"]; + [[Analytics sharedInstance] trackScreen:@"RoomMessagesSearch"]; // Observe kAppDelegateDidTapStatusBarNotificationObserver. kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { diff --git a/Riot/ViewController/RoomParticipantsViewController.m b/Riot/ViewController/RoomParticipantsViewController.m index 4b83342aa..c6d76ffeb 100644 --- a/Riot/ViewController/RoomParticipantsViewController.m +++ b/Riot/ViewController/RoomParticipantsViewController.m @@ -245,7 +245,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"RoomParticipants"]; + [[Analytics sharedInstance] trackScreen:@"RoomParticipants"]; if (memberDetailsViewController) { diff --git a/Riot/ViewController/RoomSearchViewController.m b/Riot/ViewController/RoomSearchViewController.m index ba5367be1..19d50a700 100644 --- a/Riot/ViewController/RoomSearchViewController.m +++ b/Riot/ViewController/RoomSearchViewController.m @@ -101,7 +101,7 @@ } // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"RoomsSearch"]; + [[Analytics sharedInstance] trackScreen:@"RoomsSearch"]; // Enable the search field by default at the screen opening if (self.searchBarHidden) diff --git a/Riot/ViewController/RoomSettingsViewController.m b/Riot/ViewController/RoomSettingsViewController.m index 279b6edd3..bfb2c105f 100644 --- a/Riot/ViewController/RoomSettingsViewController.m +++ b/Riot/ViewController/RoomSettingsViewController.m @@ -289,7 +289,7 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"RoomSettings"]; + [[Analytics sharedInstance] trackScreen:@"RoomSettings"]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didUpdateRules:) name:kMXNotificationCenterDidUpdateRules object:nil]; diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index 325e63ff1..eba5abf36 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -450,7 +450,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"ChatRoom"]; + [[Analytics sharedInstance] trackScreen:@"ChatRoom"]; // Refresh the room title view [self refreshRoomTitle]; diff --git a/Riot/ViewController/SettingsViewController.m b/Riot/ViewController/SettingsViewController.m index 5c7e2d5d5..7385bb0d2 100644 --- a/Riot/ViewController/SettingsViewController.m +++ b/Riot/ViewController/SettingsViewController.m @@ -423,7 +423,7 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"Settings"]; + [[Analytics sharedInstance] trackScreen:@"Settings"]; // Release the potential pushed view controller [self releasePushedViewController]; @@ -2850,22 +2850,22 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); BOOL enable = [[NSUserDefaults standardUserDefaults] boolForKey:@"enableCrashReport"]; if (enable) { - NSLog(@"[SettingsViewController] disable automatic crash report sending"); + NSLog(@"[SettingsViewController] disable automatic crash report and analytics sending"); [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"enableCrashReport"]; [[NSUserDefaults standardUserDefaults] synchronize]; - [[AppDelegate theDelegate] stopAnalytics]; + [[Analytics sharedInstance] stop]; // Remove potential crash file. [MXLogger deleteCrashLog]; } else { - NSLog(@"[SettingsViewController] enable automatic crash report sending"); + NSLog(@"[SettingsViewController] enable automatic crash report and analytics sending"); [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"enableCrashReport"]; [[NSUserDefaults standardUserDefaults] synchronize]; - [[AppDelegate theDelegate] startAnalytics]; + [[Analytics sharedInstance] start]; } } diff --git a/Riot/ViewController/UnifiedSearchViewController.m b/Riot/ViewController/UnifiedSearchViewController.m index 79149cb4a..3d6145a67 100644 --- a/Riot/ViewController/UnifiedSearchViewController.m +++ b/Riot/ViewController/UnifiedSearchViewController.m @@ -137,7 +137,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"UnifiedSearch"]; + [[Analytics sharedInstance] trackScreen:@"UnifiedSearch"]; // Let's child display the loading not the home view controller if (self.activityIndicator) diff --git a/Riot/ViewController/UsersDevicesViewController.m b/Riot/ViewController/UsersDevicesViewController.m index 4241c60c4..b2847f6ac 100644 --- a/Riot/ViewController/UsersDevicesViewController.m +++ b/Riot/ViewController/UsersDevicesViewController.m @@ -116,7 +116,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"UnknowDevices"]; + [[Analytics sharedInstance] trackScreen:@"UnknowDevices"]; [self.tableView reloadData]; } diff --git a/Riot/third_party_licenses.html b/Riot/third_party_licenses.html index 13163b0d5..b1f0e155b 100644 --- a/Riot/third_party_licenses.html +++ b/Riot/third_party_licenses.html @@ -114,19 +114,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

-
  • - GoogleAnalytics (https://www.google.com/analytics) -

    Measure your app performance. -

    Copyright (c) 2011-2016 Google Inc. All rights reserved. -

    Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with the License. You may obtain a copy of the License at: -

    https://www.apache.org/licenses/LICENSE-2.0 -

    Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -

    Google Analytics Terms of Service: https://www.google.com/analytics/terms -

    -
  • GZIP (https://github.com/nicklockwood/GZIP)