diff --git a/src/Android/Accessibility/AccessibilityActivity.cs b/src/Android/Accessibility/AccessibilityActivity.cs new file mode 100644 index 000000000..20475184c --- /dev/null +++ b/src/Android/Accessibility/AccessibilityActivity.cs @@ -0,0 +1,104 @@ +using Android.App; +using Android.Content; +using Android.OS; +using Android.Runtime; +using Android.Views; +using System; + +namespace Bit.Droid.Accessibility +{ + [Activity(Theme = "@style/MainTheme.Splash", WindowSoftInputMode = SoftInput.StateHidden)] + public class AccessibilityActivity : Activity + { + private DateTime? _lastLaunch = null; + private string _lastQueriedUri; + + protected override void OnCreate(Bundle bundle) + { + base.OnCreate(bundle); + LaunchMainActivity(Intent, 932473); + } + + protected override void OnNewIntent(Intent intent) + { + base.OnNewIntent(intent); + LaunchMainActivity(intent, 489729); + } + + protected override void OnDestroy() + { + base.OnDestroy(); + } + + protected override void OnResume() + { + base.OnResume(); + if(!Intent.HasExtra("uri")) + { + Finish(); + return; + } + Intent.RemoveExtra("uri"); + } + + protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data) + { + base.OnActivityResult(requestCode, resultCode, data); + if(data == null) + { + AccessibilityHelpers.LastCredentials = null; + } + else + { + try + { + if(data.GetStringExtra("canceled") != null) + { + AccessibilityHelpers.LastCredentials = null; + } + else + { + var uri = data.GetStringExtra("uri"); + var username = data.GetStringExtra("username"); + var password = data.GetStringExtra("password"); + AccessibilityHelpers.LastCredentials = new Credentials + { + Username = username, + Password = password, + Uri = uri, + LastUri = _lastQueriedUri + }; + } + } + catch + { + AccessibilityHelpers.LastCredentials = null; + } + } + Finish(); + } + + private void LaunchMainActivity(Intent callingIntent, int requestCode) + { + _lastQueriedUri = callingIntent?.GetStringExtra("uri"); + if(_lastQueriedUri == null) + { + Finish(); + return; + } + var now = DateTime.UtcNow; + if(_lastLaunch.HasValue && (now - _lastLaunch.Value) <= TimeSpan.FromSeconds(2)) + { + return; + } + + _lastLaunch = now; + var intent = new Intent(this, typeof(MainActivity)); + if(!callingIntent.Flags.HasFlag(ActivityFlags.LaunchedFromHistory)) + { + intent.PutExtra("uri", _lastQueriedUri); + } + StartActivityForResult(intent, requestCode); + } + } +} diff --git a/src/Android/Accessibility/AccessibilityHelpers.cs b/src/Android/Accessibility/AccessibilityHelpers.cs new file mode 100644 index 000000000..b9af9e05c --- /dev/null +++ b/src/Android/Accessibility/AccessibilityHelpers.cs @@ -0,0 +1,246 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Android.OS; +using Android.Views.Accessibility; +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"; + + public static Dictionary SupportedBrowsers => new List + { + new Browser("com.android.chrome", "url_bar"), + new Browser("com.chrome.beta", "url_bar"), + new Browser("org.chromium.chrome", "url_bar"), + new Browser("com.android.browser", "url"), + new Browser("com.brave.browser", "url_bar"), + new Browser("com.opera.browser", "url_field"), + new Browser("com.opera.browser.beta", "url_field"), + new Browser("com.opera.mini.native", "url_field"), + new Browser("com.opera.touch", "addressbarEdit"), + new Browser("com.chrome.dev", "url_bar"), + new Browser("com.chrome.canary", "url_bar"), + new Browser("com.google.android.apps.chrome", "url_bar"), + new Browser("com.google.android.apps.chrome_dev", "url_bar"), + new Browser("org.codeaurora.swe.browser", "url_bar"), + new Browser("org.iron.srware", "url_bar"), + new Browser("com.sec.android.app.sbrowser", "location_bar_edit_text"), + new Browser("com.sec.android.app.sbrowser.beta", "location_bar_edit_text"), + new Browser("com.yandex.browser", "bro_omnibar_address_title_text", + (s) => s.Split(new char[]{' ', ' '}).FirstOrDefault()), // 0 = Regular Space, 1 = No-break space (00A0) + new Browser("org.mozilla.firefox", "url_bar_title"), + new Browser("org.mozilla.firefox_beta", "url_bar_title"), + new Browser("org.mozilla.fennec_aurora", "url_bar_title"), + new Browser("org.mozilla.focus", "display_url"), + new Browser("org.mozilla.klar", "display_url"), + new Browser("org.mozilla.fenix", "mozac_browser_toolbar_url_view"), + new Browser("org.mozilla.reference.browser", "mozac_browser_toolbar_url_view"), + new Browser("com.ghostery.android.ghostery", "search_field"), + new Browser("org.adblockplus.browser", "url_bar_title"), + new Browser("com.htc.sense.browser", "title"), + new Browser("com.amazon.cloud9", "url"), + new Browser("mobi.mgeek.TunnyBrowser", "title"), + new Browser("com.nubelacorp.javelin", "enterUrl"), + new Browser("com.jerky.browser2", "enterUrl"), + new Browser("com.mx.browser", "address_editor_with_progress"), + new Browser("com.mx.browser.tablet", "address_editor_with_progress"), + new Browser("com.linkbubble.playstore", "url_text"), + new Browser("com.ksmobile.cb", "address_bar_edit_text"), + new Browser("acr.browser.lightning", "search"), + new Browser("acr.browser.barebones", "search"), + new Browser("com.microsoft.emmx", "url_bar"), + new Browser("com.duckduckgo.mobile.android", "omnibarTextInput"), + new Browser("mark.via.gp", "aw"), + new Browser("org.bromite.bromite", "url_bar"), + new Browser("com.kiwibrowser.browser", "url_bar"), + new Browser("com.ecosia.android", "url_bar"), + new Browser("com.qwant.liberty", "url_bar_title"), + }.ToDictionary(n => n.PackageName); + + // Known packages to skip + public static HashSet FilteredPackageNames => new HashSet + { + 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", + "com.ss.squarehome2" + }; + + 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 }); + } + + public static string GetUri(AccessibilityNodeInfo root) + { + var uri = string.Concat(Constants.AndroidAppProtocol, root.PackageName); + if(SupportedBrowsers.ContainsKey(root.PackageName)) + { + var browser = SupportedBrowsers[root.PackageName]; + var addressNode = root.FindAccessibilityNodeInfosByViewId( + $"{root.PackageName}:id/{browser.UriViewId}").FirstOrDefault(); + if(addressNode != null) + { + uri = ExtractUri(uri, addressNode, browser); + addressNode.Dispose(); + } + } + return uri; + } + + public static string ExtractUri(string uri, AccessibilityNodeInfo addressNode, Browser browser) + { + if(addressNode?.Text == null) + { + return uri; + } + if(addressNode.Text == null) + { + return uri; + } + uri = browser.GetUriFunction(addressNode.Text)?.Trim(); + if(uri != null && uri.Contains(".")) + { + if(!uri.Contains("://") && !uri.Contains(" ")) + { + uri = string.Concat("http://", uri); + } + 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(); + } + } + } + } + return uri; + } + + /// + /// Check to make sure it is ok to autofill still on the current screen + /// + public static bool NeedToAutofill(Credentials credentials, string currentUriString) + { + if(credentials == null) + { + return false; + } + if(Uri.TryCreate(credentials.LastUri, UriKind.Absolute, out Uri lastUri) && + 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 passwordNodes) + { + FillEditText(usernameNode, LastCredentials?.Username); + foreach(var n in passwordNodes) + { + FillEditText(n, LastCredentials?.Password); + } + } + + public static void FillEditText(AccessibilityNodeInfo editTextNode, string value) + { + if(editTextNode == null || value == null) + { + 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 condition, bool disposeIfUnused, NodeList nodes = null, + int recursionDepth = 0) + { + if(nodes == null) + { + nodes = new NodeList(); + } + var dispose = disposeIfUnused; + if(n != null && recursionDepth < 50) + { + var add = n.WindowId == e.WindowId && + !(n.ViewIdResourceName?.StartsWith(SystemUiPackage) ?? false) && + condition(n); + if(add) + { + dispose = false; + nodes.Add(n); + } + + for(var i = 0; i < n.ChildCount; i++) + { + var childNode = n.GetChild(i); + if(i > 100) + { + Android.Util.Log.Info(BitwardenTag, "Too many child iterations."); + break; + } + else if(childNode.GetHashCode() == n.GetHashCode()) + { + Android.Util.Log.Info(BitwardenTag, "Child node is the same as parent for some reason."); + } + else + { + GetWindowNodes(childNode, e, condition, true, nodes, recursionDepth++); + } + } + } + if(dispose) + { + n?.Dispose(); + } + return nodes; + } + + public static void GetNodesAndFill(AccessibilityNodeInfo root, AccessibilityEvent e, + IEnumerable passwordNodes) + { + var allEditTexts = GetWindowNodes(root, e, n => EditText(n), false); + var usernameEditText = GetUsernameEditText(allEditTexts); + FillCredentials(usernameEditText, passwordNodes); + allEditTexts.Dispose(); + usernameEditText = null; + } + + public static AccessibilityNodeInfo GetUsernameEditText(IEnumerable allEditTexts) + { + return allEditTexts.TakeWhile(n => !n.Password).LastOrDefault(); + } + } +} \ No newline at end of file diff --git a/src/Android/Accessibility/AccessibilityService.cs b/src/Android/Accessibility/AccessibilityService.cs new file mode 100644 index 000000000..ccee0bd92 --- /dev/null +++ b/src/Android/Accessibility/AccessibilityService.cs @@ -0,0 +1,310 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Android.App; +using Android.Content; +using Android.OS; +using Android.Runtime; +using Android.Views.Accessibility; +using Bit.App.Resources; +using Bit.Core; +using Bit.Core.Abstractions; +using Bit.Core.Utilities; + +namespace Bit.Droid.Accessibility +{ + [Service(Permission = Android.Manifest.Permission.BindAccessibilityService, Label = "Bitwarden")] + [IntentFilter(new string[] { "android.accessibilityservice.AccessibilityService" })] + [MetaData("android.accessibilityservice", Resource = "@xml/accessibilityservice")] + [Register("com.x8bit.bitwarden.Accessibility.AccessibilityService")] + public class AccessibilityService : Android.AccessibilityServices.AccessibilityService + { + private NotificationChannel _notificationChannel; + + private const int AutoFillNotificationId = 34573; + private const string BitwardenPackage = "com.x8bit.bitwarden"; + private const string BitwardenWebsite = "vault.bitwarden.com"; + + private IStorageService _storageService; + private bool _settingAutofillPasswordField; + private bool _settingAutofillPersistNotification; + private DateTime? _lastSettingsReload = null; + private TimeSpan _settingsReloadSpan = TimeSpan.FromMinutes(1); + private long _lastNotificationTime = 0; + private string _lastNotificationUri = null; + private HashSet _launcherPackageNames = null; + private DateTime? _lastLauncherSetBuilt = null; + private TimeSpan _rebuildLauncherSpan = TimeSpan.FromHours(1); + + public override void OnAccessibilityEvent(AccessibilityEvent e) + { + try + { + var powerManager = GetSystemService(PowerService) as PowerManager; + if(Build.VERSION.SdkInt > BuildVersionCodes.KitkatWatch && !powerManager.IsInteractive) + { + return; + } + else if(Build.VERSION.SdkInt < BuildVersionCodes.Lollipop && !powerManager.IsScreenOn) + { + return; + } + + if(SkipPackage(e?.PackageName)) + { + return; + } + + var root = RootInActiveWindow; + if(root == null || root.PackageName != e.PackageName) + { + return; + } + + // AccessibilityHelpers.PrintTestData(root, e); + LoadServices(); + var settingsTask = LoadSettingsAsync(); + + var notificationManager = GetSystemService(NotificationService) as NotificationManager; + var cancelNotification = true; + + switch(e.EventType) + { + case EventTypes.ViewFocused: + if(e.Source == null || !e.Source.Password || !_settingAutofillPasswordField) + { + break; + } + if(e.PackageName == BitwardenPackage) + { + CancelNotification(notificationManager); + break; + } + if(ScanAndAutofill(root, e, notificationManager, cancelNotification)) + { + CancelNotification(notificationManager); + } + break; + case EventTypes.WindowContentChanged: + case EventTypes.WindowStateChanged: + if(_settingAutofillPasswordField && e.Source.Password) + { + break; + } + else if(_settingAutofillPasswordField && AccessibilityHelpers.LastCredentials == null) + { + if(string.IsNullOrWhiteSpace(_lastNotificationUri)) + { + CancelNotification(notificationManager); + break; + } + var uri = AccessibilityHelpers.GetUri(root); + if(uri != _lastNotificationUri) + { + CancelNotification(notificationManager); + } + else if(uri.StartsWith(Constants.AndroidAppProtocol)) + { + CancelNotification(notificationManager, 30000); + } + break; + } + + if(e.PackageName == BitwardenPackage) + { + CancelNotification(notificationManager); + break; + } + + if(_settingAutofillPersistNotification) + { + var uri = AccessibilityHelpers.GetUri(root); + if(uri != null && !uri.Contains(BitwardenWebsite)) + { + var needToFill = AccessibilityHelpers.NeedToAutofill( + AccessibilityHelpers.LastCredentials, uri); + if(needToFill) + { + var passwordNodes = AccessibilityHelpers.GetWindowNodes(root, e, + n => n.Password, false); + needToFill = passwordNodes.Any(); + if(needToFill) + { + AccessibilityHelpers.GetNodesAndFill(root, e, passwordNodes); + } + passwordNodes.Dispose(); + } + if(!needToFill) + { + NotifyToAutofill(uri, notificationManager); + cancelNotification = false; + } + } + AccessibilityHelpers.LastCredentials = null; + } + else + { + cancelNotification = ScanAndAutofill(root, e, notificationManager, cancelNotification); + } + + if(cancelNotification) + { + CancelNotification(notificationManager); + } + break; + default: + break; + } + + notificationManager?.Dispose(); + root.Dispose(); + e.Dispose(); + } + // Suppress exceptions so that service doesn't crash. + catch { } + } + + public override void OnInterrupt() + { + // Do nothing. + } + + public bool ScanAndAutofill(AccessibilityNodeInfo root, AccessibilityEvent e, + NotificationManager notificationManager, bool cancelNotification) + { + var passwordNodes = AccessibilityHelpers.GetWindowNodes(root, e, n => n.Password, false); + if(passwordNodes.Count > 0) + { + var uri = AccessibilityHelpers.GetUri(root); + if(uri != null && !uri.Contains(BitwardenWebsite)) + { + if(AccessibilityHelpers.NeedToAutofill(AccessibilityHelpers.LastCredentials, uri)) + { + AccessibilityHelpers.GetNodesAndFill(root, e, passwordNodes); + } + else + { + NotifyToAutofill(uri, notificationManager); + cancelNotification = false; + } + } + AccessibilityHelpers.LastCredentials = null; + } + else if(AccessibilityHelpers.LastCredentials != null) + { + Task.Run(async () => + { + await Task.Delay(1000); + AccessibilityHelpers.LastCredentials = null; + }); + } + passwordNodes.Dispose(); + return cancelNotification; + } + + public void CancelNotification(NotificationManager notificationManager, long limit = 250) + { + if(Java.Lang.JavaSystem.CurrentTimeMillis() - _lastNotificationTime < limit) + { + return; + } + _lastNotificationUri = null; + notificationManager?.Cancel(AutoFillNotificationId); + } + + private void NotifyToAutofill(string uri, NotificationManager notificationManager) + { + if(notificationManager == null || string.IsNullOrWhiteSpace(uri)) + { + return; + } + + var now = Java.Lang.JavaSystem.CurrentTimeMillis(); + var intent = new Intent(this, typeof(AccessibilityActivity)); + intent.PutExtra("uri", uri); + intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop); + var pendingIntent = PendingIntent.GetActivity(this, 0, intent, PendingIntentFlags.UpdateCurrent); + + var notificationContent = Build.VERSION.SdkInt > BuildVersionCodes.KitkatWatch ? + AppResources.BitwardenAutofillServiceNotificationContent : + AppResources.BitwardenAutofillServiceNotificationContentOld; + + var builder = new Notification.Builder(this); + builder.SetSmallIcon(Resource.Drawable.notification_sm) + .SetContentTitle(AppResources.BitwardenAutofillService) + .SetContentText(notificationContent) + .SetTicker(notificationContent) + .SetWhen(now) + .SetContentIntent(pendingIntent); + + if(Build.VERSION.SdkInt > BuildVersionCodes.KitkatWatch) + { + builder.SetVisibility(NotificationVisibility.Secret) + .SetColor(Android.Support.V4.Content.ContextCompat.GetColor(ApplicationContext, + Resource.Color.primary)); + } + if(Build.VERSION.SdkInt >= BuildVersionCodes.O) + { + if(_notificationChannel == null) + { + _notificationChannel = new NotificationChannel("bitwarden_autofill_service", + AppResources.AutofillService, NotificationImportance.Low); + notificationManager.CreateNotificationChannel(_notificationChannel); + } + builder.SetChannelId(_notificationChannel.Id); + } + if(/*Build.VERSION.SdkInt <= BuildVersionCodes.N && */_settingAutofillPersistNotification) + { + builder.SetPriority(-2); + } + + _lastNotificationTime = now; + _lastNotificationUri = uri; + notificationManager.Notify(AutoFillNotificationId, builder.Build()); + builder.Dispose(); + } + + private bool SkipPackage(string eventPackageName) + { + if(string.IsNullOrWhiteSpace(eventPackageName) || + AccessibilityHelpers.FilteredPackageNames.Contains(eventPackageName) || + eventPackageName.Contains("launcher")) + { + return true; + } + if(_launcherPackageNames == null || _lastLauncherSetBuilt == null || + (DateTime.Now - _lastLauncherSetBuilt.Value) > _rebuildLauncherSpan) + { + // refresh launcher list every now and then + _lastLauncherSetBuilt = DateTime.Now; + var intent = new Intent(Intent.ActionMain); + intent.AddCategory(Intent.CategoryHome); + var resolveInfo = PackageManager.QueryIntentActivities(intent, 0); + _launcherPackageNames = resolveInfo.Select(ri => ri.ActivityInfo.PackageName).ToHashSet(); + } + return _launcherPackageNames.Contains(eventPackageName); + } + + private void LoadServices() + { + if(_storageService == null) + { + _storageService = ServiceContainer.Resolve("storageService"); + } + } + + private async Task LoadSettingsAsync() + { + var now = DateTime.UtcNow; + if(_lastSettingsReload == null || (now - _lastSettingsReload.Value) > _settingsReloadSpan) + { + _lastSettingsReload = now; + _settingAutofillPasswordField = await _storageService.GetAsync( + Constants.AccessibilityAutofillPasswordFieldKey); + _settingAutofillPersistNotification = await _storageService.GetAsync( + Constants.AccessibilityAutofillPersistNotificationKey); + } + } + } +} diff --git a/src/Android/Accessibility/Browser.cs b/src/Android/Accessibility/Browser.cs new file mode 100644 index 000000000..f6bfd076b --- /dev/null +++ b/src/Android/Accessibility/Browser.cs @@ -0,0 +1,23 @@ +using System; + +namespace Bit.Droid.Accessibility +{ + public class Browser + { + public Browser(string packageName, string uriViewId) + { + PackageName = packageName; + UriViewId = uriViewId; + } + + public Browser(string packageName, string uriViewId, Func getUriFunction) + : this(packageName, uriViewId) + { + GetUriFunction = getUriFunction; + } + + public string PackageName { get; set; } + public string UriViewId { get; set; } + public Func GetUriFunction { get; set; } = (s) => s; + } +} diff --git a/src/Android/Accessibility/Credentials.cs b/src/Android/Accessibility/Credentials.cs new file mode 100644 index 000000000..d265f07e8 --- /dev/null +++ b/src/Android/Accessibility/Credentials.cs @@ -0,0 +1,10 @@ +namespace Bit.Droid.Accessibility +{ + public class Credentials + { + public string Username { get; set; } + public string Password { get; set; } + public string Uri { get; set; } + public string LastUri { get; set; } + } +} diff --git a/src/Android/Accessibility/NodeList.cs b/src/Android/Accessibility/NodeList.cs new file mode 100644 index 000000000..9843e912f --- /dev/null +++ b/src/Android/Accessibility/NodeList.cs @@ -0,0 +1,17 @@ +using Android.Views.Accessibility; +using System; +using System.Collections.Generic; + +namespace Bit.Droid.Accessibility +{ + public class NodeList : List, IDisposable + { + public void Dispose() + { + foreach(var item in this) + { + item.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj index d868ce376..86051dfda 100644 --- a/src/Android/Android.csproj +++ b/src/Android/Android.csproj @@ -66,6 +66,12 @@ + + + + + + @@ -373,7 +379,7 @@ - + @@ -420,5 +426,17 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Android/Resources/Resource.designer.cs b/src/Android/Resources/Resource.designer.cs index 7028622c9..25a1a2031 100644 --- a/src/Android/Resources/Resource.designer.cs +++ b/src/Android/Resources/Resource.designer.cs @@ -5825,26 +5825,26 @@ namespace Bit.Droid // aapt resource value: 0x7f02005a public const int avd_hide_password = 2130837594; - // aapt resource value: 0x7f020147 - public const int avd_hide_password_1 = 2130837831; - // aapt resource value: 0x7f020148 - public const int avd_hide_password_2 = 2130837832; + public const int avd_hide_password_1 = 2130837832; // aapt resource value: 0x7f020149 - public const int avd_hide_password_3 = 2130837833; + public const int avd_hide_password_2 = 2130837833; + + // aapt resource value: 0x7f02014a + public const int avd_hide_password_3 = 2130837834; // aapt resource value: 0x7f02005b public const int avd_show_password = 2130837595; - // aapt resource value: 0x7f02014a - public const int avd_show_password_1 = 2130837834; - // aapt resource value: 0x7f02014b - public const int avd_show_password_2 = 2130837835; + public const int avd_show_password_1 = 2130837835; // aapt resource value: 0x7f02014c - public const int avd_show_password_3 = 2130837836; + public const int avd_show_password_2 = 2130837836; + + // aapt resource value: 0x7f02014d + public const int avd_show_password_3 = 2130837837; // aapt resource value: 0x7f02005c public const int card = 2130837596; @@ -6521,35 +6521,38 @@ namespace Bit.Droid // aapt resource value: 0x7f02013c public const int notification_icon_background = 2130837820; - // aapt resource value: 0x7f020145 - public const int notification_template_icon_bg = 2130837829; + // aapt resource value: 0x7f02013d + public const int notification_sm = 2130837821; // aapt resource value: 0x7f020146 - public const int notification_template_icon_low_bg = 2130837830; + public const int notification_template_icon_bg = 2130837830; - // aapt resource value: 0x7f02013d - public const int notification_tile_bg = 2130837821; + // aapt resource value: 0x7f020147 + public const int notification_template_icon_low_bg = 2130837831; // aapt resource value: 0x7f02013e - public const int notify_panel_notification_icon_bg = 2130837822; + public const int notification_tile_bg = 2130837822; // aapt resource value: 0x7f02013f - public const int refresh = 2130837823; + public const int notify_panel_notification_icon_bg = 2130837823; // aapt resource value: 0x7f020140 - public const int shield = 2130837824; + public const int refresh = 2130837824; // aapt resource value: 0x7f020141 - public const int splash_screen = 2130837825; + public const int shield = 2130837825; // aapt resource value: 0x7f020142 - public const int tooltip_frame_dark = 2130837826; + public const int splash_screen = 2130837826; // aapt resource value: 0x7f020143 - public const int tooltip_frame_light = 2130837827; + public const int tooltip_frame_dark = 2130837827; // aapt resource value: 0x7f020144 - public const int yubikey = 2130837828; + public const int tooltip_frame_light = 2130837828; + + // aapt resource value: 0x7f020145 + public const int yubikey = 2130837829; static Drawable() { diff --git a/src/Android/Resources/drawable-hdpi/notification_sm.png b/src/Android/Resources/drawable-hdpi/notification_sm.png new file mode 100644 index 000000000..a893ed8bf Binary files /dev/null and b/src/Android/Resources/drawable-hdpi/notification_sm.png differ diff --git a/src/Android/Resources/drawable-xhdpi/notification_sm.png b/src/Android/Resources/drawable-xhdpi/notification_sm.png new file mode 100644 index 000000000..dc65e1730 Binary files /dev/null and b/src/Android/Resources/drawable-xhdpi/notification_sm.png differ diff --git a/src/Android/Resources/drawable-xxhdpi/notification_sm.png b/src/Android/Resources/drawable-xxhdpi/notification_sm.png new file mode 100644 index 000000000..5d21ebb89 Binary files /dev/null and b/src/Android/Resources/drawable-xxhdpi/notification_sm.png differ diff --git a/src/Android/Resources/drawable-xxxhdpi/notification_sm.png b/src/Android/Resources/drawable-xxxhdpi/notification_sm.png new file mode 100644 index 000000000..760eff254 Binary files /dev/null and b/src/Android/Resources/drawable-xxxhdpi/notification_sm.png differ diff --git a/src/Android/Resources/drawable/notification_sm.png b/src/Android/Resources/drawable/notification_sm.png new file mode 100644 index 000000000..86b7c130a Binary files /dev/null and b/src/Android/Resources/drawable/notification_sm.png differ diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index 958ee0feb..7029bce1b 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -10,5 +10,7 @@ public static string DisableAutoTotpCopyKey = "disableAutoTotpCopy"; public static string EnvironmentUrlsKey = "environmentUrls"; public static string LastFileCacheClearKey = "lastFileCacheClear"; + public static string AccessibilityAutofillPasswordFieldKey = "accessibilityAutofillPasswordField"; + public static string AccessibilityAutofillPersistNotificationKey = "accessibilityAutofillPersistNotification"; } }