mirror of
https://github.com/bitwarden/android.git
synced 2024-12-25 02:18:27 +03:00
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:
parent
5d9a597d8d
commit
6c00ac43fc
9 changed files with 205 additions and 6 deletions
|
@ -4,6 +4,8 @@ using Android.OS;
|
||||||
using Android.Runtime;
|
using Android.Runtime;
|
||||||
using Android.Views;
|
using Android.Views;
|
||||||
using System;
|
using System;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
namespace Bit.Droid.Accessibility
|
namespace Bit.Droid.Accessibility
|
||||||
{
|
{
|
||||||
|
@ -16,13 +18,13 @@ namespace Bit.Droid.Accessibility
|
||||||
protected override void OnCreate(Bundle bundle)
|
protected override void OnCreate(Bundle bundle)
|
||||||
{
|
{
|
||||||
base.OnCreate(bundle);
|
base.OnCreate(bundle);
|
||||||
LaunchMainActivity(Intent, 932473);
|
HandleIntent(Intent, 932473);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnNewIntent(Intent intent)
|
protected override void OnNewIntent(Intent intent)
|
||||||
{
|
{
|
||||||
base.OnNewIntent(intent);
|
base.OnNewIntent(intent);
|
||||||
LaunchMainActivity(intent, 489729);
|
HandleIntent(intent, 489729);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDestroy()
|
protected override void OnDestroy()
|
||||||
|
@ -78,6 +80,21 @@ namespace Bit.Droid.Accessibility
|
||||||
Finish();
|
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)
|
private void LaunchMainActivity(Intent callingIntent, int requestCode)
|
||||||
{
|
{
|
||||||
_lastQueriedUri = callingIntent?.GetStringExtra("uri");
|
_lastQueriedUri = callingIntent?.GetStringExtra("uri");
|
||||||
|
|
|
@ -20,6 +20,8 @@ namespace Bit.Droid.Accessibility
|
||||||
public static Credentials LastCredentials = null;
|
public static Credentials LastCredentials = null;
|
||||||
public static string SystemUiPackage = "com.android.systemui";
|
public static string SystemUiPackage = "com.android.systemui";
|
||||||
public static string BitwardenTag = "bw_access";
|
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>
|
public static Dictionary<string, Browser> SupportedBrowsers => new List<Browser>
|
||||||
{
|
{
|
||||||
|
|
|
@ -29,6 +29,7 @@ namespace Bit.Droid.Accessibility
|
||||||
private const string BitwardenWebsite = "vault.bitwarden.com";
|
private const string BitwardenWebsite = "vault.bitwarden.com";
|
||||||
|
|
||||||
private IStorageService _storageService;
|
private IStorageService _storageService;
|
||||||
|
private IBroadcasterService _broadcasterService;
|
||||||
private DateTime? _lastSettingsReload = null;
|
private DateTime? _lastSettingsReload = null;
|
||||||
private TimeSpan _settingsReloadSpan = TimeSpan.FromMinutes(1);
|
private TimeSpan _settingsReloadSpan = TimeSpan.FromMinutes(1);
|
||||||
private HashSet<string> _blacklistedUris;
|
private HashSet<string> _blacklistedUris;
|
||||||
|
@ -48,6 +49,28 @@ namespace Bit.Droid.Accessibility
|
||||||
private DateTime? _lastLauncherSetBuilt = null;
|
private DateTime? _lastLauncherSetBuilt = null;
|
||||||
private TimeSpan _rebuildLauncherSpan = TimeSpan.FromHours(1);
|
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)
|
public override void OnAccessibilityEvent(AccessibilityEvent e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -175,14 +198,41 @@ namespace Bit.Droid.Accessibility
|
||||||
return filled;
|
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()
|
private void CancelOverlayPrompt()
|
||||||
{
|
{
|
||||||
_overlayAnchorObserverRunning = false;
|
_overlayAnchorObserverRunning = false;
|
||||||
|
|
||||||
if(_windowManager != null && _overlayView != null)
|
if(_windowManager != null && _overlayView != null)
|
||||||
{
|
{
|
||||||
_windowManager.RemoveViewImmediate(_overlayView);
|
try
|
||||||
System.Diagnostics.Debug.WriteLine(">>> Accessibility Overlay View Removed");
|
{
|
||||||
|
_windowManager.RemoveViewImmediate(_overlayView);
|
||||||
|
System.Diagnostics.Debug.WriteLine(">>> Accessibility Overlay View Removed");
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
_overlayView = null;
|
_overlayView = null;
|
||||||
|
@ -201,8 +251,14 @@ namespace Bit.Droid.Accessibility
|
||||||
{
|
{
|
||||||
if(!AccessibilityHelpers.OverlayPermitted())
|
if(!AccessibilityHelpers.OverlayPermitted())
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine(">>> Overlay Permission not granted");
|
if(!AccessibilityHelpers.IsAutofillTileAdded)
|
||||||
Toast.MakeText(this, AppResources.AccessibilityOverlayPermissionAlert, ToastLength.Long).Show();
|
{
|
||||||
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -392,6 +448,10 @@ namespace Bit.Droid.Accessibility
|
||||||
{
|
{
|
||||||
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||||
}
|
}
|
||||||
|
if(_broadcasterService == null)
|
||||||
|
{
|
||||||
|
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadSettingsAsync()
|
private async Task LoadSettingsAsync()
|
||||||
|
@ -405,6 +465,8 @@ namespace Bit.Droid.Accessibility
|
||||||
{
|
{
|
||||||
_blacklistedUris = new HashSet<string>(uris);
|
_blacklistedUris = new HashSet<string>(uris);
|
||||||
}
|
}
|
||||||
|
var isAutoFillTileAdded = await _storageService.GetAsync<bool?>(Constants.AutofillTileAdded);
|
||||||
|
AccessibilityHelpers.IsAutofillTileAdded = isAutoFillTileAdded.GetValueOrDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,6 +136,7 @@
|
||||||
<Compile Include="Services\CryptoPrimitiveService.cs" />
|
<Compile Include="Services\CryptoPrimitiveService.cs" />
|
||||||
<Compile Include="Services\DeviceActionService.cs" />
|
<Compile Include="Services\DeviceActionService.cs" />
|
||||||
<Compile Include="Services\LocalizeService.cs" />
|
<Compile Include="Services\LocalizeService.cs" />
|
||||||
|
<Compile Include="Tiles\AutofillTileService.cs" />
|
||||||
<Compile Include="Tiles\GeneratorTileService.cs" />
|
<Compile Include="Tiles\GeneratorTileService.cs" />
|
||||||
<Compile Include="Tiles\MyVaultTileService.cs" />
|
<Compile Include="Tiles\MyVaultTileService.cs" />
|
||||||
<Compile Include="Utilities\AndroidHelpers.cs" />
|
<Compile Include="Utilities\AndroidHelpers.cs" />
|
||||||
|
|
|
@ -16,6 +16,9 @@
|
||||||
<string name="PasswordGenerator">
|
<string name="PasswordGenerator">
|
||||||
Password Generator
|
Password Generator
|
||||||
</string>
|
</string>
|
||||||
|
<string name="ScanAndFill">
|
||||||
|
Scan & Fill
|
||||||
|
</string>
|
||||||
<string name="SelfHostedServerUrl">
|
<string name="SelfHostedServerUrl">
|
||||||
Self-hosted server URL
|
Self-hosted server URL
|
||||||
</string>
|
</string>
|
||||||
|
|
95
src/Android/Tiles/AutofillTileService.cs
Normal file
95
src/Android/Tiles/AutofillTileService.cs
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
src/App/Resources/AppResources.Designer.cs
generated
12
src/App/Resources/AppResources.Designer.cs
generated
|
@ -2859,5 +2859,17 @@ namespace Bit.App.Resources {
|
||||||
return ResourceManager.GetString("SaveAttachmentSuccess", resourceCulture);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1625,4 +1625,10 @@
|
||||||
<data name="SaveAttachmentSuccess" xml:space="preserve">
|
<data name="SaveAttachmentSuccess" xml:space="preserve">
|
||||||
<value>Attachment saved successfully</value>
|
<value>Attachment saved successfully</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="AutofillTileAccessibilityRequired" xml:space="preserve">
|
||||||
|
<value>Please enable "Auto-fill Accessibility Service" from Bitwarden Settings to use the Scan & Fill tile.</value>
|
||||||
|
</data>
|
||||||
|
<data name="AutofillTileUriNotFound" xml:space="preserve">
|
||||||
|
<value>No password fields detected</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -15,6 +15,7 @@
|
||||||
public static string LastFileCacheClearKey = "lastFileCacheClear";
|
public static string LastFileCacheClearKey = "lastFileCacheClear";
|
||||||
public static string AutofillDisableSavePromptKey = "autofillDisableSavePrompt";
|
public static string AutofillDisableSavePromptKey = "autofillDisableSavePrompt";
|
||||||
public static string AutofillBlacklistedUrisKey = "autofillBlacklistedUris";
|
public static string AutofillBlacklistedUrisKey = "autofillBlacklistedUris";
|
||||||
|
public static string AutofillTileAdded = "autofillTileAdded";
|
||||||
public static string DisableFaviconKey = "disableFavicon";
|
public static string DisableFaviconKey = "disableFavicon";
|
||||||
public static string PushRegisteredTokenKey = "pushRegisteredToken";
|
public static string PushRegisteredTokenKey = "pushRegisteredToken";
|
||||||
public static string PushCurrentTokenKey = "pushCurrentToken";
|
public static string PushCurrentTokenKey = "pushCurrentToken";
|
||||||
|
|
Loading…
Reference in a new issue