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
|
||||
{
|
||||
[Activity(Label = "bitwarden",
|
||||
[Activity(Theme = "@style/BitwardenTheme.Splash",
|
||||
Label = "bitwarden",
|
||||
Icon = "@drawable/icon",
|
||||
LaunchMode = global::Android.Content.PM.LaunchMode.SingleTask,
|
||||
WindowSoftInputMode = SoftInput.StateHidden)]
|
||||
public class AutofillActivity : Activity
|
||||
{
|
||||
|
@ -69,6 +69,7 @@ namespace Bit.Android
|
|||
}
|
||||
finally
|
||||
{
|
||||
Xamarin.Forms.MessagingCenter.Send(Xamarin.Forms.Application.Current, "SetMainPage");
|
||||
Finish();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,11 @@ namespace Bit.Android
|
|||
private const string SystemUiPackage = "com.android.systemui";
|
||||
private const string ChromePackage = "com.android.chrome";
|
||||
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 BitwardenWebsite = "bitwarden.com";
|
||||
|
||||
public override void OnAccessibilityEvent(AccessibilityEvent e)
|
||||
{
|
||||
|
@ -36,31 +40,21 @@ namespace Bit.Android
|
|||
case EventTypes.WindowStateChanged:
|
||||
var cancelNotification = true;
|
||||
var root = RootInActiveWindow;
|
||||
var isChrome = root == null ? false : root.PackageName == ChromePackage;
|
||||
var avialablePasswordNodes = GetWindowNodes(root, e, n => AvailablePasswordField(n, isChrome));
|
||||
var passwordNodes = GetWindowNodes(root, e, n => n.Password);
|
||||
|
||||
if(avialablePasswordNodes.Any())
|
||||
if(passwordNodes.Any())
|
||||
{
|
||||
var uri = string.Concat(App.Constants.AndroidAppProtocol, root.PackageName);
|
||||
if(isChrome)
|
||||
var uri = GetUri(root);
|
||||
if(uri.Contains(BitwardenWebsite))
|
||||
{
|
||||
var addressNode = root.FindAccessibilityNodeInfosByViewId("com.android.chrome:id/url_bar")
|
||||
.FirstOrDefault();
|
||||
uri = ExtractUriFromAddressField(uri, addressNode);
|
||||
|
||||
}
|
||||
else if(root.PackageName == BrowserPackage)
|
||||
{
|
||||
var addressNode = root.FindAccessibilityNodeInfosByViewId("com.android.browser:id/url")
|
||||
.FirstOrDefault();
|
||||
uri = ExtractUriFromAddressField(uri, addressNode);
|
||||
break;
|
||||
}
|
||||
|
||||
if(NeedToAutofill(AutofillActivity.LastCredentials, uri))
|
||||
{
|
||||
var allEditTexts = GetWindowNodes(root, e, n => EditText(n));
|
||||
var usernameEditText = allEditTexts.TakeWhile(n => !n.Password).LastOrDefault();
|
||||
FillCredentials(usernameEditText, avialablePasswordNodes);
|
||||
FillCredentials(usernameEditText, passwordNodes);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -73,8 +67,7 @@ namespace Bit.Android
|
|||
|
||||
if(cancelNotification)
|
||||
{
|
||||
var notificationManager = ((NotificationManager)GetSystemService(NotificationService));
|
||||
notificationManager.Cancel(AutoFillNotificationId);
|
||||
CancelNotification();
|
||||
}
|
||||
break;
|
||||
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)
|
||||
{
|
||||
|
@ -123,13 +150,6 @@ namespace Bit.Android
|
|||
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)
|
||||
{
|
||||
return n.ClassName != null && n.ClassName.Contains("EditText");
|
||||
|
@ -145,14 +165,18 @@ namespace Bit.Android
|
|||
var builder = new Notification.Builder(this);
|
||||
builder.SetSmallIcon(Resource.Drawable.notification_sm)
|
||||
.SetContentTitle("bitwarden Autofill Service")
|
||||
.SetContentText("Tap this notification to autofill a login from your bitwarden vault.")
|
||||
.SetTicker("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 vault.")
|
||||
.SetWhen(Java.Lang.JavaSystem.CurrentTimeMillis())
|
||||
.SetVisibility(NotificationVisibility.Secret)
|
||||
.SetColor(global::Android.Support.V4.Content.ContextCompat.GetColor(ApplicationContext,
|
||||
Resource.Color.primary))
|
||||
.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);
|
||||
notificationManager.Notify(AutoFillNotificationId, builder.Build());
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ namespace Bit.Android
|
|||
[Activity(Label = "bitwarden",
|
||||
Icon = "@drawable/icon",
|
||||
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation,
|
||||
LaunchMode = LaunchMode.SingleTask,
|
||||
WindowSoftInputMode = SoftInput.StateHidden)]
|
||||
public class MainActivity : FormsAppCompatActivity
|
||||
{
|
||||
|
@ -89,7 +88,7 @@ namespace Bit.Android
|
|||
|
||||
private void ReturnCredentials(VaultListPageModel.Login login)
|
||||
{
|
||||
App.App.WasFromAutofillService = true;
|
||||
App.App.FromAutofillService = true;
|
||||
Intent data = new Intent();
|
||||
if(login == null)
|
||||
{
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:accessibilityEventTypes="typeWindowStateChanged|typeWindowContentChanged"
|
||||
android:accessibilityEventTypes="typeAllMask"
|
||||
android:accessibilityFeedbackType="feedbackSpoken"
|
||||
android:accessibilityFlags="flagDefault"
|
||||
android:notificationTimeout="100"
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace Bit.App
|
|||
{
|
||||
public class App : Application
|
||||
{
|
||||
private readonly string _uri;
|
||||
private string _uri;
|
||||
private readonly IDatabaseService _databaseService;
|
||||
private readonly IConnectivity _connectivity;
|
||||
private readonly IUserDialogs _userDialogs;
|
||||
|
@ -31,7 +31,7 @@ namespace Bit.App
|
|||
private readonly IGoogleAnalyticsService _googleAnalyticsService;
|
||||
private readonly ILocalizeService _localizeService;
|
||||
|
||||
public static bool WasFromAutofillService { get; set; } = false;
|
||||
public static bool FromAutofillService { get; set; } = false;
|
||||
|
||||
public App(
|
||||
string uri,
|
||||
|
@ -61,6 +61,7 @@ namespace Bit.App
|
|||
SetCulture();
|
||||
SetStyles();
|
||||
|
||||
FromAutofillService = !string.IsNullOrWhiteSpace(_uri);
|
||||
if(authService.IsAuthenticated && _uri != null)
|
||||
{
|
||||
MainPage = new ExtendedNavigationPage(new VaultAutofillListLoginsPage(_uri));
|
||||
|
@ -89,12 +90,16 @@ namespace Bit.App
|
|||
{
|
||||
Device.BeginInvokeOnMainThread(() => Logout(args));
|
||||
});
|
||||
|
||||
MessagingCenter.Subscribe<Application>(Current, "SetMainPage", async (sender) =>
|
||||
{
|
||||
await SetMainPageFromAutofill();
|
||||
});
|
||||
}
|
||||
|
||||
protected async override void OnStart()
|
||||
{
|
||||
// Handle when your app starts
|
||||
ResumeFromAutofill();
|
||||
await CheckLockAsync(false);
|
||||
_databaseService.CreateTables();
|
||||
await Task.Run(() => FullSyncAsync()).ConfigureAwait(false);
|
||||
|
@ -102,11 +107,12 @@ namespace Bit.App
|
|||
Debug.WriteLine("OnStart");
|
||||
}
|
||||
|
||||
protected override void OnSleep()
|
||||
protected async override void OnSleep()
|
||||
{
|
||||
// Handle when your app sleeps
|
||||
Debug.WriteLine("OnSleep");
|
||||
|
||||
await SetMainPageFromAutofill(true);
|
||||
if(Device.OS == TargetPlatform.Android && !TopPageIsLock())
|
||||
{
|
||||
_settings.AddOrUpdateValue(Constants.LastActivityDate, DateTime.UtcNow);
|
||||
|
@ -123,7 +129,6 @@ namespace Bit.App
|
|||
|
||||
// Handle when your app resumes
|
||||
Debug.WriteLine("OnResume");
|
||||
ResumeFromAutofill();
|
||||
|
||||
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;
|
||||
MainPage = new MainPage();
|
||||
return;
|
||||
}
|
||||
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 XLabs.Ioc;
|
||||
using Bit.App.Utilities;
|
||||
using PushNotification.Plugin.Abstractions;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using Plugin.Connectivity.Abstractions;
|
||||
using System.Threading;
|
||||
using Bit.App.Models;
|
||||
using System.Collections.Generic;
|
||||
|
|
Loading…
Reference in a new issue