new autofill feature settings

This commit is contained in:
Kyle Spearrin 2017-05-29 11:38:03 -04:00
parent 72d4952812
commit ffb51c1515
8 changed files with 361 additions and 51 deletions

View file

@ -6,6 +6,8 @@ using Android.App;
using Android.Content; using Android.Content;
using Android.OS; using Android.OS;
using Android.Views.Accessibility; using Android.Views.Accessibility;
using Bit.App.Abstractions;
using XLabs.Ioc;
namespace Bit.Android namespace Bit.Android
{ {
@ -53,6 +55,14 @@ namespace Bit.Android
new Browser("com.ksmobile.cb", "address_bar_edit_text") new Browser("com.ksmobile.cb", "address_bar_edit_text")
}.ToDictionary(n => n.PackageName); }.ToDictionary(n => n.PackageName);
private readonly IAppSettingsService _appSettings;
private long _lastNotification = 0;
public AutofillService()
{
_appSettings = Resolver.Resolve<IAppSettingsService>();
}
public override void OnAccessibilityEvent(AccessibilityEvent e) public override void OnAccessibilityEvent(AccessibilityEvent e)
{ {
try try
@ -71,64 +81,68 @@ namespace Bit.Android
*/ */
var notificationManager = (NotificationManager)GetSystemService(NotificationService); var notificationManager = (NotificationManager)GetSystemService(NotificationService);
var cancelNotification = true;
switch(e.EventType) switch(e.EventType)
{ {
case EventTypes.WindowContentChanged: case EventTypes.ViewFocused:
case EventTypes.WindowStateChanged: if(!e.Source.Password || !_appSettings.AutofillPasswordField)
var cancelNotification = true;
if(e.PackageName == BitwardenPackage)
{ {
notificationManager?.Cancel(AutoFillNotificationId);
break; break;
} }
var uri = GetUri(root); if(e.PackageName == BitwardenPackage)
if(uri != null && !uri.Contains(BitwardenWebsite))
{ {
var needToFill = NeedToAutofill(AutofillActivity.LastCredentials, uri); CancelNotification(notificationManager);
if(needToFill) break;
{
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;
}
} }
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;
}
/* if(e.PackageName == BitwardenPackage)
var passwordNodes = GetWindowNodes(root, e, n => n.Password, false); {
if(passwordNodes.Count > 0) CancelNotification(notificationManager);
break;
}
if(_appSettings.AutofillPersistNotification)
{ {
var uri = GetUri(root); var uri = GetUri(root);
if(uri != null && !uri.Contains(BitwardenWebsite)) 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 passwordNodes = GetWindowNodes(root, e, n => n.Password, false);
var usernameEditText = allEditTexts.TakeWhile(n => !n.Password).LastOrDefault(); needToFill = passwordNodes.Any();
FillCredentials(usernameEditText, passwordNodes); if(needToFill)
{
var allEditTexts = GetWindowNodes(root, e, n => EditText(n), false);
var usernameEditText = allEditTexts.TakeWhile(n => !n.Password).LastOrDefault();
FillCredentials(usernameEditText, passwordNodes);
allEditTexts.Dispose(); allEditTexts.Dispose();
usernameEditText.Dispose(); usernameEditText.Dispose();
passwordNodes.Dispose();
}
} }
else
if(!needToFill)
{ {
NotifyToAutofill(uri, notificationManager); NotifyToAutofill(uri, notificationManager);
cancelNotification = false; cancelNotification = false;
@ -137,14 +151,14 @@ namespace Bit.Android
AutofillActivity.LastCredentials = null; AutofillActivity.LastCredentials = null;
} }
else
passwordNodes.Dispose(); {
*/ cancelNotification = ScanAndAutofill(root, e, notificationManager, cancelNotification);
}
if(cancelNotification) if(cancelNotification)
{ {
notificationManager?.Cancel(AutoFillNotificationId); CancelNotification(notificationManager);
} }
break; break;
default: 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) private string GetUri(AccessibilityNodeInfo root)
{ {
var uri = string.Concat(App.Constants.AndroidAppProtocol, root.PackageName); var uri = string.Concat(App.Constants.AndroidAppProtocol, root.PackageName);
@ -243,6 +299,7 @@ namespace Bit.Android
return; return;
} }
var now = Java.Lang.JavaSystem.CurrentTimeMillis();
var intent = new Intent(this, typeof(AutofillActivity)); var intent = new Intent(this, typeof(AutofillActivity));
intent.PutExtra("uri", uri); intent.PutExtra("uri", uri);
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop); intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
@ -253,7 +310,7 @@ namespace Bit.Android
.SetContentTitle(App.Resources.AppResources.BitwardenAutofillService) .SetContentTitle(App.Resources.AppResources.BitwardenAutofillService)
.SetContentText(App.Resources.AppResources.BitwardenAutofillServiceNotificationContent) .SetContentText(App.Resources.AppResources.BitwardenAutofillServiceNotificationContent)
.SetTicker(App.Resources.AppResources.BitwardenAutofillServiceNotificationContent) .SetTicker(App.Resources.AppResources.BitwardenAutofillServiceNotificationContent)
.SetWhen(Java.Lang.JavaSystem.CurrentTimeMillis()) .SetWhen(now)
.SetContentIntent(pendingIntent); .SetContentIntent(pendingIntent);
if(Build.VERSION.SdkInt > BuildVersionCodes.KitkatWatch) if(Build.VERSION.SdkInt > BuildVersionCodes.KitkatWatch)
@ -263,11 +320,12 @@ namespace Bit.Android
Resource.Color.primary)); Resource.Color.primary));
} }
if(Build.VERSION.SdkInt <= BuildVersionCodes.N) if(Build.VERSION.SdkInt <= BuildVersionCodes.N && _appSettings.AutofillPersistNotification)
{ {
builder.SetPriority(-1); builder.SetPriority(-1);
} }
_lastNotification = now;
notificationManager.Notify(AutoFillNotificationId, builder.Build()); notificationManager.Notify(AutoFillNotificationId, builder.Build());
builder.Dispose(); builder.Dispose();

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/AutoFillServiceDescription" android:description="@string/AutoFillServiceDescription"
android:accessibilityEventTypes="typeWindowStateChanged|typeWindowContentChanged" android:accessibilityEventTypes="typeWindowStateChanged|typeWindowContentChanged|typeViewFocused"
android:accessibilityFeedbackType="feedbackGeneric" android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagReportViewIds" android:accessibilityFlags="flagReportViewIds"
android:notificationTimeout="100" android:notificationTimeout="100"

