Added Quick Settings tile for triggering accessibility autofill (#795)

* Added Quick Settings tile for triggering accessibility autofill

* Fix crash when tile attempt to cancel non-visible but non-null overlay

* Persist tile state plus cleanup
This commit is contained in:
Matt Portune 2020-03-26 12:15:33 -04:00 committed by GitHub
parent 5d9a597d8d
commit 6c00ac43fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 205 additions and 6 deletions

View File

@ -4,6 +4,8 @@ using Android.OS;
using Android.Runtime;
using Android.Views;
using System;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
namespace Bit.Droid.Accessibility
{
@ -16,13 +18,13 @@ namespace Bit.Droid.Accessibility
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
LaunchMainActivity(Intent, 932473);
HandleIntent(Intent, 932473);
}
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
LaunchMainActivity(intent, 489729);
HandleIntent(intent, 489729);
}
protected override void OnDestroy()
@ -78,6 +80,21 @@ namespace Bit.Droid.Accessibility
Finish();
}
private void HandleIntent(Intent callingIntent, int requestCode)
{
if(callingIntent?.GetBooleanExtra("autofillTileClicked", false) ?? false)
{
Intent.RemoveExtra("autofillTileClicked");
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
messagingService.Send("OnAutofillTileClick");
Finish();
}
else
{
LaunchMainActivity(callingIntent, requestCode);
}
}
private void LaunchMainActivity(Intent callingIntent, int requestCode)
{
_lastQueriedUri = callingIntent?.GetStringExtra("uri");

View File

@ -20,6 +20,8 @@ namespace Bit.Droid.Accessibility
public static Credentials LastCredentials = null;
public static string SystemUiPackage = "com.android.systemui";
public static string BitwardenTag = "bw_access";
public static bool IsAutofillTileAdded = false;
public static bool IsAccessibilityBroadcastReady = false;
public static Dictionary<string, Browser> SupportedBrowsers => new List<Browser>
{

View File

@ -29,6 +29,7 @@ namespace Bit.Droid.Accessibility
private const string BitwardenWebsite = "vault.bitwarden.com";
private IStorageService _storageService;
private IBroadcasterService _broadcasterService;
private DateTime? _lastSettingsReload = null;
private TimeSpan _settingsReloadSpan = TimeSpan.FromMinutes(1);
private HashSet<string> _blacklistedUris;
@ -48,6 +49,28 @@ namespace Bit.Droid.Accessibility
private DateTime? _lastLauncherSetBuilt = null;
private TimeSpan _rebuildLauncherSpan = TimeSpan.FromHours(1);
public override void OnCreate()
{
base.OnCreate();
LoadServices();
var settingsTask = LoadSettingsAsync();
_broadcasterService.Subscribe(nameof(AccessibilityService), (message) =>
{
if(message.Command == "OnAutofillTileClick")
{
var runnable = new Java.Lang.Runnable(OnAutofillTileClick);
_handler.PostDelayed(runnable, 250);
}
});
AccessibilityHelpers.IsAccessibilityBroadcastReady = true;
}
public override void OnDestroy()
{
AccessibilityHelpers.IsAccessibilityBroadcastReady = false;
_broadcasterService.Unsubscribe(nameof(AccessibilityService));
}
public override void OnAccessibilityEvent(AccessibilityEvent e)
{
try
@ -174,6 +197,29 @@ namespace Bit.Droid.Accessibility
passwordNodes.Dispose();
return filled;
}
private void OnAutofillTileClick()
{
CancelOverlayPrompt();
var root = RootInActiveWindow;
if(root != null && root.PackageName != BitwardenPackage &&
root.PackageName != AccessibilityHelpers.SystemUiPackage &&
!SkipPackage(root.PackageName))
{
var uri = AccessibilityHelpers.GetUri(root);
if(!string.IsNullOrWhiteSpace(uri))
{
var intent = new Intent(this, typeof(AccessibilityActivity));
intent.PutExtra("uri", uri);
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
StartActivity(intent);
return;
}
}
Toast.MakeText(this, AppResources.AutofillTileUriNotFound, ToastLength.Long).Show();
}
private void CancelOverlayPrompt()
{
@ -181,8 +227,12 @@ namespace Bit.Droid.Accessibility
if(_windowManager != null && _overlayView != null)
{
_windowManager.RemoveViewImmediate(_overlayView);
System.Diagnostics.Debug.WriteLine(">>> Accessibility Overlay View Removed");
try
{
_windowManager.RemoveViewImmediate(_overlayView);
System.Diagnostics.Debug.WriteLine(">>> Accessibility Overlay View Removed");
}
catch { }
}
_overlayView = null;
@ -201,8 +251,14 @@ namespace Bit.Droid.Accessibility
{
if(!AccessibilityHelpers.OverlayPermitted())
{
System.Diagnostics.Debug.WriteLine(">>> Overlay Permission not granted");
Toast.MakeText(this, AppResources.AccessibilityOverlayPermissionAlert, ToastLength.Long).Show();
if(!AccessibilityHelpers.IsAutofillTileAdded)
{
// 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
// have _not_ added the autofill tile
System.Diagnostics.Debug.WriteLine(">>> Overlay Permission not granted");
Toast.MakeText(this, AppResources.AccessibilityOverlayPermissionAlert, ToastLength.Long).Show();
}
return;
}
@ -392,6 +448,10 @@ namespace Bit.Droid.Accessibility
{
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
}
if(_broadcasterService == null)
{
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
}
}
private async Task LoadSettingsAsync()
@ -405,6 +465,8 @@ namespace Bit.Droid.Accessibility
{
_blacklistedUris = new HashSet<string>(uris);
}
var isAutoFillTileAdded = await _storageService.GetAsync<bool?>(Constants.AutofillTileAdded);
AccessibilityHelpers.IsAutofillTileAdded = isAutoFillTileAdded.GetValueOrDefault();
}
}
}

