UI support in app settings for handling overlay permission requirement in Accessibility Service implementation (#697)

* UI support in app settings for handling overlay permission requirement in Accessibility Service implementation

* Cleaned up shorthand operator with new var
This commit is contained in:
Matt Portune 2020-01-10 10:20:19 -05:00 committed by Kyle Spearrin
parent fbe8708378
commit 641122b16f
26 changed files with 883 additions and 703 deletions

View file

@ -268,11 +268,11 @@ namespace Bit.Droid.Accessibility
return allEditTexts.TakeWhile(n => !n.Password).LastOrDefault();
}
public static bool OverlayPermitted(Context context)
public static bool OverlayPermitted()
{
if(Build.VERSION.SdkInt >= BuildVersionCodes.M)
{
return Settings.CanDrawOverlays(context.ApplicationContext);
return Settings.CanDrawOverlays(Android.App.Application.Context);
}
else
{
@ -281,22 +281,6 @@ namespace Bit.Droid.Accessibility
}
}
public static bool OpenOverlaySettings(Context context, string packageName)
{
try
{
var intent = new Intent(Settings.ActionManageOverlayPermission);
intent.SetPackage(packageName);
intent.SetFlags(ActivityFlags.NewTask);
context.StartActivity(intent);
return true;
}
catch
{
return false;
}
}
public static LinearLayout GetOverlayView(Context context)
{
var inflater = (LayoutInflater)context.GetSystemService(Context.LayoutInflaterService);

View file

@ -193,7 +193,7 @@ namespace Bit.Droid.Accessibility
private void OverlayPromptToAutofill(AccessibilityNodeInfo root, AccessibilityEvent e)
{
if(!AccessibilityHelpers.OverlayPermitted(this))
if(!AccessibilityHelpers.OverlayPermitted())
{
System.Diagnostics.Debug.WriteLine(">>> Overlay Permission not granted");
Toast.MakeText(this, AppResources.AccessibilityOverlayPermissionAlert, ToastLength.Long).Show();

View file

@ -307,12 +307,6 @@
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\autofill_use.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\accessibility_notification.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\accessibility_notification_icon.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\accessibility_step1.png" />
</ItemGroup>
@ -325,12 +319,6 @@
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\yubikey.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\accessibility_notification.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\accessibility_notification_icon.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\accessibility_step1.png" />
</ItemGroup>
@ -352,12 +340,6 @@
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\yubikey.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\accessibility_notification.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\accessibility_notification_icon.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\accessibility_step1.png" />
</ItemGroup>
@ -553,5 +535,23 @@
<SubType>Designer</SubType>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\accessibility_overlay.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\accessibility_overlay.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\accessibility_overlay.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\accessibility_permission.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\accessibility_permission.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\accessibility_permission.png" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
</Project>

File diff suppressed because it is too large Load diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -625,6 +625,40 @@ namespace Bit.Droid.Services
}
}
public bool AutofillAccessibilityOverlayPermitted()
{
return Accessibility.AccessibilityHelpers.OverlayPermitted();
}
public void OpenAccessibilityOverlayPermissionSettings()
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
try
{
var intent = new Intent(Settings.ActionManageOverlayPermission);
intent.SetData(Android.Net.Uri.Parse("package:com.x8bit.bitwarden"));
activity.StartActivity(intent);
}
catch(ActivityNotFoundException)
{
// can't open overlay permission management, fall back to app settings
var intent = new Intent(Settings.ActionApplicationDetailsSettings);
intent.SetData(Android.Net.Uri.Parse("package:com.x8bit.bitwarden"));
activity.StartActivity(intent);
}
catch
{
var alertBuilder = new AlertDialog.Builder(activity);
alertBuilder.SetMessage(AppResources.BitwardenAutofillGoToSettings);
alertBuilder.SetCancelable(true);
alertBuilder.SetPositiveButton(AppResources.Ok, (sender, args) =>
{
(sender as AlertDialog)?.Cancel();
});
alertBuilder.Create().Show();
}
}
public bool AutofillServiceEnabled()
{
if(Build.VERSION.SdkInt < BuildVersionCodes.O)

View file

@ -35,9 +35,11 @@ namespace Bit.App.Abstractions
void CloseAutofill();
void Background();
bool AutofillAccessibilityServiceRunning();
bool AutofillAccessibilityOverlayPermitted();
bool AutofillServiceEnabled();
string GetBuildNumber();
void OpenAccessibilitySettings();
void OpenAccessibilityOverlayPermissionSettings();
void OpenAutofillSettings();
bool UsingDarkTheme();
}

