2016-08-27 01:56:09 +03:00
|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Linq;
|
|
|
|
using Android.AccessibilityServices;
|
|
|
|
using Android.App;
|
|
|
|
using Android.Content;
|
|
|
|
using Android.OS;
|
|
|
|
using Android.Views.Accessibility;
|
|
|
|
|
|
|
|
namespace Bit.Android
|
|
|
|
{
|
2017-01-24 07:32:52 +03:00
|
|
|
[Service(Permission = "android.permission.BIND_ACCESSIBILITY_SERVICE", Label = "bitwarden")]
|
|
|
|
[IntentFilter(new string[] { "android.accessibilityservice.AccessibilityService" })]
|
|
|
|
[MetaData("android.accessibilityservice", Resource = "@xml/accessibilityservice")]
|
2016-08-27 01:56:09 +03:00
|
|
|
public class AutofillService : AccessibilityService
|
|
|
|
{
|
2017-01-24 07:32:52 +03:00
|
|
|
private const int AutoFillNotificationId = 34573;
|
|
|
|
private const string SystemUiPackage = "com.android.systemui";
|
2017-01-31 03:26:39 +03:00
|
|
|
private const string BitwardenPackage = "com.x8bit.bitwarden";
|
2017-02-01 04:45:51 +03:00
|
|
|
private const string BitwardenWebsite = "bitwarden.com";
|
2016-12-23 06:37:16 +03:00
|
|
|
|
2017-02-01 08:38:35 +03:00
|
|
|
public static bool Enabled { get; set; } = false;
|
2017-02-03 03:39:00 +03:00
|
|
|
private static Dictionary<string, string[]> BrowserPackages => new Dictionary<string, string[]>
|
|
|
|
{
|
|
|
|
{ "com.android.chrome", new string[] { "url_bar" } },
|
|
|
|
{ "com.chrome.beta", new string[] { "url_bar" } },
|
|
|
|
{ "com.android.browser", new string[] { "url" } },
|
|
|
|
{ "com.brave.browser", new string[] { "url_bar" } },
|
|
|
|
{ "com.opera.browser", new string[] { "url_field" } },
|
|
|
|
{ "com.opera.browser.beta", new string[] { "url_field" } },
|
|
|
|
{ "com.opera.mini.native", new string[] { "url_field" } },
|
|
|
|
{ "com.chrome.dev", new string[] { "url_bar" } },
|
|
|
|
{ "com.chrome.canary", new string[] { "url_bar" } },
|
|
|
|
{ "com.google.android.apps.chrome", new string[] { "url_bar" } },
|
|
|
|
{ "com.google.android.apps.chrome_dev", new string[] { "url_bar" } },
|
|
|
|
{ "org.iron.srware", new string[] { "url_bar" } },
|
|
|
|
{ "com.sec.android.app.sbrowser", new string[] { "sbrowser_url_bar" } },
|
|
|
|
{ "com.yandex.browser", new string[] { "bro_common_omnibox_host", "bro_common_omnibox_edit_text" } },
|
|
|
|
{ "org.mozilla.firefox", new string[] { "url_bar_title" } },
|
|
|
|
{ "org.mozilla.firefox_beta", new string[] { "url_bar_title" } },
|
|
|
|
{ "com.ghostery.android.ghostery",new string[] { "search_field" } },
|
|
|
|
{ "org.adblockplus.browser", new string[] { "url_bar_title" } },
|
|
|
|
{ "com.htc.sense.browser", new string[] { "title" } },
|
|
|
|
{ "com.amazon.cloud9", new string[] { "url" } },
|
|
|
|
{ "mobi.mgeek.TunnyBrowser", new string[] { "title" } },
|
|
|
|
{ "com.nubelacorp.javelin", new string[] { "enterUrl" } },
|
|
|
|
{ "com.jerky.browser2", new string[] { "enterUrl" } },
|
|
|
|
{ "com.mx.browser", new string[] { "address_editor_with_progress" } },
|
|
|
|
{ "com.mx.browser.tablet", new string[] { "address_editor_with_progress"} },
|
|
|
|
{ "com.linkbubble.playstore", new string[] { "url_text" }}
|
|
|
|
};
|
2017-02-01 08:38:35 +03:00
|
|
|
|
2016-08-27 01:56:09 +03:00
|
|
|
public override void OnAccessibilityEvent(AccessibilityEvent e)
|
|
|
|
{
|
|
|
|
var eventType = e.EventType;
|
2017-01-24 07:32:52 +03:00
|
|
|
var packageName = e.PackageName;
|
|
|
|
|
2017-02-03 07:36:40 +03:00
|
|
|
if(packageName == SystemUiPackage)
|
2017-01-24 07:32:52 +03:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-08-27 01:56:09 +03:00
|
|
|
switch(eventType)
|
|
|
|
{
|
2016-12-23 06:37:16 +03:00
|
|
|
case EventTypes.WindowContentChanged:
|
|
|
|
case EventTypes.WindowStateChanged:
|
2017-02-03 07:36:40 +03:00
|
|
|
if(packageName == BitwardenPackage)
|
|
|
|
{
|
|
|
|
CancelNotification();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2017-01-31 03:26:39 +03:00
|
|
|
var cancelNotification = true;
|
2016-12-23 06:37:16 +03:00
|
|
|
var root = RootInActiveWindow;
|
2017-02-01 04:45:51 +03:00
|
|
|
var passwordNodes = GetWindowNodes(root, e, n => n.Password);
|
2016-12-23 06:37:16 +03:00
|
|
|
|
2017-02-01 04:45:51 +03:00
|
|
|
if(passwordNodes.Any())
|
2017-01-24 07:32:52 +03:00
|
|
|
{
|
2017-02-01 04:45:51 +03:00
|
|
|
var uri = GetUri(root);
|
|
|
|
if(uri.Contains(BitwardenWebsite))
|
2016-12-23 06:37:16 +03:00
|
|
|
{
|
2017-02-01 04:45:51 +03:00
|
|
|
break;
|
2016-12-23 06:37:16 +03:00
|
|
|
}
|
|
|
|
|
2017-01-31 03:26:39 +03:00
|
|
|
if(NeedToAutofill(AutofillActivity.LastCredentials, uri))
|
2017-01-24 07:32:52 +03:00
|
|
|
{
|
2017-01-31 03:26:39 +03:00
|
|
|
var allEditTexts = GetWindowNodes(root, e, n => EditText(n));
|
|
|
|
var usernameEditText = allEditTexts.TakeWhile(n => !n.Password).LastOrDefault();
|
2017-02-01 04:45:51 +03:00
|
|
|
FillCredentials(usernameEditText, passwordNodes);
|
2016-12-23 06:37:16 +03:00
|
|
|
}
|
2017-01-24 07:32:52 +03:00
|
|
|
else
|
2016-12-23 06:37:16 +03:00
|
|
|
{
|
2017-01-31 03:26:39 +03:00
|
|
|
NotifyToAutofill(uri);
|
2017-01-24 07:32:52 +03:00
|
|
|
cancelNotification = false;
|
2016-12-23 06:37:16 +03:00
|
|
|
}
|
2017-01-31 03:26:39 +03:00
|
|
|
|
|
|
|
AutofillActivity.LastCredentials = null;
|
2016-08-27 01:56:09 +03:00
|
|
|
}
|
2017-01-24 07:32:52 +03:00
|
|
|
|
|
|
|
if(cancelNotification)
|
|
|
|
{
|
2017-02-01 04:45:51 +03:00
|
|
|
CancelNotification();
|
2017-01-24 07:32:52 +03:00
|
|
|
}
|
2016-08-27 01:56:09 +03:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public override void OnInterrupt()
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
2016-12-23 06:37:16 +03:00
|
|
|
|
2017-02-01 08:38:35 +03:00
|
|
|
protected override void OnServiceConnected()
|
|
|
|
{
|
|
|
|
base.OnServiceConnected();
|
|
|
|
Enabled = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public override void OnDestroy()
|
|
|
|
{
|
|
|
|
base.OnDestroy();
|
|
|
|
Enabled = false;
|
|
|
|
}
|
|
|
|
|
2017-02-01 04:45:51 +03:00
|
|
|
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);
|
2017-02-03 03:39:00 +03:00
|
|
|
if(BrowserPackages.ContainsKey(root.PackageName))
|
2017-02-01 04:45:51 +03:00
|
|
|
{
|
2017-02-03 03:39:00 +03:00
|
|
|
foreach(var addressViewId in BrowserPackages[root.PackageName])
|
|
|
|
{
|
|
|
|
var addressNode = root.FindAccessibilityNodeInfosByViewId(
|
|
|
|
$"{root.PackageName}:id/{addressViewId}").FirstOrDefault();
|
|
|
|
if(addressNode == null)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2017-02-01 04:45:51 +03:00
|
|
|
|
2017-02-03 03:39:00 +03:00
|
|
|
uri = ExtractUri(uri, addressNode);
|
|
|
|
break;
|
|
|
|
}
|
2017-02-01 04:45:51 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return uri;
|
|
|
|
}
|
|
|
|
|
|
|
|
private string ExtractUri(string uri, AccessibilityNodeInfo addressNode)
|
2016-12-23 06:37:16 +03:00
|
|
|
{
|
2017-01-24 07:32:52 +03:00
|
|
|
if(addressNode != null)
|
2016-12-23 06:37:16 +03:00
|
|
|
{
|
2017-01-24 07:32:52 +03:00
|
|
|
uri = addressNode.Text;
|
|
|
|
if(!uri.Contains("://"))
|
|
|
|
{
|
|
|
|
uri = string.Concat("http://", uri);
|
|
|
|
}
|
2017-02-01 06:53:32 +03:00
|
|
|
else if(Build.VERSION.SdkInt <= BuildVersionCodes.KitkatWatch)
|
|
|
|
{
|
|
|
|
var parts = uri.Split(new string[] { ". " }, StringSplitOptions.None);
|
|
|
|
if(parts.Length > 1)
|
|
|
|
{
|
|
|
|
var urlPart = parts.FirstOrDefault(p => p.StartsWith("http"));
|
|
|
|
if(urlPart != null)
|
|
|
|
{
|
|
|
|
uri = urlPart.Trim();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-12-23 06:37:16 +03:00
|
|
|
}
|
|
|
|
|
2017-01-24 07:32:52 +03:00
|
|
|
return uri;
|
2016-12-23 06:37:16 +03:00
|
|
|
}
|
|
|
|
|
2017-01-31 03:26:39 +03:00
|
|
|
/// <summary>
|
|
|
|
/// Check to make sure it is ok to autofill still on the current screen
|
|
|
|
/// </summary>
|
|
|
|
private bool NeedToAutofill(AutofillCredentials creds, string currentUriString)
|
2016-12-23 06:37:16 +03:00
|
|
|
{
|
2017-01-31 03:26:39 +03:00
|
|
|
if(creds == null)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Uri credsUri, lastUri, currentUri;
|
|
|
|
if(Uri.TryCreate(creds.Uri, UriKind.Absolute, out credsUri) &&
|
|
|
|
Uri.TryCreate(creds.LastUri, UriKind.Absolute, out lastUri) &&
|
|
|
|
Uri.TryCreate(currentUriString, UriKind.Absolute, out currentUri) &&
|
|
|
|
credsUri.Host == currentUri.Host && lastUri.Host == currentUri.Host)
|
2017-01-24 07:32:52 +03:00
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2016-12-23 06:37:16 +03:00
|
|
|
}
|
|
|
|
|
2017-01-24 07:32:52 +03:00
|
|
|
private static bool EditText(AccessibilityNodeInfo n)
|
2016-12-23 06:37:16 +03:00
|
|
|
{
|
2017-01-24 07:32:52 +03:00
|
|
|
return n.ClassName != null && n.ClassName.Contains("EditText");
|
2016-12-23 06:37:16 +03:00
|
|
|
}
|
|
|
|
|
2017-01-31 03:26:39 +03:00
|
|
|
private void NotifyToAutofill(string uri)
|
2016-12-23 06:37:16 +03:00
|
|
|
{
|
2017-01-24 07:32:52 +03:00
|
|
|
var intent = new Intent(this, typeof(AutofillActivity));
|
|
|
|
intent.PutExtra("uri", uri);
|
|
|
|
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
|
|
|
|
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, PendingIntentFlags.UpdateCurrent);
|
2016-12-23 06:37:16 +03:00
|
|
|
|
|
|
|
var builder = new Notification.Builder(this);
|
2017-01-29 07:58:26 +03:00
|
|
|
builder.SetSmallIcon(Resource.Drawable.notification_sm)
|
2017-02-01 08:38:35 +03:00
|
|
|
.SetContentTitle(App.Resources.AppResources.BitwardenAutofillService)
|
|
|
|
.SetContentText(App.Resources.AppResources.BitwardenAutofillServiceNotificationContent)
|
|
|
|
.SetTicker(App.Resources.AppResources.BitwardenAutofillServiceNotificationContent)
|
2017-01-31 03:26:39 +03:00
|
|
|
.SetWhen(Java.Lang.JavaSystem.CurrentTimeMillis())
|
2017-01-24 07:32:52 +03:00
|
|
|
.SetContentIntent(pendingIntent);
|
2017-01-29 07:58:26 +03:00
|
|
|
|
2017-02-01 04:45:51 +03:00
|
|
|
if(Build.VERSION.SdkInt > BuildVersionCodes.KitkatWatch)
|
|
|
|
{
|
|
|
|
builder.SetVisibility(NotificationVisibility.Secret)
|
|
|
|
.SetColor(global::Android.Support.V4.Content.ContextCompat.GetColor(ApplicationContext,
|
|
|
|
Resource.Color.primary));
|
|
|
|
}
|
|
|
|
|
2016-12-23 06:37:16 +03:00
|
|
|
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
|
2017-01-24 07:32:52 +03:00
|
|
|
notificationManager.Notify(AutoFillNotificationId, builder.Build());
|
2016-12-23 06:37:16 +03:00
|
|
|
}
|
|
|
|
|
2017-01-24 07:32:52 +03:00
|
|
|
private void FillCredentials(AccessibilityNodeInfo usernameNode, IEnumerable<AccessibilityNodeInfo> passwordNodes)
|
2016-12-23 06:37:16 +03:00
|
|
|
{
|
2017-01-28 07:32:48 +03:00
|
|
|
FillEditText(usernameNode, AutofillActivity.LastCredentials.Username);
|
2017-01-31 03:26:39 +03:00
|
|
|
foreach(var n in passwordNodes)
|
2017-01-24 07:32:52 +03:00
|
|
|
{
|
2017-01-31 03:26:39 +03:00
|
|
|
FillEditText(n, AutofillActivity.LastCredentials.Password);
|
2017-01-24 07:32:52 +03:00
|
|
|
}
|
2016-12-23 06:37:16 +03:00
|
|
|
}
|
|
|
|
|
2017-01-24 07:32:52 +03:00
|
|
|
private static void FillEditText(AccessibilityNodeInfo editTextNode, string value)
|
2016-12-23 06:37:16 +03:00
|
|
|
{
|
2017-01-31 03:26:39 +03:00
|
|
|
if(editTextNode == null || value == null)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-01-24 07:32:52 +03:00
|
|
|
var bundle = new Bundle();
|
|
|
|
bundle.PutString(AccessibilityNodeInfo.ActionArgumentSetTextCharsequence, value);
|
|
|
|
editTextNode.PerformAction(global::Android.Views.Accessibility.Action.SetText, bundle);
|
2016-12-23 06:37:16 +03:00
|
|
|
}
|
|
|
|
|
2017-01-31 03:26:39 +03:00
|
|
|
private IEnumerable<AccessibilityNodeInfo> GetWindowNodes(AccessibilityNodeInfo n,
|
|
|
|
AccessibilityEvent e, Func<AccessibilityNodeInfo, bool> p)
|
2016-12-23 06:37:16 +03:00
|
|
|
{
|
|
|
|
if(n != null)
|
|
|
|
{
|
2017-01-31 03:26:39 +03:00
|
|
|
if(n.WindowId == e.WindowId && !(n.ViewIdResourceName?.StartsWith(SystemUiPackage) ?? false) && p(n))
|
2016-12-23 06:37:16 +03:00
|
|
|
{
|
|
|
|
yield return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
for(int i = 0; i < n.ChildCount; i++)
|
|
|
|
{
|
2017-01-31 03:26:39 +03:00
|
|
|
foreach(var node in GetWindowNodes(n.GetChild(i), e, p))
|
2016-12-23 06:37:16 +03:00
|
|
|
{
|
2017-01-24 07:32:52 +03:00
|
|
|
yield return node;
|
2016-12-23 06:37:16 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-08-27 01:56:09 +03:00
|
|
|
}
|
2017-01-28 07:32:48 +03:00
|
|
|
}
|