mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 23:25:45 +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.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");
|
||||
|
|
|
@ -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>
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
@ -175,14 +198,41 @@ namespace Bit.Droid.Accessibility
|
|||
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()
|
||||
{
|
||||
_overlayAnchorObserverRunning = false;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
<string name="PasswordGenerator">
|
||||
Password Generator
|
||||
</string>
|
||||
<string name="ScanAndFill">
|
||||
Scan & Fill
|
||||
</string>
|
||||
<string name="SelfHostedServerUrl">
|
||||
Self-hosted server URL
|
||||
</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);
|
||||
}
|
||||
}
|
||||
|
||||
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">
|
||||
<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 & Fill tile.</value>
|
||||
</data>
|
||||
<data name="AutofillTileUriNotFound" xml:space="preserve">
|
||||
<value>No password fields detected</value>
|
||||
</data>
|
||||
</root>
|
|
@ -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";
|
||||
|
|
Loading…
Reference in a new issue