Enhanced autofill settings (#1150)

* enhanced autofill settings

* cleanup
This commit is contained in:
Matt Portune 2020-11-17 09:37:57 -05:00 committed by GitHub
parent edab722a76
commit c71deb5051
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 533 additions and 391 deletions

View file

@ -255,13 +255,13 @@ namespace Bit.Droid.Accessibility
if (!AccessibilityHelpers.OverlayPermitted()) if (!AccessibilityHelpers.OverlayPermitted())
{ {
if (!AccessibilityHelpers.IsAutofillTileAdded) if (Build.VERSION.SdkInt <= BuildVersionCodes.M)
{ {
// The user has the option of only using the autofill tile and leaving the overlay permission // The user has the option of only using the autofill tile and leaving the overlay permission
// disabled, so only show this toast if they're using accessibility without overlay permission and // disabled, so only show this toast if they're using accessibility without overlay permission on
// have _not_ added the autofill tile // a version of Android without quick-action tile support
System.Diagnostics.Debug.WriteLine(">>> Overlay Permission not granted"); System.Diagnostics.Debug.WriteLine(">>> Overlay Permission not granted");
Toast.MakeText(this, AppResources.AccessibilityOverlayPermissionAlert, ToastLength.Long).Show(); Toast.MakeText(this, AppResources.AccessibilityDrawOverPermissionAlert, ToastLength.Long).Show();
} }
return; return;
} }

View file

@ -161,28 +161,10 @@
<None Include="upload-keystore.jks.enc" /> <None Include="upload-keystore.jks.enc" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\accessibility_overlay.png" />
<AndroidResource Include="Resources\drawable-hdpi\accessibility_permission.png" />
<AndroidResource Include="Resources\drawable-hdpi\accessibility_step1.png" />
<AndroidResource Include="Resources\drawable-hdpi\accessibility_step2.png" />
<AndroidResource Include="Resources\drawable-hdpi\autofill_enable.png" />
<AndroidResource Include="Resources\drawable-hdpi\autofill_use.png" />
<AndroidResource Include="Resources\drawable-hdpi\logo_legacy.png" /> <AndroidResource Include="Resources\drawable-hdpi\logo_legacy.png" />
<AndroidResource Include="Resources\drawable-hdpi\logo_white_legacy.png" /> <AndroidResource Include="Resources\drawable-hdpi\logo_white_legacy.png" />
<AndroidResource Include="Resources\drawable-xhdpi\accessibility_overlay.png" />
<AndroidResource Include="Resources\drawable-xhdpi\accessibility_permission.png" />
<AndroidResource Include="Resources\drawable-xhdpi\accessibility_step1.png" />
<AndroidResource Include="Resources\drawable-xhdpi\accessibility_step2.png" />
<AndroidResource Include="Resources\drawable-xhdpi\autofill_enable.png" />
<AndroidResource Include="Resources\drawable-xhdpi\autofill_use.png" />
<AndroidResource Include="Resources\drawable-xhdpi\logo_legacy.png" /> <AndroidResource Include="Resources\drawable-xhdpi\logo_legacy.png" />
<AndroidResource Include="Resources\drawable-xhdpi\logo_white_legacy.png" /> <AndroidResource Include="Resources\drawable-xhdpi\logo_white_legacy.png" />
<AndroidResource Include="Resources\drawable-xxhdpi\accessibility_overlay.png" />
<AndroidResource Include="Resources\drawable-xxhdpi\accessibility_permission.png" />
<AndroidResource Include="Resources\drawable-xxhdpi\accessibility_step1.png" />
<AndroidResource Include="Resources\drawable-xxhdpi\accessibility_step2.png" />
<AndroidResource Include="Resources\drawable-xxhdpi\autofill_enable.png" />
<AndroidResource Include="Resources\drawable-xxhdpi\autofill_use.png" />
<AndroidResource Include="Resources\drawable-xxhdpi\logo_legacy.png" /> <AndroidResource Include="Resources\drawable-xxhdpi\logo_legacy.png" />
<AndroidResource Include="Resources\drawable-xxhdpi\logo_white_legacy.png" /> <AndroidResource Include="Resources\drawable-xxhdpi\logo_white_legacy.png" />
<AndroidResource Include="Resources\drawable\card.xml" /> <AndroidResource Include="Resources\drawable\card.xml" />

View file

@ -143,13 +143,13 @@ namespace Bit.Droid.Autofill
} }
public static FillResponse BuildFillResponse(Parser parser, List<FilledItem> items, bool locked, public static FillResponse BuildFillResponse(Parser parser, List<FilledItem> items, bool locked,
FillRequest fillRequest = null) bool inlineAutofillEnabled, FillRequest fillRequest = null)
{ {
// Acquire inline presentation specs on Android 11+ // Acquire inline presentation specs on Android 11+
IList<InlinePresentationSpec> inlinePresentationSpecs = null; IList<InlinePresentationSpec> inlinePresentationSpecs = null;
var inlinePresentationSpecsCount = 0; var inlinePresentationSpecsCount = 0;
var inlineMaxSuggestedCount = 0; var inlineMaxSuggestedCount = 0;
if (fillRequest != null && (int)Build.VERSION.SdkInt >= 30) if (inlineAutofillEnabled && fillRequest != null && (int)Build.VERSION.SdkInt >= 30)
{ {
var inlineSuggestionsRequest = fillRequest.InlineSuggestionsRequest; var inlineSuggestionsRequest = fillRequest.InlineSuggestionsRequest;
inlineMaxSuggestedCount = inlineSuggestionsRequest?.MaxSuggestionCount ?? 0; inlineMaxSuggestedCount = inlineSuggestionsRequest?.MaxSuggestionCount ?? 0;

View file

@ -47,6 +47,8 @@ namespace Bit.Droid.Autofill
return; return;
} }
var inlineAutofillEnabled = await _storageService.GetAsync<bool?>(Constants.InlineAutofillEnabledKey) ?? true;
if (_vaultTimeoutService == null) if (_vaultTimeoutService == null)
{ {
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService"); _vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
@ -64,7 +66,7 @@ namespace Bit.Droid.Autofill
} }
// build response // build response
var response = AutofillHelpers.BuildFillResponse(parser, items, locked, request); var response = AutofillHelpers.BuildFillResponse(parser, items, locked, inlineAutofillEnabled, request);
callback.OnSuccess(response); callback.OnSuccess(response);
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

View file

@ -378,6 +378,34 @@ namespace Bit.Droid.Services
} }
} }
public void DisableAutofillService()
{
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var type = Java.Lang.Class.FromType(typeof(AutofillManager));
var manager = activity.GetSystemService(type) as AutofillManager;
manager.DisableAutofillServices();
}
catch { }
}
public bool AutofillServicesEnabled()
{
if (Build.VERSION.SdkInt <= BuildVersionCodes.M)
{
// Android 5-6: Both accessibility & overlay are required or nothing happens
return AutofillAccessibilityServiceRunning() && AutofillAccessibilityOverlayPermitted();
}
if (Build.VERSION.SdkInt == BuildVersionCodes.N)
{
// Android 7: Only accessibility is required (overlay is optional when using quick-action tile)
return AutofillAccessibilityServiceRunning();
}
// Android 8+: Either autofill or accessibility is required
return AutofillServiceEnabled() || AutofillAccessibilityServiceRunning();
}
public string GetBuildNumber() public string GetBuildNumber()
{ {
return Application.Context.ApplicationContext.PackageManager.GetPackageInfo( return Application.Context.ApplicationContext.PackageManager.GetPackageInfo(

View file

@ -36,6 +36,8 @@ namespace Bit.App.Abstractions
bool AutofillAccessibilityServiceRunning(); bool AutofillAccessibilityServiceRunning();
bool AutofillAccessibilityOverlayPermitted(); bool AutofillAccessibilityOverlayPermitted();
bool AutofillServiceEnabled(); bool AutofillServiceEnabled();
void DisableAutofillService();
bool AutofillServicesEnabled();
string GetBuildNumber(); string GetBuildNumber();
void OpenAccessibilitySettings(); void OpenAccessibilitySettings();
void OpenAccessibilityOverlayPermissionSettings(); void OpenAccessibilityOverlayPermissionSettings();

View file

@ -57,8 +57,8 @@
<Compile Update="Pages\Settings\ExtensionPage.xaml.cs"> <Compile Update="Pages\Settings\ExtensionPage.xaml.cs">
<DependentUpon>ExtensionPage.xaml</DependentUpon> <DependentUpon>ExtensionPage.xaml</DependentUpon>
</Compile> </Compile>
<Compile Update="Pages\Settings\AutofillServicePage.xaml.cs"> <Compile Update="Pages\Settings\AutofillServicesPage.xaml.cs">
<DependentUpon>AutofillServicePage.xaml</DependentUpon> <DependentUpon>AutofillServicesPage.xaml</DependentUpon>
</Compile> </Compile>
<Compile Update="Pages\Settings\FolderAddEditPage.xaml.cs"> <Compile Update="Pages\Settings\FolderAddEditPage.xaml.cs">
<DependentUpon>FolderAddEditPage.xaml</DependentUpon> <DependentUpon>FolderAddEditPage.xaml</DependentUpon>
@ -72,9 +72,6 @@
<Compile Update="Pages\Settings\OptionsPage.xaml.cs"> <Compile Update="Pages\Settings\OptionsPage.xaml.cs">
<DependentUpon>OptionsPage.xaml</DependentUpon> <DependentUpon>OptionsPage.xaml</DependentUpon>
</Compile> </Compile>
<Compile Update="Pages\Settings\AccessibilityServicePage.xaml.cs">
<DependentUpon>AccessibilityServicePage.xaml</DependentUpon>
</Compile>
<Compile Update="Pages\Settings\SyncPage.xaml.cs"> <Compile Update="Pages\Settings\SyncPage.xaml.cs">
<DependentUpon>SyncPage.xaml</DependentUpon> <DependentUpon>SyncPage.xaml</DependentUpon>
</Compile> </Compile>

View file

@ -1,109 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<pages:BaseContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Pages.AccessibilityServicePage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
x:DataType="pages:AccessibilityServicePageViewModel"
Title="{Binding PageTitle}">
<ContentPage.BindingContext>
<pages:AccessibilityServicePageViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
</ResourceDictionary>
</ContentPage.Resources>
<ScrollView>
<StackLayout Spacing="20"
Padding="10, 30"
VerticalOptions="FillAndExpand">
<Label Text="{u:I18n AutofillAccessibilityDescription}"
VerticalOptions="Start"
HorizontalTextAlignment="Center"
LineBreakMode="WordWrap" />
<StackLayout IsVisible="{Binding Enabled, Converter={StaticResource inverseBool}}"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center"
Spacing="20">
<StackLayout Orientation="Horizontal" HorizontalOptions="Center">
<Label Text="{u:I18n Status}" />
<Label Text="{u:I18n Disabled}"
StyleClass="text-danger, text-bold" />
</StackLayout>
<Image Source="accessibility_step1.png"
HorizontalOptions="Center"
Margin="0, 20, 0, 0"
WidthRequest="300"
HeightRequest="98" />
<Label Text="{u:I18n BitwardenAutofillServiceStep1}"
HorizontalTextAlignment="Center"
LineBreakMode="WordWrap"
StyleClass="text-sm" />
<Image Source="accessibility_step2.png"
HorizontalOptions="Center"
Margin="0, 10, 0, 0"
WidthRequest="300"
HeightRequest="67" />
<Label Text="{u:I18n BitwardenAutofillServiceStep2}"
HorizontalTextAlignment="Center"
LineBreakMode="WordWrap"
StyleClass="text-sm" />
</StackLayout>
<StackLayout IsVisible="{Binding EnabledWithoutPermission}"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center"
Spacing="20">
<StackLayout Orientation="Horizontal" HorizontalOptions="Center">
<Label Text="{u:I18n Status}" />
<Label Text="{u:I18n Disabled}"
StyleClass="text-danger, text-bold" />
</StackLayout>
<Image Source="accessibility_permission.png"
HorizontalOptions="Center"
Margin="0, 20, 0, 0"
WidthRequest="300"
HeightRequest="142" />
<Label Text="{u:I18n BitwardenAutofillServiceStep3}"
HorizontalTextAlignment="Center"
LineBreakMode="WordWrap"
StyleClass="text-sm" />
</StackLayout>
<StackLayout IsVisible="{Binding EnabledAndPermitted}"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center"
Spacing="20">
<StackLayout Orientation="Horizontal" HorizontalOptions="Center">
<Label Text="{u:I18n Status}" />
<Label Text="{u:I18n Enabled}"
StyleClass="text-success, text-bold" />
</StackLayout>
<Image Source="accessibility_overlay.png"
HorizontalOptions="Center"
Margin="0, 20, 0, 0"
WidthRequest="300"
HeightRequest="172" />
<Label Text="{u:I18n BitwardenAutofillServiceOverlay}"
HorizontalTextAlignment="Center"
LineBreakMode="WordWrap"
StyleClass="text-sm" />
</StackLayout>
<StackLayout Spacing="10">
<Button Text="{u:I18n BitwardenAutofillServiceOpenAccessibilitySettings}"
Clicked="Settings_Clicked"
HorizontalOptions="Fill"
VerticalOptions="End"
IsVisible="{Binding Enabled, Converter={StaticResource inverseBool}}"></Button>
<Button Text="{u:I18n BitwardenAutofillServiceOpenOverlayPermissionSettings}"
Clicked="OverlayPermissionSettings_Clicked"
HorizontalOptions="Fill"
VerticalOptions="End"
IsVisible="{Binding Permitted, Converter={StaticResource inverseBool}}"></Button>
</StackLayout>
</StackLayout>
</ScrollView>
</pages:BaseContentPage>

View file

@ -1,66 +0,0 @@
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core.Utilities;
namespace Bit.App.Pages
{
public class AccessibilityServicePageViewModel : BaseViewModel
{
private readonly IDeviceActionService _deviceActionService;
private bool _enabled;
private bool _permitted;
public AccessibilityServicePageViewModel()
{
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
PageTitle = AppResources.AutofillAccessibilityService;
}
public bool Enabled
{
get => _enabled;
set => SetProperty(ref _enabled, value,
additionalPropertyNames: new string[]
{
nameof(EnabledWithoutPermission),
nameof(EnabledAndPermitted)
});
}
public bool Permitted
{
get => _permitted;
set => SetProperty(ref _permitted, value,
additionalPropertyNames: new string[]
{
nameof(EnabledWithoutPermission),
nameof(EnabledAndPermitted)
});
}
public bool EnabledWithoutPermission => Enabled && !Permitted;
public bool EnabledAndPermitted => Enabled && Permitted;
public void OpenSettings()
{
_deviceActionService.OpenAccessibilitySettings();
}
public void OpenOverlayPermissionSettings()
{
_deviceActionService.OpenAccessibilityOverlayPermissionSettings();
}
public void UpdateEnabled()
{
Enabled = _deviceActionService.AutofillAccessibilityServiceRunning();
}
public void UpdatePermitted()
{
Permitted = _deviceActionService.AutofillAccessibilityOverlayPermitted();
}
}
}

View file

@ -1,66 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<pages:BaseContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Pages.AutofillServicePage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
x:DataType="pages:AutofillServicePageViewModel"
Title="{Binding PageTitle}">
<ContentPage.BindingContext>
<pages:AutofillServicePageViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
</ResourceDictionary>
</ContentPage.Resources>
<ScrollView>
<StackLayout Spacing="20"
Padding="10, 30"
VerticalOptions="FillAndExpand">
<Label Text="{u:I18n AutofillServiceDescription}"
VerticalOptions="Start"
HorizontalTextAlignment="Center"
LineBreakMode="WordWrap" />
<StackLayout IsVisible="{Binding Enabled, Converter={StaticResource inverseBool}}"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center"
Spacing="20">
<StackLayout Orientation="Horizontal" HorizontalOptions="CenterAndExpand">
<Label Text="{u:I18n Status}" />
<Label Text="{u:I18n Disabled}"
StyleClass="text-danger, text-bold" />
</StackLayout>
<Image Source="autofill_enable.png"
HorizontalOptions="Center"
Margin="0, 20, 0, 0"
WidthRequest="300"
HeightRequest="118" />
</StackLayout>
<StackLayout IsVisible="{Binding Enabled}"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center"
Spacing="20">
<StackLayout Orientation="Horizontal" HorizontalOptions="Center">
<Label Text="{u:I18n Status}" />
<Label Text="{u:I18n Enabled}"
StyleClass="text-success, text-bold" />
</StackLayout>
<Image Source="autofill_use.png"
HorizontalOptions="Center"
Margin="0, 20, 0, 0"
WidthRequest="300"
HeightRequest="128" />
</StackLayout>
<Button Text="{u:I18n BitwardenAutofillServiceOpenAutofillSettings}"
Clicked="Settings_Clicked"
HorizontalOptions="Fill"
VerticalOptions="End"
IsVisible="{Binding Enabled, Converter={StaticResource inverseBool}}"></Button>
</StackLayout>
</ScrollView>
</pages:BaseContentPage>

View file

@ -1,52 +0,0 @@
using System;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public partial class AutofillServicePage : BaseContentPage
{
private readonly AutofillServicePageViewModel _vm;
private readonly SettingsPage _settingsPage;
private DateTime? _timerStarted = null;
private TimeSpan _timerMaxLength = TimeSpan.FromMinutes(5);
public AutofillServicePage(SettingsPage settingsPage)
{
InitializeComponent();
_vm = BindingContext as AutofillServicePageViewModel;
_vm.Page = this;
_settingsPage = settingsPage;
}
protected override void OnAppearing()
{
_vm.UpdateEnabled();
_timerStarted = DateTime.UtcNow;
Device.StartTimer(new TimeSpan(0, 0, 2), () =>
{
if (_timerStarted == null || (DateTime.UtcNow - _timerStarted) > _timerMaxLength)
{
return false;
}
_vm.UpdateEnabled();
return true;
});
base.OnAppearing();
}
protected override void OnDisappearing()
{
_timerStarted = null;
_settingsPage.BuildList();
base.OnDisappearing();
}
private void Settings_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
_vm.OpenSettings();
}
}
}
}

View file

@ -1,35 +0,0 @@
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core.Utilities;
namespace Bit.App.Pages
{
public class AutofillServicePageViewModel : BaseViewModel
{
private readonly IDeviceActionService _deviceActionService;
private bool _enabled;
public AutofillServicePageViewModel()
{
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
PageTitle = AppResources.AutofillService;
}
public bool Enabled
{
get => _enabled;
set => SetProperty(ref _enabled, value);
}
public void OpenSettings()
{
_deviceActionService.OpenAutofillSettings();
}
public void UpdateEnabled()
{
Enabled = _deviceActionService.AutofillServiceEnabled();
}
}
}

View file

@ -0,0 +1,133 @@
<?xml version="1.0" encoding="utf-8" ?>
<pages:BaseContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Pages.AutofillServicesPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
x:DataType="pages:AutofillServicesPageViewModel"
Title="{Binding PageTitle}">
<ContentPage.BindingContext>
<pages:AutofillServicesPageViewModel />
</ContentPage.BindingContext>
<ScrollView>
<StackLayout Padding="0" Spacing="20">
<StackLayout
StyleClass="box"
IsVisible="{Binding AutofillServiceVisible}">
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="{u:I18n AutofillService}"
StyleClass="box-label, box-label-regular"
HorizontalOptions="StartAndExpand" />
<RelativeLayout HorizontalOptions="End">
<Switch
x:Name="AutofillServiceSwitch"
IsToggled="{Binding AutofillServiceToggled}"
StyleClass="box-value"
HorizontalOptions="End" />
<Button
Clicked="ToggleAutofillService"
BackgroundColor="Transparent"
RelativeLayout.XConstraint="0"
RelativeLayout.YConstraint="0"
RelativeLayout.WidthConstraint="{ConstraintExpression Type=RelativeToView, ElementName=AutofillServiceSwitch, Property=Width}"
RelativeLayout.HeightConstraint="{ConstraintExpression Type=RelativeToView, ElementName=AutofillServiceSwitch, Property=Height}" />
</RelativeLayout>
</StackLayout>
<Label
Text="{u:I18n AutofillServiceDescription}"
StyleClass="box-footer-label, box-footer-label-switch" />
</StackLayout>
<StackLayout
StyleClass="box"
IsVisible="{Binding InlineAutofillVisible}">
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="{u:I18n InlineAutofill}"
StyleClass="box-label, box-label-regular"
IsEnabled="{Binding InlineAutofillEnabled}"
HorizontalOptions="StartAndExpand" />
<RelativeLayout HorizontalOptions="End">
<Switch
x:Name="InlineAutofillSwitch"
IsEnabled="{Binding InlineAutofillEnabled}"
IsToggled="{Binding InlineAutofillToggled}"
StyleClass="box-value"
HorizontalOptions="End" />
<Button
Clicked="ToggleInlineAutofill"
IsEnabled="{Binding InlineAutofillEnabled}"
BackgroundColor="Transparent"
RelativeLayout.XConstraint="0"
RelativeLayout.YConstraint="0"
RelativeLayout.WidthConstraint="{ConstraintExpression Type=RelativeToView, ElementName=InlineAutofillSwitch, Property=Width}"
RelativeLayout.HeightConstraint="{ConstraintExpression Type=RelativeToView, ElementName=InlineAutofillSwitch, Property=Height}" />
</RelativeLayout>
</StackLayout>
<Label
Text="{u:I18n InlineAutofillDescription}"
StyleClass="box-footer-label, box-footer-label-switch"
IsEnabled="{Binding InlineAutofillEnabled}"/>
</StackLayout>
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="{u:I18n Accessibility}"
StyleClass="box-label, box-label-regular"
HorizontalOptions="StartAndExpand" />
<RelativeLayout HorizontalOptions="End">
<Switch
x:Name="AccessibilitySwitch"
IsToggled="{Binding AccessibilityToggled}"
StyleClass="box-value"
HorizontalOptions="End" />
<Button
Clicked="ToggleAccessibility"
BackgroundColor="Transparent"
RelativeLayout.XConstraint="0"
RelativeLayout.YConstraint="0"
RelativeLayout.WidthConstraint="{ConstraintExpression Type=RelativeToView, ElementName=AccessibilitySwitch, Property=Width}"
RelativeLayout.HeightConstraint="{ConstraintExpression Type=RelativeToView, ElementName=AccessibilitySwitch, Property=Height}" />
</RelativeLayout>
</StackLayout>
<Label
Text="{Binding AccessibilityDescriptionLabel}"
StyleClass="box-footer-label, box-footer-label-switch" />
</StackLayout>
<StackLayout
StyleClass="box"
IsVisible="{Binding DrawOverVisible}">
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="{u:I18n DrawOver}"
StyleClass="box-label, box-label-regular"
IsEnabled="{Binding DrawOverEnabled}"
HorizontalOptions="StartAndExpand" />
<RelativeLayout HorizontalOptions="End">
<Switch
x:Name="DrawOverSwitch"
IsEnabled="{Binding DrawOverEnabled}"
IsToggled="{Binding DrawOverToggled}"
StyleClass="box-value"
HorizontalOptions="End" />
<Button
Clicked="ToggleDrawOver"
IsEnabled="{Binding DrawOverEnabled}"
BackgroundColor="Transparent"
RelativeLayout.XConstraint="0"
RelativeLayout.YConstraint="0"
RelativeLayout.WidthConstraint="{ConstraintExpression Type=RelativeToView, ElementName=DrawOverSwitch, Property=Width}"
RelativeLayout.HeightConstraint="{ConstraintExpression Type=RelativeToView, ElementName=DrawOverSwitch, Property=Height}" />
</RelativeLayout>
</StackLayout>
<Label
Text="{Binding DrawOverDescriptionLabel}"
StyleClass="box-footer-label, box-footer-label-switch"
IsEnabled="{Binding InlineAutofillEnabled}"/>
</StackLayout>
</StackLayout>
</ScrollView>
</pages:BaseContentPage>

View file

@ -3,34 +3,33 @@ using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
public partial class AccessibilityServicePage : BaseContentPage public partial class AutofillServicesPage : BaseContentPage
{ {
private readonly AccessibilityServicePageViewModel _vm; private readonly AutofillServicesPageViewModel _vm;
private readonly SettingsPage _settingsPage; private readonly SettingsPage _settingsPage;
private DateTime? _timerStarted = null; private DateTime? _timerStarted = null;
private TimeSpan _timerMaxLength = TimeSpan.FromMinutes(5); private TimeSpan _timerMaxLength = TimeSpan.FromMinutes(5);
public AccessibilityServicePage(SettingsPage settingsPage) public AutofillServicesPage(SettingsPage settingsPage)
{ {
InitializeComponent(); InitializeComponent();
_vm = BindingContext as AccessibilityServicePageViewModel; _vm = BindingContext as AutofillServicesPageViewModel;
_vm.Page = this; _vm.Page = this;
_settingsPage = settingsPage; _settingsPage = settingsPage;
} }
protected override void OnAppearing() protected async override void OnAppearing()
{ {
await _vm.InitAsync();
_vm.UpdateEnabled(); _vm.UpdateEnabled();
_vm.UpdatePermitted();
_timerStarted = DateTime.UtcNow; _timerStarted = DateTime.UtcNow;
Device.StartTimer(new TimeSpan(0, 0, 3), () => Device.StartTimer(new TimeSpan(0, 0, 0, 0, 500), () =>
{ {
if (_timerStarted == null || (DateTime.UtcNow - _timerStarted) > _timerMaxLength) if (_timerStarted == null || (DateTime.UtcNow - _timerStarted) > _timerMaxLength)
{ {
return false; return false;
} }
_vm.UpdateEnabled(); _vm.UpdateEnabled();
_vm.UpdatePermitted();
return true; return true;
}); });
base.OnAppearing(); base.OnAppearing();
@ -43,19 +42,32 @@ namespace Bit.App.Pages
base.OnDisappearing(); base.OnDisappearing();
} }
private void Settings_Clicked(object sender, EventArgs e) private void ToggleAutofillService(object sender, EventArgs e)
{ {
if (DoOnce()) if (DoOnce())
{ {
_vm.OpenSettings(); _vm.ToggleAutofillService();
} }
} }
private void OverlayPermissionSettings_Clicked(object sender, EventArgs e) private void ToggleInlineAutofill(object sender, EventArgs e)
{
_vm.ToggleInlineAutofill();
}
private void ToggleAccessibility(object sender, EventArgs e)
{ {
if (DoOnce()) if (DoOnce())
{ {
_vm.OpenOverlayPermissionSettings(); _vm.ToggleAccessibility();
}
}
private void ToggleDrawOver(object sender, EventArgs e)
{
if (DoOnce())
{
_vm.ToggleDrawOver();
} }
} }
} }

View file

@ -0,0 +1,201 @@
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.App.Services;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
namespace Bit.App.Pages
{
public class AutofillServicesPageViewModel : BaseViewModel
{
private readonly IDeviceActionService _deviceActionService;
private readonly IStorageService _storageService;
private readonly MobileI18nService _i18nService;
private bool _autofillServiceToggled;
private bool _inlineAutofillToggled;
private bool _accessibilityToggled;
private bool _drawOverToggled;
private bool _inited;
public AutofillServicesPageViewModel()
{
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService;
PageTitle = AppResources.AutofillServices;
}
#region Autofill Service
public bool AutofillServiceVisible
{
get => _deviceActionService.SystemMajorVersion() >= 26;
}
public bool AutofillServiceToggled
{
get => _autofillServiceToggled;
set => SetProperty(ref _autofillServiceToggled, value,
additionalPropertyNames: new string[]
{
nameof(InlineAutofillEnabled)
});
}
#endregion
#region Inline Autofill
public bool InlineAutofillVisible
{
get => _deviceActionService.SystemMajorVersion() >= 30;
}
public bool InlineAutofillEnabled
{
get => AutofillServiceToggled;
}
public bool InlineAutofillToggled
{
get => _inlineAutofillToggled;
set
{
if (SetProperty(ref _inlineAutofillToggled, value))
{
var task = UpdateInlineAutofillToggledAsync();
}
}
}
#endregion
#region Accessibility
public string AccessibilityDescriptionLabel
{
get
{
if (_deviceActionService.SystemMajorVersion() <= 22)
{
// Android 5
return _i18nService.T("AccessibilityDescription");
}
if (_deviceActionService.SystemMajorVersion() == 23)
{
// Android 6
return _i18nService.T("AccessibilityDescription2");
}
if (_deviceActionService.SystemMajorVersion() == 24 || _deviceActionService.SystemMajorVersion() == 25)
{
// Android 7
return _i18nService.T("AccessibilityDescription3");
}
// Android 8+
return _i18nService.T("AccessibilityDescription4");
}
}
public bool AccessibilityToggled
{
get => _accessibilityToggled;
set => SetProperty(ref _accessibilityToggled, value,
additionalPropertyNames: new string[]
{
nameof(DrawOverEnabled)
});
}
#endregion
#region Draw-Over
public bool DrawOverVisible
{
get => _deviceActionService.SystemMajorVersion() >= 23;
}
public string DrawOverDescriptionLabel
{
get
{
if (_deviceActionService.SystemMajorVersion() <= 23)
{
// Android 6
return _i18nService.T("DrawOverDescription");
}
if (_deviceActionService.SystemMajorVersion() == 24 || _deviceActionService.SystemMajorVersion() == 25)
{
// Android 7
return _i18nService.T("DrawOverDescription2");
}
// Android 8+
return _i18nService.T("DrawOverDescription3");
}
}
public bool DrawOverEnabled
{
get => AccessibilityToggled;
}
public bool DrawOverToggled
{
get => _drawOverToggled;
set => SetProperty(ref _drawOverToggled, value);
}
#endregion
public async Task InitAsync()
{
InlineAutofillToggled = await _storageService.GetAsync<bool?>(Constants.InlineAutofillEnabledKey) ?? true;
_inited = true;
}
public void ToggleAutofillService()
{
if (!AutofillServiceToggled)
{
_deviceActionService.OpenAutofillSettings();
}
else
{
_deviceActionService.DisableAutofillService();
}
}
public void ToggleInlineAutofill()
{
InlineAutofillToggled = !InlineAutofillToggled;
}
public void ToggleAccessibility()
{
_deviceActionService.OpenAccessibilitySettings();
}
public void ToggleDrawOver()
{
_deviceActionService.OpenAccessibilityOverlayPermissionSettings();
}
public void UpdateEnabled()
{
AutofillServiceToggled = _deviceActionService.AutofillServiceEnabled();
AccessibilityToggled = _deviceActionService.AutofillAccessibilityServiceRunning();
DrawOverToggled = _deviceActionService.AutofillAccessibilityOverlayPermitted();
}
private async Task UpdateInlineAutofillToggledAsync()
{
if (_inited)
{
await _storageService.SaveAsync(Constants.InlineAutofillEnabledKey, InlineAutofillToggled);
}
}
}
}

View file

@ -57,13 +57,9 @@ namespace Bit.App.Pages
{ {
await Navigation.PushModalAsync(new NavigationPage(new SyncPage())); await Navigation.PushModalAsync(new NavigationPage(new SyncPage()));
} }
else if (item.Name == AppResources.AutofillAccessibilityService) else if (item.Name == AppResources.AutofillServices)
{ {
await Navigation.PushModalAsync(new NavigationPage(new AccessibilityServicePage(this))); await Navigation.PushModalAsync(new NavigationPage(new AutofillServicesPage(this)));
}
else if (item.Name == AppResources.AutofillService)
{
await Navigation.PushModalAsync(new NavigationPage(new AutofillServicePage(this)));
} }
else if (item.Name == AppResources.PasswordAutofill) else if (item.Name == AppResources.PasswordAutofill)
{ {

View file

@ -326,22 +326,10 @@ namespace Bit.App.Pages
var autofillItems = new List<SettingsPageListItem>(); var autofillItems = new List<SettingsPageListItem>();
if (Device.RuntimePlatform == Device.Android) if (Device.RuntimePlatform == Device.Android)
{ {
if (_deviceActionService.SupportsAutofillService())
{
autofillItems.Add(new SettingsPageListItem
{
Name = AppResources.AutofillService,
SubLabel = _deviceActionService.AutofillServiceEnabled() ?
AppResources.Enabled : AppResources.Disabled
});
}
var accessibilityEnabled = _deviceActionService.AutofillAccessibilityServiceRunning() &&
_deviceActionService.AutofillAccessibilityOverlayPermitted();
autofillItems.Add(new SettingsPageListItem autofillItems.Add(new SettingsPageListItem
{ {
Name = AppResources.AutofillAccessibilityService, Name = AppResources.AutofillServices,
SubLabel = accessibilityEnabled ? SubLabel = _deviceActionService.AutofillServicesEnabled() ?
AppResources.Enabled : AppResources.Disabled AppResources.Enabled : AppResources.Disabled
}); });
} }

View file

@ -3074,5 +3074,83 @@ namespace Bit.App.Resources {
return ResourceManager.GetString("PrivacyPolicy", resourceCulture); return ResourceManager.GetString("PrivacyPolicy", resourceCulture);
} }
} }
public static string AccessibilityDrawOverPermissionAlert {
get {
return ResourceManager.GetString("AccessibilityDrawOverPermissionAlert", resourceCulture);
}
}
public static string AutofillServices {
get {
return ResourceManager.GetString("AutofillServices", resourceCulture);
}
}
public static string InlineAutofill {
get {
return ResourceManager.GetString("InlineAutofill", resourceCulture);
}
}
public static string InlineAutofillDescription {
get {
return ResourceManager.GetString("InlineAutofillDescription", resourceCulture);
}
}
public static string Accessibility {
get {
return ResourceManager.GetString("Accessibility", resourceCulture);
}
}
public static string AccessibilityDescription {
get {
return ResourceManager.GetString("AccessibilityDescription", resourceCulture);
}
}
public static string AccessibilityDescription2 {
get {
return ResourceManager.GetString("AccessibilityDescription2", resourceCulture);
}
}
public static string AccessibilityDescription3 {
get {
return ResourceManager.GetString("AccessibilityDescription3", resourceCulture);
}
}
public static string AccessibilityDescription4 {
get {
return ResourceManager.GetString("AccessibilityDescription4", resourceCulture);
}
}
public static string DrawOver {
get {
return ResourceManager.GetString("DrawOver", resourceCulture);
}
}
public static string DrawOverDescription {
get {
return ResourceManager.GetString("DrawOverDescription", resourceCulture);
}
}
public static string DrawOverDescription2 {
get {
return ResourceManager.GetString("DrawOverDescription2", resourceCulture);
}
}
public static string DrawOverDescription3 {
get {
return ResourceManager.GetString("DrawOverDescription3", resourceCulture);
}
}
} }
} }