View file

@ -25,18 +25,40 @@
VerticalOptions="Start"
HorizontalTextAlignment="Center"
LineBreakMode="WordWrap" />
<StackLayout IsVisible="{Binding Enabled, Converter={StaticResource inverseBool}}"
VerticalOptions="CenterAndExpand"
<StackLayout VerticalOptions="CenterAndExpand"
HorizontalOptions="Center"
Spacing="20">
<StackLayout Orientation="Horizontal" HorizontalOptions="CenterAndExpand">
Spacing="10">
<StackLayout IsVisible="{Binding Enabled, Converter={StaticResource inverseBool}}"
Orientation="Horizontal" HorizontalOptions="Center">
<Label Text="{u:I18n Status}" />
<Label Text="{u:I18n Disabled}"
StyleClass="text-danger, text-bold" />
</StackLayout>
<StackLayout IsVisible="{Binding Enabled}"
Orientation="Horizontal" HorizontalOptions="Center">
<Label Text="{u:I18n Status}" />
<Label Text="{u:I18n Enabled}"
StyleClass="text-success, text-bold" />
</StackLayout>
<StackLayout IsVisible="{Binding Permitted, Converter={StaticResource inverseBool}}"
Orientation="Horizontal" HorizontalOptions="Center">
<Label Text="{u:I18n OverlayPermission}" />
<Label Text="{u:I18n Denied}"
StyleClass="text-danger, text-bold" />
</StackLayout>
<StackLayout IsVisible="{Binding Permitted}"
Orientation="Horizontal" HorizontalOptions="Center">
<Label Text="{u:I18n OverlayPermission}" />
<Label Text="{u:I18n Granted}"
StyleClass="text-success, text-bold" />
</StackLayout>
</StackLayout>
<StackLayout IsVisible="{Binding Enabled, Converter={StaticResource inverseBool}}"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center"
Spacing="20">
<Image Source="accessibility_step1.png"
HorizontalOptions="Center"
Margin="0, 20, 0, 0"
WidthRequest="300"
HeightRequest="98" />
<Label Text="{u:I18n BitwardenAutofillServiceStep1}"
@ -53,26 +75,28 @@
LineBreakMode="WordWrap"
StyleClass="text-sm" />
</StackLayout>
<StackLayout IsVisible="{Binding Enabled}"
<StackLayout IsVisible="{Binding EnabledWithoutPermission}"
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_notification_icon.png"
<Image Source="accessibility_permission.png"
HorizontalOptions="Center"
Margin="0, 20, 0, 0"
WidthRequest="300"
HeightRequest="54" />
<Image Source="accessibility_notification.png"
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">
<Image Source="accessibility_overlay.png"
HorizontalOptions="Center"
Margin="0, 20, 0, 0"
WidthRequest="300"
HeightRequest="74" />
<Label Text="{u:I18n BitwardenAutofillServiceNotification}"
HeightRequest="172" />
<Label Text="{u:I18n BitwardenAutofillServiceOverlay}"
HorizontalTextAlignment="Center"
LineBreakMode="WordWrap"
StyleClass="text-sm" />
@ -81,6 +105,10 @@
Clicked="Settings_Clicked"
HorizontalOptions="Fill"
VerticalOptions="End"></Button>
<Button Text="{u:I18n BitwardenAutofillServiceOpenOverlayPermissionSettings}"
Clicked="OverlayPermissionSettings_Clicked"
HorizontalOptions="Fill"
VerticalOptions="End"></Button>
</StackLayout>
</ScrollView>

