From 7c29f8b77abf8278f162886ec3c5e91d25a03b43 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Thu, 4 Aug 2016 19:35:56 -0400 Subject: [PATCH] More GA event tracking throughout. Added dispatch when ios app is backgrounded. --- .../Services/GoogleAnalyticsService.cs | 3 +- .../Services/IGoogleAnalyticsService.cs | 6 ++- .../Pages/Settings/SettingsAddFolderPage.cs | 3 ++ .../Pages/Settings/SettingsEditFolderPage.cs | 3 ++ src/App/Pages/Settings/SettingsHelpPage.cs | 9 ++++ src/App/Pages/Settings/SettingsSyncPage.cs | 3 ++ src/App/Pages/Tools/ToolsExtensionPage.cs | 13 ++++-- src/App/Pages/Vault/VaultAddSitePage.cs | 3 ++ src/App/Pages/Vault/VaultEditSitePage.cs | 4 ++ .../Services/GoogleAnalyticsService.cs | 7 ++- src/iOS.Extension/LoadingViewController.cs | 19 ++++++-- src/iOS.Extension/SiteAddViewController.cs | 2 +- src/iOS/AppDelegate.cs | 45 ++++++++++++++++++- 13 files changed, 106 insertions(+), 14 deletions(-) diff --git a/src/Android/Services/GoogleAnalyticsService.cs b/src/Android/Services/GoogleAnalyticsService.cs index 62f733e7b..35cab0b91 100644 --- a/src/Android/Services/GoogleAnalyticsService.cs +++ b/src/Android/Services/GoogleAnalyticsService.cs @@ -87,9 +87,10 @@ namespace Bit.Android.Services } } - public void Dispatch() + public void Dispatch(Action completionHandler = null) { _instance.DispatchLocalHits(); + completionHandler?.Invoke(); } } } diff --git a/src/App/Abstractions/Services/IGoogleAnalyticsService.cs b/src/App/Abstractions/Services/IGoogleAnalyticsService.cs index 802f37350..32f56a1fb 100644 --- a/src/App/Abstractions/Services/IGoogleAnalyticsService.cs +++ b/src/App/Abstractions/Services/IGoogleAnalyticsService.cs @@ -1,4 +1,6 @@ -namespace Bit.App.Abstractions +using System; + +namespace Bit.App.Abstractions { public interface IGoogleAnalyticsService { @@ -8,6 +10,6 @@ void TrackExtensionEvent(string eventName, string label = null); void TrackEvent(string category, string eventName, string label = null); void TrackException(string message, bool fatal); - void Dispatch(); + void Dispatch(Action completionHandler = null); } } diff --git a/src/App/Pages/Settings/SettingsAddFolderPage.cs b/src/App/Pages/Settings/SettingsAddFolderPage.cs index ebdd0bb22..db51169e5 100644 --- a/src/App/Pages/Settings/SettingsAddFolderPage.cs +++ b/src/App/Pages/Settings/SettingsAddFolderPage.cs @@ -16,12 +16,14 @@ namespace Bit.App.Pages private readonly IFolderService _folderService; private readonly IUserDialogs _userDialogs; private readonly IConnectivity _connectivity; + private readonly IGoogleAnalyticsService _googleAnalyticsService; public SettingsAddFolderPage() { _folderService = Resolver.Resolve(); _userDialogs = Resolver.Resolve(); _connectivity = Resolver.Resolve(); + _googleAnalyticsService = Resolver.Resolve(); Init(); } @@ -79,6 +81,7 @@ namespace Bit.App.Pages { await Navigation.PopModalAsync(); _userDialogs.Toast("New folder created."); + _googleAnalyticsService.TrackAppEvent("CreatedFolder"); } else if(saveTask.Result.Errors.Count() > 0) { diff --git a/src/App/Pages/Settings/SettingsEditFolderPage.cs b/src/App/Pages/Settings/SettingsEditFolderPage.cs index 4f8c77ca9..bdcf9b0b2 100644 --- a/src/App/Pages/Settings/SettingsEditFolderPage.cs +++ b/src/App/Pages/Settings/SettingsEditFolderPage.cs @@ -16,6 +16,7 @@ namespace Bit.App.Pages private readonly IFolderService _folderService; private readonly IUserDialogs _userDialogs; private readonly IConnectivity _connectivity; + private readonly IGoogleAnalyticsService _googleAnalyticsService; public SettingsEditFolderPage(string folderId) { @@ -23,6 +24,7 @@ namespace Bit.App.Pages _folderService = Resolver.Resolve(); _userDialogs = Resolver.Resolve(); _connectivity = Resolver.Resolve(); + _googleAnalyticsService = Resolver.Resolve(); Init(); } @@ -93,6 +95,7 @@ namespace Bit.App.Pages { await Navigation.PopModalAsync(); _userDialogs.Toast("Folder updated."); + _googleAnalyticsService.TrackAppEvent("EditedFolder"); } else if(saveTask.Result.Errors.Count() > 0) { diff --git a/src/App/Pages/Settings/SettingsHelpPage.cs b/src/App/Pages/Settings/SettingsHelpPage.cs index 1884f5d71..f5ade3d21 100644 --- a/src/App/Pages/Settings/SettingsHelpPage.cs +++ b/src/App/Pages/Settings/SettingsHelpPage.cs @@ -1,13 +1,19 @@ using System; using Bit.App.Controls; using Xamarin.Forms; +using Bit.App.Abstractions; +using XLabs.Ioc; namespace Bit.App.Pages { public class SettingsHelpPage : ExtendedContentPage { + private readonly IGoogleAnalyticsService _googleAnalyticsService; + public SettingsHelpPage() { + _googleAnalyticsService = Resolver.Resolve(); + Init(); } @@ -103,16 +109,19 @@ namespace Bit.App.Pages private void EmailCell_Tapped(object sender, EventArgs e) { + _googleAnalyticsService.TrackAppEvent("HelpEmail"); Device.OpenUri(new Uri("mailto:hello@bitwarden.com")); } private void WebsiteCell_Tapped(object sender, EventArgs e) { + _googleAnalyticsService.TrackAppEvent("HelpWebsite"); Device.OpenUri(new Uri("https://bitwarden.com")); } private void BugCell_Tapped(object sender, EventArgs e) { + _googleAnalyticsService.TrackAppEvent("HelpBug"); Device.OpenUri(new Uri("https://github.com/bitwarden/mobile")); } diff --git a/src/App/Pages/Settings/SettingsSyncPage.cs b/src/App/Pages/Settings/SettingsSyncPage.cs index 61cc5292a..0932a7aa7 100644 --- a/src/App/Pages/Settings/SettingsSyncPage.cs +++ b/src/App/Pages/Settings/SettingsSyncPage.cs @@ -17,6 +17,7 @@ namespace Bit.App.Pages private readonly IUserDialogs _userDialogs; private readonly IConnectivity _connectivity; private readonly ISettings _settings; + private readonly IGoogleAnalyticsService _googleAnalyticsService; public SettingsSyncPage() { @@ -24,6 +25,7 @@ namespace Bit.App.Pages _userDialogs = Resolver.Resolve(); _connectivity = Resolver.Resolve(); _settings = Resolver.Resolve(); + _googleAnalyticsService = Resolver.Resolve(); Init(); } @@ -88,6 +90,7 @@ namespace Bit.App.Pages if(succeeded) { _userDialogs.Toast("Syncing complete."); + _googleAnalyticsService.TrackAppEvent("Synced"); } else { diff --git a/src/App/Pages/Tools/ToolsExtensionPage.cs b/src/App/Pages/Tools/ToolsExtensionPage.cs index 39db8f2dc..09a840f37 100644 --- a/src/App/Pages/Tools/ToolsExtensionPage.cs +++ b/src/App/Pages/Tools/ToolsExtensionPage.cs @@ -6,6 +6,7 @@ using Bit.App.Models.Page; using Plugin.Settings.Abstractions; using Xamarin.Forms; using XLabs.Ioc; +using Bit.App.Abstractions; namespace Bit.App.Pages { @@ -13,11 +14,13 @@ namespace Bit.App.Pages { private readonly IUserDialogs _userDialogs; private readonly ISettings _settings; + private readonly IGoogleAnalyticsService _googleAnalyticsService; public ToolsExtensionPage() { _userDialogs = Resolver.Resolve(); _settings = Resolver.Resolve(); + _googleAnalyticsService = Resolver.Resolve(); Model = new AppExtensionPageModel(_settings); Init(); @@ -59,7 +62,7 @@ namespace Bit.App.Pages var notStartedButton = new Button { Text = "Enable App Extension", - Command = new Command(() => ShowExtension()), + Command = new Command(() => ShowExtension("NotStartedEnable")), VerticalOptions = LayoutOptions.End, HorizontalOptions = LayoutOptions.Fill, Style = (Style)Application.Current.Resources["btn-primary"] @@ -108,7 +111,7 @@ namespace Bit.App.Pages var notActivatedButton = new Button { Text = "Enable App Extension", - Command = new Command(() => ShowExtension()), + Command = new Command(() => ShowExtension("NotActivatedEnable")), VerticalOptions = LayoutOptions.End, HorizontalOptions = LayoutOptions.Fill, Style = (Style)Application.Current.Resources["btn-primary"] @@ -167,7 +170,7 @@ namespace Bit.App.Pages var activatedButtonReenable = new Button { Text = "Re-enable App Extension", - Command = new Command(() => ShowExtension()), + Command = new Command(() => ShowExtension("Re-enable")), VerticalOptions = LayoutOptions.End, HorizontalOptions = LayoutOptions.Fill, Style = (Style)Application.Current.Resources["btn-primaryAccent"] @@ -200,13 +203,15 @@ namespace Bit.App.Pages BindingContext = Model; } - private void ShowExtension() + private void ShowExtension(string type) { + _googleAnalyticsService.TrackAppEvent("ShowExtension", type); MessagingCenter.Send(Application.Current, "ShowAppExtension", this); } public void EnabledExtension(bool enabled) { + _googleAnalyticsService.TrackAppEvent("EnabledExtension", enabled.ToString()); Model.Started = true; if(!Model.Activated && enabled) { diff --git a/src/App/Pages/Vault/VaultAddSitePage.cs b/src/App/Pages/Vault/VaultAddSitePage.cs index 9a7f89bff..33437f297 100644 --- a/src/App/Pages/Vault/VaultAddSitePage.cs +++ b/src/App/Pages/Vault/VaultAddSitePage.cs @@ -18,6 +18,7 @@ namespace Bit.App.Pages private readonly IFolderService _folderService; private readonly IUserDialogs _userDialogs; private readonly IConnectivity _connectivity; + private readonly IGoogleAnalyticsService _googleAnalyticsService; public VaultAddSitePage() { @@ -25,6 +26,7 @@ namespace Bit.App.Pages _folderService = Resolver.Resolve(); _userDialogs = Resolver.Resolve(); _connectivity = Resolver.Resolve(); + _googleAnalyticsService = Resolver.Resolve(); Init(); } @@ -139,6 +141,7 @@ namespace Bit.App.Pages { await Navigation.PopModalAsync(); _userDialogs.Toast("New site created."); + _googleAnalyticsService.TrackAppEvent("CreatedSite"); } else if(saveTask.Result.Errors.Count() > 0) { diff --git a/src/App/Pages/Vault/VaultEditSitePage.cs b/src/App/Pages/Vault/VaultEditSitePage.cs index a13dfb6cc..27472271d 100644 --- a/src/App/Pages/Vault/VaultEditSitePage.cs +++ b/src/App/Pages/Vault/VaultEditSitePage.cs @@ -18,6 +18,7 @@ namespace Bit.App.Pages private readonly IFolderService _folderService; private readonly IUserDialogs _userDialogs; private readonly IConnectivity _connectivity; + private readonly IGoogleAnalyticsService _googleAnalyticsService; public VaultEditSitePage(string siteId) { @@ -26,6 +27,7 @@ namespace Bit.App.Pages _folderService = Resolver.Resolve(); _userDialogs = Resolver.Resolve(); _connectivity = Resolver.Resolve(); + _googleAnalyticsService = Resolver.Resolve(); Init(); } @@ -174,6 +176,7 @@ namespace Bit.App.Pages { await Navigation.PopModalAsync(); _userDialogs.Toast("Site updated."); + _googleAnalyticsService.TrackAppEvent("EditedSite"); } else if(saveTask.Result.Errors.Count() > 0) { @@ -241,6 +244,7 @@ namespace Bit.App.Pages { await Navigation.PopModalAsync(); _userDialogs.Toast("Site deleted."); + _googleAnalyticsService.TrackAppEvent("DeletedSite"); } else if((await deleteTask).Errors.Count() > 0) { diff --git a/src/iOS.Core/Services/GoogleAnalyticsService.cs b/src/iOS.Core/Services/GoogleAnalyticsService.cs index f9a918383..c4706299f 100644 --- a/src/iOS.Core/Services/GoogleAnalyticsService.cs +++ b/src/iOS.Core/Services/GoogleAnalyticsService.cs @@ -62,9 +62,12 @@ namespace Bit.iOS.Core.Services _tracker.Send(dict); } - public void Dispatch() + public void Dispatch(Action completionHandler = null) { - Gai.SharedInstance.Dispatch(); + Gai.SharedInstance.Dispatch((result) => + { + completionHandler?.Invoke(); + }); } private void SetUserId() diff --git a/src/iOS.Extension/LoadingViewController.cs b/src/iOS.Extension/LoadingViewController.cs index 6419e4f47..9f72a15ae 100644 --- a/src/iOS.Extension/LoadingViewController.cs +++ b/src/iOS.Extension/LoadingViewController.cs @@ -228,7 +228,6 @@ namespace Bit.iOS.Extension Constants.AppExtensionOldPasswordKey, password); } - _googleAnalyticsService.TrackExtensionEvent("AutoFilled", _context.ProviderType); CompleteRequest(itemData); } @@ -240,7 +239,21 @@ namespace Bit.iOS.Extension var resultsItem = new NSExtensionItem { Attachments = new NSItemProvider[] { resultsProvider } }; var returningItems = new NSExtensionItem[] { resultsItem }; - ExtensionContext.CompleteRequest(returningItems, null); + if(itemData != null) + { + _googleAnalyticsService.TrackExtensionEvent("AutoFilled", _context.ProviderType); + } + else + { + _googleAnalyticsService.TrackExtensionEvent("Closed", _context.ProviderType); + } + + _googleAnalyticsService.Dispatch(() => + { + NSRunLoop.Main.BeginInvokeOnMainThread(() => { + ExtensionContext.CompleteRequest(returningItems, null); + }); + }); } private void SetIoc() @@ -296,7 +309,7 @@ namespace Bit.iOS.Extension var dict = list as NSDictionary; action(dict); - _googleAnalyticsService.TrackExtensionEvent("ProviderType", type); + _googleAnalyticsService.TrackExtensionEvent("ProcessItemProvider", type); Debug.WriteLine("BW LOG, ProviderType: " + _context.ProviderType); Debug.WriteLine("BW LOG, Url: " + _context.Url); diff --git a/src/iOS.Extension/SiteAddViewController.cs b/src/iOS.Extension/SiteAddViewController.cs index fb77569e5..952e525ce 100644 --- a/src/iOS.Extension/SiteAddViewController.cs +++ b/src/iOS.Extension/SiteAddViewController.cs @@ -166,7 +166,7 @@ namespace Bit.iOS.Extension if(saveTask.Result.Succeeded) { - _googleAnalyticsService.TrackExtensionEvent("SiteCreated"); + _googleAnalyticsService.TrackExtensionEvent("CreatedSite"); if(SiteListController != null) { SiteListController.DismissModal(); diff --git a/src/iOS/AppDelegate.cs b/src/iOS/AppDelegate.cs index c4a154680..0f97835dd 100644 --- a/src/iOS/AppDelegate.cs +++ b/src/iOS/AppDelegate.cs @@ -26,12 +26,15 @@ using Bit.App.Pages; using PushNotification.Plugin.Abstractions; using HockeyApp.iOS; using Bit.iOS.Core; +using Google.Analytics; namespace Bit.iOS { [Register("AppDelegate")] public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate { + private GaiCompletionHandler _dispatchHandler = null; + public ISettings Settings { get; set; } public override bool FinishedLaunching(UIApplication app, NSDictionary options) @@ -138,8 +141,10 @@ namespace Bit.iOS UIApplication.SharedApplication.SetStatusBarHidden(true, false); // Log the date/time we last backgrounded + Settings.AddOrUpdateValue(App.Constants.SettingLastBackgroundedDate, DateTime.UtcNow); - Settings.AddOrUpdateValue(Bit.App.Constants.SettingLastBackgroundedDate, DateTime.UtcNow); + // Dispatch Google Analytics + SendGoogleAnalyticsHitsInBackground(); base.DidEnterBackground(uiApplication); Debug.WriteLine("DidEnterBackground"); @@ -176,6 +181,11 @@ namespace Bit.iOS public override void WillEnterForeground(UIApplication uiApplication) { SendResumedMessage(); + + // Restores the dispatch interval because dispatchWithCompletionHandler + // has disabled automatic dispatching. + Gai.SharedInstance.DispatchInterval = 10; + base.WillEnterForeground(uiApplication); Debug.WriteLine("WillEnterForeground"); } @@ -273,5 +283,38 @@ namespace Bit.iOS Resolver.SetResolver(new UnityResolver(container)); } + + /// + /// This method sends any queued hits when the app enters the background. + /// ref: https://developers.google.com/analytics/devguides/collection/ios/v3/dispatch + /// + private void SendGoogleAnalyticsHitsInBackground() + { + var taskExpired = false; + var taskId = UIApplication.SharedApplication.BeginBackgroundTask(() => + { + taskExpired = true; + }); + + if(taskId == UIApplication.BackgroundTaskInvalid) + { + return; + } + + _dispatchHandler = (result) => + { + // Send hits until no hits are left, a dispatch error occurs, or the background task expires. + if(_dispatchHandler != null && result == DispatchResult.Good && !taskExpired) + { + Gai.SharedInstance.Dispatch(_dispatchHandler); + } + else + { + UIApplication.SharedApplication.EndBackgroundTask(taskId); + } + }; + + Gai.SharedInstance.Dispatch(_dispatchHandler); + } } }