mirror of
https://github.com/bitwarden/android.git
synced 2024-12-24 09:58:27 +03:00
accessibility service WIP
This commit is contained in:
parent
47e427a851
commit
2c446f939e
6 changed files with 86 additions and 50 deletions
|
@ -6,9 +6,9 @@ using Android.Views;
|
||||||
|
|
||||||
namespace Bit.Android
|
namespace Bit.Android
|
||||||
{
|
{
|
||||||
[Activity(Label = "bitwarden",
|
[Activity(Theme = "@style/BitwardenTheme.Splash",
|
||||||
|
Label = "bitwarden",
|
||||||
Icon = "@drawable/icon",
|
Icon = "@drawable/icon",
|
||||||
LaunchMode = global::Android.Content.PM.LaunchMode.SingleTask,
|
|
||||||
WindowSoftInputMode = SoftInput.StateHidden)]
|
WindowSoftInputMode = SoftInput.StateHidden)]
|
||||||
public class AutofillActivity : Activity
|
public class AutofillActivity : Activity
|
||||||
{
|
{
|
||||||
|
@ -69,6 +69,7 @@ namespace Bit.Android
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
Xamarin.Forms.MessagingCenter.Send(Xamarin.Forms.Application.Current, "SetMainPage");
|
||||||
Finish();
|
Finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,11 @@ namespace Bit.Android
|
||||||
private const string SystemUiPackage = "com.android.systemui";
|
private const string SystemUiPackage = "com.android.systemui";
|
||||||
private const string ChromePackage = "com.android.chrome";
|
private const string ChromePackage = "com.android.chrome";
|
||||||
private const string BrowserPackage = "com.android.browser";
|
private const string BrowserPackage = "com.android.browser";
|
||||||
|
private const string BravePackage = "com.brave.browser";
|
||||||
|
private const string OperaPackage = "com.opera.browser";
|
||||||
|
private const string OperaMiniPackage = "com.opera.mini.native";
|
||||||
private const string BitwardenPackage = "com.x8bit.bitwarden";
|
private const string BitwardenPackage = "com.x8bit.bitwarden";
|
||||||
|
private const string BitwardenWebsite = "bitwarden.com";
|
||||||
|
|
||||||
public override void OnAccessibilityEvent(AccessibilityEvent e)
|
public override void OnAccessibilityEvent(AccessibilityEvent e)
|
||||||
{
|
{
|
||||||
|
@ -36,31 +40,21 @@ namespace Bit.Android
|
||||||
case EventTypes.WindowStateChanged:
|
case EventTypes.WindowStateChanged:
|
||||||
var cancelNotification = true;
|
var cancelNotification = true;
|
||||||
var root = RootInActiveWindow;
|
var root = RootInActiveWindow;
|
||||||
var isChrome = root == null ? false : root.PackageName == ChromePackage;
|
var passwordNodes = GetWindowNodes(root, e, n => n.Password);
|
||||||
var avialablePasswordNodes = GetWindowNodes(root, e, n => AvailablePasswordField(n, isChrome));
|
|
||||||
|
|
||||||
if(avialablePasswordNodes.Any())
|
if(passwordNodes.Any())
|
||||||
{
|
{
|
||||||
var uri = string.Concat(App.Constants.AndroidAppProtocol, root.PackageName);
|
var uri = GetUri(root);
|
||||||
if(isChrome)
|
if(uri.Contains(BitwardenWebsite))
|
||||||
{
|
{
|
||||||
var addressNode = root.FindAccessibilityNodeInfosByViewId("com.android.chrome:id/url_bar")
|
break;
|
||||||
.FirstOrDefault();
|
|
||||||
uri = ExtractUriFromAddressField(uri, addressNode);
|
|
||||||
|
|
||||||
}
|
|
||||||
else if(root.PackageName == BrowserPackage)
|
|
||||||
{
|
|
||||||
var addressNode = root.FindAccessibilityNodeInfosByViewId("com.android.browser:id/url")
|
|
||||||
.FirstOrDefault();
|
|
||||||
uri = ExtractUriFromAddressField(uri, addressNode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(NeedToAutofill(AutofillActivity.LastCredentials, uri))
|
if(NeedToAutofill(AutofillActivity.LastCredentials, uri))
|
||||||
{
|
{
|
||||||
var allEditTexts = GetWindowNodes(root, e, n => EditText(n));
|
var allEditTexts = GetWindowNodes(root, e, n => EditText(n));
|
||||||
var usernameEditText = allEditTexts.TakeWhile(n => !n.Password).LastOrDefault();
|
var usernameEditText = allEditTexts.TakeWhile(n => !n.Password).LastOrDefault();
|
||||||
FillCredentials(usernameEditText, avialablePasswordNodes);
|
FillCredentials(usernameEditText, passwordNodes);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -73,8 +67,7 @@ namespace Bit.Android
|
||||||
|
|
||||||
if(cancelNotification)
|
if(cancelNotification)
|
||||||
{
|
{
|
||||||
var notificationManager = ((NotificationManager)GetSystemService(NotificationService));
|
CancelNotification();
|
||||||
notificationManager.Cancel(AutoFillNotificationId);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -87,7 +80,41 @@ namespace Bit.Android
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ExtractUriFromAddressField(string uri, AccessibilityNodeInfo addressNode)
|
private void CancelNotification()
|
||||||
|
{
|
||||||
|
var notificationManager = ((NotificationManager)GetSystemService(NotificationService));
|
||||||
|
notificationManager.Cancel(AutoFillNotificationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetUri(AccessibilityNodeInfo root)
|
||||||
|
{
|
||||||
|
var uri = string.Concat(App.Constants.AndroidAppProtocol, root.PackageName);
|
||||||
|
string addressViewId = null;
|
||||||
|
|
||||||
|
if(root.PackageName == ChromePackage || root.PackageName == BravePackage)
|
||||||
|
{
|
||||||
|
addressViewId = "url_bar";
|
||||||
|
}
|
||||||
|
else if(true || root.PackageName == BrowserPackage)
|
||||||
|
{
|
||||||
|
addressViewId = "url";
|
||||||
|
}
|
||||||
|
else if(root.PackageName == OperaPackage || root.PackageName == OperaMiniPackage)
|
||||||
|
{
|
||||||
|
addressViewId = "url_field";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!string.IsNullOrWhiteSpace(addressViewId))
|
||||||
|
{
|
||||||
|
var addressNode = root.FindAccessibilityNodeInfosByViewId(
|
||||||
|
$"{root.PackageName}:id/{addressViewId}").FirstOrDefault();
|
||||||
|
uri = ExtractUri(uri, addressNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ExtractUri(string uri, AccessibilityNodeInfo addressNode)
|
||||||
{
|
{
|
||||||
if(addressNode != null)
|
if(addressNode != null)
|
||||||
{
|
{
|
||||||
|
@ -123,13 +150,6 @@ namespace Bit.Android
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool AvailablePasswordField(AccessibilityNodeInfo n, bool isChrome)
|
|
||||||
{
|
|
||||||
// chrome sends password field values in many conditions when the field is still actually empty
|
|
||||||
// ex. placeholders, nearby label, etc
|
|
||||||
return n.Password && (isChrome || string.IsNullOrWhiteSpace(n.Text));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool EditText(AccessibilityNodeInfo n)
|
private static bool EditText(AccessibilityNodeInfo n)
|
||||||
{
|
{
|
||||||
return n.ClassName != null && n.ClassName.Contains("EditText");
|
return n.ClassName != null && n.ClassName.Contains("EditText");
|
||||||
|
@ -145,14 +165,18 @@ namespace Bit.Android
|
||||||
var builder = new Notification.Builder(this);
|
var builder = new Notification.Builder(this);
|
||||||
builder.SetSmallIcon(Resource.Drawable.notification_sm)
|
builder.SetSmallIcon(Resource.Drawable.notification_sm)
|
||||||
.SetContentTitle("bitwarden Autofill Service")
|
.SetContentTitle("bitwarden Autofill Service")
|
||||||
.SetContentText("Tap this notification to autofill a login from your bitwarden vault.")
|
.SetContentText("Tap this notification to autofill a login from your vault.")
|
||||||
.SetTicker("Tap this notification to autofill a login from your bitwarden vault.")
|
.SetTicker("Tap this notification to autofill a login from your vault.")
|
||||||
.SetWhen(Java.Lang.JavaSystem.CurrentTimeMillis())
|
.SetWhen(Java.Lang.JavaSystem.CurrentTimeMillis())
|
||||||
.SetVisibility(NotificationVisibility.Secret)
|
|
||||||
.SetColor(global::Android.Support.V4.Content.ContextCompat.GetColor(ApplicationContext,
|
|
||||||
Resource.Color.primary))
|
|
||||||
.SetContentIntent(pendingIntent);
|
.SetContentIntent(pendingIntent);
|
||||||
|
|
||||||
|
if(Build.VERSION.SdkInt > BuildVersionCodes.KitkatWatch)
|
||||||
|
{
|
||||||
|
builder.SetVisibility(NotificationVisibility.Secret)
|
||||||
|
.SetColor(global::Android.Support.V4.Content.ContextCompat.GetColor(ApplicationContext,
|
||||||
|
Resource.Color.primary));
|
||||||
|
}
|
||||||
|
|
||||||
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
|
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
|
||||||
notificationManager.Notify(AutoFillNotificationId, builder.Build());
|
notificationManager.Notify(AutoFillNotificationId, builder.Build());
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ namespace Bit.Android
|
||||||
[Activity(Label = "bitwarden",
|
[Activity(Label = "bitwarden",
|
||||||
Icon = "@drawable/icon",
|
Icon = "@drawable/icon",
|
||||||
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation,
|
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation,
|
||||||
LaunchMode = LaunchMode.SingleTask,
|
|
||||||
WindowSoftInputMode = SoftInput.StateHidden)]
|
WindowSoftInputMode = SoftInput.StateHidden)]
|
||||||
public class MainActivity : FormsAppCompatActivity
|
public class MainActivity : FormsAppCompatActivity
|
||||||
{
|
{
|
||||||
|
@ -89,7 +88,7 @@ namespace Bit.Android
|
||||||
|
|
||||||
private void ReturnCredentials(VaultListPageModel.Login login)
|
private void ReturnCredentials(VaultListPageModel.Login login)
|
||||||
{
|
{
|
||||||
App.App.WasFromAutofillService = true;
|
App.App.FromAutofillService = true;
|
||||||
Intent data = new Intent();
|
Intent data = new Intent();
|
||||||
if(login == null)
|
if(login == null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
|
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:accessibilityEventTypes="typeWindowStateChanged|typeWindowContentChanged"
|
android:accessibilityEventTypes="typeAllMask"
|
||||||
android:accessibilityFeedbackType="feedbackSpoken"
|
android:accessibilityFeedbackType="feedbackSpoken"
|
||||||
android:accessibilityFlags="flagDefault"
|
android:accessibilityFlags="flagDefault"
|
||||||
android:notificationTimeout="100"
|
android:notificationTimeout="100"
|
||||||
|
|
|
@ -19,7 +19,7 @@ namespace Bit.App
|
||||||
{
|
{
|
||||||
public class App : Application
|
public class App : Application
|
||||||
{
|
{
|
||||||
private readonly string _uri;
|
private string _uri;
|
||||||
private readonly IDatabaseService _databaseService;
|
private readonly IDatabaseService _databaseService;
|
||||||
private readonly IConnectivity _connectivity;
|
private readonly IConnectivity _connectivity;
|
||||||
private readonly IUserDialogs _userDialogs;
|
private readonly IUserDialogs _userDialogs;
|
||||||
|
@ -31,7 +31,7 @@ namespace Bit.App
|
||||||
private readonly IGoogleAnalyticsService _googleAnalyticsService;
|
private readonly IGoogleAnalyticsService _googleAnalyticsService;
|
||||||
private readonly ILocalizeService _localizeService;
|
private readonly ILocalizeService _localizeService;
|
||||||
|
|
||||||
public static bool WasFromAutofillService { get; set; } = false;
|
public static bool FromAutofillService { get; set; } = false;
|
||||||
|
|
||||||
public App(
|
public App(
|
||||||
string uri,
|
string uri,
|
||||||
|
@ -61,6 +61,7 @@ namespace Bit.App
|
||||||
SetCulture();
|
SetCulture();
|
||||||
SetStyles();
|
SetStyles();
|
||||||
|
|
||||||
|
FromAutofillService = !string.IsNullOrWhiteSpace(_uri);
|
||||||
if(authService.IsAuthenticated && _uri != null)
|
if(authService.IsAuthenticated && _uri != null)
|
||||||
{
|
{
|
||||||
MainPage = new ExtendedNavigationPage(new VaultAutofillListLoginsPage(_uri));
|
MainPage = new ExtendedNavigationPage(new VaultAutofillListLoginsPage(_uri));
|
||||||
|
@ -89,12 +90,16 @@ namespace Bit.App
|
||||||
{
|
{
|
||||||
Device.BeginInvokeOnMainThread(() => Logout(args));
|
Device.BeginInvokeOnMainThread(() => Logout(args));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
MessagingCenter.Subscribe<Application>(Current, "SetMainPage", async (sender) =>
|
||||||
|
{
|
||||||
|
await SetMainPageFromAutofill();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async override void OnStart()
|
protected async override void OnStart()
|
||||||
{
|
{
|
||||||
// Handle when your app starts
|
// Handle when your app starts
|
||||||
ResumeFromAutofill();
|
|
||||||
await CheckLockAsync(false);
|
await CheckLockAsync(false);
|
||||||
_databaseService.CreateTables();
|
_databaseService.CreateTables();
|
||||||
await Task.Run(() => FullSyncAsync()).ConfigureAwait(false);
|
await Task.Run(() => FullSyncAsync()).ConfigureAwait(false);
|
||||||
|
@ -102,11 +107,12 @@ namespace Bit.App
|
||||||
Debug.WriteLine("OnStart");
|
Debug.WriteLine("OnStart");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnSleep()
|
protected async override void OnSleep()
|
||||||
{
|
{
|
||||||
// Handle when your app sleeps
|
// Handle when your app sleeps
|
||||||
Debug.WriteLine("OnSleep");
|
Debug.WriteLine("OnSleep");
|
||||||
|
|
||||||
|
await SetMainPageFromAutofill(true);
|
||||||
if(Device.OS == TargetPlatform.Android && !TopPageIsLock())
|
if(Device.OS == TargetPlatform.Android && !TopPageIsLock())
|
||||||
{
|
{
|
||||||
_settings.AddOrUpdateValue(Constants.LastActivityDate, DateTime.UtcNow);
|
_settings.AddOrUpdateValue(Constants.LastActivityDate, DateTime.UtcNow);
|
||||||
|
@ -123,7 +129,6 @@ namespace Bit.App
|
||||||
|
|
||||||
// Handle when your app resumes
|
// Handle when your app resumes
|
||||||
Debug.WriteLine("OnResume");
|
Debug.WriteLine("OnResume");
|
||||||
ResumeFromAutofill();
|
|
||||||
|
|
||||||
if(Device.OS == TargetPlatform.Android)
|
if(Device.OS == TargetPlatform.Android)
|
||||||
{
|
{
|
||||||
|
@ -137,16 +142,26 @@ namespace Bit.App
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ResumeFromAutofill()
|
private async Task SetMainPageFromAutofill(bool skipAlreadyOnCheck = false)
|
||||||
{
|
{
|
||||||
if(Device.OS == TargetPlatform.Android && WasFromAutofillService)
|
if(Device.OS != TargetPlatform.Android)
|
||||||
{
|
{
|
||||||
WasFromAutofillService = false;
|
return;
|
||||||
MainPage = new MainPage();
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
var alreadyOnMainPage = MainPage as MainPage;
|
||||||
|
if(!skipAlreadyOnCheck && alreadyOnMainPage != null)
|
||||||
{
|
{
|
||||||
WasFromAutofillService = !string.IsNullOrWhiteSpace(_uri);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(FromAutofillService || !string.IsNullOrWhiteSpace(_uri))
|
||||||
|
{
|
||||||
|
// delay some so that we dont see the screen change as autofill closes
|
||||||
|
await Task.Delay(1000);
|
||||||
|
MainPage = new MainPage();
|
||||||
|
_uri = null;
|
||||||
|
FromAutofillService = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,6 @@ using Bit.App.Resources;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
using XLabs.Ioc;
|
using XLabs.Ioc;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using PushNotification.Plugin.Abstractions;
|
|
||||||
using Plugin.Settings.Abstractions;
|
|
||||||
using Plugin.Connectivity.Abstractions;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
Loading…
Reference in a new issue