View file

@ -21,6 +21,7 @@ namespace Bit.App.Pages
protected override void OnAppearing()
{
_vm.UpdateEnabled();
_vm.UpdatePermitted();
_timerStarted = DateTime.UtcNow;
Device.StartTimer(new TimeSpan(0, 0, 3), () =>
{
@ -29,6 +30,7 @@ namespace Bit.App.Pages
return false;
}
_vm.UpdateEnabled();
_vm.UpdatePermitted();
return true;
});
base.OnAppearing();
@ -48,5 +50,13 @@ namespace Bit.App.Pages
_vm.OpenSettings();
}
}
private void OverlayPermissionSettings_Clicked(object sender, EventArgs e)
{
if(DoOnce())
{
_vm.OpenOverlayPermissionSettings();
}
}
}
}

View file

@ -9,6 +9,7 @@ namespace Bit.App.Pages
private readonly IDeviceActionService _deviceActionService;
private bool _enabled;
private bool _permitted;
public AccessibilityServicePageViewModel()
{
@ -19,17 +20,47 @@ namespace Bit.App.Pages
public bool Enabled
{
get => _enabled;
set => SetProperty(ref _enabled, value);
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

@ -24,7 +24,7 @@ namespace Bit.App.Pages
if(Device.RuntimePlatform == Device.Android)
{
ToolbarItems.RemoveAt(0);
_vm.ShowAndroidAccessibilitySettings = true;
_vm.ShowAndroidAccessibilitySettings = false;
_vm.ShowAndroidAutofillSettings = _deviceActionService.SupportsAutofillService();
_themeDescriptionLabel.Text = string.Concat(_themeDescriptionLabel.Text, " ",
AppResources.RestartIsRequired);

View file

@ -292,10 +292,13 @@ namespace Bit.App.Pages
AppResources.Enabled : AppResources.Disabled
});
}
var accessibilityEnabled = _deviceActionService.AutofillAccessibilityServiceRunning() &&
_deviceActionService.AutofillAccessibilityOverlayPermitted();
autofillItems.Add(new SettingsPageListItem
{
Name = AppResources.AutofillAccessibilityService,
SubLabel = _deviceActionService.AutofillAccessibilityServiceRunning() ?
SubLabel = accessibilityEnabled ?
AppResources.Enabled : AppResources.Disabled
});
}

View file

