diff --git a/Podfile b/Podfile index 7bd510b47..576314529 100644 --- a/Podfile +++ b/Podfile @@ -24,5 +24,7 @@ pod 'MatrixKit', :git => 'https://github.com/matrix-org/matrix-ios-kit.git', :br pod 'GBDeviceInfo', '~> 3.4.0' +pod 'Google/Analytics' + end diff --git a/Podfile.lock b/Podfile.lock index 823fb2d89..9cfa2a194 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -20,9 +20,26 @@ PODS: - AFNetworking/UIKit (2.6.3): - AFNetworking/NSURLConnection - AFNetworking/NSURLSession + - FirebaseAnalytics (3.2.0): + - FirebaseInstanceID (~> 1.0) + - GoogleInterchangeUtilities (~> 1.2) + - GoogleSymbolUtilities (~> 1.1) + - GoogleUtilities (~> 1.3) + - FirebaseInstanceID (1.0.6) - GBDeviceInfo (3.4.1): - GBJailbreakDetection (~> 1.0) - GBJailbreakDetection (1.3.0) + - Google/Analytics (3.0.3): + - Google/Core + - GoogleAnalytics (~> 3.12) + - Google/Core (3.0.3): + - FirebaseAnalytics (~> 3.2) + - GoogleAnalytics (3.14.0) + - GoogleInterchangeUtilities (1.2.1): + - GoogleSymbolUtilities (~> 1.0) + - GoogleSymbolUtilities (1.1.1) + - GoogleUtilities (1.3.1): + - GoogleSymbolUtilities (~> 1.0) - HPGrowingTextView (1.1) - libPhoneNumber-iOS (0.8.13) - MatrixKit (0.3.9): @@ -34,6 +51,7 @@ PODS: DEPENDENCIES: - GBDeviceInfo (~> 3.4.0) + - Google/Analytics - MatrixKit (from `https://github.com/matrix-org/matrix-ios-kit.git`, branch `develop`) - MatrixSDK (from `https://github.com/matrix-org/matrix-ios-sdk.git`, branch `develop`) @@ -55,8 +73,15 @@ CHECKOUT OPTIONS: SPEC CHECKSUMS: AFNetworking: cb8d14a848e831097108418f5d49217339d4eb60 + FirebaseAnalytics: 6f75b04e6bfb8e0b6e9ef298e04c24eea3b13863 + FirebaseInstanceID: d014d574053a2fe84478f12f7bae96979e7051bb GBDeviceInfo: bab0f43d351b6f641a626cd7d21492abef1afe46 GBJailbreakDetection: a216773574b62dddb6c876ffdb52c54ac05e27e0 + Google: 813c467362eabc11385f5a5cc9ad0cb651a58f4e + GoogleAnalytics: 9be1afdb8deeac4bb5f13ca7f7d3b9db2a1f43dc + GoogleInterchangeUtilities: def8415a862effc67d549d5b5b0b9c7a2f97d4de + GoogleSymbolUtilities: 33117db1b5f290c6fbf259585e4885b4c84b98d7 + GoogleUtilities: 56c5ac05b7aa5dc417a1bb85221a9516e04d7032 HPGrowingTextView: 88a716d97fb853bcb08a4a08e4727da17efc9b19 libPhoneNumber-iOS: 3a37004baf779b4262ab29a31a87d2ae668159d4 MatrixKit: 9c8ceb305c33019bfe2c6f06512f60d80381ceb7 diff --git a/Vector.xcodeproj/project.pbxproj b/Vector.xcodeproj/project.pbxproj index a8c8b7900..8a947b847 100644 --- a/Vector.xcodeproj/project.pbxproj +++ b/Vector.xcodeproj/project.pbxproj @@ -89,6 +89,7 @@ F003AA7C1C68A1F6008B430C /* ExpandedRoomTitleView.xib in Resources */ = {isa = PBXBuildFile; fileRef = F003AA7B1C68A1F6008B430C /* ExpandedRoomTitleView.xib */; }; F003AA801C690628008B430C /* RoomAvatarTitleView.m in Sources */ = {isa = PBXBuildFile; fileRef = F003AA7E1C690628008B430C /* RoomAvatarTitleView.m */; }; F003AA811C690628008B430C /* RoomAvatarTitleView.xib in Resources */ = {isa = PBXBuildFile; fileRef = F003AA7F1C690628008B430C /* RoomAvatarTitleView.xib */; }; + F008D6FC1D0180FB0049444D /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = F008D6FB1D0180FB0049444D /* GoogleService-Info.plist */; }; F00C47861BFF77C800DBABC9 /* RecentTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = F00C47841BFF77C800DBABC9 /* RecentTableViewCell.m */; }; F00C47871BFF77C800DBABC9 /* RecentTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F00C47851BFF77C800DBABC9 /* RecentTableViewCell.xib */; }; F00C478B1BFF854400DBABC9 /* RecentsDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = F00C478A1BFF854400DBABC9 /* RecentsDataSource.m */; }; @@ -372,6 +373,7 @@ F003AA7D1C690628008B430C /* RoomAvatarTitleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RoomAvatarTitleView.h; path = RoomTitle/RoomAvatarTitleView.h; sourceTree = ""; }; F003AA7E1C690628008B430C /* RoomAvatarTitleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RoomAvatarTitleView.m; path = RoomTitle/RoomAvatarTitleView.m; sourceTree = ""; }; F003AA7F1C690628008B430C /* RoomAvatarTitleView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = RoomAvatarTitleView.xib; path = RoomTitle/RoomAvatarTitleView.xib; sourceTree = ""; }; + F008D6FB1D0180FB0049444D /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; F00C47831BFF77C800DBABC9 /* RecentTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RecentTableViewCell.h; sourceTree = ""; }; F00C47841BFF77C800DBABC9 /* RecentTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RecentTableViewCell.m; sourceTree = ""; }; F00C47851BFF77C800DBABC9 /* RecentTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = RecentTableViewCell.xib; sourceTree = ""; }; @@ -884,6 +886,7 @@ F094A9A51B78D8F000B1FBBF /* Supporting Files */ = { isa = PBXGroup; children = ( + F008D6FB1D0180FB0049444D /* GoogleService-Info.plist */, F067F2BB1CF6F0EA00F35EE8 /* third_party_licenses.html */, F094AA031B78E3D400B1FBBF /* empty.mm */, F094AA041B78E3D400B1FBBF /* Vector-Defaults.plist */, @@ -1228,6 +1231,7 @@ F03FBCD81CBC49B6000A5770 /* disclosure_icon@2x.png in Resources */, F03FBCD01CBBF521000A5770 /* shrink_icon@3x.png in Resources */, F02528D81C11B6FC00E1FE1B /* camera_picture.png in Resources */, + F008D6FC1D0180FB0049444D /* GoogleService-Info.plist in Resources */, F02528E01C11B6FC00E1FE1B /* create_room.png in Resources */, F02528DF1C11B6FC00E1FE1B /* camera_video.png in Resources */, F02BB04C1CBE2EE70022A025 /* PreviewRoomTitleView.xib in Resources */, diff --git a/Vector/AppDelegate.h b/Vector/AppDelegate.h index f08818876..c4bcc7326 100644 --- a/Vector/AppDelegate.h +++ b/Vector/AppDelegate.h @@ -81,6 +81,10 @@ extern NSString *const kAppDelegateDidTapStatusBarNotification; - (void)selectMatrixAccount:(void (^)(MXKAccount *selectedAccount))onSelection; +#pragma mark - Crash reports handling + +- (void)configureGoogleAnalytics; + #pragma mark - APNS methods - (void)registerUserNotificationSettings; diff --git a/Vector/AppDelegate.m b/Vector/AppDelegate.m index a2ae51c8a..b6a21d637 100644 --- a/Vector/AppDelegate.m +++ b/Vector/AppDelegate.m @@ -16,6 +16,8 @@ #import "AppDelegate.h" +#import + #import "RecentsDataSource.h" #import "RoomDataSource.h" @@ -299,6 +301,9 @@ NSString *const kAppDelegateDidTapStatusBarNotification = @"kAppDelegateDidTapSt [[NSUserDefaults standardUserDefaults] registerDefaults:defaults]; [[NSUserDefaults standardUserDefaults] synchronize]; + // Configure Google Analytics here if the option is enabled + [self configureGoogleAnalytics]; + // Add matrix observers, and initialize matrix sessions if the app is not launched in background. [self initMatrixSessions]; @@ -379,6 +384,9 @@ NSString *const kAppDelegateDidTapStatusBarNotification = @"kAppDelegateDidTapSt [self refreshApplicationIconBadgeNumber]; _isAppForeground = NO; + + // GA: End a session while the app is in background + [[[GAI sharedInstance] defaultTracker] set:kGAISessionControl value:@"end"]; } - (void)applicationWillEnterForeground:(UIApplication *)application @@ -395,6 +403,9 @@ NSString *const kAppDelegateDidTapStatusBarNotification = @"kAppDelegateDidTapSt [[MXKAccountManager sharedManager] prepareSessionForActiveAccounts]; _isAppForeground = YES; + + // GA: Start a new session. The next hit from this tracker will be the first in a new session. + [[[GAI sharedInstance] defaultTracker] set:kGAISessionControl value:@"start"]; } - (void)applicationDidBecomeActive:(UIApplication *)application @@ -403,20 +414,6 @@ NSString *const kAppDelegateDidTapStatusBarNotification = @"kAppDelegateDidTapSt remoteNotificationRoomId = nil; - // Check if the app crashed last time - if ([MXLogger crashLog]) - { -#ifndef DEBUG - // In distributed version, clear the cache to not annoy user more. - // In debug mode, the developer will be pleased to investigate what is wrong in the cache. - NSLog(@"[AppDelegate] Clear the cache due to app crash"); - [self reloadMatrixSessions:YES]; -#endif - - // Ask the user to send a bug report - [[RageShakeManager sharedManager] promptCrashReportInViewController:self.window.rootViewController]; - } - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. // Start monitoring reachability @@ -466,6 +463,14 @@ NSString *const kAppDelegateDidTapStatusBarNotification = @"kAppDelegateDidTapSt { NSLog(@"[AppDelegate] applicationWillTerminate"); // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. + + // End a session. The next hit from this tracker will be the last in the current session. + [[[GAI sharedInstance] defaultTracker] set:kGAISessionControl value:@"end"]; + + // Flush pending GA messages + [[GAI sharedInstance] dispatch]; + + [MXLogger logCrashes:NO]; } - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler @@ -625,6 +630,68 @@ NSString *const kAppDelegateDidTapStatusBarNotification = @"kAppDelegateDidTapSt } } +#pragma mark - Crash report handling + +- (void)configureGoogleAnalytics +{ + // Check whether the user has enabled the sending of crash reports. + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"enableCrashReport"]) + { + // Catch and log crashes + [MXLogger logCrashes:YES]; + [MXLogger setBuildVersion:[AppDelegate theDelegate].build]; + + // Configure tracker from GoogleService-Info.plist. + NSError *configureError; + [[GGLContext sharedInstance] configureWithError:&configureError]; + NSAssert(!configureError, @"Error configuring Google services: %@", configureError); + + // Optional: configure GAI options. + GAI *gai = [GAI sharedInstance]; + // Disable GA UncaughtException: their crash reports are quite limited (100 first chars of the stack trace) + // Let's MXLogger manage them + gai.trackUncaughtExceptions = NO; + + // Set Google Analytics dispatch interval to e.g. 20 seconds. + gai.dispatchInterval = 20; + + // Check if there is crash log to send to GA + [self checkExceptionToReport]; + } + else if ([[NSUserDefaults standardUserDefaults] objectForKey:@"enableCrashReport"]) + { + NSLog(@"[AppDelegate] The user decides to do not use Google Analytics"); + } +} + +// Check if there is a crash log to send to server +- (void)checkExceptionToReport +{ + // Check if the app crashed last time + NSString *filePath = [MXLogger crashLog]; + if (filePath) + { + NSString *description = [[NSString alloc] initWithContentsOfFile:filePath + usedEncoding:nil + error:nil]; + + NSLog(@"[AppDelegate] Send crash log to Google Analytics:\n%@", description); + + // Send it via Google Analytics + // The doc says the exception description must not exceeed 100 chars but it seems + // to accept much more. + // https://developers.google.com/analytics/devguides/collection/ios/v3/exceptions#overview + id tracker = [[GAI sharedInstance] defaultTracker]; + [tracker send:[[GAIDictionaryBuilder + createExceptionWithDescription:description + withFatal:[NSNumber numberWithBool:YES]] build]]; + [[GAI sharedInstance] dispatch]; + + // The crash file can be removed now + [MXLogger deleteCrashLog]; + } +} + #pragma mark - APNS methods - (void)registerUserNotificationSettings diff --git a/Vector/Assets/en.lproj/Vector.strings b/Vector/Assets/en.lproj/Vector.strings index e28f2ec9e..ded9086a7 100644 --- a/Vector/Assets/en.lproj/Vector.strings +++ b/Vector/Assets/en.lproj/Vector.strings @@ -278,3 +278,6 @@ "no_voip_title" = "Incoming call"; "no_voip" = "%@ is calling you but the iOS Vector app does not support calls yet.\nYou can ignore this notification and answer the call from another device or you can reject it."; +// Crash report +"google_analytics_use_prompt" = "Do you accept to help us to improve the application behaviour by sending crash reports?"; + diff --git a/Vector/GoogleService-Info.plist b/Vector/GoogleService-Info.plist new file mode 100644 index 000000000..07b7e57a2 --- /dev/null +++ b/Vector/GoogleService-Info.plist @@ -0,0 +1,26 @@ + + + + + TRACKING_ID + UA-65410662-3 + PLIST_VERSION + 1 + BUNDLE_ID + im.vector.app + PROJECT_ID + vector-ios + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:802695454745:ios:3120c24f6ef22f2b + + \ No newline at end of file diff --git a/Vector/ViewController/HomeViewController.m b/Vector/ViewController/HomeViewController.m index 7c6c3c66e..6c34cab3f 100644 --- a/Vector/ViewController/HomeViewController.m +++ b/Vector/ViewController/HomeViewController.m @@ -232,6 +232,13 @@ } else { + // Check whether the user has been already prompted to send crash reports. + // (Check whether 'enableCrashReport' flag has been set once) + if (![[NSUserDefaults standardUserDefaults] objectForKey:@"enableCrashReport"]) + { + [self promptUserBeforeUsingGoogleAnalytics]; + } + // Release the current selected room (if any) except if the Room ViewController is still visible (see splitViewController.isCollapsed condition) if (!self.splitViewController || self.splitViewController.isCollapsed) { @@ -496,6 +503,52 @@ #pragma mark - Internal methods +- (void)promptUserBeforeUsingGoogleAnalytics +{ + NSLog(@"[HomeViewController]: Invite the user to send crash reports"); + + __weak typeof(self) weakSelf = self; + + [currentAlert dismiss:NO]; + + currentAlert = [[MXKAlert alloc] initWithTitle:NSLocalizedStringFromTable(@"google_analytics_use_prompt", @"Vector", nil) + message:nil + style:MXKAlertStyleAlert]; + + currentAlert.cancelButtonIndex = [currentAlert addActionWithTitle:[NSBundle mxk_localizedStringForKey:@"no"] + style:MXKAlertActionStyleDefault + handler:^(MXKAlert *alert) { + + [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"enableCrashReport"]; + [[NSUserDefaults standardUserDefaults] synchronize]; + + if (weakSelf) + { + __strong __typeof(weakSelf)strongSelf = weakSelf; + strongSelf->currentAlert = nil; + } + + }]; + [currentAlert addActionWithTitle:[NSBundle mxk_localizedStringForKey:@"yes"] + style:MXKAlertActionStyleDefault + handler:^(MXKAlert *alert) { + + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"enableCrashReport"]; + [[NSUserDefaults standardUserDefaults] synchronize]; + + if (weakSelf) + { + __strong __typeof(weakSelf)strongSelf = weakSelf; + strongSelf->currentAlert = nil; + } + + [[AppDelegate theDelegate] configureGoogleAnalytics]; + + }]; + + [currentAlert showInViewController:self]; +} + // Made the currently displayed child update its selected cell - (void)refreshCurrentSelectedCellInChild:(BOOL)forceVisible { @@ -789,7 +842,7 @@ currentAlert = nil; } - NSLog(@"[RoomCreation] Create new room failed"); + NSLog(@"[HomeViewController] Create new room failed"); // Alert user [[AppDelegate theDelegate] showErrorAsAlert:error]; diff --git a/Vector/main.m b/Vector/main.m index 93c6699aa..fe14eabea 100644 --- a/Vector/main.m +++ b/Vector/main.m @@ -26,9 +26,6 @@ int main(int argc, char * argv[]) { if (!isatty(STDERR_FILENO)) { [MXLogger redirectNSLogToFiles:YES]; } - // Catch and log crashes - [MXLogger logCrashes:YES]; - [MXLogger setBuildVersion:[AppDelegate theDelegate].build]; return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }