Skip to content

Commit

Permalink
Fixed Clipboard clear after x seconds depending on what the user set.…
Browse files Browse the repository at this point in the history
… Also refactored a bit to make the Clipboard a custom service to provide a better way to handle this situation #1464
  • Loading branch information
fedemkr committed Dec 9, 2021
1 parent ea1ee2c commit 79e3a41
Show file tree
Hide file tree
Showing 17 changed files with 150 additions and 122 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 @@ -49,9 +49,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 @@ -63,9 +61,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 @@ -75,7 +70,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 @@ -123,10 +117,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 @@ -395,30 +385,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
7 changes: 6 additions & 1 deletion src/App/Pages/Vault/ViewPageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Xamarin.Forms;

Expand All @@ -26,6 +27,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 +57,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 +658,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
15 changes: 3 additions & 12 deletions src/App/Services/MobilePlatformUtilsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,23 @@
using Plugin.Fingerprint.Abstractions;
using System;
using System.Collections.Generic;
using System.Diagnostics.SymbolStore;
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 +203,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
17 changes: 10 additions & 7 deletions src/App/Utilities/AppHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ public static async Task<string> CipherListOptions(ContentPage page, CipherView
var platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
var eventService = ServiceContainer.Resolve<IEventService>("eventService");
var vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
var clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");

var options = new List<string> { AppResources.View };
if (!cipher.IsDeleted)
{
Expand Down Expand Up @@ -92,15 +94,15 @@ public static async Task<string> CipherListOptions(ContentPage page, CipherView
}
else if (selection == AppResources.CopyUsername)
{
await platformUtilsService.CopyToClipboardAsync(cipher.Login.Username);
await clipboardService.CopyTextAsync(cipher.Login.Username);
platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Username));
}
else if (selection == AppResources.CopyPassword)
{
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
{
await platformUtilsService.CopyToClipboardAsync(cipher.Login.Password);
await clipboardService.CopyTextAsync(cipher.Login.Password);
platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedPassword, cipher.Id);
Expand All @@ -114,7 +116,7 @@ public static async Task<string> CipherListOptions(ContentPage page, CipherView
var totp = await totpService.GetCodeAsync(cipher.Login.Totp);
if (!string.IsNullOrWhiteSpace(totp))
{
await platformUtilsService.CopyToClipboardAsync(totp);
await clipboardService.CopyTextAsync(totp);
platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.VerificationCodeTotp));
}
Expand All @@ -128,7 +130,7 @@ public static async Task<string> CipherListOptions(ContentPage page, CipherView
{
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
{
await platformUtilsService.CopyToClipboardAsync(cipher.Card.Number);
await clipboardService.CopyTextAsync(cipher.Card.Number);
platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Number));
}
Expand All @@ -137,15 +139,15 @@ public static async Task<string> CipherListOptions(ContentPage page, CipherView
{
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
{
await platformUtilsService.CopyToClipboardAsync(cipher.Card.Code);
await clipboardService.CopyTextAsync(cipher.Card.Code);
platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.SecurityCode));
var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedCardCode, cipher.Id);
}
}
else if (selection == AppResources.CopyNotes)
{
await platformUtilsService.CopyToClipboardAsync(cipher.Notes);
await clipboardService.CopyTextAsync(cipher.Notes);
platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Notes));
}
Expand Down Expand Up @@ -200,7 +202,8 @@ public static async Task CopySendUrlAsync(SendView send)
return;
}
var platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
await platformUtilsService.CopyToClipboardAsync(GetSendUrl(send));
var clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
await clipboardService.CopyTextAsync(GetSendUrl(send));
platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.SendLink));
}
Expand Down
Loading

0 comments on commit 79e3a41

Please sign in to comment.