View file

@ -6,5 +6,7 @@ namespace Bit.App.Abstractions
{ {
bool Locked { get; set; } bool Locked { get; set; }
DateTime LastActivity { get; set; } DateTime LastActivity { get; set; }
bool AutofillPersistNotification { get; set; }
bool AutofillPasswordField { get; set; }
} }
} }

View file

@ -8,6 +8,8 @@
public const string SettingPinUnlockOn = "setting:pinUnlockOn"; public const string SettingPinUnlockOn = "setting:pinUnlockOn";
public const string SettingLockSeconds = "setting:lockSeconds"; public const string SettingLockSeconds = "setting:lockSeconds";
public const string SettingGaOptOut = "setting:googleAnalyticsOptOut"; public const string SettingGaOptOut = "setting:googleAnalyticsOptOut";
public const string AutofillPersistNotification = "setting:persistNotification";
public const string AutofillPasswordField = "setting:autofillPasswordField";
public const string PasswordGeneratorLength = "pwGenerator:length"; public const string PasswordGeneratorLength = "pwGenerator:length";
public const string PasswordGeneratorUppercase = "pwGenerator:uppercase"; public const string PasswordGeneratorUppercase = "pwGenerator:uppercase";

View file

@ -16,6 +16,7 @@ namespace Bit.App.Pages
private readonly IAuthService _authService; private readonly IAuthService _authService;
private readonly IUserDialogs _userDialogs; private readonly IUserDialogs _userDialogs;
private readonly ISettings _settings; private readonly ISettings _settings;
private readonly IAppSettingsService _appSettings;
private readonly IFingerprint _fingerprint; private readonly IFingerprint _fingerprint;
private readonly IPushNotification _pushNotification; private readonly IPushNotification _pushNotification;
private readonly IGoogleAnalyticsService _googleAnalyticsService; private readonly IGoogleAnalyticsService _googleAnalyticsService;
@ -25,6 +26,7 @@ namespace Bit.App.Pages
_authService = Resolver.Resolve<IAuthService>(); _authService = Resolver.Resolve<IAuthService>();
_userDialogs = Resolver.Resolve<IUserDialogs>(); _userDialogs = Resolver.Resolve<IUserDialogs>();
_settings = Resolver.Resolve<ISettings>(); _settings = Resolver.Resolve<ISettings>();
_appSettings = Resolver.Resolve<IAppSettingsService>();
_fingerprint = Resolver.Resolve<IFingerprint>(); _fingerprint = Resolver.Resolve<IFingerprint>();
_pushNotification = Resolver.Resolve<IPushNotification>(); _pushNotification = Resolver.Resolve<IPushNotification>();
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>(); _googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
@ -35,6 +37,12 @@ namespace Bit.App.Pages
private StackLayout StackLayout { get; set; } private StackLayout StackLayout { get; set; }
private ExtendedSwitchCell AnalyticsCell { get; set; } private ExtendedSwitchCell AnalyticsCell { get; set; }
private Label AnalyticsLabel { 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() private void Init()
{ {
@ -70,6 +78,84 @@ namespace Bit.App.Pages
Spacing = 0 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 var scrollView = new ScrollView
{ {
Content = StackLayout Content = StackLayout
@ -91,6 +177,9 @@ namespace Bit.App.Pages
AnalyticsCell.OnChanged += AnalyticsCell_Changed; AnalyticsCell.OnChanged += AnalyticsCell_Changed;
StackLayout.LayoutChanged += Layout_LayoutChanged; StackLayout.LayoutChanged += Layout_LayoutChanged;
AutofillAlwaysCell.OnChanged += AutofillAlwaysCell_OnChanged;
AutofillPasswordFieldCell.OnChanged += AutofillPasswordFieldCell_OnChanged;
AutofillPersistNotificationCell.OnChanged += AutofillPersistNotificationCell_OnChanged;
} }
protected override void OnDisappearing() protected override void OnDisappearing()
@ -99,6 +188,9 @@ namespace Bit.App.Pages
AnalyticsCell.OnChanged -= AnalyticsCell_Changed; AnalyticsCell.OnChanged -= AnalyticsCell_Changed;
StackLayout.LayoutChanged -= Layout_LayoutChanged; StackLayout.LayoutChanged -= Layout_LayoutChanged;
AutofillAlwaysCell.OnChanged -= AutofillAlwaysCell_OnChanged;
AutofillPasswordFieldCell.OnChanged -= AutofillPasswordFieldCell_OnChanged;
AutofillPersistNotificationCell.OnChanged -= AutofillPersistNotificationCell_OnChanged;
} }
private void Layout_LayoutChanged(object sender, EventArgs e) private void Layout_LayoutChanged(object sender, EventArgs e)
@ -118,6 +210,55 @@ namespace Bit.App.Pages
_googleAnalyticsService.SetAppOptOut(cell.On); _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 private class FormTableView : ExtendedTableView
{ {
public FormTableView() public FormTableView()
@ -130,5 +271,16 @@ namespace Bit.App.Pages
NoFooter = true; 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);
}
}
} }
} }

