diff --git a/src/Core/App.xaml.cs b/src/Core/App.xaml.cs
index a3d2d1406..0b5b90d8f 100644
--- a/src/Core/App.xaml.cs
+++ b/src/Core/App.xaml.cs
@@ -10,6 +10,7 @@ using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Models.Response;
+using Bit.Core.Pages;
using Bit.Core.Services;
using Bit.Core.Utilities;
@@ -74,6 +75,8 @@ namespace Bit.App
}
public static Window CurrentWindow { get; private set; }
+ public static Window AutofillWindow { get; private set; }
+ public static Window MainWindow { get; private set; }
public void SetOptions(AppOptions appOptions)
{
@@ -84,28 +87,28 @@ namespace Bit.App
{
if (activationState != null
&& activationState.State.TryGetValue("autofillFramework", out string autofillFramework)
- && autofillFramework == "true") //TODO: There are likely better ways to filter this. Maybe using Options.
+ && autofillFramework == "true"
+ && activationState.State.ContainsKey("autofillFrameworkCipherId")) //AutofillExternalActivity
{
- 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;
- }
+ return new Window(new NavigationPage()); //No actual page needed. Only used for auto-filling the fields directly (externally)
+ }
+ else if (Options.FromAutofillFramework || Options.Uri != null || Options.OtpData != null || Options.CreateSend != null) //"Internal" Autofill and Uri/Otp/CreateSend
+ {
+ AutofillWindow = new Window(new NavigationPage(new AndroidExtSplashPage(Options)));
+ CurrentWindow = AutofillWindow;
+ return CurrentWindow;
}
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?
+ CurrentWindow = MainWindow;
return CurrentWindow;
}
else
{
- CurrentWindow = new Window(new NavigationPage(new HomePage(Options)));
+ MainWindow = new Window(new NavigationPage(new HomePage(Options)));
+ CurrentWindow = MainWindow;
return CurrentWindow;
}
}
diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj
index 3471f8e48..d0426ef8e 100644
--- a/src/Core/Core.csproj
+++ b/src/Core/Core.csproj
@@ -64,7 +64,6 @@
-
@@ -92,4 +91,9 @@
True
+
+
+ MSBuild:Compile
+
+
\ No newline at end of file
diff --git a/src/Core/Pages/AndroidExtSplashPage.xaml b/src/Core/Pages/AndroidExtSplashPage.xaml
new file mode 100644
index 000000000..e54fc5a37
--- /dev/null
+++ b/src/Core/Pages/AndroidExtSplashPage.xaml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Core/Pages/AndroidExtSplashPage.xaml.cs b/src/Core/Pages/AndroidExtSplashPage.xaml.cs
new file mode 100644
index 000000000..90485c534
--- /dev/null
+++ b/src/Core/Pages/AndroidExtSplashPage.xaml.cs
@@ -0,0 +1,105 @@
+using Bit.App.Models;
+using Bit.App.Pages;
+using Bit.App.Utilities.AccountManagement;
+using Bit.Core.Abstractions;
+using Bit.Core.Utilities;
+
+namespace Bit.Core.Pages;
+
+public partial class AndroidExtSplashPage : ContentPage
+{
+ private IConditionedAwaiterManager _conditionedAwaiterManager;
+ private IVaultTimeoutService _vaultTimeoutService;
+ private IStateService _stateService;
+ private AppOptions _appOptions;
+
+ public AndroidExtSplashPage(AppOptions appOptions)
+ {
+ InitializeComponent();
+ _appOptions = appOptions;
+ _conditionedAwaiterManager = ServiceContainer.Resolve();
+ _vaultTimeoutService = ServiceContainer.Resolve("vaultTimeoutService");
+ _stateService = ServiceContainer.Resolve();
+ }
+
+ private async Task GetNavigationToExecuteAsync(AppOptions options, Window window)
+ {
+ await _conditionedAwaiterManager.GetAwaiterForPrecondition(AwaiterPrecondition.EnvironmentUrlsInited);
+
+ var authed = await _stateService.IsAuthenticatedAsync();
+ if (authed)
+ {
+ if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync() ||
+ await _vaultTimeoutService.ShouldLogOutByTimeoutAsync())
+ {
+ // TODO implement orgIdentifier flow to SSO Login page, same as email flow below
+ // var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
+
+ var email = await _stateService.GetEmailAsync();
+ options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
+ var navParams = new LoginNavigationParams(email);
+ if (navParams is LoginNavigationParams loginParams)
+ {
+ window.Page = new NavigationPage(new LoginPage(loginParams.Email, options));
+ }
+ }
+ else if (await _vaultTimeoutService.IsLockedAsync() ||
+ await _vaultTimeoutService.ShouldLockAsync())
+ {
+ /* //TODO: is lockParams needed here?
+ if (navParams is LockNavigationParams lockParams)
+ {
+ return () => new Window(new NavigationPage(new LockPage(Options, lockParams.AutoPromptBiometric)));
+ }
+ else
+ {*/
+ window.Page = new NavigationPage(new LockPage(options));
+ /*}*/
+ }
+ else if (options.FromAutofillFramework && options.SaveType.HasValue)
+ {
+ window.Page = new NavigationPage(new CipherAddEditPage(appOptions: options));
+ }
+ else if (options.Uri != null)
+ {
+ window.Page = new NavigationPage(new CipherSelectionPage(options));
+ }
+ else if (options.OtpData != null)
+ {
+ window.Page = new NavigationPage(new CipherSelectionPage(options));
+ }
+ else if (options.CreateSend != null)
+ {
+ window.Page = new NavigationPage(new SendAddEditPage(options));
+ }
+ else
+ {
+ window.Page = new TabsPage(options);
+ }
+ }
+ else
+ {
+ options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
+ if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync() ||
+ await _vaultTimeoutService.ShouldLogOutByTimeoutAsync())
+ {
+ // TODO implement orgIdentifier flow to SSO Login page, same as email flow below
+ // var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
+
+ var email = await _stateService.GetEmailAsync();
+ await _stateService.SetRememberedEmailAsync(email);
+
+ window.Page = new NavigationPage(new HomePage(options));
+ }
+ else
+ {
+ window.Page = new NavigationPage(new HomePage(options));
+ }
+ }
+ }
+
+ private async void AndroidExtSplashPage_OnLoaded(object sender, EventArgs e)
+ {
+ await GetNavigationToExecuteAsync(_appOptions, this.Window);
+ }
+}
diff --git a/src/Core/Pages/TabsPage.cs b/src/Core/Pages/TabsPage.cs
index 6c2c64ec2..c9d02e320 100644
--- a/src/Core/Pages/TabsPage.cs
+++ b/src/Core/Pages/TabsPage.cs
@@ -144,7 +144,17 @@ namespace Bit.App.Pages
private void OnUnloaded(object sender, EventArgs e)
{
- Handler?.DisconnectHandler();
+ try
+ {
+ Handler?.DisconnectHandler();
+ }
+ catch (Exception ex)
+ {
+ //Workaround: Currently the Disconnect Handler needs to be manually called from the App: https://github.com/dotnet/maui/issues/3604
+ // In some specific edges cases the MauiContext can be gone when we call this. (for example filling a field using Accessibility)
+ // In those scenarios the app should just be "closing" anyway, so we just want to avoid the exception.
+ System.Diagnostics.Debug.WriteLine(ex.Message);
+ }
}
public void ResetToVaultPage()