2019-04-30 20:33:00 +02:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
2020-04-24 20:45:11 +02:00
|
|
|
|
using Android.App;
|
2020-01-09 18:17:16 +01:00
|
|
|
|
using Android.Content;
|
|
|
|
|
using Android.Graphics;
|
2019-04-30 20:33:00 +02:00
|
|
|
|
using Android.OS;
|
2020-01-09 18:17:16 +01:00
|
|
|
|
using Android.Provider;
|
2020-04-24 20:45:11 +02:00
|
|
|
|
using Android.Runtime;
|
2020-01-09 18:17:16 +01:00
|
|
|
|
using Android.Views;
|
2019-04-30 20:33:00 +02:00
|
|
|
|
using Android.Views.Accessibility;
|
2020-01-09 18:17:16 +01:00
|
|
|
|
using Android.Widget;
|
|
|
|
|
using Bit.App.Resources;
|
2019-04-30 20:33:00 +02:00
|
|
|
|
using Bit.Core;
|
|
|
|
|
|
|
|
|
|
namespace Bit.Droid.Accessibility
|
|
|
|
|
{
|
|
|
|
|
public static class AccessibilityHelpers
|
|
|
|
|
{
|
|
|
|
|
public static Credentials LastCredentials = null;
|
|
|
|
|
public static string SystemUiPackage = "com.android.systemui";
|
|
|
|
|
public static string BitwardenTag = "bw_access";
|
2020-03-26 17:15:33 +01:00
|
|
|
|
public static bool IsAutofillTileAdded = false;
|
|
|
|
|
public static bool IsAccessibilityBroadcastReady = false;
|
2019-04-30 20:33:00 +02:00
|
|
|
|
|
2020-05-05 16:52:54 +02:00
|
|
|
|
// Be sure to keep these two sections sorted alphabetically
|
2019-04-30 20:33:00 +02:00
|
|
|
|
public static Dictionary<string, Browser> SupportedBrowsers => new List<Browser>
|
|
|
|
|
{
|
2020-05-05 16:52:54 +02:00
|
|
|
|
// [Section A] Entries also present in the list of Autofill Framework
|
|
|
|
|
//
|
|
|
|
|
// So keep them in sync with:
|
|
|
|
|
// - AutofillHelpers.{TrustedBrowsers,CompatBrowsers}
|
|
|
|
|
// - Resources/xml/autofillservice.xml
|
2021-02-22 17:22:53 +01:00
|
|
|
|
new Browser("alook.browser", "search_fragment_input_view"),
|
2020-04-30 17:32:56 +02:00
|
|
|
|
new Browser("com.amazon.cloud9", "url"),
|
2019-04-30 20:33:00 +02:00
|
|
|
|
new Browser("com.android.browser", "url"),
|
2020-04-30 17:32:56 +02:00
|
|
|
|
new Browser("com.android.chrome", "url_bar"),
|
2020-12-21 19:15:29 +01:00
|
|
|
|
// Rem. for "com.android.htmlviewer": doesn't have a URL bar, therefore not present here.
|
2020-05-04 15:26:44 +02:00
|
|
|
|
new Browser("com.avast.android.secure.browser", "editor"),
|
2020-05-06 23:32:53 +02:00
|
|
|
|
new Browser("com.avg.android.secure.browser", "editor"),
|
2019-04-30 20:33:00 +02:00
|
|
|
|
new Browser("com.brave.browser", "url_bar"),
|
2020-04-21 14:48:29 +02:00
|
|
|
|
new Browser("com.brave.browser_beta", "url_bar"),
|
2020-05-04 15:23:09 +02:00
|
|
|
|
new Browser("com.brave.browser_default", "url_bar"),
|
|
|
|
|
new Browser("com.brave.browser_dev", "url_bar"),
|
|
|
|
|
new Browser("com.brave.browser_nightly", "url_bar"),
|
2020-04-30 17:32:56 +02:00
|
|
|
|
new Browser("com.chrome.beta", "url_bar"),
|
|
|
|
|
new Browser("com.chrome.canary", "url_bar"),
|
|
|
|
|
new Browser("com.chrome.dev", "url_bar"),
|
|
|
|
|
new Browser("com.duckduckgo.mobile.android", "omnibarTextInput"),
|
|
|
|
|
new Browser("com.ecosia.android", "url_bar"),
|
|
|
|
|
new Browser("com.google.android.apps.chrome", "url_bar"),
|
|
|
|
|
new Browser("com.google.android.apps.chrome_dev", "url_bar"),
|
|
|
|
|
new Browser("com.kiwibrowser.browser", "url_bar"),
|
|
|
|
|
new Browser("com.microsoft.emmx", "url_bar"),
|
2020-10-21 15:10:31 +02:00
|
|
|
|
new Browser("com.mmbox.browser", "search_box"),
|
|
|
|
|
new Browser("com.mmbox.xbrowser", "search_box"),
|
2020-04-30 17:32:56 +02:00
|
|
|
|
new Browser("com.naver.whale", "url_bar"),
|
2019-04-30 20:33:00 +02:00
|
|
|
|
new Browser("com.opera.browser", "url_field"),
|
|
|
|
|
new Browser("com.opera.browser.beta", "url_field"),
|
|
|
|
|
new Browser("com.opera.mini.native", "url_field"),
|
2020-05-01 20:00:01 +02:00
|
|
|
|
new Browser("com.opera.mini.native.beta", "url_field"),
|
2019-04-30 20:33:00 +02:00
|
|
|
|
new Browser("com.opera.touch", "addressbarEdit"),
|
2020-10-15 18:24:40 +02:00
|
|
|
|
new Browser("com.qwant.liberty", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy (before v4)
|
2019-04-30 20:33:00 +02:00
|
|
|
|
new Browser("com.sec.android.app.sbrowser", "location_bar_edit_text"),
|
|
|
|
|
new Browser("com.sec.android.app.sbrowser.beta", "location_bar_edit_text"),
|
2020-05-23 03:06:11 +02:00
|
|
|
|
new Browser("com.stoutner.privacybrowser.free", "url_edittext"),
|
|
|
|
|
new Browser("com.stoutner.privacybrowser.standard", "url_edittext"),
|
2020-04-30 17:32:56 +02:00
|
|
|
|
new Browser("com.vivaldi.browser", "url_bar"),
|
|
|
|
|
new Browser("com.vivaldi.browser.snapshot", "url_bar"),
|
|
|
|
|
new Browser("com.vivaldi.browser.sopranos", "url_bar"),
|
2020-01-29 18:59:17 +01:00
|
|
|
|
new Browser("com.yandex.browser", "bro_omnibar_address_title_text,bro_omnibox_collapsed_title",
|
2019-04-30 20:33:00 +02:00
|
|
|
|
(s) => s.Split(new char[]{' ', ' '}).FirstOrDefault()), // 0 = Regular Space, 1 = No-break space (00A0)
|
2020-12-21 19:15:29 +01:00
|
|
|
|
new Browser("com.z28j.feel", "g2"),
|
2020-09-22 15:17:47 +02:00
|
|
|
|
new Browser("idm.internet.download.manager", "search"),
|
|
|
|
|
new Browser("idm.internet.download.manager.adm.lite", "search"),
|
|
|
|
|
new Browser("idm.internet.download.manager.plus", "search"),
|
2020-10-05 15:15:01 +02:00
|
|
|
|
new Browser("io.github.forkmaintainers.iceraven", "mozac_browser_toolbar_url_view"),
|
2021-02-22 17:22:53 +01:00
|
|
|
|
new Browser("mark.via", "am,an"),
|
|
|
|
|
new Browser("mark.via.gp", "as"),
|
2020-05-23 05:00:27 +02:00
|
|
|
|
new Browser("org.adblockplus.browser", "url_bar,url_bar_title"), // 2nd = Legacy (before v2)
|
|
|
|
|
new Browser("org.adblockplus.browser.beta", "url_bar,url_bar_title"), // 2nd = Legacy (before v2)
|
2020-04-30 17:32:56 +02:00
|
|
|
|
new Browser("org.bromite.bromite", "url_bar"),
|
2020-06-03 05:18:30 +02:00
|
|
|
|
new Browser("org.bromite.chromium", "url_bar"),
|
2020-04-30 17:32:56 +02:00
|
|
|
|
new Browser("org.chromium.chrome", "url_bar"),
|
|
|
|
|
new Browser("org.codeaurora.swe.browser", "url_bar"),
|
2020-05-26 16:41:27 +02:00
|
|
|
|
new Browser("org.gnu.icecat", "url_bar_title,mozac_browser_toolbar_url_view"), // 2nd = Anticipation
|
2020-04-30 17:32:56 +02:00
|
|
|
|
new Browser("org.mozilla.fenix", "mozac_browser_toolbar_url_view"),
|
2020-12-21 19:15:29 +01:00
|
|
|
|
new Browser("org.mozilla.fenix.nightly", "mozac_browser_toolbar_url_view"), // [DEPRECATED ENTRY]
|
|
|
|
|
new Browser("org.mozilla.fennec_aurora", "mozac_browser_toolbar_url_view,url_bar_title"), // [DEPRECATED ENTRY]
|
2020-10-15 18:24:40 +02:00
|
|
|
|
new Browser("org.mozilla.fennec_fdroid", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy
|
|
|
|
|
new Browser("org.mozilla.firefox", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy
|
2020-05-23 15:38:24 +02:00
|
|
|
|
new Browser("org.mozilla.firefox_beta", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy
|
2019-04-30 20:33:00 +02:00
|
|
|
|
new Browser("org.mozilla.focus", "display_url"),
|
|
|
|
|
new Browser("org.mozilla.klar", "display_url"),
|
|
|
|
|
new Browser("org.mozilla.reference.browser", "mozac_browser_toolbar_url_view"),
|
2020-05-01 20:00:01 +02:00
|
|
|
|
new Browser("org.mozilla.rocket", "display_url"),
|
2020-12-21 19:15:29 +01:00
|
|
|
|
new Browser("org.torproject.torbrowser", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy (before v10.0.3)
|
2020-10-15 18:24:40 +02:00
|
|
|
|
new Browser("org.torproject.torbrowser_alpha", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy (before v10.0a8)
|
2020-10-15 18:34:32 +02:00
|
|
|
|
new Browser("org.ungoogled.chromium.extensions.stable", "url_bar"),
|
|
|
|
|
new Browser("org.ungoogled.chromium.stable", "url_bar"),
|
2020-05-05 16:52:54 +02:00
|
|
|
|
|
|
|
|
|
// [Section B] Entries only present here
|
|
|
|
|
//
|
|
|
|
|
// FIXME: Test the compatibility of these with Autofill Framework
|
|
|
|
|
new Browser("acr.browser.barebones", "search"),
|
|
|
|
|
new Browser("acr.browser.lightning", "search"),
|
|
|
|
|
new Browser("com.feedback.browser.wjbrowser", "addressbar_url"),
|
|
|
|
|
new Browser("com.ghostery.android.ghostery", "search_field"),
|
|
|
|
|
new Browser("com.htc.sense.browser", "title"),
|
|
|
|
|
new Browser("com.jerky.browser2", "enterUrl"),
|
|
|
|
|
new Browser("com.ksmobile.cb", "address_bar_edit_text"),
|
|
|
|
|
new Browser("com.linkbubble.playstore", "url_text"),
|
|
|
|
|
new Browser("com.mx.browser", "address_editor_with_progress"),
|
|
|
|
|
new Browser("com.mx.browser.tablet", "address_editor_with_progress"),
|
|
|
|
|
new Browser("com.nubelacorp.javelin", "enterUrl"),
|
|
|
|
|
new Browser("jp.co.fenrir.android.sleipnir", "url_text"),
|
|
|
|
|
new Browser("jp.co.fenrir.android.sleipnir_black", "url_text"),
|
|
|
|
|
new Browser("jp.co.fenrir.android.sleipnir_test", "url_text"),
|
|
|
|
|
new Browser("mobi.mgeek.TunnyBrowser", "title"),
|
|
|
|
|
new Browser("org.iron.srware", "url_bar"),
|
2019-04-30 20:33:00 +02:00
|
|
|
|
}.ToDictionary(n => n.PackageName);
|
|
|
|
|
|
|
|
|
|
// Known packages to skip
|
|
|
|
|
public static HashSet<string> FilteredPackageNames => new HashSet<string>
|
|
|
|
|
{
|
|
|
|
|
SystemUiPackage,
|
|
|
|
|
"com.google.android.googlequicksearchbox",
|
|
|
|
|
"com.google.android.apps.nexuslauncher",
|
|
|
|
|
"com.google.android.launcher",
|
|
|
|
|
"com.computer.desktop.ui.launcher",
|
|
|
|
|
"com.launcher.notelauncher",
|
|
|
|
|
"com.anddoes.launcher",
|
|
|
|
|
"com.actionlauncher.playstore",
|
|
|
|
|
"ch.deletescape.lawnchair.plah",
|
|
|
|
|
"com.microsoft.launcher",
|
|
|
|
|
"com.teslacoilsw.launcher",
|
|
|
|
|
"com.teslacoilsw.launcher.prime",
|
|
|
|
|
"is.shortcut",
|
|
|
|
|
"me.craftsapp.nlauncher",
|
2019-10-17 14:00:58 +02:00
|
|
|
|
"com.ss.squarehome2",
|
|
|
|
|
"com.treydev.pns"
|
2019-04-30 20:33:00 +02:00
|
|
|
|
};
|
2020-08-07 17:02:08 +02:00
|
|
|
|
|
|
|
|
|
// Be sure to keep these sections sorted alphabetically
|
2020-05-06 15:48:34 +02:00
|
|
|
|
public static Dictionary<string, KnownUsernameField> KnownUsernameFields => new List<KnownUsernameField>
|
|
|
|
|
{
|
2020-08-07 17:02:08 +02:00
|
|
|
|
/**************************************************************************************
|
|
|
|
|
* SECTION A ——— World-renowned web sites/applications
|
|
|
|
|
*************************************************************************************/
|
|
|
|
|
|
|
|
|
|
// REM.: For this type of web sites/applications, the Top 100 (SimilarWeb, 2019)
|
|
|
|
|
// and the Top 50 (Alexa Internet, 2020) are covered. National variants
|
|
|
|
|
// have been added when available. Mobile and desktop versions supported.
|
|
|
|
|
//
|
|
|
|
|
// A few other popular web sites/applications have also been added.
|
|
|
|
|
//
|
|
|
|
|
// Could not be added, however:
|
|
|
|
|
// web sites/applications that don't use an "id" attribute for their login field.
|
|
|
|
|
|
|
|
|
|
// NOTE: The case of OAuth compatible web sites/applications that also provide
|
|
|
|
|
// a "user ID only" login page in this situation
|
|
|
|
|
// was taken into account in the tests as well.
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* A
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// Amazon ——— ap_email_login = mobile / ap_email = desktop (amazon.co.jp currently uses ap_email in both cases, as of July 2020).
|
|
|
|
|
new KnownUsernameField("amazon.ae", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
|
|
|
new KnownUsernameField("amazon.ca", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
|
|
|
new KnownUsernameField("amazon.cn", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
|
|
|
new KnownUsernameField("amazon.co.jp", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
|
|
|
new KnownUsernameField("amazon.co.uk", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
|
|
|
new KnownUsernameField("amazon.com", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
|
|
|
new KnownUsernameField("amazon.com.au", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
|
|
|
new KnownUsernameField("amazon.com.br", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
|
|
|
new KnownUsernameField("amazon.com.mx", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
|
|
|
new KnownUsernameField("amazon.com.tr", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
|
|
|
new KnownUsernameField("amazon.de", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
|
|
|
new KnownUsernameField("amazon.es", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
|
|
|
new KnownUsernameField("amazon.fr", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
|
|
|
new KnownUsernameField("amazon.in", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
|
|
|
new KnownUsernameField("amazon.it", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
|
|
|
new KnownUsernameField("amazon.nl", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
2021-03-16 19:57:45 +01:00
|
|
|
|
new KnownUsernameField("amazon.pl", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
2020-08-07 17:02:08 +02:00
|
|
|
|
new KnownUsernameField("amazon.sa", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
2021-03-16 19:57:45 +01:00
|
|
|
|
new KnownUsernameField("amazon.se", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
2020-08-07 17:02:08 +02:00
|
|
|
|
new KnownUsernameField("amazon.sg", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
|
|
|
|
|
|
|
|
// Amazon Web Services
|
|
|
|
|
new KnownUsernameField("signin.aws.amazon.com", new (string, string)[] { ("signin", "resolving_input") }),
|
|
|
|
|
|
|
|
|
|
// Atlassian
|
|
|
|
|
new KnownUsernameField("id.atlassian.com", new (string, string)[] { ("login", "username") }),
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* B
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// Bitly ——— enterprise users.
|
|
|
|
|
new KnownUsernameField("bitly.com", new (string, string)[] { ("/sso/url_slug", "url_slug") }),
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* E
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// eBay ——— 1st = traditional access / 2nd = direct access (i.e. https://signin.ebay.tld/).
|
|
|
|
|
new KnownUsernameField("signin.befr.ebay.be", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
|
|
|
new KnownUsernameField("signin.benl.ebay.be", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
|
|
|
new KnownUsernameField("signin.cafr.ebay.ca", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
|
|
|
new KnownUsernameField("signin.ebay.at", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
|
|
|
new KnownUsernameField("signin.ebay.be", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
|
|
|
new KnownUsernameField("signin.ebay.ca", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
|
|
|
new KnownUsernameField("signin.ebay.ch", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
|
|
|
new KnownUsernameField("signin.ebay.co.uk", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
|
|
|
new KnownUsernameField("signin.ebay.com", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
|
|
|
new KnownUsernameField("signin.ebay.com.au", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
|
|
|
new KnownUsernameField("signin.ebay.com.hk", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
|
|
|
new KnownUsernameField("signin.ebay.com.my", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
|
|
|
new KnownUsernameField("signin.ebay.com.sg", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
|
|
|
new KnownUsernameField("signin.ebay.de", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
|
|
|
new KnownUsernameField("signin.ebay.es", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
|
|
|
new KnownUsernameField("signin.ebay.fr", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
|
|
|
new KnownUsernameField("signin.ebay.ie", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
2020-08-17 21:13:13 +02:00
|
|
|
|
new KnownUsernameField("signin.ebay.in", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
2020-08-07 17:02:08 +02:00
|
|
|
|
new KnownUsernameField("signin.ebay.it", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
|
|
|
new KnownUsernameField("signin.ebay.nl", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
|
|
|
new KnownUsernameField("signin.ebay.ph", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
|
|
|
new KnownUsernameField("signin.ebay.pl", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* G
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// Google ——— 1st = used in most cases (v2) / 2nd = used in some cases (v1).
|
|
|
|
|
new KnownUsernameField("accounts.google.com", new (string, string)[] { ("identifier", "identifierId"), ("ServiceLogin", "Email") }),
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* P
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// PayPal ——— 1st = traditional access / 2nd = access using OAuth.
|
|
|
|
|
new KnownUsernameField("paypal.com", new (string, string)[] { ("signin", "email"), ("contains:/connect/", "email") }),
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* T
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// Tumblr ——— despite "signup" in its ID, it's the login field (the website offers registration if the account doesn't exist).
|
|
|
|
|
new KnownUsernameField("tumblr.com", new (string, string)[] { ("login", "signup_determine_email") }),
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Y
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// Yandex
|
2020-08-20 15:56:11 +02:00
|
|
|
|
new KnownUsernameField("passport.yandex.az", new (string, string)[] { ("auth", "passp-field-login") }),
|
2020-08-07 17:02:08 +02:00
|
|
|
|
new KnownUsernameField("passport.yandex.by", new (string, string)[] { ("auth", "passp-field-login") }),
|
2020-08-20 15:56:11 +02:00
|
|
|
|
new KnownUsernameField("passport.yandex.co.il", new (string, string)[] { ("auth", "passp-field-login") }),
|
2020-08-07 17:02:08 +02:00
|
|
|
|
new KnownUsernameField("passport.yandex.com", new (string, string)[] { ("auth", "passp-field-login") }),
|
2020-08-20 15:56:11 +02:00
|
|
|
|
new KnownUsernameField("passport.yandex.com.am", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
|
|
|
new KnownUsernameField("passport.yandex.com.ge", new (string, string)[] { ("auth", "passp-field-login") }),
|
2020-08-07 17:02:08 +02:00
|
|
|
|
new KnownUsernameField("passport.yandex.com.tr", new (string, string)[] { ("auth", "passp-field-login") }),
|
2020-08-20 15:56:11 +02:00
|
|
|
|
new KnownUsernameField("passport.yandex.ee", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
|
|
|
new KnownUsernameField("passport.yandex.fi", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
|
|
|
new KnownUsernameField("passport.yandex.fr", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
|
|
|
new KnownUsernameField("passport.yandex.kg", new (string, string)[] { ("auth", "passp-field-login") }),
|
2020-08-07 17:02:08 +02:00
|
|
|
|
new KnownUsernameField("passport.yandex.kz", new (string, string)[] { ("auth", "passp-field-login") }),
|
2020-08-20 15:56:11 +02:00
|
|
|
|
new KnownUsernameField("passport.yandex.lt", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
|
|
|
new KnownUsernameField("passport.yandex.lv", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
|
|
|
new KnownUsernameField("passport.yandex.md", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
|
|
|
new KnownUsernameField("passport.yandex.pl", new (string, string)[] { ("auth", "passp-field-login") }),
|
2020-08-07 17:02:08 +02:00
|
|
|
|
new KnownUsernameField("passport.yandex.ru", new (string, string)[] { ("auth", "passp-field-login") }),
|
2020-08-20 15:56:11 +02:00
|
|
|
|
new KnownUsernameField("passport.yandex.tj", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
|
|
|
new KnownUsernameField("passport.yandex.tm", new (string, string)[] { ("auth", "passp-field-login") }),
|
2020-08-07 17:02:08 +02:00
|
|
|
|
new KnownUsernameField("passport.yandex.ua", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
|
|
|
new KnownUsernameField("passport.yandex.uz", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
|
|
|
|
|
|
|
|
/**************************************************************************************
|
|
|
|
|
* SECTION B ——— Top 100 worldwide
|
|
|
|
|
*************************************************************************************/
|
|
|
|
|
|
|
|
|
|
// As of July 2020, all entries that needed to be added from
|
|
|
|
|
// Top 100 (SimilarWeb, 2019) and Top 50 (Alexa Internet, 2020)
|
|
|
|
|
// matched section A.
|
|
|
|
|
//
|
|
|
|
|
// Therefore, no entry currently.
|
|
|
|
|
|
|
|
|
|
/**************************************************************************************
|
|
|
|
|
* SECTION C ——— Top 20 for selected countries
|
|
|
|
|
*************************************************************************************/
|
|
|
|
|
|
|
|
|
|
// REM.: For these selected countries, the Top 20 (SimilarWeb, 2020)
|
|
|
|
|
// and the Top 20 (Alexa Internet, 2020) are covered.
|
|
|
|
|
// Mobile and desktop versions supported.
|
|
|
|
|
//
|
|
|
|
|
// Could not be added, however:
|
|
|
|
|
// web sites/applications that don't use an "id" attribute for their login field.
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Japan
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// NTT DOCOMO ——— mainly used for "My docomo".
|
|
|
|
|
new KnownUsernameField("cfg.smt.docomo.ne.jp", new (string, string)[] { ("contains:/auth/", "Di_Uid") }),
|
2020-10-15 19:10:08 +02:00
|
|
|
|
new KnownUsernameField("id.smt.docomo.ne.jp", new (string, string)[] { ("contains:/cgi7/", "Di_Uid") }),
|
2020-08-07 17:02:08 +02:00
|
|
|
|
|
|
|
|
|
/**************************************************************************************
|
|
|
|
|
* SECTION D ——— Miscellaneous
|
|
|
|
|
*************************************************************************************/
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Various entries ——— Following user requests, etc.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// No entry, currently.
|
|
|
|
|
|
|
|
|
|
/**************************************************************************************
|
|
|
|
|
* SECTION Z ——— Special forms
|
|
|
|
|
*
|
|
|
|
|
* Despite "user ID + password" fields both visible, detection rules required.
|
|
|
|
|
*************************************************************************************/
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Main
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// No entry, currently.
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Test/example purposes only
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// GitHub ——— VERY special case (signup form, just to test the proper functioning of special forms).
|
|
|
|
|
new KnownUsernameField("github.com", new (string, string)[] { ("", "user[login]-footer") }),
|
2020-05-06 15:48:34 +02:00
|
|
|
|
}.ToDictionary(n => n.UriAuthority);
|
2019-04-30 20:33:00 +02:00
|
|
|
|
|
|
|
|
|
public static void PrintTestData(AccessibilityNodeInfo root, AccessibilityEvent e)
|
|
|
|
|
{
|
|
|
|
|
var testNodes = GetWindowNodes(root, e, n => n.ViewIdResourceName != null && n.Text != null, false);
|
|
|
|
|
var testNodesData = testNodes.Select(n => new { id = n.ViewIdResourceName, text = n.Text });
|
2020-03-28 14:16:28 +01:00
|
|
|
|
foreach (var node in testNodesData)
|
2019-10-11 15:29:33 +02:00
|
|
|
|
{
|
|
|
|
|
System.Diagnostics.Debug.WriteLine("Node: {0} = {1}", node.id, node.text);
|
|
|
|
|
}
|
2019-04-30 20:33:00 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static string GetUri(AccessibilityNodeInfo root)
|
|
|
|
|
{
|
|
|
|
|
var uri = string.Concat(Constants.AndroidAppProtocol, root.PackageName);
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (SupportedBrowsers.ContainsKey(root.PackageName))
|
2019-04-30 20:33:00 +02:00
|
|
|
|
{
|
|
|
|
|
var browser = SupportedBrowsers[root.PackageName];
|
2020-01-29 18:59:17 +01:00
|
|
|
|
AccessibilityNodeInfo addressNode = null;
|
2020-03-28 14:16:28 +01:00
|
|
|
|
foreach (var uriViewId in browser.UriViewId.Split(","))
|
2020-01-29 18:59:17 +01:00
|
|
|
|
{
|
|
|
|
|
addressNode = root.FindAccessibilityNodeInfosByViewId(
|
|
|
|
|
$"{root.PackageName}:id/{uriViewId}").FirstOrDefault();
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (addressNode != null)
|
2020-01-29 18:59:17 +01:00
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (addressNode != null)
|
2019-04-30 20:33:00 +02:00
|
|
|
|
{
|
|
|
|
|
uri = ExtractUri(uri, addressNode, browser);
|
2020-01-27 23:36:20 +01:00
|
|
|
|
addressNode.Recycle();
|
2019-04-30 20:33:00 +02:00
|
|
|
|
}
|
2020-01-03 21:19:20 +01:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Return null to prevent overwriting notification pendingIntent uri with browser packageName
|
|
|
|
|
// (we login to pages, not browsers)
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2019-04-30 20:33:00 +02:00
|
|
|
|
}
|
|
|
|
|
return uri;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-29 17:04:50 +02:00
|
|
|
|
private static string ExtractUri(string uri, AccessibilityNodeInfo addressNode, Browser browser)
|
2019-04-30 20:33:00 +02:00
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (addressNode?.Text == null)
|
2019-04-30 20:33:00 +02:00
|
|
|
|
{
|
|
|
|
|
return uri;
|
|
|
|
|
}
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (addressNode.Text == null)
|
2019-04-30 20:33:00 +02:00
|
|
|
|
{
|
|
|
|
|
return uri;
|
|
|
|
|
}
|
|
|
|
|
uri = browser.GetUriFunction(addressNode.Text)?.Trim();
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (uri != null && uri.Contains("."))
|
2019-04-30 20:33:00 +02:00
|
|
|
|
{
|
2020-04-29 19:09:46 +02:00
|
|
|
|
var hasHttpProtocol = uri.StartsWith("http://") || uri.StartsWith("https://");
|
|
|
|
|
if (!hasHttpProtocol && uri.Contains("."))
|
2019-04-30 20:33:00 +02:00
|
|
|
|
{
|
2020-12-21 16:58:26 +01:00
|
|
|
|
if (Uri.TryCreate("https://" + uri, UriKind.Absolute, out var _))
|
2020-04-29 19:09:46 +02:00
|
|
|
|
{
|
2020-12-21 16:58:26 +01:00
|
|
|
|
return string.Concat("https://", uri);
|
2020-04-29 19:09:46 +02:00
|
|
|
|
}
|
2019-04-30 20:33:00 +02:00
|
|
|
|
}
|
2020-12-21 16:58:26 +01:00
|
|
|
|
if (Uri.TryCreate(uri, UriKind.Absolute, out var _))
|
2020-04-30 22:47:29 +02:00
|
|
|
|
{
|
|
|
|
|
return uri;
|
|
|
|
|
}
|
2019-04-30 20:33:00 +02:00
|
|
|
|
}
|
|
|
|
|
return uri;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Check to make sure it is ok to autofill still on the current screen
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static bool NeedToAutofill(Credentials credentials, string currentUriString)
|
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (credentials == null)
|
2019-04-30 20:33:00 +02:00
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (Uri.TryCreate(credentials.LastUri, UriKind.Absolute, out Uri lastUri) &&
|
2019-04-30 20:33:00 +02:00
|
|
|
|
Uri.TryCreate(currentUriString, UriKind.Absolute, out Uri currentUri))
|
|
|
|
|
{
|
|
|
|
|
return lastUri.Host == currentUri.Host;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static bool EditText(AccessibilityNodeInfo n)
|
|
|
|
|
{
|
|
|
|
|
return n?.ClassName?.Contains("EditText") ?? false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void FillCredentials(AccessibilityNodeInfo usernameNode,
|
|
|
|
|
IEnumerable<AccessibilityNodeInfo> passwordNodes)
|
|
|
|
|
{
|
|
|
|
|
FillEditText(usernameNode, LastCredentials?.Username);
|
2020-03-28 14:16:28 +01:00
|
|
|
|
foreach (var n in passwordNodes)
|
2019-04-30 20:33:00 +02:00
|
|
|
|
{
|
|
|
|
|
FillEditText(n, LastCredentials?.Password);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void FillEditText(AccessibilityNodeInfo editTextNode, string value)
|
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (editTextNode == null || value == null)
|
2019-04-30 20:33:00 +02:00
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
var bundle = new Bundle();
|
|
|
|
|
bundle.PutString(AccessibilityNodeInfo.ActionArgumentSetTextCharsequence, value);
|
|
|
|
|
editTextNode.PerformAction(Android.Views.Accessibility.Action.SetText, bundle);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static NodeList GetWindowNodes(AccessibilityNodeInfo n, AccessibilityEvent e,
|
|
|
|
|
Func<AccessibilityNodeInfo, bool> condition, bool disposeIfUnused, NodeList nodes = null,
|
|
|
|
|
int recursionDepth = 0)
|
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (nodes == null)
|
2019-04-30 20:33:00 +02:00
|
|
|
|
{
|
|
|
|
|
nodes = new NodeList();
|
|
|
|
|
}
|
|
|
|
|
var dispose = disposeIfUnused;
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (n != null && recursionDepth < 100)
|
2019-04-30 20:33:00 +02:00
|
|
|
|
{
|
|
|
|
|
var add = n.WindowId == e.WindowId &&
|
|
|
|
|
!(n.ViewIdResourceName?.StartsWith(SystemUiPackage) ?? false) &&
|
|
|
|
|
condition(n);
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (add)
|
2019-04-30 20:33:00 +02:00
|
|
|
|
{
|
|
|
|
|
dispose = false;
|
|
|
|
|
nodes.Add(n);
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-28 14:16:28 +01:00
|
|
|
|
for (var i = 0; i < n.ChildCount; i++)
|
2019-04-30 20:33:00 +02:00
|
|
|
|
{
|
|
|
|
|
var childNode = n.GetChild(i);
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (childNode == null)
|
2020-01-27 23:36:20 +01:00
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2020-03-28 14:16:28 +01:00
|
|
|
|
else if (i > 100)
|
2019-04-30 20:33:00 +02:00
|
|
|
|
{
|
|
|
|
|
Android.Util.Log.Info(BitwardenTag, "Too many child iterations.");
|
|
|
|
|
break;
|
|
|
|
|
}
|
2020-03-28 14:16:28 +01:00
|
|
|
|
else if (childNode.GetHashCode() == n.GetHashCode())
|
2019-04-30 20:33:00 +02:00
|
|
|
|
{
|
|
|
|
|
Android.Util.Log.Info(BitwardenTag, "Child node is the same as parent for some reason.");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
GetWindowNodes(childNode, e, condition, true, nodes, recursionDepth++);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (dispose)
|
2019-04-30 20:33:00 +02:00
|
|
|
|
{
|
2020-01-27 23:36:20 +01:00
|
|
|
|
n?.Recycle();
|
2020-01-29 13:23:49 +01:00
|
|
|
|
n?.Dispose();
|
2019-04-30 20:33:00 +02:00
|
|
|
|
}
|
|
|
|
|
return nodes;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-06 15:48:34 +02:00
|
|
|
|
public static AccessibilityNodeInfo GetUsernameEditText(string uriString,
|
|
|
|
|
IEnumerable<AccessibilityNodeInfo> allEditTexts)
|
2019-04-30 20:33:00 +02:00
|
|
|
|
{
|
2020-07-01 16:31:12 +02:00
|
|
|
|
string uriAuthority = null;
|
2020-05-06 15:48:34 +02:00
|
|
|
|
string uriKey = null;
|
|
|
|
|
string uriLocalPath = null;
|
|
|
|
|
if (Uri.TryCreate(uriString, UriKind.Absolute, out var uri))
|
|
|
|
|
{
|
2020-07-01 16:31:12 +02:00
|
|
|
|
uriAuthority = uri.Authority;
|
2020-08-06 18:38:25 +02:00
|
|
|
|
uriKey = uriAuthority.StartsWith("www.", StringComparison.Ordinal) ? uriAuthority.Substring(4) : uriAuthority;
|
2020-05-06 15:48:34 +02:00
|
|
|
|
uriLocalPath = uri.LocalPath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(uriKey))
|
|
|
|
|
{
|
|
|
|
|
// Uncomment this to log values necessary for username field discovery
|
|
|
|
|
// foreach (var editText in allEditTexts)
|
|
|
|
|
// {
|
|
|
|
|
// System.Diagnostics.Debug.WriteLine(">>> uriKey: {0}, uriLocalPath: {1}, viewId: {2}", uriKey,
|
|
|
|
|
// uriLocalPath, editText.ViewIdResourceName);
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
if (KnownUsernameFields.ContainsKey(uriKey))
|
|
|
|
|
{
|
|
|
|
|
var usernameField = KnownUsernameFields[uriKey];
|
2020-08-06 18:38:25 +02:00
|
|
|
|
(string UriPathWanted, string UsernameViewId)[] accessOptions = usernameField.AccessOptions;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < accessOptions.Length; i++)
|
2020-05-06 15:48:34 +02:00
|
|
|
|
{
|
2020-08-06 18:38:25 +02:00
|
|
|
|
string curUriPathWanted = accessOptions[i].UriPathWanted;
|
|
|
|
|
string curUsernameViewId = accessOptions[i].UsernameViewId;
|
|
|
|
|
bool uriLocalPathMatches = false;
|
|
|
|
|
|
|
|
|
|
// Case-sensitive comparison
|
|
|
|
|
if (curUriPathWanted.StartsWith("startswith:", StringComparison.Ordinal))
|
|
|
|
|
{
|
|
|
|
|
curUriPathWanted = curUriPathWanted.Substring(11);
|
|
|
|
|
uriLocalPathMatches = uriLocalPath.StartsWith(curUriPathWanted, StringComparison.Ordinal);
|
|
|
|
|
}
|
|
|
|
|
else if (curUriPathWanted.StartsWith("contains:", StringComparison.Ordinal))
|
|
|
|
|
{
|
|
|
|
|
curUriPathWanted = curUriPathWanted.Substring(9);
|
|
|
|
|
uriLocalPathMatches = uriLocalPath.Contains(curUriPathWanted, StringComparison.Ordinal);
|
|
|
|
|
}
|
|
|
|
|
else if (curUriPathWanted.StartsWith("endswith:", StringComparison.Ordinal))
|
|
|
|
|
{
|
|
|
|
|
curUriPathWanted = curUriPathWanted.Substring(9);
|
|
|
|
|
uriLocalPathMatches = uriLocalPath.EndsWith(curUriPathWanted, StringComparison.Ordinal);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Case-insensitive comparison
|
|
|
|
|
else if (curUriPathWanted.StartsWith("istartswith:", StringComparison.Ordinal))
|
|
|
|
|
{
|
|
|
|
|
curUriPathWanted = curUriPathWanted.Substring(12);
|
|
|
|
|
uriLocalPathMatches = uriLocalPath.StartsWith(curUriPathWanted, StringComparison.OrdinalIgnoreCase);
|
|
|
|
|
}
|
|
|
|
|
else if (curUriPathWanted.StartsWith("icontains:", StringComparison.Ordinal))
|
|
|
|
|
{
|
|
|
|
|
curUriPathWanted = curUriPathWanted.Substring(10);
|
|
|
|
|
uriLocalPathMatches = uriLocalPath.Contains(curUriPathWanted, StringComparison.OrdinalIgnoreCase);
|
|
|
|
|
}
|
|
|
|
|
else if (curUriPathWanted.StartsWith("iendswith:", StringComparison.Ordinal))
|
|
|
|
|
{
|
|
|
|
|
curUriPathWanted = curUriPathWanted.Substring(10);
|
|
|
|
|
uriLocalPathMatches = uriLocalPath.EndsWith(curUriPathWanted, StringComparison.OrdinalIgnoreCase);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Default type of comparison
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
uriLocalPathMatches = uriLocalPath.EndsWith(curUriPathWanted, StringComparison.Ordinal);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (uriLocalPathMatches)
|
2020-05-06 15:48:34 +02:00
|
|
|
|
{
|
2020-08-06 18:38:25 +02:00
|
|
|
|
foreach (var editText in allEditTexts)
|
2020-05-06 15:48:34 +02:00
|
|
|
|
{
|
2020-08-06 18:38:25 +02:00
|
|
|
|
foreach (var usernameViewId in curUsernameViewId.Split(","))
|
2020-05-06 15:48:34 +02:00
|
|
|
|
{
|
2020-08-06 18:38:25 +02:00
|
|
|
|
if (usernameViewId == editText.ViewIdResourceName)
|
|
|
|
|
{
|
|
|
|
|
return editText;
|
|
|
|
|
}
|
2020-05-06 15:48:34 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-30 20:33:00 +02:00
|
|
|
|
|
2020-05-06 15:48:34 +02:00
|
|
|
|
// no match found, attempt to establish username field based on password field
|
|
|
|
|
return GetUsernameEditTextIfPasswordExists(allEditTexts);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static AccessibilityNodeInfo GetUsernameEditTextIfPasswordExists(
|
2020-01-13 23:14:57 +01:00
|
|
|
|
IEnumerable<AccessibilityNodeInfo> allEditTexts)
|
2019-04-30 20:33:00 +02:00
|
|
|
|
{
|
2020-01-13 23:14:57 +01:00
|
|
|
|
AccessibilityNodeInfo previousEditText = null;
|
2020-03-28 14:16:28 +01:00
|
|
|
|
foreach (var editText in allEditTexts)
|
2020-01-13 23:14:57 +01:00
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (editText.Password)
|
2020-01-13 23:14:57 +01:00
|
|
|
|
{
|
|
|
|
|
return previousEditText;
|
|
|
|
|
}
|
|
|
|
|
previousEditText = editText;
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static bool IsUsernameEditText(AccessibilityNodeInfo root, AccessibilityEvent e)
|
|
|
|
|
{
|
|
|
|
|
var allEditTexts = GetWindowNodes(root, e, n => EditText(n), false);
|
2020-05-06 15:48:34 +02:00
|
|
|
|
var uriString = GetUri(root);
|
|
|
|
|
var usernameEditText = GetUsernameEditText(uriString, allEditTexts);
|
2020-01-27 23:36:20 +01:00
|
|
|
|
|
|
|
|
|
var isUsernameEditText = false;
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (usernameEditText != null)
|
2020-01-27 23:36:20 +01:00
|
|
|
|
{
|
|
|
|
|
isUsernameEditText = IsSameNode(usernameEditText, e.Source);
|
2020-01-13 23:14:57 +01:00
|
|
|
|
}
|
2020-01-27 23:36:20 +01:00
|
|
|
|
allEditTexts.Dispose();
|
|
|
|
|
|
|
|
|
|
return isUsernameEditText;
|
2020-01-13 23:14:57 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-27 23:36:20 +01:00
|
|
|
|
public static bool IsSameNode(AccessibilityNodeInfo node1, AccessibilityNodeInfo node2)
|
2020-01-13 23:14:57 +01:00
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (node1 != null && node2 != null)
|
2020-01-13 23:14:57 +01:00
|
|
|
|
{
|
2020-01-27 23:36:20 +01:00
|
|
|
|
return node1.Equals(node2) || node1.GetHashCode() == node2.GetHashCode();
|
2020-01-13 23:14:57 +01:00
|
|
|
|
}
|
|
|
|
|
return false;
|
2019-04-30 20:33:00 +02:00
|
|
|
|
}
|
2020-01-09 18:17:16 +01:00
|
|
|
|
|
2020-01-10 16:20:19 +01:00
|
|
|
|
public static bool OverlayPermitted()
|
2020-01-09 18:17:16 +01:00
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
|
2020-01-09 18:17:16 +01:00
|
|
|
|
{
|
2020-04-24 20:45:11 +02:00
|
|
|
|
if (Settings.CanDrawOverlays(Application.Context))
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var appOpsMgr = (AppOpsManager)Application.Context.GetSystemService(Context.AppOpsService);
|
|
|
|
|
var mode = appOpsMgr.CheckOpNoThrow("android:system_alert_window", Process.MyUid(),
|
|
|
|
|
Application.Context.PackageName);
|
|
|
|
|
if (mode == AppOpsManagerMode.Allowed || mode == AppOpsManagerMode.Ignored)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var wm = Application.Context.GetSystemService(Context.WindowService)
|
|
|
|
|
.JavaCast<IWindowManager>();
|
|
|
|
|
if (wm == null)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
var testView = new View(Application.Context);
|
|
|
|
|
var layoutParams = GetOverlayLayoutParams();
|
|
|
|
|
wm.AddView(testView, layoutParams);
|
|
|
|
|
wm.RemoveView(testView);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
catch { }
|
|
|
|
|
|
|
|
|
|
return false;
|
2020-01-09 18:17:16 +01:00
|
|
|
|
}
|
2020-04-24 20:45:11 +02:00
|
|
|
|
|
|
|
|
|
// older android versions are always true
|
|
|
|
|
return true;
|
2020-01-09 18:17:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static LinearLayout GetOverlayView(Context context)
|
|
|
|
|
{
|
|
|
|
|
var inflater = (LayoutInflater)context.GetSystemService(Context.LayoutInflaterService);
|
|
|
|
|
var view = (LinearLayout)inflater.Inflate(Resource.Layout.autofill_listitem, null);
|
|
|
|
|
var text1 = (TextView)view.FindViewById(Resource.Id.text1);
|
|
|
|
|
var text2 = (TextView)view.FindViewById(Resource.Id.text2);
|
|
|
|
|
var icon = (ImageView)view.FindViewById(Resource.Id.icon);
|
|
|
|
|
text1.Text = AppResources.AutofillWithBitwarden;
|
|
|
|
|
text2.Text = AppResources.GoToMyVault;
|
2020-11-03 18:30:34 +01:00
|
|
|
|
icon.SetImageResource(Resource.Drawable.shield);
|
2020-01-09 18:17:16 +01:00
|
|
|
|
return view;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-13 23:14:57 +01:00
|
|
|
|
public static WindowManagerLayoutParams GetOverlayLayoutParams()
|
|
|
|
|
{
|
|
|
|
|
WindowManagerTypes windowManagerType;
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
|
2020-01-13 23:14:57 +01:00
|
|
|
|
{
|
|
|
|
|
windowManagerType = WindowManagerTypes.ApplicationOverlay;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
windowManagerType = WindowManagerTypes.Phone;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var layoutParams = new WindowManagerLayoutParams(
|
|
|
|
|
ViewGroup.LayoutParams.WrapContent,
|
|
|
|
|
ViewGroup.LayoutParams.WrapContent,
|
|
|
|
|
windowManagerType,
|
|
|
|
|
WindowManagerFlags.NotFocusable | WindowManagerFlags.NotTouchModal,
|
|
|
|
|
Format.Transparent);
|
2020-02-06 01:40:44 +01:00
|
|
|
|
layoutParams.Gravity = GravityFlags.Top | GravityFlags.Left;
|
2020-01-13 23:14:57 +01:00
|
|
|
|
|
|
|
|
|
return layoutParams;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-09 20:57:06 +02:00
|
|
|
|
public static Point GetOverlayAnchorPosition(AccessibilityService service, AccessibilityNodeInfo anchorView,
|
|
|
|
|
int overlayViewHeight, bool isOverlayAboveAnchor)
|
2020-01-09 18:17:16 +01:00
|
|
|
|
{
|
2020-01-13 23:14:57 +01:00
|
|
|
|
var anchorViewRect = new Rect();
|
|
|
|
|
anchorView.GetBoundsInScreen(anchorViewRect);
|
2020-02-06 01:40:44 +01:00
|
|
|
|
var anchorViewX = anchorViewRect.Left;
|
|
|
|
|
var anchorViewY = isOverlayAboveAnchor ? anchorViewRect.Top : anchorViewRect.Bottom;
|
2020-01-27 23:36:20 +01:00
|
|
|
|
anchorViewRect.Dispose();
|
2020-02-06 01:40:44 +01:00
|
|
|
|
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (isOverlayAboveAnchor)
|
2020-01-29 13:23:49 +01:00
|
|
|
|
{
|
2020-02-06 01:40:44 +01:00
|
|
|
|
anchorViewY -= overlayViewHeight;
|
2020-01-29 13:23:49 +01:00
|
|
|
|
}
|
2020-04-09 20:57:06 +02:00
|
|
|
|
anchorViewY -= GetStatusBarHeight(service);
|
2020-01-29 13:23:49 +01:00
|
|
|
|
|
2020-02-06 01:40:44 +01:00
|
|
|
|
return new Point(anchorViewX, anchorViewY);
|
2020-01-13 23:14:57 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-04-09 20:57:06 +02:00
|
|
|
|
public static Point GetOverlayAnchorPosition(AccessibilityService service, AccessibilityNodeInfo anchorNode,
|
|
|
|
|
AccessibilityNodeInfo root, IEnumerable<AccessibilityWindowInfo> windows, int overlayViewHeight,
|
|
|
|
|
bool isOverlayAboveAnchor)
|
2020-01-13 23:14:57 +01:00
|
|
|
|
{
|
|
|
|
|
Point point = null;
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (anchorNode != null)
|
2020-01-13 23:14:57 +01:00
|
|
|
|
{
|
2020-01-29 14:46:21 +01:00
|
|
|
|
// Update node's info since this is still a reference from an older event
|
|
|
|
|
anchorNode.Refresh();
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (!anchorNode.VisibleToUser)
|
2020-01-27 23:36:20 +01:00
|
|
|
|
{
|
|
|
|
|
return new Point(-1, -1);
|
|
|
|
|
}
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (!anchorNode.Focused)
|
2020-01-29 13:23:49 +01:00
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2020-01-27 23:36:20 +01:00
|
|
|
|
|
|
|
|
|
// node.VisibleToUser doesn't always give us exactly what we want, so attempt to tighten up the range
|
|
|
|
|
// of visibility
|
2020-04-03 01:24:31 +02:00
|
|
|
|
var inputMethodHeight = 0;
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (windows != null)
|
2020-01-27 23:36:20 +01:00
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (IsStatusBarExpanded(windows))
|
2020-01-27 23:36:20 +01:00
|
|
|
|
{
|
|
|
|
|
return new Point(-1, -1);
|
|
|
|
|
}
|
2020-04-03 01:24:31 +02:00
|
|
|
|
inputMethodHeight = GetInputMethodHeight(windows);
|
2020-02-06 01:40:44 +01:00
|
|
|
|
}
|
2020-04-03 01:24:31 +02:00
|
|
|
|
var minY = 0;
|
|
|
|
|
var rootNodeHeight = GetNodeHeight(root);
|
|
|
|
|
if (rootNodeHeight == -1)
|
2020-02-06 01:40:44 +01:00
|
|
|
|
{
|
2020-04-03 01:24:31 +02:00
|
|
|
|
return null;
|
2020-01-27 23:36:20 +01:00
|
|
|
|
}
|
2020-04-09 20:57:06 +02:00
|
|
|
|
var maxY = rootNodeHeight - GetNavigationBarHeight(service) - GetStatusBarHeight(service) -
|
|
|
|
|
inputMethodHeight;
|
2020-01-27 23:36:20 +01:00
|
|
|
|
|
2020-04-09 20:57:06 +02:00
|
|
|
|
point = GetOverlayAnchorPosition(service, anchorNode, overlayViewHeight, isOverlayAboveAnchor);
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (point.Y < minY)
|
2020-01-13 23:14:57 +01:00
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (isOverlayAboveAnchor)
|
2020-02-06 01:40:44 +01:00
|
|
|
|
{
|
|
|
|
|
// view nearing bounds, anchor to bottom
|
|
|
|
|
point.X = -1;
|
|
|
|
|
point.Y = 0;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// view out of bounds, hide overlay
|
|
|
|
|
point.X = -1;
|
|
|
|
|
point.Y = -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-04-03 01:24:31 +02:00
|
|
|
|
else if (point.Y > (maxY - overlayViewHeight))
|
2020-02-06 01:40:44 +01:00
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (isOverlayAboveAnchor)
|
2020-02-06 01:40:44 +01:00
|
|
|
|
{
|
|
|
|
|
// view out of bounds, hide overlay
|
|
|
|
|
point.X = -1;
|
|
|
|
|
point.Y = -1;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// view nearing bounds, anchor to top
|
|
|
|
|
point.X = 0;
|
|
|
|
|
point.Y = -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-04-03 01:24:31 +02:00
|
|
|
|
else if (isOverlayAboveAnchor && point.Y < (maxY - (overlayViewHeight * 2) - GetNodeHeight(anchorNode)))
|
2020-02-06 01:40:44 +01:00
|
|
|
|
{
|
|
|
|
|
// This else block forces the overlay to return to bottom alignment as soon as space is available
|
|
|
|
|
// below the anchor view. Removing this will change the behavior to wait until there isn't enough
|
|
|
|
|
// space above the anchor view before returning to bottom alignment.
|
2020-01-27 23:36:20 +01:00
|
|
|
|
point.X = -1;
|
2020-02-06 01:40:44 +01:00
|
|
|
|
point.Y = 0;
|
2020-01-13 23:14:57 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return point;
|
2020-01-09 18:17:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-27 23:36:20 +01:00
|
|
|
|
public static bool IsStatusBarExpanded(IEnumerable<AccessibilityWindowInfo> windows)
|
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (windows != null && windows.Any())
|
2020-01-27 23:36:20 +01:00
|
|
|
|
{
|
|
|
|
|
var isSystemWindowsOnly = true;
|
2020-03-28 14:16:28 +01:00
|
|
|
|
foreach (var window in windows)
|
2020-01-27 23:36:20 +01:00
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (window.Type != AccessibilityWindowType.System)
|
2020-01-27 23:36:20 +01:00
|
|
|
|
{
|
|
|
|
|
isSystemWindowsOnly = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return isSystemWindowsOnly;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-03 01:24:31 +02:00
|
|
|
|
public static int GetInputMethodHeight(IEnumerable<AccessibilityWindowInfo> windows)
|
2020-01-27 23:36:20 +01:00
|
|
|
|
{
|
2020-04-03 01:24:31 +02:00
|
|
|
|
var inputMethodWindowHeight = 0;
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (windows != null)
|
2020-01-27 23:36:20 +01:00
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
foreach (var window in windows)
|
2020-01-27 23:36:20 +01:00
|
|
|
|
{
|
2020-04-03 01:24:31 +02:00
|
|
|
|
if (window.Type == AccessibilityWindowType.InputMethod)
|
2020-01-27 23:36:20 +01:00
|
|
|
|
{
|
2020-04-03 01:24:31 +02:00
|
|
|
|
var windowRect = new Rect();
|
|
|
|
|
window.GetBoundsInScreen(windowRect);
|
|
|
|
|
inputMethodWindowHeight = windowRect.Height();
|
|
|
|
|
break;
|
2020-01-27 23:36:20 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-04-03 01:24:31 +02:00
|
|
|
|
return inputMethodWindowHeight;
|
2020-01-27 23:36:20 +01:00
|
|
|
|
}
|
2020-04-09 20:57:06 +02:00
|
|
|
|
|
|
|
|
|
public static bool IsAutofillServicePromptVisible(IEnumerable<AccessibilityWindowInfo> windows)
|
|
|
|
|
{
|
2020-06-08 14:25:24 +02:00
|
|
|
|
// Autofill framework not available until API 26
|
|
|
|
|
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
|
|
|
|
|
{
|
|
|
|
|
return windows?.Any(w => w.Title?.ToLower().Contains("autofill") ?? false) ?? false;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
2020-04-09 20:57:06 +02:00
|
|
|
|
}
|
2020-01-27 23:36:20 +01:00
|
|
|
|
|
|
|
|
|
public static int GetNodeHeight(AccessibilityNodeInfo node)
|
|
|
|
|
{
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (node == null)
|
2020-02-06 01:40:44 +01:00
|
|
|
|
{
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
2020-01-27 23:36:20 +01:00
|
|
|
|
var nodeRect = new Rect();
|
|
|
|
|
node.GetBoundsInScreen(nodeRect);
|
|
|
|
|
var nodeRectHeight = nodeRect.Height();
|
|
|
|
|
nodeRect.Dispose();
|
|
|
|
|
return nodeRectHeight;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-09 20:57:06 +02:00
|
|
|
|
private static int GetStatusBarHeight(AccessibilityService service)
|
2020-01-09 18:17:16 +01:00
|
|
|
|
{
|
2020-04-09 20:57:06 +02:00
|
|
|
|
return GetSystemResourceDimenPx(service, "status_bar_height");
|
2020-01-09 18:17:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-04-09 20:57:06 +02:00
|
|
|
|
private static int GetNavigationBarHeight(AccessibilityService service)
|
2020-01-09 18:17:16 +01:00
|
|
|
|
{
|
2020-04-09 20:57:06 +02:00
|
|
|
|
return GetSystemResourceDimenPx(service, "navigation_bar_height");
|
2020-01-09 18:17:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-04-09 20:57:06 +02:00
|
|
|
|
private static int GetSystemResourceDimenPx(AccessibilityService service, string resName)
|
2020-01-09 18:17:16 +01:00
|
|
|
|
{
|
2020-04-09 20:57:06 +02:00
|
|
|
|
var resourceId = service.Resources.GetIdentifier(resName, "dimen", "android");
|
2020-03-28 14:16:28 +01:00
|
|
|
|
if (resourceId > 0)
|
2020-01-09 18:17:16 +01:00
|
|
|
|
{
|
2020-04-09 20:57:06 +02:00
|
|
|
|
return service.Resources.GetDimensionPixelSize(resourceId);
|
2020-01-09 18:17:16 +01:00
|
|
|
|
}
|
2020-04-09 20:57:06 +02:00
|
|
|
|
return 0;
|
2020-01-09 18:17:16 +01:00
|
|
|
|
}
|
2019-04-30 20:33:00 +02:00
|
|
|
|
}
|
2019-07-01 17:35:58 +02:00
|
|
|
|
}
|