Skip to content

Commit

Permalink
Fix Clipboard clear after time on iOS (#1679)
Browse files Browse the repository at this point in the history
* Fixed Clipboard clear after x seconds depending on what the user set. Also refactored a bit to make the Clipboard a custom service to provide a better way to handle this situation #1464

* Clear some usings #1464
  • Loading branch information
fedemkr authored Dec 10, 2021
1 parent 23a164b commit 705b8ac
Show file tree
Hide file tree
Showing 17 changed files with 157 additions and 131 deletions.
1 change: 1 addition & 0 deletions src/Android/Android.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@
<Compile Include="Utilities\ThemeHelpers.cs" />
<Compile Include="WebAuthCallbackActivity.cs" />
<Compile Include="Renderers\SelectableLabelRenderer.cs" />
<Compile Include="Services\ClipboardService.cs" />
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="Assets\FontAwesome.ttf" />
Expand Down
34 changes: 0 additions & 34 deletions src/Android/MainActivity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,7 @@ public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivit
private IBroadcasterService _broadcasterService;
private IUserService _userService;
private IAppIdService _appIdService;
private IStorageService _storageService;
private IEventService _eventService;
private PendingIntent _clearClipboardPendingIntent;
private PendingIntent _eventUploadPendingIntent;
private AppOptions _appOptions;
private string _activityKey = $"{nameof(MainActivity)}_{Java.Lang.JavaSystem.CurrentTimeMillis().ToString()}";
Expand All @@ -48,9 +46,6 @@ protected override void OnCreate(Bundle savedInstanceState)
var eventUploadIntent = new Intent(this, typeof(EventUploadReceiver));
_eventUploadPendingIntent = PendingIntent.GetBroadcast(this, 0, eventUploadIntent,
PendingIntentFlags.UpdateCurrent);
var clearClipboardIntent = new Intent(this, typeof(ClearClipboardAlarmReceiver));
_clearClipboardPendingIntent = PendingIntent.GetBroadcast(this, 0, clearClipboardIntent,
PendingIntentFlags.UpdateCurrent);

var policy = new StrictMode.ThreadPolicy.Builder().PermitAll().Build();
StrictMode.SetThreadPolicy(policy);
Expand All @@ -60,7 +55,6 @@ protected override void OnCreate(Bundle savedInstanceState)
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_eventService = ServiceContainer.Resolve<IEventService>("eventService");

TabLayoutResource = Resource.Layout.Tabbar;
Expand Down Expand Up @@ -108,10 +102,6 @@ protected override void OnCreate(Bundle savedInstanceState)
{
ExitApp();
}
else if (message.Command == "copiedToClipboard")
{
var task = ClearClipboardAlarmAsync(message.Data as Tuple<string, int?, bool>);
}
});
}

Expand Down Expand Up @@ -388,30 +378,6 @@ private void ExitApp()
Java.Lang.JavaSystem.Exit(0);
}

private async Task ClearClipboardAlarmAsync(Tuple<string, int?, bool> data)
{
if (data.Item3)
{
return;
}
var clearMs = data.Item2;
if (clearMs == null)
{
var clearSeconds = await _storageService.GetAsync<int?>(Constants.ClearClipboardKey);
if (clearSeconds != null)
{
clearMs = clearSeconds.Value * 1000;
}
}
if (clearMs == null)
{
return;
}
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + clearMs.Value;
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
alarmManager.Set(AlarmType.Rtc, triggerMs, _clearClipboardPendingIntent);
}

private void StartEventAlarm()
{
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
Expand Down
1 change: 1 addition & 0 deletions src/Android/MainApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ private void RegisterLocalServices()
ServiceContainer.Register<ICryptoPrimitiveService>("cryptoPrimitiveService", cryptoPrimitiveService);
ServiceContainer.Register<IStorageService>("storageService", mobileStorageService);
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService);
ServiceContainer.Register<IClipboardService>("clipboardService", new ClipboardService(mobileStorageService));
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
ServiceContainer.Register<IBiometricService>("biometricService", biometricService);
Expand Down
57 changes: 57 additions & 0 deletions src/Android/Services/ClipboardService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System;
using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Droid.Receivers;
using Plugin.CurrentActivity;
using Xamarin.Essentials;