View File

@ -136,6 +136,7 @@
<Compile Include="Services\CryptoPrimitiveService.cs" />
<Compile Include="Services\DeviceActionService.cs" />
<Compile Include="Services\LocalizeService.cs" />
<Compile Include="Tiles\AutofillTileService.cs" />
<Compile Include="Tiles\GeneratorTileService.cs" />
<Compile Include="Tiles\MyVaultTileService.cs" />
<Compile Include="Utilities\AndroidHelpers.cs" />

View File

@ -16,6 +16,9 @@
<string name="PasswordGenerator">
Password Generator
</string>
<string name="ScanAndFill">
Scan &amp; Fill
</string>
<string name="SelfHostedServerUrl">
Self-hosted server URL
</string>

View File

@ -0,0 +1,95 @@
using Android;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Service.QuickSettings;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Bit.Droid.Accessibility;
using Java.Lang;
namespace Bit.Droid.Tile
{
[Service(Permission = Manifest.Permission.BindQuickSettingsTile, Label = "@string/ScanAndFill",
Icon = "@drawable/shield")]
[IntentFilter(new string[] { ActionQsTile })]
[Register("com.x8bit.bitwarden.AutofillTileService")]
public class AutofillTileService : TileService
{
private IStorageService _storageService;
public override void OnTileAdded()
{
base.OnTileAdded();
SetTileAdded(true);
}
public override void OnStartListening()
{
base.OnStartListening();
}
public override void OnStopListening()
{
base.OnStopListening();
}
public override void OnTileRemoved()
{
base.OnTileRemoved();
SetTileAdded(false);
}
public override void OnClick()
{
base.OnClick();
if(IsLocked)
{
UnlockAndRun(new Runnable(ScanAndFill));
}
else
{
ScanAndFill();
}
}
private void SetTileAdded(bool isAdded)
{
AccessibilityHelpers.IsAutofillTileAdded = isAdded;
if(_storageService == null)
{
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
}
_storageService.SaveAsync(Constants.AutofillTileAdded, isAdded);
}
private void ScanAndFill()
{
if(!AccessibilityHelpers.IsAccessibilityBroadcastReady)
{
ShowConfigErrorDialog();
return;
}
var intent = new Intent(this, typeof(AccessibilityActivity));
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
intent.PutExtra("autofillTileClicked", true);
StartActivityAndCollapse(intent);
}
private void ShowConfigErrorDialog()
{
var alertBuilder = new AlertDialog.Builder(this);
alertBuilder.SetMessage(AppResources.AutofillTileAccessibilityRequired);
alertBuilder.SetCancelable(true);
alertBuilder.SetPositiveButton(AppResources.Ok, (sender, args) =>
{
(sender as AlertDialog)?.Cancel();
});
ShowDialog(alertBuilder.Create());
}
}
}

View File

@ -2859,5 +2859,17 @@ namespace Bit.App.Resources {
return ResourceManager.GetString("SaveAttachmentSuccess", resourceCulture);
}
}
public static string AutofillTileAccessibilityRequired {
get {
return ResourceManager.GetString("AutofillTileAccessibilityRequired", resourceCulture);
}
}
public static string AutofillTileUriNotFound {
get {
return ResourceManager.GetString("AutofillTileUriNotFound", resourceCulture);
}
}
}
}

View File

@ -1625,4 +1625,10 @@
<data name="SaveAttachmentSuccess" xml:space="preserve">
<value>Attachment saved successfully</value>
</data>
<data name="AutofillTileAccessibilityRequired" xml:space="preserve">
<value>Please enable "Auto-fill Accessibility Service" from Bitwarden Settings to use the Scan &amp; Fill tile.</value>
</data>
<data name="AutofillTileUriNotFound" xml:space="preserve">
<value>No password fields detected</value>
</data>
</root>

View File

@ -15,6 +15,7 @@
public static string LastFileCacheClearKey = "lastFileCacheClear";
public static string AutofillDisableSavePromptKey = "autofillDisableSavePrompt";
public static string AutofillBlacklistedUrisKey = "autofillBlacklistedUris";
public static string AutofillTileAdded = "autofillTileAdded";
public static string DisableFaviconKey = "disableFavicon";
public static string PushRegisteredTokenKey = "pushRegisteredToken";
public static string PushCurrentTokenKey = "pushCurrentToken";