diff --git a/src/Android/MainActivity.cs b/src/Android/MainActivity.cs index f70537aef..d676ec3d4 100644 --- a/src/Android/MainActivity.cs +++ b/src/Android/MainActivity.cs @@ -12,6 +12,9 @@ using System; using Android.Content; using Bit.Droid.Utilities; using Bit.Droid.Receivers; +using Bit.App.Models; +using Bit.Core.Enums; +using Android.Nfc; namespace Bit.Droid { @@ -28,6 +31,7 @@ namespace Bit.Droid private IMessagingService _messagingService; private IBroadcasterService _broadcasterService; private PendingIntent _lockAlarmPendingIntent; + private AppOptions _appOptions; protected override void OnCreate(Bundle savedInstanceState) { @@ -45,7 +49,8 @@ namespace Bit.Droid base.OnCreate(savedInstanceState); Xamarin.Essentials.Platform.Init(this, savedInstanceState); Xamarin.Forms.Forms.Init(this, savedInstanceState); - LoadApplication(new App.App()); + _appOptions = GetOptions(); + LoadApplication(new App.App(_appOptions)); _broadcasterService.Subscribe(nameof(MainActivity), (message) => { @@ -122,5 +127,63 @@ namespace Bit.Droid } } } + + private void ListenYubiKey(bool listen) + { + if(!_deviceActionService.SupportsNfc()) + { + return; + } + var adapter = NfcAdapter.GetDefaultAdapter(this); + if(listen) + { + var intent = new Intent(this, Class); + intent.AddFlags(ActivityFlags.SingleTop); + var pendingIntent = PendingIntent.GetActivity(this, 0, intent, 0); + // register for all NDEF tags starting with http och https + var ndef = new IntentFilter(NfcAdapter.ActionNdefDiscovered); + ndef.AddDataScheme("http"); + ndef.AddDataScheme("https"); + var filters = new IntentFilter[] { ndef }; + try + { + // register for foreground dispatch so we'll receive tags according to our intent filters + adapter.EnableForegroundDispatch(this, pendingIntent, filters, null); + } + catch { } + } + else + { + adapter.DisableForegroundDispatch(this); + } + } + + private AppOptions GetOptions() + { + var options = new AppOptions + { + Uri = Intent.GetStringExtra("uri") ?? Intent.GetStringExtra("autofillFrameworkUri"), + MyVaultTile = Intent.GetBooleanExtra("myVaultTile", false), + FromAutofillFramework = Intent.GetBooleanExtra("autofillFramework", false) + }; + var fillType = Intent.GetIntExtra("autofillFrameworkFillType", 0); + if(fillType > 0) + { + options.FillType = (CipherType)fillType; + } + if(Intent.GetBooleanExtra("autofillFrameworkSave", false)) + { + options.SaveType = (CipherType)Intent.GetIntExtra("autofillFrameworkType", 0); + options.SaveName = Intent.GetStringExtra("autofillFrameworkName"); + options.SaveUsername = Intent.GetStringExtra("autofillFrameworkUsername"); + options.SavePassword = Intent.GetStringExtra("autofillFrameworkPassword"); + options.SaveCardName = Intent.GetStringExtra("autofillFrameworkCardName"); + options.SaveCardNumber = Intent.GetStringExtra("autofillFrameworkCardNumber"); + options.SaveCardExpMonth = Intent.GetStringExtra("autofillFrameworkCardExpMonth"); + options.SaveCardExpYear = Intent.GetStringExtra("autofillFrameworkCardExpYear"); + options.SaveCardCode = Intent.GetStringExtra("autofillFrameworkCardCode"); + } + return options; + } } } diff --git a/src/Android/Services/DeviceActionService.cs b/src/Android/Services/DeviceActionService.cs index bc770ed15..933f739da 100644 --- a/src/Android/Services/DeviceActionService.cs +++ b/src/Android/Services/DeviceActionService.cs @@ -6,12 +6,14 @@ using Android; using Android.App; using Android.Content; using Android.Content.PM; +using Android.Nfc; using Android.OS; using Android.Provider; using Android.Support.V4.App; using Android.Support.V4.Content; using Android.Text; using Android.Text.Method; +using Android.Views.Autofill; using Android.Webkit; using Android.Widget; using Bit.App.Abstractions; @@ -291,6 +293,31 @@ namespace Bit.Droid.Services return false; } + public bool SupportsNfc() + { + var activity = (MainActivity)CrossCurrentActivity.Current.Activity; + var manager = activity.GetSystemService(Context.NfcService) as NfcManager; + return manager.DefaultAdapter?.IsEnabled ?? false; + } + + public bool SupportsCamera() + { + var activity = (MainActivity)CrossCurrentActivity.Current.Activity; + return activity.PackageManager.HasSystemFeature(PackageManager.FeatureCamera); + } + + public bool SupportsAutofillService() + { + if(Build.VERSION.SdkInt < BuildVersionCodes.O) + { + return false; + } + var activity = (MainActivity)CrossCurrentActivity.Current.Activity; + var type = Java.Lang.Class.FromType(typeof(AutofillManager)); + var manager = activity.GetSystemService(type) as AutofillManager; + return manager.IsAutofillSupported; + } + private bool DeleteDir(Java.IO.File dir) { if(dir != null && dir.IsDirectory) diff --git a/src/App/Abstractions/IDeviceActionService.cs b/src/App/Abstractions/IDeviceActionService.cs index 5d63af5d5..9ee2ea512 100644 --- a/src/App/Abstractions/IDeviceActionService.cs +++ b/src/App/Abstractions/IDeviceActionService.cs @@ -18,5 +18,8 @@ namespace Bit.App.Abstractions string okButtonText = null, string cancelButtonText = null, bool numericKeyboard = false); void RateApp(); bool SupportsFaceId(); + bool SupportsNfc(); + bool SupportsCamera(); + bool SupportsAutofillService(); } -} \ No newline at end of file +} diff --git a/src/App/App.xaml.cs b/src/App/App.xaml.cs index 0484d7bae..cb12d7adb 100644 --- a/src/App/App.xaml.cs +++ b/src/App/App.xaml.cs @@ -1,4 +1,5 @@ -using Bit.App.Models; +using Bit.App.Abstractions; +using Bit.App.Models; using Bit.App.Pages; using Bit.App.Resources; using Bit.App.Services; @@ -34,9 +35,12 @@ namespace Bit.App private readonly IPlatformUtilsService _platformUtilsService; private readonly IAuthService _authService; private readonly IStorageService _storageService; + private readonly IDeviceActionService _deviceActionService; + private readonly AppOptions _appOptions; - public App() + public App(AppOptions appOptions) { + _appOptions = appOptions ?? new AppOptions(); _userService = ServiceContainer.Resolve("userService"); _broadcasterService = ServiceContainer.Resolve("broadcasterService"); _messagingService = ServiceContainer.Resolve("messagingService"); @@ -56,6 +60,7 @@ namespace Bit.App _passwordGenerationService = ServiceContainer.Resolve( "passwordGenerationService"); _i18nService = ServiceContainer.Resolve("i18nService") as MobileI18nService; + _deviceActionService = ServiceContainer.Resolve("deviceActionService"); InitializeComponent(); SetCulture(); @@ -107,21 +112,24 @@ namespace Bit.App }); } - protected override void OnStart() + protected async override void OnStart() { System.Diagnostics.Debug.WriteLine("XF App: OnStart"); + await ClearCacheIfNeededAsync(); } protected async override void OnSleep() { System.Diagnostics.Debug.WriteLine("XF App: OnSleep"); await HandleLockingAsync(); + SetTabsPageFromAutofill(); } - protected override void OnResume() + protected async override void OnResume() { System.Diagnostics.Debug.WriteLine("XF App: OnResume"); _messagingService.Send("cancelLockTimer"); + await ClearCacheIfNeededAsync(); } private void SetCulture() @@ -168,6 +176,10 @@ namespace Bit.App { Current.MainPage = new NavigationPage(new LockPage()); } + else if(_appOptions.FromAutofillFramework && _appOptions.SaveType.HasValue) + { + Current.MainPage = new NavigationPage(new AddEditPage(appOptions: _appOptions)); + } else { Current.MainPage = new TabsPage(); @@ -205,5 +217,30 @@ namespace Bit.App await _lockService.LockAsync(true); } } + + private async Task ClearCacheIfNeededAsync() + { + var lastClear = await _storageService.GetAsync(Constants.LastFileCacheClearKey); + if((DateTime.UtcNow - lastClear.GetValueOrDefault(DateTime.MinValue)).TotalDays >= 1) + { + var task = Task.Run(() => _deviceActionService.ClearCacheAsync()); + } + } + + private void SetTabsPageFromAutofill() + { + if(Device.RuntimePlatform == Device.Android && !string.IsNullOrWhiteSpace(_appOptions.Uri) && + !_appOptions.FromAutofillFramework) + { + Task.Run(() => + { + Device.BeginInvokeOnMainThread(() => + { + Current.MainPage = new TabsPage(); + _appOptions.Uri = null; + }); + }); + } + } } } diff --git a/src/App/Models/AppOptions.cs b/src/App/Models/AppOptions.cs new file mode 100644 index 000000000..43648b00a --- /dev/null +++ b/src/App/Models/AppOptions.cs @@ -0,0 +1,21 @@ +using Bit.Core.Enums; + +namespace Bit.App.Models +{ + public class AppOptions + { + public bool MyVaultTile { get; set; } + public bool FromAutofillFramework { get; set; } + public CipherType? FillType { get; set; } + public string Uri { get; set; } + public CipherType? SaveType { get; set; } + public string SaveName { get; set; } + public string SaveUsername { get; set; } + public string SavePassword { get; set; } + public string SaveCardName { get; set; } + public string SaveCardNumber { get; set; } + public string SaveCardExpMonth { get; set; } + public string SaveCardExpYear { get; set; } + public string SaveCardCode { get; set; } + } +} diff --git a/src/App/Pages/Vault/AddEditPage.xaml.cs b/src/App/Pages/Vault/AddEditPage.xaml.cs index e5d483967..92f4e3461 100644 --- a/src/App/Pages/Vault/AddEditPage.xaml.cs +++ b/src/App/Pages/Vault/AddEditPage.xaml.cs @@ -1,4 +1,5 @@ -using Bit.Core.Enums; +using Bit.App.Models; +using Bit.Core.Enums; using System.Collections.Generic; using Xamarin.Forms; @@ -7,13 +8,16 @@ namespace Bit.App.Pages public partial class AddEditPage : BaseContentPage { private AddEditPageViewModel _vm; + private readonly AppOptions _appOptions; public AddEditPage( string cipherId = null, CipherType? type = null, string folderId = null, - string collectionId = null) + string collectionId = null, + AppOptions appOptions = null) { + _appOptions = appOptions; InitializeComponent(); _vm = BindingContext as AddEditPageViewModel; _vm.Page = this; @@ -40,7 +44,7 @@ namespace Bit.App.Pages protected override async void OnAppearing() { base.OnAppearing(); - await LoadOnAppearedAsync(_scrollView, true, () => _vm.LoadAsync()); + await LoadOnAppearedAsync(_scrollView, true, () => _vm.LoadAsync(_appOptions)); if(_vm.EditMode && Device.RuntimePlatform == Device.Android) { if(_vm.Cipher.OrganizationId == null) diff --git a/src/App/Pages/Vault/AddEditPageViewModel.cs b/src/App/Pages/Vault/AddEditPageViewModel.cs index 0ff5807db..b0a4167d0 100644 --- a/src/App/Pages/Vault/AddEditPageViewModel.cs +++ b/src/App/Pages/Vault/AddEditPageViewModel.cs @@ -1,4 +1,5 @@ using Bit.App.Abstractions; +using Bit.App.Models; using Bit.App.Resources; using Bit.Core.Abstractions; using Bit.Core.Enums; @@ -264,7 +265,7 @@ namespace Bit.App.Pages PageTitle = EditMode ? AppResources.EditItem : AppResources.AddItem; } - public async Task LoadAsync() + public async Task LoadAsync(AppOptions appOptions = null) { var myEmail = await _userService.GetEmailAsync(); OwnershipOptions.Add(new KeyValuePair(myEmail, null)); @@ -310,6 +311,21 @@ namespace Bit.App.Pages Cipher.Login.Uris = new List { new LoginUriView() }; Cipher.SecureNote.Type = SecureNoteType.Generic; TypeSelectedIndex = TypeOptions.FindIndex(k => k.Value == Cipher.Type); + + if(appOptions != null) + { + Cipher.Type = appOptions.SaveType.GetValueOrDefault(Cipher.Type); + Cipher.Login.Username = appOptions.SaveUsername; + Cipher.Login.Password = appOptions.SavePassword; + Cipher.Card.Code = appOptions.SaveCardCode; + if(int.TryParse(appOptions.SaveCardExpMonth, out int month) && month <= 12 && month >= 1) + { + Cipher.Card.ExpMonth = month.ToString(); + } + Cipher.Card.ExpYear = appOptions.SaveCardExpYear; + Cipher.Card.CardholderName = appOptions.SaveCardName; + Cipher.Card.Number = appOptions.SaveCardNumber; + } } FolderSelectedIndex = string.IsNullOrWhiteSpace(Cipher.FolderId) ? FolderOptions.Count - 1 : diff --git a/src/iOS/AppDelegate.cs b/src/iOS/AppDelegate.cs index eeec3ecaa..b4d6be573 100644 --- a/src/iOS/AppDelegate.cs +++ b/src/iOS/AppDelegate.cs @@ -26,7 +26,7 @@ namespace Bit.iOS FFImageLoading.Forms.Platform.CachedImageRenderer.Init(); - LoadApplication(new App.App()); + LoadApplication(new App.App(null)); return base.FinishedLaunching(app, options); } diff --git a/src/iOS/Services/DeviceActionService.cs b/src/iOS/Services/DeviceActionService.cs index 60f8db7f6..097fd9554 100644 --- a/src/iOS/Services/DeviceActionService.cs +++ b/src/iOS/Services/DeviceActionService.cs @@ -233,6 +233,21 @@ namespace Bit.iOS.Services return context.BiometryType == LABiometryType.FaceId; } + public bool SupportsNfc() + { + return CoreNFC.NFCNdefReaderSession.ReadingAvailable; + } + + public bool SupportsCamera() + { + return true; + } + + public bool SupportsAutofillService() + { + return true; + } + private void ImagePicker_FinishedPickingMedia(object sender, UIImagePickerMediaPickedEventArgs e) { if(sender is UIImagePickerController picker)