namespace Bit.Droid.Services
{
public class ClipboardService : IClipboardService
{
private readonly IStorageService _storageService;
private readonly Lazy<PendingIntent> _clearClipboardPendingIntent;

public ClipboardService(IStorageService storageService)
{
_storageService = storageService;

_clearClipboardPendingIntent = new Lazy<PendingIntent>(() =>
PendingIntent.GetBroadcast(CrossCurrentActivity.Current.Activity,
0,
new Intent(CrossCurrentActivity.Current.Activity, typeof(ClearClipboardAlarmReceiver)),
PendingIntentFlags.UpdateCurrent));
}

public async Task CopyTextAsync(string text, int expiresInMs = -1)
{
await Clipboard.SetTextAsync(text);

await ClearClipboardAlarmAsync(expiresInMs);
}

private async Task ClearClipboardAlarmAsync(int expiresInMs = -1)
{
var clearMs = expiresInMs;
if (clearMs < 0)
{
// if not set then we need to check if the user set this config
var clearSeconds = await _storageService.GetAsync<int?>(Constants.ClearClipboardKey);
if (clearSeconds != null)
{
clearMs = clearSeconds.Value * 1000;
}
}
if (clearMs < 0)
{
return;
}
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + clearMs;
var alarmManager = CrossCurrentActivity.Current.Activity.GetSystemService(Context.AlarmService) as AlarmManager;
alarmManager.Set(AlarmType.Rtc, triggerMs, _clearClipboardPendingIntent.Value);
}
}
}
4 changes: 3 additions & 1 deletion src/App/Pages/Generator/GeneratorHistoryPageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class GeneratorHistoryPageViewModel : BaseViewModel
{
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IPasswordGenerationService _passwordGenerationService;
private readonly IClipboardService _clipboardService;

private bool _showNoData;

Expand All @@ -20,6 +21,7 @@ public GeneratorHistoryPageViewModel()
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>(
"passwordGenerationService");
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");

PageTitle = AppResources.PasswordHistory;
History = new ExtendedObservableCollection<GeneratedPasswordHistory>();
Expand Down Expand Up @@ -51,7 +53,7 @@ public async Task ClearAsync()

private async void CopyAsync(GeneratedPasswordHistory ph)
{
await _platformUtilsService.CopyToClipboardAsync(ph.Password);
await _clipboardService.CopyTextAsync(ph.Password);
_platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
}
Expand Down
5 changes: 4 additions & 1 deletion src/App/Pages/Generator/GeneratorPageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class GeneratorPageViewModel : BaseViewModel
{
private readonly IPasswordGenerationService _passwordGenerationService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IClipboardService _clipboardService;

private PasswordGenerationOptions _options;
private PasswordGeneratorPolicyOptions _enforcedPolicyOptions;
Expand All @@ -38,6 +39,8 @@ public GeneratorPageViewModel()
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>(
"passwordGenerationService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");

PageTitle = AppResources.PasswordGenerator;
TypeOptions = new List<string> { AppResources.Password, AppResources.Passphrase };
}
Expand Down Expand Up @@ -305,7 +308,7 @@ public async Task SliderInputAsync()

public async Task CopyAsync()
{
await _platformUtilsService.CopyToClipboardAsync(Password);
await _clipboardService.CopyTextAsync(Password);
_platformUtilsService.ShowToast("success", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
}
Expand Down
4 changes: 3 additions & 1 deletion src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class SettingsPageViewModel : BaseViewModel
private readonly IPolicyService _policyService;
private readonly ILocalizeService _localizeService;
private readonly IKeyConnectorService _keyConnectorService;
private readonly IClipboardService _clipboardService;

private const int CustomVaultTimeoutValue = -100;

Expand Down Expand Up @@ -78,6 +79,7 @@ public SettingsPageViewModel()
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");

GroupedItems = new ExtendedObservableCollection<SettingsPageListGroup>();
PageTitle = AppResources.Settings;
Expand Down Expand Up @@ -135,7 +137,7 @@ public async Task AboutAsync()
AppResources.Close);
if (copy)
{
await _platformUtilsService.CopyToClipboardAsync(debugText);
await _clipboardService.CopyTextAsync(debugText);
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/App/Pages/Vault/PasswordHistoryPageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ public class PasswordHistoryPageViewModel : BaseViewModel
{
private readonly IPlatformUtilsService _platformUtilsService;
private readonly ICipherService _cipherService;
private readonly IClipboardService _clipboardService;

private bool _showNoData;

public PasswordHistoryPageViewModel()
{
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");

PageTitle = AppResources.PasswordHistory;
History = new ExtendedObservableCollection<PasswordHistoryView>();
Expand All @@ -45,7 +47,7 @@ public async Task InitAsync()

private async void CopyAsync(PasswordHistoryView ph)
{
await _platformUtilsService.CopyToClipboardAsync(ph.Password);
await _clipboardService.CopyTextAsync(ph.Password);
_platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
}
Expand Down
16 changes: 10 additions & 6 deletions src/App/Pages/Vault/ViewPageViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
using Bit.App.Abstractions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace Bit.App.Pages
Expand All @@ -26,6 +26,8 @@ public class ViewPageViewModel : BaseViewModel
private readonly IEventService _eventService;
private readonly IPasswordRepromptService _passwordRepromptService;
private readonly ILocalizeService _localizeService;
private readonly IClipboardService _clipboardService;

private CipherView _cipher;
private List<ViewPageFieldViewModel> _fields;
private bool _canAccessPremium;
Expand Down Expand Up @@ -54,6 +56,8 @@ public ViewPageViewModel()
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");

CopyCommand = new Command<string>((id) => CopyAsync(id, null));
CopyUriCommand = new Command<LoginUriView>(CopyUri);
CopyFieldCommand = new Command<FieldView>(CopyField);
Expand Down Expand Up @@ -653,7 +657,7 @@ private async void CopyAsync(string id, string text = null)

if (text != null)
{
await _platformUtilsService.CopyToClipboardAsync(text);
await _clipboardService.CopyTextAsync(text);
if (!string.IsNullOrWhiteSpace(name))
{
_platformUtilsService.ShowToast("info", null, string.Format(AppResources.ValueHasBeenCopied, name));
Expand Down
22 changes: 6 additions & 16 deletions src/App/Services/MobilePlatformUtilsService.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
using Bit.App.Abstractions;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Plugin.Fingerprint;
using Plugin.Fingerprint.Abstractions;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xamarin.Essentials;
using Xamarin.Forms;

namespace Bit.App.Services
{
public class MobilePlatformUtilsService : IPlatformUtilsService
{
{
private static readonly Random _random = new Random();

private const int DialogPromiseExpiration = 600000; // 10 minutes

private readonly IDeviceActionService _deviceActionService;
private readonly IMessagingService _messagingService;
private readonly IBroadcasterService _broadcasterService;

private readonly Dictionary<int, Tuple<TaskCompletionSource<bool>, DateTime>> _showDialogResolves =
new Dictionary<int, Tuple<TaskCompletionSource<bool>, DateTime>>();

Expand Down Expand Up @@ -201,17 +202,6 @@ public bool IsSelfHost()
return false;
}

public async Task CopyToClipboardAsync(string text, Dictionary<string, object> options = null)
{
var clearMs = options != null && options.ContainsKey("clearMs") ? (int?)options["clearMs"] : null;
var clearing = options != null && options.ContainsKey("clearing") ? (bool)options["clearing"] : false;
await Clipboard.SetTextAsync(text);
if (!clearing)
{
_messagingService.Send("copiedToClipboard", new Tuple<string, int?, bool>(text, clearMs, clearing));
}
}

public async Task<string> ReadFromClipboardAsync(Dictionary<string, object> options = null)
{
return await Clipboard.GetTextAsync();
Expand Down
Loading

1 comment on commit 705b8ac

@jpmorby
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change / modification setting LocalOnly = true is a feature breaking alteration. Please revert / set to false to enable Universal Clipboard to work as it is intended

Please sign in to comment.