From 17acb57732468ea0442b5ed36c35f4e0e8c36ebc Mon Sep 17 00:00:00 2001 From: Dinis Vieira Date: Sun, 10 Dec 2023 15:05:08 +0000 Subject: [PATCH] PM-3349 Added base structure for avoiding Android Autofill crash. This workaround works but it's not complete as it can't handle the entire workflow when showing CipherSelectionPAge (like checking if it should show LockPage) --- src/App/Platforms/Android/MainActivity.cs | 5 +- src/Core/App.xaml.cs | 100 ++++++++++++++++-- src/Core/Pages/Accounts/LockPage.xaml.cs | 2 +- .../Accounts/LoginApproveDevicePage.xaml.cs | 2 +- src/Core/Pages/Accounts/LoginPage.xaml.cs | 2 +- .../LoginPasswordlessRequestPage.xaml.cs | 2 +- src/Core/Pages/Accounts/LoginSsoPage.xaml.cs | 4 +- .../Pages/Accounts/SetPasswordPage.xaml.cs | 2 +- src/Core/Pages/Accounts/TwoFactorPage.xaml.cs | 4 +- .../Pages/Vault/CipherAddEditPage.xaml.cs | 2 +- src/Core/Utilities/AppHelpers.cs | 6 +- src/iOS.Core/Services/DeviceActionService.cs | 2 +- 12 files changed, 107 insertions(+), 26 deletions(-) diff --git a/src/App/Platforms/Android/MainActivity.cs b/src/App/Platforms/Android/MainActivity.cs index ea72a165cb..88fbceb8c8 100644 --- a/src/App/Platforms/Android/MainActivity.cs +++ b/src/App/Platforms/Android/MainActivity.cs @@ -74,6 +74,10 @@ namespace Bit.Droid // this needs to be called here before base.OnCreate(...) Intent?.Validate(); + //We need to get and set the Options before calling OnCreate as that will "trigger" CreateWindow on App.xaml.cs + _appOptions = GetOptions(); + ((Bit.App.App)Microsoft.Maui.Controls.Application.Current).SetOptions(_appOptions); + base.OnCreate(savedInstanceState); _deviceActionService.SetScreenCaptureAllowedAsync().FireAndForget(_ => @@ -89,7 +93,6 @@ namespace Bit.Droid toplayout.FilterTouchesWhenObscured = true; } - _appOptions = GetOptions(); CreateNotificationChannel(); DisableAndroidFontScale(); diff --git a/src/Core/App.xaml.cs b/src/Core/App.xaml.cs index 7c714c03ad..a3d2d14063 100644 --- a/src/Core/App.xaml.cs +++ b/src/Core/App.xaml.cs @@ -44,6 +44,85 @@ namespace Bit.App // Links: https://github.com/dotnet/maui/issues/11501 and https://bitwarden.atlassian.net/wiki/spaces/NMME/pages/664862722/MainPage+Assignments+not+working+on+Android+on+Background+or+App+resume private readonly Queue _onResumeActions = new Queue(); +#if ANDROID + + /* + * ** Workaround for our Android crashes when trying to use Autofill ** + * + * This workaround works by managing the "Window Creation" ourselves. When we get an Autofill initialization we should create a new window instead of reusing the "Main/Current Window". + * While this workaround works, it's hard to execute the "workflow" that devices where we should navigate to. Below are some of the things we tried: + * 1 - Tried creating "new Window(new NavigationPage)" and invoking the code for handling the Navigations afterward. Issue with this is that the code that handles the navigations doesn't know which "Window" to use and calls the default "Window.Page" + * 2 - Tried using CustomWindow implementations to track the "WindowCreated" event and to be able to distinguish the different Window types (Default Window or Window for Autofill for example). + * This solution had a bit of overhear work and still required the app to set something line "new Window(new NavigationPage)" before actually knowing where we wanted to navigate to. + * + * Ideally we could figure out the Navigation we want to do before CreateWindow (on MainActivity.OnCreate) for example. But this needs to be done in async anyway we can't do async in both CreateWindow and MainActivity.OnCreate + */ + + public new static Page MainPage + { + get + { + return CurrentWindow?.Page; + } + set + { + if (CurrentWindow != null) + { + CurrentWindow.Page = value; + } + } + } + + public static Window CurrentWindow { get; private set; } + + public void SetOptions(AppOptions appOptions) + { + Options = appOptions; + } + + protected override Window CreateWindow(IActivationState activationState) + { + if (activationState != null + && activationState.State.TryGetValue("autofillFramework", out string autofillFramework) + && autofillFramework == "true") //TODO: There are likely better ways to filter this. Maybe using Options. + { + if (activationState.State.ContainsKey("autofillFrameworkCipherId")) //TODO: There are likely better ways to filter this. Maybe using Options. + { + return new Window(new NavigationPage()); //No actual page needed. Only used for auto-filling the fields directly (externally) + } + else + { + //TODO: IMPORTANT Question: this works if we want to show the CipherSelection, but we are skipping all our ASYNC Navigation workflows where we (for example) check if the user is logged in and/or needs to enter auth again. + var autofillWindow = new Window(new NavigationPage(new CipherSelectionPage(Options))); + return autofillWindow; + } + } + else if(CurrentWindow != null) + { + //TODO: This likely crashes if we try to have two apps side-by-side on Android + //TODO: Question: In these scenarios should a new Window be created or can the same one be reused? + return CurrentWindow; + } + else + { + CurrentWindow = new Window(new NavigationPage(new HomePage(Options))); + return CurrentWindow; + } + } +#elif IOS + public new static Page MainPage + { + get + { + return Application.Current.MainPage; + } + set + { + Application.Current.MainPage = value; + } + } +#endif + public App() : this(null) { } @@ -432,11 +511,11 @@ namespace Bit.App Options.Uri = null; if (isLocked) { - MainPage = new NavigationPage(new LockPage()); + App.MainPage = new NavigationPage(new LockPage()); } else { - MainPage = new TabsPage(); + App.MainPage = new TabsPage(); } }); }); @@ -463,7 +542,6 @@ namespace Bit.App { UpdateThemeAsync(); }; - MainPage = new NavigationPage(new HomePage(Options)); _accountsManager.NavigateOnAccountChangeAsync().FireAndForget(); ServiceContainer.Resolve("platformUtilsService").Init(); } @@ -535,36 +613,36 @@ namespace Bit.App switch (navTarget) { case NavigationTarget.HomeLogin: - MainPage = new NavigationPage(new HomePage(Options)); + App.MainPage = new NavigationPage(new HomePage(Options)); break; case NavigationTarget.Login: if (navParams is LoginNavigationParams loginParams) { - MainPage = new NavigationPage(new LoginPage(loginParams.Email, Options)); + App.MainPage = new NavigationPage(new LoginPage(loginParams.Email, Options)); } break; case NavigationTarget.Lock: if (navParams is LockNavigationParams lockParams) { - MainPage = new NavigationPage(new LockPage(Options, lockParams.AutoPromptBiometric)); + App.MainPage = new NavigationPage(new LockPage(Options, lockParams.AutoPromptBiometric)); } else { - MainPage = new NavigationPage(new LockPage(Options)); + App.MainPage = new NavigationPage(new LockPage(Options)); } break; case NavigationTarget.Home: - MainPage = new TabsPage(Options); + App.MainPage = new TabsPage(Options); break; case NavigationTarget.AddEditCipher: - MainPage = new NavigationPage(new CipherAddEditPage(appOptions: Options)); + App.MainPage = new NavigationPage(new CipherAddEditPage(appOptions: Options)); break; case NavigationTarget.AutofillCiphers: case NavigationTarget.OtpCipherSelection: - MainPage = new NavigationPage(new CipherSelectionPage(Options)); + App.MainPage = new NavigationPage(new CipherSelectionPage(Options)); break; case NavigationTarget.SendAddEdit: - MainPage = new NavigationPage(new SendAddEditPage(Options)); + App.MainPage = new NavigationPage(new SendAddEditPage(Options)); break; } } diff --git a/src/Core/Pages/Accounts/LockPage.xaml.cs b/src/Core/Pages/Accounts/LockPage.xaml.cs index d60820eb60..0cf421abe1 100644 --- a/src/Core/Pages/Accounts/LockPage.xaml.cs +++ b/src/Core/Pages/Accounts/LockPage.xaml.cs @@ -199,7 +199,7 @@ namespace Bit.App.Pages } var previousPage = await AppHelpers.ClearPreviousPage(); - Application.Current.MainPage = new TabsPage(_appOptions, previousPage); + App.MainPage = new TabsPage(_appOptions, previousPage); } } } diff --git a/src/Core/Pages/Accounts/LoginApproveDevicePage.xaml.cs b/src/Core/Pages/Accounts/LoginApproveDevicePage.xaml.cs index ea8eb015f7..65d212b5aa 100644 --- a/src/Core/Pages/Accounts/LoginApproveDevicePage.xaml.cs +++ b/src/Core/Pages/Accounts/LoginApproveDevicePage.xaml.cs @@ -36,7 +36,7 @@ namespace Bit.App.Pages return; } var previousPage = await AppHelpers.ClearPreviousPage(); - Application.Current.MainPage = new TabsPage(_appOptions, previousPage); + App.MainPage = new TabsPage(_appOptions, previousPage); } private async Task StartLogInWithMasterPasswordAsync() diff --git a/src/Core/Pages/Accounts/LoginPage.xaml.cs b/src/Core/Pages/Accounts/LoginPage.xaml.cs index 82125add3b..84f62879f7 100644 --- a/src/Core/Pages/Accounts/LoginPage.xaml.cs +++ b/src/Core/Pages/Accounts/LoginPage.xaml.cs @@ -193,7 +193,7 @@ namespace Bit.App.Pages return; } var previousPage = await AppHelpers.ClearPreviousPage(); - Application.Current.MainPage = new TabsPage(_appOptions, previousPage); + App.MainPage = new TabsPage(_appOptions, previousPage); } private async Task UpdateTempPasswordAsync() diff --git a/src/Core/Pages/Accounts/LoginPasswordlessRequestPage.xaml.cs b/src/Core/Pages/Accounts/LoginPasswordlessRequestPage.xaml.cs index ddc7addbce..ad5e3aba33 100644 --- a/src/Core/Pages/Accounts/LoginPasswordlessRequestPage.xaml.cs +++ b/src/Core/Pages/Accounts/LoginPasswordlessRequestPage.xaml.cs @@ -53,7 +53,7 @@ namespace Bit.App.Pages return; } var previousPage = await AppHelpers.ClearPreviousPage(); - Application.Current.MainPage = new TabsPage(_appOptions, previousPage); + App.MainPage = new TabsPage(_appOptions, previousPage); } private async Task UpdateTempPasswordAsync() diff --git a/src/Core/Pages/Accounts/LoginSsoPage.xaml.cs b/src/Core/Pages/Accounts/LoginSsoPage.xaml.cs index ad05b5a257..3760f961e1 100644 --- a/src/Core/Pages/Accounts/LoginSsoPage.xaml.cs +++ b/src/Core/Pages/Accounts/LoginSsoPage.xaml.cs @@ -120,11 +120,11 @@ namespace Bit.App.Pages if (await _vaultTimeoutService.IsLockedAsync()) { - Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions)); + App.MainPage = new NavigationPage(new LockPage(_appOptions)); } else { - Application.Current.MainPage = new TabsPage(_appOptions, null); + App.MainPage = new TabsPage(_appOptions, null); } } } diff --git a/src/Core/Pages/Accounts/SetPasswordPage.xaml.cs b/src/Core/Pages/Accounts/SetPasswordPage.xaml.cs index 3fdf38f8e8..5504b9d435 100644 --- a/src/Core/Pages/Accounts/SetPasswordPage.xaml.cs +++ b/src/Core/Pages/Accounts/SetPasswordPage.xaml.cs @@ -69,7 +69,7 @@ namespace Bit.App.Pages return; } var previousPage = await AppHelpers.ClearPreviousPage(); - Application.Current.MainPage = new TabsPage(_appOptions, previousPage); + App.MainPage = new TabsPage(_appOptions, previousPage); } } } diff --git a/src/Core/Pages/Accounts/TwoFactorPage.xaml.cs b/src/Core/Pages/Accounts/TwoFactorPage.xaml.cs index ee92ca5386..1b218dc3de 100644 --- a/src/Core/Pages/Accounts/TwoFactorPage.xaml.cs +++ b/src/Core/Pages/Accounts/TwoFactorPage.xaml.cs @@ -192,7 +192,7 @@ namespace Bit.App.Pages private void TwoFactorAuthSuccessWithSSOLocked() { - Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions)); + App.MainPage = new NavigationPage(new LockPage(_appOptions)); } private async Task TwoFactorAuthSuccessToMainAsync() @@ -202,7 +202,7 @@ namespace Bit.App.Pages return; } var previousPage = await AppHelpers.ClearPreviousPage(); - Application.Current.MainPage = new TabsPage(_appOptions, previousPage); + App.MainPage = new TabsPage(_appOptions, previousPage); } private void Token_TextChanged(object sender, TextChangedEventArgs e) diff --git a/src/Core/Pages/Vault/CipherAddEditPage.xaml.cs b/src/Core/Pages/Vault/CipherAddEditPage.xaml.cs index a8e0d5f661..792e4a63a3 100644 --- a/src/Core/Pages/Vault/CipherAddEditPage.xaml.cs +++ b/src/Core/Pages/Vault/CipherAddEditPage.xaml.cs @@ -184,7 +184,7 @@ namespace Bit.App.Pages { if (FromAutofillFramework) { - Microsoft.Maui.Controls.Application.Current.MainPage = new TabsPage(); + App.MainPage = new TabsPage(); return true; } return base.OnBackButtonPressed(); diff --git a/src/Core/Utilities/AppHelpers.cs b/src/Core/Utilities/AppHelpers.cs index 07db2fb81b..c16e978495 100644 --- a/src/Core/Utilities/AppHelpers.cs +++ b/src/Core/Utilities/AppHelpers.cs @@ -430,19 +430,19 @@ namespace Bit.App.Utilities { if (appOptions.FromAutofillFramework && appOptions.SaveType.HasValue) { - Application.Current.MainPage = new NavigationPage(new CipherAddEditPage(appOptions: appOptions)); + App.MainPage = new NavigationPage(new CipherAddEditPage(appOptions: appOptions)); return true; } if (appOptions.Uri != null || appOptions.OtpData != null) { - Application.Current.MainPage = new NavigationPage(new CipherSelectionPage(appOptions)); + App.MainPage = new NavigationPage(new CipherSelectionPage(appOptions)); return true; } if (appOptions.CreateSend != null) { - Application.Current.MainPage = new NavigationPage(new SendAddEditPage(appOptions)); + App.MainPage = new NavigationPage(new SendAddEditPage(appOptions)); return true; } } diff --git a/src/iOS.Core/Services/DeviceActionService.cs b/src/iOS.Core/Services/DeviceActionService.cs index 36f13679f9..3b91a5da4e 100644 --- a/src/iOS.Core/Services/DeviceActionService.cs +++ b/src/iOS.Core/Services/DeviceActionService.cs @@ -252,7 +252,7 @@ namespace Bit.iOS.Core.Services { if (Application.Current is App.App app && app.Options != null && !app.Options.IosExtension) { - return app.MainPage.DisplayActionSheet(title, cancel, destruction, buttons); + return Bit.App.App.MainPage.DisplayActionSheet(title, cancel, destruction, buttons); } var vc = GetPresentedViewController(); if (vc is null)