diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj
index 64d9816bc..4cd7ff816 100644
--- a/src/Android/Android.csproj
+++ b/src/Android/Android.csproj
@@ -120,6 +120,7 @@
+
diff --git a/src/Android/MainActivity.cs b/src/Android/MainActivity.cs
index 292e50149..2e8480d87 100644
--- a/src/Android/MainActivity.cs
+++ b/src/Android/MainActivity.cs
@@ -39,8 +39,10 @@ namespace Bit.Droid
private IAppIdService _appIdService;
private IStorageService _storageService;
private IStateService _stateService;
+ private IEventService _eventService;
private PendingIntent _lockAlarmPendingIntent;
private PendingIntent _clearClipboardPendingIntent;
+ private PendingIntent _eventUploadPendingIntent;
private AppOptions _appOptions;
private string _activityKey = $"{nameof(MainActivity)}_{Java.Lang.JavaSystem.CurrentTimeMillis().ToString()}";
private Java.Util.Regex.Pattern _otpPattern =
@@ -48,6 +50,9 @@ namespace Bit.Droid
protected override void OnCreate(Bundle savedInstanceState)
{
+ var eventUploadIntent = new Intent(this, typeof(EventUploadReceiver));
+ _eventUploadPendingIntent = PendingIntent.GetBroadcast(this, 0, eventUploadIntent,
+ PendingIntentFlags.UpdateCurrent);
var alarmIntent = new Intent(this, typeof(LockAlarmReceiver));
_lockAlarmPendingIntent = PendingIntent.GetBroadcast(this, 0, alarmIntent,
PendingIntentFlags.UpdateCurrent);
@@ -65,6 +70,7 @@ namespace Bit.Droid
_appIdService = ServiceContainer.Resolve("appIdService");
_storageService = ServiceContainer.Resolve("storageService");
_stateService = ServiceContainer.Resolve("stateService");
+ _eventService = ServiceContainer.Resolve("eventService");
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
@@ -91,10 +97,10 @@ namespace Bit.Droid
{
if(message.Command == "scheduleLockTimer")
{
+ var alarmManager = GetSystemService(AlarmService) as AlarmManager;
var lockOptionMinutes = (int)message.Data;
var lockOptionMs = lockOptionMinutes * 60000;
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + lockOptionMs + 10;
- var alarmManager = GetSystemService(AlarmService) as AlarmManager;
alarmManager.Set(AlarmType.RtcWakeup, triggerMs, _lockAlarmPendingIntent);
}
else if(message.Command == "cancelLockTimer")
@@ -102,6 +108,14 @@ namespace Bit.Droid
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
alarmManager.Cancel(_lockAlarmPendingIntent);
}
+ else if(message.Command == "startEventTimer")
+ {
+ StartEventAlarm();
+ }
+ else if(message.Command == "stopEventTimer")
+ {
+ var task = StopEventAlarmAsync();
+ }
else if(message.Command == "finishMainActivity")
{
Xamarin.Forms.Device.BeginInvokeOnMainThread(() => Finish());
@@ -372,5 +386,18 @@ namespace Bit.Droid
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
alarmManager.Set(AlarmType.Rtc, triggerMs, _clearClipboardPendingIntent);
}
+
+ private void StartEventAlarm()
+ {
+ var alarmManager = GetSystemService(AlarmService) as AlarmManager;
+ alarmManager.SetInexactRepeating(AlarmType.ElapsedRealtime, 120000, 300000, _eventUploadPendingIntent);
+ }
+
+ private async Task StopEventAlarmAsync()
+ {
+ var alarmManager = GetSystemService(AlarmService) as AlarmManager;
+ alarmManager.Cancel(_eventUploadPendingIntent);
+ await _eventService.UploadEventsAsync();
+ }
}
}
diff --git a/src/Android/Receivers/EventUploadReceiver.cs b/src/Android/Receivers/EventUploadReceiver.cs
new file mode 100644
index 000000000..37b865a41
--- /dev/null
+++ b/src/Android/Receivers/EventUploadReceiver.cs
@@ -0,0 +1,16 @@
+using Android.Content;
+using Bit.Core.Abstractions;
+using Bit.Core.Utilities;
+
+namespace Bit.Droid.Receivers
+{
+ [BroadcastReceiver(Name = "com.x8bit.bitwarden.EventUploadReceiver", Exported = false)]
+ public class EventUploadReceiver : BroadcastReceiver
+ {
+ public async override void OnReceive(Context context, Intent intent)
+ {
+ var eventService = ServiceContainer.Resolve("eventService");
+ await eventService.UploadEventsAsync();
+ }
+ }
+}
diff --git a/src/App/App.xaml.cs b/src/App/App.xaml.cs
index 3d5a9455a..0404f19b8 100644
--- a/src/App/App.xaml.cs
+++ b/src/App/App.xaml.cs
@@ -173,6 +173,7 @@ namespace Bit.App
SyncIfNeeded();
}
}
+ _messagingService.Send("startEventTimer");
}
protected async override void OnSleep()
@@ -184,6 +185,7 @@ namespace Bit.App
}
SetTabsPageFromAutofill();
await HandleLockingAsync();
+ _messagingService.Send("stopEventTimer");
}
protected override void OnResume()
@@ -198,6 +200,7 @@ namespace Bit.App
private async void ResumedAsync()
{
_messagingService.Send("cancelLockTimer");
+ _messagingService.Send("startEventTimer");
await ClearCacheIfNeededAsync();
await TryClearCiphersCacheAsync();
Prime();
diff --git a/src/App/Pages/Vault/AddEditPageViewModel.cs b/src/App/Pages/Vault/AddEditPageViewModel.cs
index cf3977a25..831e8f114 100644
--- a/src/App/Pages/Vault/AddEditPageViewModel.cs
+++ b/src/App/Pages/Vault/AddEditPageViewModel.cs
@@ -23,6 +23,7 @@ namespace Bit.App.Pages
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IAuditService _auditService;
private readonly IMessagingService _messagingService;
+ private readonly IEventService _eventService;
private CipherView _cipher;
private bool _showNotesSeparator;
private bool _showPassword;
@@ -34,6 +35,7 @@ namespace Bit.App.Pages
private int _folderSelectedIndex;
private int _ownershipSelectedIndex;
private bool _hasCollections;
+ private string _previousCipherId;
private List _writeableCollections;
private string[] _additionalCipherProperties = new string[]
{
@@ -74,6 +76,7 @@ namespace Bit.App.Pages
_auditService = ServiceContainer.Resolve("auditService");
_messagingService = ServiceContainer.Resolve("messagingService");
_collectionService = ServiceContainer.Resolve("collectionService");
+ _eventService = ServiceContainer.Resolve("eventService");
GeneratePasswordCommand = new Command(GeneratePassword);
TogglePasswordCommand = new Command(TogglePassword);
ToggleCardCodeCommand = new Command(ToggleCardCode);
@@ -365,9 +368,16 @@ namespace Bit.App.Pages
}
if(Cipher.Fields != null)
{
- Fields.ResetWithRange(Cipher.Fields?.Select(f => new AddEditPageFieldViewModel(f)));
+ Fields.ResetWithRange(Cipher.Fields?.Select(f => new AddEditPageFieldViewModel(Cipher, f)));
}
}
+
+ if(EditMode && _previousCipherId != CipherId)
+ {
+ var task = _eventService.CollectAsync(EventType.Cipher_ClientViewed, CipherId);
+ }
+ _previousCipherId = CipherId;
+
return true;
}
@@ -591,7 +601,7 @@ namespace Bit.App.Pages
Fields = new ExtendedObservableCollection();
}
var type = _fieldTypeOptions.FirstOrDefault(f => f.Value == typeSelection).Key;
- Fields.Add(new AddEditPageFieldViewModel(new FieldView
+ Fields.Add(new AddEditPageFieldViewModel(Cipher, new FieldView
{
Type = type,
Name = string.IsNullOrWhiteSpace(name) ? null : name
@@ -602,11 +612,19 @@ namespace Bit.App.Pages
public void TogglePassword()
{
ShowPassword = !ShowPassword;
+ if(EditMode && ShowPassword)
+ {
+ var task = _eventService.CollectAsync(EventType.Cipher_ClientToggledPasswordVisible, CipherId);
+ }
}
public void ToggleCardCode()
{
ShowCardCode = !ShowCardCode;
+ if(EditMode && ShowCardCode)
+ {
+ var task = _eventService.CollectAsync(EventType.Cipher_ClientToggledCardCodeVisible, CipherId);
+ }
}
public async Task UpdateTotpKeyAsync(string key)
@@ -720,6 +738,7 @@ namespace Bit.App.Pages
public class AddEditPageFieldViewModel : ExtendedViewModel
{
private FieldView _field;
+ private CipherView _cipher;
private bool _showHiddenValue;
private bool _booleanValue;
private string[] _additionalFieldProperties = new string[]
@@ -729,8 +748,9 @@ namespace Bit.App.Pages
nameof(IsTextType),
};
- public AddEditPageFieldViewModel(FieldView field)
+ public AddEditPageFieldViewModel(CipherView cipher, FieldView field)
{
+ _cipher = cipher;
Field = field;
ToggleHiddenValueCommand = new Command(ToggleHiddenValue);
BooleanValue = IsBooleanType && field.Value == "true";
@@ -775,6 +795,11 @@ namespace Bit.App.Pages
public void ToggleHiddenValue()
{
ShowHiddenValue = !ShowHiddenValue;
+ if(ShowHiddenValue && _cipher?.Id != null)
+ {
+ var eventService = ServiceContainer.Resolve("eventService");
+ var task = eventService.CollectAsync(EventType.Cipher_ClientToggledHiddenFieldVisible, _cipher.Id);
+ }
}
public void TriggerFieldChanged()
diff --git a/src/App/Pages/Vault/ViewPageViewModel.cs b/src/App/Pages/Vault/ViewPageViewModel.cs
index b898b5e1c..dfad94037 100644
--- a/src/App/Pages/Vault/ViewPageViewModel.cs
+++ b/src/App/Pages/Vault/ViewPageViewModel.cs
@@ -22,6 +22,7 @@ namespace Bit.App.Pages
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IAuditService _auditService;
private readonly IMessagingService _messagingService;
+ private readonly IEventService _eventService;
private CipherView _cipher;
private List _fields;
private bool _canAccessPremium;
@@ -32,6 +33,7 @@ namespace Bit.App.Pages
private string _totpSec;
private bool _totpLow;
private DateTime? _totpInterval = null;
+ private string _previousCipherId;
public ViewPageViewModel()
{
@@ -42,6 +44,7 @@ namespace Bit.App.Pages
_platformUtilsService = ServiceContainer.Resolve("platformUtilsService");
_auditService = ServiceContainer.Resolve("auditService");
_messagingService = ServiceContainer.Resolve("messagingService");
+ _eventService = ServiceContainer.Resolve("eventService");
CopyCommand = new Command((id) => CopyAsync(id, null));
CopyUriCommand = new Command(CopyUri);
CopyFieldCommand = new Command(CopyField);
@@ -217,7 +220,7 @@ namespace Bit.App.Pages
}
Cipher = await cipher.DecryptAsync();
CanAccessPremium = await _userService.CanAccessPremiumAsync();
- Fields = Cipher.Fields?.Select(f => new ViewPageFieldViewModel(f)).ToList();
+ Fields = Cipher.Fields?.Select(f => new ViewPageFieldViewModel(Cipher, f)).ToList();
if(Cipher.Type == Core.Enums.CipherType.Login && !string.IsNullOrWhiteSpace(Cipher.Login.Totp) &&
(Cipher.OrganizationUseTotp || CanAccessPremium))
@@ -236,6 +239,11 @@ namespace Bit.App.Pages
return true;
});
}
+ if(_previousCipherId != CipherId)
+ {
+ var task = _eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientViewed, CipherId);
+ }
+ _previousCipherId = CipherId;
finishedLoadingAction?.Invoke();
return true;
}
@@ -248,11 +256,20 @@ namespace Bit.App.Pages
public void TogglePassword()
{
ShowPassword = !ShowPassword;
+ if(ShowPassword)
+ {
+ var task = _eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientToggledPasswordVisible, CipherId);
+ }
}
public void ToggleCardCode()
{
ShowCardCode = !ShowCardCode;
+ if(ShowCardCode)
+ {
+ var task = _eventService.CollectAsync(
+ Core.Enums.EventType.Cipher_ClientToggledCardCodeVisible, CipherId);
+ }
}
public async Task DeleteAsync()
@@ -434,7 +451,7 @@ namespace Bit.App.Pages
{
name = AppResources.URI;
}
- else if(id == "FieldValue")
+ else if(id == "FieldValue" || id == "H_FieldValue")
{
name = AppResources.Value;
}
@@ -456,6 +473,18 @@ namespace Bit.App.Pages
{
_platformUtilsService.ShowToast("info", null, string.Format(AppResources.ValueHasBeenCopied, name));
}
+ if(id == "LoginPassword")
+ {
+ await _eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedPassword, CipherId);
+ }
+ else if(id == "CardCode")
+ {
+ await _eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedCardCode, CipherId);
+ }
+ else if(id == "H_FieldValue")
+ {
+ await _eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedHiddenField, CipherId);
+ }
}
}
@@ -466,7 +495,7 @@ namespace Bit.App.Pages
private void CopyField(FieldView field)
{
- CopyAsync("FieldValue", field.Value);
+ CopyAsync(field.Type == Core.Enums.FieldType.Hidden ? "H_FieldValue" : "FieldValue", field.Value);
}
private void LaunchUri(LoginUriView uri)
@@ -481,10 +510,12 @@ namespace Bit.App.Pages
public class ViewPageFieldViewModel : ExtendedViewModel
{
private FieldView _field;
+ private CipherView _cipher;
private bool _showHiddenValue;
- public ViewPageFieldViewModel(FieldView field)
+ public ViewPageFieldViewModel(CipherView cipher, FieldView field)
{
+ _cipher = cipher;
Field = field;
ToggleHiddenValueCommand = new Command(ToggleHiddenValue);
}
@@ -526,6 +557,12 @@ namespace Bit.App.Pages
public void ToggleHiddenValue()
{
ShowHiddenValue = !ShowHiddenValue;
+ if(ShowHiddenValue)
+ {
+ var eventService = ServiceContainer.Resolve("eventService");
+ var task = eventService.CollectAsync(
+ Core.Enums.EventType.Cipher_ClientToggledHiddenFieldVisible, _cipher.Id);
+ }
}
}
}
diff --git a/src/App/Utilities/AppHelpers.cs b/src/App/Utilities/AppHelpers.cs
index 40e9c2e1e..6e4b3298d 100644
--- a/src/App/Utilities/AppHelpers.cs
+++ b/src/App/Utilities/AppHelpers.cs
@@ -16,6 +16,7 @@ namespace Bit.App.Utilities
public static async Task CipherListOptions(ContentPage page, CipherView cipher)
{
var platformUtilsService = ServiceContainer.Resolve("platformUtilsService");
+ var eventService = ServiceContainer.Resolve("eventService");
var options = new List { AppResources.View, AppResources.Edit };
if(cipher.Type == Core.Enums.CipherType.Login)
{
@@ -79,6 +80,7 @@ namespace Bit.App.Utilities
await platformUtilsService.CopyToClipboardAsync(cipher.Login.Password);
platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
+ var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedPassword, cipher.Id);
}
else if(selection == AppResources.CopyTotp)
{
@@ -106,6 +108,7 @@ namespace Bit.App.Utilities
await platformUtilsService.CopyToClipboardAsync(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)
{
diff --git a/src/Core/Enums/EventType.cs b/src/Core/Enums/EventType.cs
index 8f4e28ad2..d9411e31c 100644
--- a/src/Core/Enums/EventType.cs
+++ b/src/Core/Enums/EventType.cs
@@ -43,5 +43,6 @@
Organization_Updated = 1600,
Organization_PurgedVault = 1601,
+ // Organization_ClientExportedVault = 1602,
}
}