From 5a6aec51f39d924af743685e160e5e1f72e2360d Mon Sep 17 00:00:00 2001 From: Federico Maccaroni Date: Fri, 21 Jan 2022 20:14:48 -0300 Subject: [PATCH] Fix Progress dialog crash on tombstoning (#1682) * Changed ProgressDialog because deprecated and improved the dismissal of the dialog in order for it not to crash the app on certain situations * Removed android version check given that our minimum is greater that the check --- src/Android/Android.csproj | 1 + .../layout/progress_dialog_layout.xml | 27 +++++ src/Android/Services/DeviceActionService.cs | 103 ++++++++++++++++-- src/Android/Utilities/ThemeHelpers.cs | 4 + src/App/Pages/Vault/AddEditPageViewModel.cs | 26 ++++- 5 files changed, 146 insertions(+), 15 deletions(-) create mode 100644 src/Android/Resources/layout/progress_dialog_layout.xml diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj index 0b8ca91a4..462afcaf9 100644 --- a/src/Android/Android.csproj +++ b/src/Android/Android.csproj @@ -191,6 +191,7 @@ + diff --git a/src/Android/Resources/layout/progress_dialog_layout.xml b/src/Android/Resources/layout/progress_dialog_layout.xml new file mode 100644 index 000000000..0515dc3e1 --- /dev/null +++ b/src/Android/Resources/layout/progress_dialog_layout.xml @@ -0,0 +1,27 @@ + + + + + + + + + + diff --git a/src/Android/Services/DeviceActionService.cs b/src/Android/Services/DeviceActionService.cs index 9bb004161..c83689c97 100644 --- a/src/Android/Services/DeviceActionService.cs +++ b/src/Android/Services/DeviceActionService.cs @@ -13,6 +13,7 @@ using Android.OS; using Android.Provider; using Android.Text; using Android.Text.Method; +using Android.Views; using Android.Views.Autofill; using Android.Views.InputMethods; using Android.Webkit; @@ -27,6 +28,7 @@ using Bit.Core.Enums; using Bit.Core.Models.View; using Bit.Core.Utilities; using Bit.Droid.Autofill; +using Bit.Droid.Utilities; using Plugin.CurrentActivity; namespace Bit.Droid.Services @@ -37,7 +39,9 @@ namespace Bit.Droid.Services private readonly IMessagingService _messagingService; private readonly IBroadcasterService _broadcasterService; private readonly Func _eventServiceFunc; - private ProgressDialog _progressDialog; + private AlertDialog _progressDialog; + object _progressDialogLock = new object(); + private bool _cameraPermissionsDenied; private Toast _toast; private string _userAgent; @@ -108,22 +112,101 @@ namespace Bit.Droid.Services { await HideLoadingAsync(); } - var activity = (MainActivity)CrossCurrentActivity.Current.Activity; - _progressDialog = new ProgressDialog(activity); - _progressDialog.SetMessage(text); - _progressDialog.SetCancelable(false); + + var activity = CrossCurrentActivity.Current.Activity; + var inflater = (LayoutInflater)activity.GetSystemService(Context.LayoutInflaterService); + var dialogView = inflater.Inflate(Resource.Layout.progress_dialog_layout, null); + + var txtLoading = dialogView.FindViewById(Resource.Id.txtLoading); + txtLoading.Text = text; + txtLoading.SetTextColor(ThemeHelpers.TextColor); + + _progressDialog = new AlertDialog.Builder(activity) + .SetView(dialogView) + .SetCancelable(false) + .Create(); _progressDialog.Show(); } public Task HideLoadingAsync() { - if (_progressDialog != null) + // Based on https://github.com/redth-org/AndHUD/blob/master/AndHUD/AndHUD.cs + lock (_progressDialogLock) { - _progressDialog.Dismiss(); - _progressDialog.Dispose(); - _progressDialog = null; + if (_progressDialog is null) + { + return Task.CompletedTask; + } + + void actionDismiss() + { + try + { + if (IsAlive(_progressDialog) && IsAlive(_progressDialog.Window)) + { + _progressDialog.Hide(); + _progressDialog.Dismiss(); + } + } + catch + { + // ignore + } + + _progressDialog = null; + } + + // First try the SynchronizationContext + if (Application.SynchronizationContext != null) + { + Application.SynchronizationContext.Send(state => actionDismiss(), null); + return Task.CompletedTask; + } + + // Otherwise try OwnerActivity on dialog + var ownerActivity = _progressDialog?.OwnerActivity; + if (IsAlive(ownerActivity)) + { + ownerActivity.RunOnUiThread(actionDismiss); + return Task.CompletedTask; + } + + // Otherwise try get it from the Window Context + if (_progressDialog?.Window?.Context is Activity windowActivity && IsAlive(windowActivity)) + { + windowActivity.RunOnUiThread(actionDismiss); + return Task.CompletedTask; + } + + // Finally if all else fails, let's see if current activity is MainActivity + if (CrossCurrentActivity.Current.Activity is MainActivity activity && IsAlive(activity)) + { + activity.RunOnUiThread(actionDismiss); + return Task.CompletedTask; + } + + return Task.CompletedTask; } - return Task.FromResult(0); + } + + bool IsAlive(Java.Lang.Object @object) + { + if (@object == null) + return false; + + if (@object.Handle == IntPtr.Zero) + return false; + + if (@object is Activity activity) + { + if (activity.IsFinishing) + return false; + + if (activity.IsDestroyed) + return false; + } + + return true; } public bool OpenFile(byte[] fileData, string id, string fileName) diff --git a/src/Android/Utilities/ThemeHelpers.cs b/src/Android/Utilities/ThemeHelpers.cs index 017985b71..b33aabc53 100644 --- a/src/Android/Utilities/ThemeHelpers.cs +++ b/src/Android/Utilities/ThemeHelpers.cs @@ -36,6 +36,10 @@ namespace Bit.Droid.Utilities { get => ThemeManager.GetResourceColor("SwitchThumbColor").ToAndroid(); } + public static Color TextColor + { + get => ThemeManager.GetResourceColor("TextColor").ToAndroid(); + } public static void SetAppearance(string theme, bool osDarkModeEnabled) { diff --git a/src/App/Pages/Vault/AddEditPageViewModel.cs b/src/App/Pages/Vault/AddEditPageViewModel.cs index 6499fb863..52d648b38 100644 --- a/src/App/Pages/Vault/AddEditPageViewModel.cs +++ b/src/App/Pages/Vault/AddEditPageViewModel.cs @@ -13,7 +13,9 @@ using System.Threading.Tasks; using Bit.App.Controls; using Bit.Core; using Xamarin.Forms; -using View = Xamarin.Forms.View; +#if !FDROID +using Microsoft.AppCenter.Crashes; +#endif namespace Bit.App.Pages { @@ -494,9 +496,12 @@ namespace Bit.App.Pages try { await _deviceActionService.ShowLoadingAsync(AppResources.Saving); + await _cipherService.SaveWithServerAsync(cipher); Cipher.Id = cipher.Id; + await _deviceActionService.HideLoadingAsync(); + _platformUtilsService.ShowToast("success", null, EditMode && !CloneMode ? AppResources.ItemUpdated : AppResources.NewItemCreated); _messagingService.Send(EditMode && !CloneMode ? "editedCipher" : "addedCipher", Cipher.Id); @@ -512,19 +517,30 @@ namespace Bit.App.Pages { ViewPage?.UpdateCipherId(this.Cipher.Id); } - await Page.Navigation.PopModalAsync(); + // if the app is tombstoned then PopModalAsync would throw index out of bounds + if (Page.Navigation?.ModalStack?.Count > 0) + { + await Page.Navigation.PopModalAsync(); + } } return true; } - catch (ApiException e) + catch (ApiException apiEx) { await _deviceActionService.HideLoadingAsync(); - if (e?.Error != null) + if (apiEx?.Error != null) { - await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(), + await _platformUtilsService.ShowDialogAsync(apiEx.Error.GetSingleMessage(), AppResources.AnErrorHasOccurred); } } + catch(Exception genex) + { +#if !FDROID + Crashes.TrackError(genex); +#endif + await _deviceActionService.HideLoadingAsync(); + } return false; }