bitwarden-mobile/src/Android/Services/ClipboardService.cs

92 lines
3.5 KiB
C#

using System;
using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Android.OS;
using Bit.Core.Abstractions;
using Bit.Droid.Receivers;
using Bit.Droid.Utilities;
using Xamarin.Essentials;
namespace Bit.Droid.Services
{
public class ClipboardService : IClipboardService
{
private readonly IStateService _stateService;
private readonly Lazy<PendingIntent> _clearClipboardPendingIntent;
public ClipboardService(IStateService stateService)
{
_stateService = stateService;
_clearClipboardPendingIntent = new Lazy<PendingIntent>(() =>
PendingIntent.GetBroadcast(Application.Context,
0,
new Intent(Application.Context, typeof(ClearClipboardAlarmReceiver)),
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, false)));
}
public async Task CopyTextAsync(string text, int expiresInMs = -1, bool isSensitive = true)
{
try
{
// Xamarin.Essentials.Clipboard currently doesn't support the IS_SENSITIVE flag for API 33+
if ((int)Build.VERSION.SdkInt < 33)
{
await Clipboard.SetTextAsync(text);
}
else
{
CopyToClipboard(text, isSensitive);
}
await ClearClipboardAlarmAsync(expiresInMs);
}
catch (Java.Lang.SecurityException ex) when (ex.Message.Contains("does not belong to"))
{
// #1962 Just ignore, the content is copied either way but there is some app interfering in the process
// that the OS catches and just throws this exception.
}
}
public bool IsCopyNotificationHandledByPlatform()
{
// Android 13+ provides built-in notification when text is copied to the clipboard
return (int)Build.VERSION.SdkInt >= 33;
}
private void CopyToClipboard(string text, bool isSensitive = true)
{
var clipboardManager = Application.Context.GetSystemService(Context.ClipboardService) as ClipboardManager;
var clipData = ClipData.NewPlainText("bitwarden", text);
if (isSensitive)
{
clipData.Description.Extras ??= new PersistableBundle();
clipData.Description.Extras.PutBoolean("android.content.extra.IS_SENSITIVE", true);
}
clipboardManager.PrimaryClip = clipData;
}
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 _stateService.GetClearClipboardAsync();
if (clearSeconds != null)
{
clearMs = clearSeconds.Value * 1000;
}
}
if (clearMs < 0)
{
return;
}
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + clearMs;
var alarmManager = Application.Context.GetSystemService(Context.AlarmService) as AlarmManager;
alarmManager.Set(AlarmType.Rtc, triggerMs, _clearClipboardPendingIntent.Value);
}
}
}