From 2c446f939e1f573bd42e6d9e148d163caca5c678 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Tue, 31 Jan 2017 20:45:51 -0500 Subject: [PATCH] accessibility service WIP --- src/Android/AutofillActivity.cs | 5 +- src/Android/AutofillService.cs | 86 ++++++++++++------- src/Android/MainActivity.cs | 3 +- .../Resources/xml/accessibilityservice.xml | 2 +- src/App/App.cs | 37 +++++--- .../Vault/VaultAutofillListLoginsPage.cs | 3 - 6 files changed, 86 insertions(+), 50 deletions(-) diff --git a/src/Android/AutofillActivity.cs b/src/Android/AutofillActivity.cs index a2b2d8d41..5bea00240 100644 --- a/src/Android/AutofillActivity.cs +++ b/src/Android/AutofillActivity.cs @@ -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(); } } diff --git a/src/Android/AutofillService.cs b/src/Android/AutofillService.cs index daa844f66..fc3c308f6 100644 --- a/src/Android/AutofillService.cs +++ b/src/Android/AutofillService.cs @@ -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()); } diff --git a/src/Android/MainActivity.cs b/src/Android/MainActivity.cs index 73392fe13..2371dfc25 100644 --- a/src/Android/MainActivity.cs +++ b/src/Android/MainActivity.cs @@ -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) { diff --git a/src/Android/Resources/xml/accessibilityservice.xml b/src/Android/Resources/xml/accessibilityservice.xml index ea2613f83..a8a416552 100644 --- a/src/Android/Resources/xml/accessibilityservice.xml +++ b/src/Android/Resources/xml/accessibilityservice.xml @@ -1,6 +1,6 @@  Logout(args)); }); + + MessagingCenter.Subscribe(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; } } diff --git a/src/App/Pages/Vault/VaultAutofillListLoginsPage.cs b/src/App/Pages/Vault/VaultAutofillListLoginsPage.cs index 9f97e0d27..d22b65e41 100644 --- a/src/App/Pages/Vault/VaultAutofillListLoginsPage.cs +++ b/src/App/Pages/Vault/VaultAutofillListLoginsPage.cs @@ -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;