View file

@ -151,6 +151,24 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Always Scan.
/// </summary>
public static string AutofillAlways {
get {
return ResourceManager.GetString("AutofillAlways", resourceCulture);
}
}
/// <summary>
/// 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..
/// </summary>
public static string AutofillAlwaysDescription {
get {
return ResourceManager.GetString("AutofillAlwaysDescription", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Use the bitwarden accessibility service to auto-fill your logins across apps and the web.. /// Looks up a localized string similar to Use the bitwarden accessibility service to auto-fill your logins across apps and the web..
/// </summary> /// </summary>
@ -169,6 +187,42 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Scan When Password Field Focused.
/// </summary>
public static string AutofillPasswordField {
get {
return ResourceManager.GetString("AutofillPasswordField", resourceCulture);
}
}
/// <summary>
/// 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..
/// </summary>
public static string AutofillPasswordFieldDescription {
get {
return ResourceManager.GetString("AutofillPasswordFieldDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Persist Notification.
/// </summary>
public static string AutofillPersistNotification {
get {
return ResourceManager.GetString("AutofillPersistNotification", resourceCulture);
}
}
/// <summary>
/// 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..
/// </summary>
public static string AutofillPersistNotificationDescription {
get {
return ResourceManager.GetString("AutofillPersistNotificationDescription", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Auto-fill Service. /// Looks up a localized string similar to Auto-fill Service.
/// </summary> /// </summary>

View file

@ -831,4 +831,22 @@
<data name="Features" xml:space="preserve"> <data name="Features" xml:space="preserve">
<value>Features</value> <value>Features</value>
</data> </data>
<data name="AutofillPasswordField" xml:space="preserve">
<value>Scan When Password Field Focused</value>
</data>
<data name="AutofillPasswordFieldDescription" xml:space="preserve">
<value>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.</value>
</data>
<data name="AutofillPersistNotification" xml:space="preserve">
<value>Persist Notification</value>
</data>
<data name="AutofillPersistNotificationDescription" xml:space="preserve">
<value>Always offer an auto-fill notification and only scan for fields after attempting an auto-fill. This setting may help conserve battery life.</value>
</data>
<data name="AutofillAlways" xml:space="preserve">
<value>Always Scan</value>
</data>
<data name="AutofillAlwaysDescription" xml:space="preserve">
<value>Always scan the screen for fields and only offer an auto-fill notification if password fields are found. This is the default setting.</value>
</data>
</root> </root>

View file

@ -37,5 +37,29 @@ namespace Bit.App.Services
_settings.AddOrUpdateValue(Constants.LastActivityDate, value); _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);
}
}
} }
} }