View file

@ -1744,4 +1744,43 @@
<data name="PrivacyPolicy" xml:space="preserve"> <data name="PrivacyPolicy" xml:space="preserve">
<value>Privacy Policy</value> <value>Privacy Policy</value>
</data> </data>
<data name="AccessibilityDrawOverPermissionAlert" xml:space="preserve">
<value>Bitwarden needs attention - Enable "Draw-Over" in "Auto-fill Services" from Bitwarden Settings</value>
</data>
<data name="AutofillServices" xml:space="preserve">
<value>Auto-fill Services</value>
</data>
<data name="InlineAutofill" xml:space="preserve">
<value>Use Inline Autofill</value>
</data>
<data name="InlineAutofillDescription" xml:space="preserve">
<value>Use inline autofill if your selected IME (keyboard) supports it. If your configuration is not supported (or this option is disabled), the default Autofill overlay will be used.</value>
</data>
<data name="Accessibility" xml:space="preserve">
<value>Use Accessibility</value>
</data>
<data name="AccessibilityDescription" xml:space="preserve">
<value>Use the Bitwarden Accessibility Service to auto-fill your logins across apps and the web. When enabled, we'll display a popup when login fields are selected.</value>
</data>
<data name="AccessibilityDescription2" xml:space="preserve">
<value>Use the Bitwarden Accessibility Service to auto-fill your logins across apps and the web. (Requires Draw-Over to be enabled as well)</value>
</data>
<data name="AccessibilityDescription3" xml:space="preserve">
<value>Use the Bitwarden Accessibility Service to use the Autofill Quick-Action Tile, and/or show a popup using Draw-Over (if enabled).</value>
</data>
<data name="AccessibilityDescription4" xml:space="preserve">
<value>Required to use the Autofill Quick-Action Tile, or to augment the Autofill Service by using Draw-Over (if enabled).</value>
</data>
<data name="DrawOver" xml:space="preserve">
<value>Use Draw-Over</value>
</data>
<data name="DrawOverDescription" xml:space="preserve">
<value>When enabled, allows the Bitwarden Accessibility Service to display a popup when login fields are selected.</value>
</data>
<data name="DrawOverDescription2" xml:space="preserve">
<value>If enabled, the Bitwarden Accessibility Service will display a popup when login fields are selected to assist with auto-filling your logins.</value>
</data>
<data name="DrawOverDescription3" xml:space="preserve">
<value>If enabled, accessibility will show a popup to augment the Autofill Service for older apps that don't support the Android Autofill Framework.</value>
</data>
</root> </root>

