From ffb51c1515835ddacdf3b8c354e23c6bcaec5d8b Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Mon, 29 May 2017 11:38:03 -0400 Subject: [PATCH] new autofill feature settings --- src/Android/AutofillService.cs | 154 +++++++++++------ .../Resources/xml/accessibilityservice.xml | 2 +- .../Services/IAppSettingsService.cs | 2 + src/App/Constants.cs | 2 + .../Pages/Settings/SettingsFeaturesPage.cs | 156 +++++++++++++++++- src/App/Resources/AppResources.Designer.cs | 54 ++++++ src/App/Resources/AppResources.resx | 18 ++ src/App/Services/AppSettingsService.cs | 24 +++ 8 files changed, 361 insertions(+), 51 deletions(-) diff --git a/src/Android/AutofillService.cs b/src/Android/AutofillService.cs index 19b3573d0..2d3620454 100644 --- a/src/Android/AutofillService.cs +++ b/src/Android/AutofillService.cs @@ -6,6 +6,8 @@ using Android.App; using Android.Content; using Android.OS; using Android.Views.Accessibility; +using Bit.App.Abstractions; +using XLabs.Ioc; namespace Bit.Android { @@ -53,6 +55,14 @@ namespace Bit.Android new Browser("com.ksmobile.cb", "address_bar_edit_text") }.ToDictionary(n => n.PackageName); + private readonly IAppSettingsService _appSettings; + private long _lastNotification = 0; + + public AutofillService() + { + _appSettings = Resolver.Resolve(); + } + public override void OnAccessibilityEvent(AccessibilityEvent e) { try @@ -71,64 +81,68 @@ namespace Bit.Android */ var notificationManager = (NotificationManager)GetSystemService(NotificationService); + var cancelNotification = true; + switch(e.EventType) { - case EventTypes.WindowContentChanged: - case EventTypes.WindowStateChanged: - var cancelNotification = true; - - if(e.PackageName == BitwardenPackage) + case EventTypes.ViewFocused: + if(!e.Source.Password || !_appSettings.AutofillPasswordField) { - notificationManager?.Cancel(AutoFillNotificationId); break; } - var uri = GetUri(root); - if(uri != null && !uri.Contains(BitwardenWebsite)) + if(e.PackageName == BitwardenPackage) { - var needToFill = NeedToAutofill(AutofillActivity.LastCredentials, uri); - if(needToFill) - { - var passwordNodes = GetWindowNodes(root, e, n => n.Password, false); - needToFill = passwordNodes.Any(); - if(needToFill) - { - var allEditTexts = GetWindowNodes(root, e, n => EditText(n), false); - var usernameEditText = allEditTexts.TakeWhile(n => !n.Password).LastOrDefault(); - FillCredentials(usernameEditText, passwordNodes); - - allEditTexts.Dispose(); - usernameEditText.Dispose(); - passwordNodes.Dispose(); - } - } - - if(!needToFill) - { - NotifyToAutofill(uri, notificationManager); - cancelNotification = false; - } + CancelNotification(notificationManager); + break; } - AutofillActivity.LastCredentials = null; + if(ScanAndAutofill(root, e, notificationManager, cancelNotification)) + { + CancelNotification(notificationManager); + } + break; + case EventTypes.WindowContentChanged: + case EventTypes.WindowStateChanged: + if(_appSettings.AutofillPasswordField && e.Source.Password) + { + break; + } + else if(_appSettings.AutofillPasswordField) + { + CancelNotification(notificationManager); + break; + } - /* - var passwordNodes = GetWindowNodes(root, e, n => n.Password, false); - if(passwordNodes.Count > 0) + if(e.PackageName == BitwardenPackage) + { + CancelNotification(notificationManager); + break; + } + + if(_appSettings.AutofillPersistNotification) { var uri = GetUri(root); if(uri != null && !uri.Contains(BitwardenWebsite)) { - if(NeedToAutofill(AutofillActivity.LastCredentials, uri)) + var needToFill = NeedToAutofill(AutofillActivity.LastCredentials, uri); + if(needToFill) { - var allEditTexts = GetWindowNodes(root, e, n => EditText(n), false); - var usernameEditText = allEditTexts.TakeWhile(n => !n.Password).LastOrDefault(); - FillCredentials(usernameEditText, passwordNodes); + var passwordNodes = GetWindowNodes(root, e, n => n.Password, false); + needToFill = passwordNodes.Any(); + if(needToFill) + { + var allEditTexts = GetWindowNodes(root, e, n => EditText(n), false); + var usernameEditText = allEditTexts.TakeWhile(n => !n.Password).LastOrDefault(); + FillCredentials(usernameEditText, passwordNodes); - allEditTexts.Dispose(); - usernameEditText.Dispose(); + allEditTexts.Dispose(); + usernameEditText.Dispose(); + passwordNodes.Dispose(); + } } - else + + if(!needToFill) { NotifyToAutofill(uri, notificationManager); cancelNotification = false; @@ -137,14 +151,14 @@ namespace Bit.Android AutofillActivity.LastCredentials = null; } - - passwordNodes.Dispose(); - */ - + else + { + cancelNotification = ScanAndAutofill(root, e, notificationManager, cancelNotification); + } if(cancelNotification) { - notificationManager?.Cancel(AutoFillNotificationId); + CancelNotification(notificationManager); } break; default: @@ -164,6 +178,48 @@ namespace Bit.Android } + public bool ScanAndAutofill(AccessibilityNodeInfo root, AccessibilityEvent e, + NotificationManager notificationManager, bool cancelNotification) + { + var passwordNodes = GetWindowNodes(root, e, n => n.Password, false); + if(passwordNodes.Count > 0) + { + var uri = GetUri(root); + if(uri != null && !uri.Contains(BitwardenWebsite)) + { + if(NeedToAutofill(AutofillActivity.LastCredentials, uri)) + { + var allEditTexts = GetWindowNodes(root, e, n => EditText(n), false); + var usernameEditText = allEditTexts.TakeWhile(n => !n.Password).LastOrDefault(); + FillCredentials(usernameEditText, passwordNodes); + + allEditTexts.Dispose(); + usernameEditText.Dispose(); + } + else + { + NotifyToAutofill(uri, notificationManager); + cancelNotification = false; + } + } + + AutofillActivity.LastCredentials = null; + } + + passwordNodes.Dispose(); + return cancelNotification; + } + + public void CancelNotification(NotificationManager notificationManager) + { + if(Java.Lang.JavaSystem.CurrentTimeMillis() - _lastNotification < 250) + { + return; + } + + notificationManager?.Cancel(AutoFillNotificationId); + } + private string GetUri(AccessibilityNodeInfo root) { var uri = string.Concat(App.Constants.AndroidAppProtocol, root.PackageName); @@ -243,6 +299,7 @@ namespace Bit.Android return; } + var now = Java.Lang.JavaSystem.CurrentTimeMillis(); var intent = new Intent(this, typeof(AutofillActivity)); intent.PutExtra("uri", uri); intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop); @@ -253,7 +310,7 @@ namespace Bit.Android .SetContentTitle(App.Resources.AppResources.BitwardenAutofillService) .SetContentText(App.Resources.AppResources.BitwardenAutofillServiceNotificationContent) .SetTicker(App.Resources.AppResources.BitwardenAutofillServiceNotificationContent) - .SetWhen(Java.Lang.JavaSystem.CurrentTimeMillis()) + .SetWhen(now) .SetContentIntent(pendingIntent); if(Build.VERSION.SdkInt > BuildVersionCodes.KitkatWatch) @@ -263,11 +320,12 @@ namespace Bit.Android Resource.Color.primary)); } - if(Build.VERSION.SdkInt <= BuildVersionCodes.N) + if(Build.VERSION.SdkInt <= BuildVersionCodes.N && _appSettings.AutofillPersistNotification) { builder.SetPriority(-1); } + _lastNotification = now; notificationManager.Notify(AutoFillNotificationId, builder.Build()); builder.Dispose(); diff --git a/src/Android/Resources/xml/accessibilityservice.xml b/src/Android/Resources/xml/accessibilityservice.xml index 60b82260a..2bd2f7400 100644 --- a/src/Android/Resources/xml/accessibilityservice.xml +++ b/src/Android/Resources/xml/accessibilityservice.xml @@ -1,7 +1,7 @@  (); _userDialogs = Resolver.Resolve(); _settings = Resolver.Resolve(); + _appSettings = Resolver.Resolve(); _fingerprint = Resolver.Resolve(); _pushNotification = Resolver.Resolve(); _googleAnalyticsService = Resolver.Resolve(); @@ -35,6 +37,12 @@ namespace Bit.App.Pages private StackLayout StackLayout { get; set; } private ExtendedSwitchCell AnalyticsCell { get; set; } private Label AnalyticsLabel { get; set; } + private ExtendedSwitchCell AutofillPersistNotificationCell { get; set; } + private Label AutofillPersistNotificationLabel { get; set; } + private ExtendedSwitchCell AutofillPasswordFieldCell { get; set; } + private Label AutofillPasswordFieldLabel { get; set; } + private ExtendedSwitchCell AutofillAlwaysCell { get; set; } + private Label AutofillAlwaysLabel { get; set; } private void Init() { @@ -70,6 +78,84 @@ namespace Bit.App.Pages Spacing = 0 }; + if(Device.OS == TargetPlatform.Android) + { + AutofillAlwaysCell = new ExtendedSwitchCell + { + Text = AppResources.AutofillAlways, + On = !_appSettings.AutofillPersistNotification && !_appSettings.AutofillPasswordField + }; + + var autofillAlwaysTable = new FormTableView + { + Root = new TableRoot + { + new TableSection(AppResources.AutofillService) + { + AutofillAlwaysCell + } + } + }; + + AutofillAlwaysLabel = new FormTableLabel(this) + { + Text = AppResources.AutofillAlwaysDescription + }; + + AutofillPersistNotificationCell = new ExtendedSwitchCell + { + Text = AppResources.AutofillPersistNotification, + On = _appSettings.AutofillPersistNotification + }; + + var autofillPersistNotificationTable = new FormTableView + { + NoHeader = true, + Root = new TableRoot + { + new TableSection(" ") + { + AutofillPersistNotificationCell + } + } + }; + + AutofillPersistNotificationLabel = new FormTableLabel(this) + { + Text = AppResources.AutofillPersistNotificationDescription + }; + + AutofillPasswordFieldCell = new ExtendedSwitchCell + { + Text = AppResources.AutofillPasswordField, + On = _appSettings.AutofillPasswordField + }; + + var autofillPasswordFieldTable = new FormTableView + { + NoHeader = true, + Root = new TableRoot + { + new TableSection(" ") + { + AutofillPasswordFieldCell + } + } + }; + + AutofillPasswordFieldLabel = new FormTableLabel(this) + { + Text = AppResources.AutofillPasswordFieldDescription + }; + + StackLayout.Children.Add(autofillAlwaysTable); + StackLayout.Children.Add(AutofillAlwaysLabel); + StackLayout.Children.Add(autofillPasswordFieldTable); + StackLayout.Children.Add(AutofillPasswordFieldLabel); + StackLayout.Children.Add(autofillPersistNotificationTable); + StackLayout.Children.Add(AutofillPersistNotificationLabel); + } + var scrollView = new ScrollView { Content = StackLayout @@ -88,17 +174,23 @@ namespace Bit.App.Pages protected override void OnAppearing() { base.OnAppearing(); - + AnalyticsCell.OnChanged += AnalyticsCell_Changed; StackLayout.LayoutChanged += Layout_LayoutChanged; + AutofillAlwaysCell.OnChanged += AutofillAlwaysCell_OnChanged; + AutofillPasswordFieldCell.OnChanged += AutofillPasswordFieldCell_OnChanged; + AutofillPersistNotificationCell.OnChanged += AutofillPersistNotificationCell_OnChanged; } protected override void OnDisappearing() { base.OnDisappearing(); - + AnalyticsCell.OnChanged -= AnalyticsCell_Changed; StackLayout.LayoutChanged -= Layout_LayoutChanged; + AutofillAlwaysCell.OnChanged -= AutofillAlwaysCell_OnChanged; + AutofillPasswordFieldCell.OnChanged -= AutofillPasswordFieldCell_OnChanged; + AutofillPersistNotificationCell.OnChanged -= AutofillPersistNotificationCell_OnChanged; } private void Layout_LayoutChanged(object sender, EventArgs e) @@ -118,6 +210,55 @@ namespace Bit.App.Pages _googleAnalyticsService.SetAppOptOut(cell.On); } + private void AutofillAlwaysCell_OnChanged(object sender, ToggledEventArgs e) + { + var cell = sender as ExtendedSwitchCell; + if(cell == null) + { + return; + } + + if(cell.On) + { + AutofillPasswordFieldCell.On = false; + AutofillPersistNotificationCell.On = false; + _appSettings.AutofillPersistNotification = false; + _appSettings.AutofillPasswordField = false; + } + } + + private void AutofillPersistNotificationCell_OnChanged(object sender, ToggledEventArgs e) + { + var cell = sender as ExtendedSwitchCell; + if(cell == null) + { + return; + } + + _appSettings.AutofillPersistNotification = cell.On; + if(cell.On) + { + AutofillPasswordFieldCell.On = false; + AutofillAlwaysCell.On = false; + } + } + + private void AutofillPasswordFieldCell_OnChanged(object sender, ToggledEventArgs e) + { + var cell = sender as ExtendedSwitchCell; + if(cell == null) + { + return; + } + + _appSettings.AutofillPasswordField = cell.On; + if(cell.On) + { + AutofillPersistNotificationCell.On = false; + AutofillAlwaysCell.On = false; + } + } + private class FormTableView : ExtendedTableView { public FormTableView() @@ -130,5 +271,16 @@ namespace Bit.App.Pages NoFooter = true; } } + + private class FormTableLabel : Label + { + public FormTableLabel(Page page) + { + LineBreakMode = LineBreakMode.WordWrap; + FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)); + Style = (Style)Application.Current.Resources["text-muted"]; + Margin = new Thickness(15, (page.IsLandscape() ? 5 : 0), 15, 25); + } + } } } diff --git a/src/App/Resources/AppResources.Designer.cs b/src/App/Resources/AppResources.Designer.cs index 7799e9b25..33bb32b4d 100644 --- a/src/App/Resources/AppResources.Designer.cs +++ b/src/App/Resources/AppResources.Designer.cs @@ -151,6 +151,24 @@ namespace Bit.App.Resources { } } + /// + /// Looks up a localized string similar to Always Scan. + /// + public static string AutofillAlways { + get { + return ResourceManager.GetString("AutofillAlways", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Always scan the screen for fields and only offer an auto-fill notification if password fields are found. This is the default setting.. + /// + public static string AutofillAlwaysDescription { + get { + return ResourceManager.GetString("AutofillAlwaysDescription", resourceCulture); + } + } + /// /// Looks up a localized string similar to Use the bitwarden accessibility service to auto-fill your logins across apps and the web.. /// @@ -169,6 +187,42 @@ namespace Bit.App.Resources { } } + /// + /// Looks up a localized string similar to Scan When Password Field Focused. + /// + public static string AutofillPasswordField { + get { + return ResourceManager.GetString("AutofillPasswordField", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Only scan the screen for fields and offer an auto-fill notification whenever you select a password field. This setting may help conserve battery life.. + /// + public static string AutofillPasswordFieldDescription { + get { + return ResourceManager.GetString("AutofillPasswordFieldDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Persist Notification. + /// + public static string AutofillPersistNotification { + get { + return ResourceManager.GetString("AutofillPersistNotification", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Always offer an auto-fill notification and only scan for fields after attempting an auto-fill. This setting may help conserve battery life.. + /// + public static string AutofillPersistNotificationDescription { + get { + return ResourceManager.GetString("AutofillPersistNotificationDescription", resourceCulture); + } + } + /// /// Looks up a localized string similar to Auto-fill Service. /// diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx index 891a06fc7..7088c9b60 100644 --- a/src/App/Resources/AppResources.resx +++ b/src/App/Resources/AppResources.resx @@ -831,4 +831,22 @@ Features + + Scan When Password Field Focused + + + Only scan the screen for fields and offer an auto-fill notification whenever you select a password field. This setting may help conserve battery life. + + + Persist Notification + + + Always offer an auto-fill notification and only scan for fields after attempting an auto-fill. This setting may help conserve battery life. + + + Always Scan + + + Always scan the screen for fields and only offer an auto-fill notification if password fields are found. This is the default setting. + \ No newline at end of file diff --git a/src/App/Services/AppSettingsService.cs b/src/App/Services/AppSettingsService.cs index 04a8cae29..e3c66320b 100644 --- a/src/App/Services/AppSettingsService.cs +++ b/src/App/Services/AppSettingsService.cs @@ -37,5 +37,29 @@ namespace Bit.App.Services _settings.AddOrUpdateValue(Constants.LastActivityDate, value); } } + + public bool AutofillPersistNotification + { + get + { + return _settings.GetValueOrDefault(Constants.AutofillPersistNotification, false); + } + set + { + _settings.AddOrUpdateValue(Constants.AutofillPersistNotification, value); + } + } + + public bool AutofillPasswordField + { + get + { + return _settings.GetValueOrDefault(Constants.AutofillPasswordField, false); + } + set + { + _settings.AddOrUpdateValue(Constants.AutofillPasswordField, value); + } + } } }