@ -70,7 +70,7 @@ namespace Bit.App.Resources {
}
/// <summary>
/// Looks up a localized string similar to Bitwarden requires additional accessibility configuration. Open the app for more info..
/// Looks up a localized string similar to Bitwarden needs attention - See &quot;Auto-fill Accessibility Service&quot; from Bitwarden Settings.
/// </summary>
public static string AccessibilityOverlayPermissionAlert {
get {
@ -681,15 +681,6 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to When you see a Bitwarden auto-fill notification, you can tap it to launch the auto-fill service..
/// </summary>
public static string BitwardenAutofillServiceNotification {
get {
return ResourceManager.GetString("BitwardenAutofillServiceNotification", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Tap this notification to auto-fill an item from your vault..
/// </summary>
@ -726,6 +717,33 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Open Overlay Permission Settings.
/// </summary>
public static string BitwardenAutofillServiceOpenOverlayPermissionSettings {
get {
return ResourceManager.GetString("BitwardenAutofillServiceOpenOverlayPermissionSettings", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to When you select an input field and see a Bitwarden auto-fill overlay, you can tap it to launch the auto-fill service..
/// </summary>
public static string BitwardenAutofillServiceOverlay {
get {
return ResourceManager.GetString("BitwardenAutofillServiceOverlay", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to 3. On the Android App Settings screen for Bitwarden, go to the &quot;Display over other apps&quot; options (under Advanced) and tap the toggle to enable overlay support..
/// </summary>
public static string BitwardenAutofillServiceOverlayPermission {
get {
return ResourceManager.GetString("BitwardenAutofillServiceOverlayPermission", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to You are searching for an auto-fill item for &quot;{0}&quot;..
/// </summary>
@ -753,6 +771,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to 3. On the Android App Settings screen for Bitwarden, select &quot;Display over other apps&quot; (under &quot;Advanced&quot;) and switch on the toggle to allow the overlay..
/// </summary>
public static string BitwardenAutofillServiceStep3 {
get {
return ResourceManager.GetString("BitwardenAutofillServiceStep3", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Black.
/// </summary>
@ -1212,6 +1239,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Denied.
/// </summary>
public static string Denied {
get {
return ResourceManager.GetString("Denied", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Disable Automatic TOTP Copy.
/// </summary>
@ -1869,6 +1905,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Granted.
/// </summary>
public static string Granted {
get {
return ResourceManager.GetString("Granted", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Help and Feedback.
/// </summary>
@ -2904,6 +2949,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Permission.
/// </summary>
public static string OverlayPermission {
get {
return ResourceManager.GetString("OverlayPermission", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Ownership.
/// </summary>

View file

@ -728,8 +728,8 @@
<value>There are no items in your vault for {0}.</value>
<comment>This is used for the autofill service. ex. "There are no items in your vault for twitter.com".</comment>
</data>
<data name="BitwardenAutofillServiceNotification" xml:space="preserve">
<value>When you see a Bitwarden auto-fill notification, you can tap it to launch the auto-fill service.</value>
<data name="BitwardenAutofillServiceOverlay" xml:space="preserve">
<value>When you select an input field and see a Bitwarden auto-fill overlay, you can tap it to launch the auto-fill service.</value>
</data>
<data name="BitwardenAutofillServiceNotificationContent" xml:space="preserve">
<value>Tap this notification to auto-fill an item from your vault.</value>
@ -1588,6 +1588,24 @@
<value>Use Biometrics To Unlock</value>
</data>
<data name="AccessibilityOverlayPermissionAlert" xml:space="preserve">
<value>Bitwarden requires additional accessibility configuration. Open the app for more info.</value>
<value>Bitwarden needs attention - See "Auto-fill Accessibility Service" from Bitwarden Settings</value>
</data>
<data name="BitwardenAutofillServiceOverlayPermission" xml:space="preserve">
<value>3. On the Android App Settings screen for Bitwarden, go to the "Display over other apps" options (under Advanced) and tap the toggle to enable overlay support.</value>
</data>
<data name="OverlayPermission" xml:space="preserve">
<value>Permission</value>
</data>
<data name="BitwardenAutofillServiceOpenOverlayPermissionSettings" xml:space="preserve">
<value>Open Overlay Permission Settings</value>
</data>
<data name="BitwardenAutofillServiceStep3" xml:space="preserve">
<value>3. On the Android App Settings screen for Bitwarden, select "Display over other apps" (under "Advanced") and switch on the toggle to allow the overlay.</value>
</data>
<data name="Denied" xml:space="preserve">
<value>Denied</value>
</data>
<data name="Granted" xml:space="preserve">
<value>Granted</value>
</data>
</root>

View file

@ -516,6 +516,16 @@ namespace Bit.iOS.Core.Services
url.StopAccessingSecurityScopedResource();
}
public bool AutofillAccessibilityOverlayPermitted()
{
throw new NotImplementedException();
}
public void OpenAccessibilityOverlayPermissionSettings()
{
throw new NotImplementedException();
}
public class PickerDelegate : UIDocumentPickerDelegate
{
private readonly DeviceActionService _deviceActionService;