View file

@ -35,6 +35,7 @@ namespace Bit.App.Services
Constants.iOSAutoFillClearCiphersCacheKey, Constants.iOSAutoFillClearCiphersCacheKey,
Constants.iOSExtensionClearCiphersCacheKey, Constants.iOSExtensionClearCiphersCacheKey,
Constants.EnvironmentUrlsKey, Constants.EnvironmentUrlsKey,
Constants.InlineAutofillEnabledKey,
}; };
private readonly HashSet<string> _migrateToPreferences = new HashSet<string> private readonly HashSet<string> _migrateToPreferences = new HashSet<string>

View file

@ -36,6 +36,7 @@
public static string TriedV1Resync = "triedV1Resync"; public static string TriedV1Resync = "triedV1Resync";
public static string EventCollectionKey = "eventCollection"; public static string EventCollectionKey = "eventCollection";
public static string PreviousPageKey = "previousPage"; public static string PreviousPageKey = "previousPage";
public static string InlineAutofillEnabledKey = "inlineAutofillEnabled";
public const int SelectFileRequestCode = 42; public const int SelectFileRequestCode = 42;
public const int SelectFilePermissionRequestCode = 43; public const int SelectFilePermissionRequestCode = 43;
public const int SaveFileRequestCode = 44; public const int SaveFileRequestCode = 44;

View file

@ -392,6 +392,16 @@ namespace Bit.iOS.Core.Services
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void DisableAutofillService()
{
throw new NotImplementedException();
}
public bool AutofillServicesEnabled()
{
throw new NotImplementedException();
}
public string GetBuildNumber() public string GetBuildNumber()
{ {
return NSBundle.MainBundle.InfoDictionary["CFBundleVersion"].ToString(); return NSBundle.MainBundle.InfoDictionary["CFBundleVersion"].ToString();