Fix up Android Accessibility behind a flag (#14089)

* Fix up Android Accessibility behind a flag

* - automation id

* - set back button to standard "Navigate Up"

* - view compat

* - add flag check

* - fix assigning of delegate

* - add flag to forms appcompat activity

* - fix nav drawer
This commit is contained in:
Shane Neuville 2021-06-09 03:25:30 -05:00 коммит произвёл GitHub
Родитель 0e06946961
Коммит 5209ec5a4e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
14 изменённых файлов: 202 добавлений и 34 удалений

Просмотреть файл

@ -19,6 +19,7 @@
<dependency id="Xamarin.Google.Android.Material" version="1.2.1.1" />
<dependency id="Xamarin.AndroidX.Legacy.Support.V4" version="1.0.0.6" />
<dependency id="Xamarin.AndroidX.Browser" version="1.3.0" />
<dependency id="Xamarin.AndroidX.Navigation.UI" version="2.3.2.1" />
</group>
<group targetFramework="uap10.0.14393">
<dependency id="NETStandard.Library" version="2.0.1"/>

Просмотреть файл

@ -49,8 +49,11 @@ namespace Xamarin.Forms.ControlGallery.Android
#else
Forms.SetFlags("UseLegacyRenderers");
#endif
// Forms.SetFlags("AccessibilityExperimental");
Forms.Init(this, bundle);
// null out the assembly on the Resource Manager
// so all of our tests run without using the reflection APIs
// At some point the Resources class types will go away so

Просмотреть файл

@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Text;
using Android.Views.Accessibility;
using AndroidX.AppCompat.Widget;
using AndroidX.Core.View;
using AndroidX.Core.View.Accessibiity;
using Xamarin.Forms.Platform.Android.FastRenderers;
namespace Xamarin.Forms.Platform.Android
{
class AccessibilityDelegateAutomationId : AccessibilityDelegateCompat
{
BindableObject _element;
public AccessibilityDelegateAutomationId(BindableObject element) : base()
{
_element = element;
}
public override void OnInitializeAccessibilityNodeInfo(global::Android.Views.View host, AccessibilityNodeInfoCompat info)
{
base.OnInitializeAccessibilityNodeInfo(host, info);
if (_element == null)
return;
if(Flags.IsAccessibilityExperimentalSet())
{
var value = AutomationPropertiesProvider.ConcatenateNameAndHelpText(_element);
if (!string.IsNullOrWhiteSpace(value))
{
host.ContentDescription = value;
}
else if(host.ContentDescription == (_element as VisualElement)?.AutomationId)
{
host.ContentDescription = null;
}
}
}
protected override void Dispose(bool disposing)
{
_element = null;
base.Dispose(disposing);
}
}
}

Просмотреть файл

@ -8,6 +8,7 @@ using Android.Text;
using Android.Text.Style;
using Android.Util;
using Android.Widget;
using AndroidX.Core.View;
namespace Xamarin.Forms.Platform.Android.AppCompat
{
@ -60,8 +61,8 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
var textField = CreateNativeControl();
SetNativeControl(textField);
ControlUsedForAutomation.SetAccessibilityDelegate(_pickerAccessibilityDelegate = new EntryAccessibilityDelegate(Element));
ViewCompat.SetAccessibilityDelegate(
ControlUsedForAutomation, _pickerAccessibilityDelegate = new EntryAccessibilityDelegate(Element));
}
UpdateFont();
UpdatePicker();

Просмотреть файл

@ -1,15 +1,16 @@
using Android.Views.Accessibility;
using AndroidX.Core.View.Accessibiity;
using Xamarin.Forms.Platform.Android.FastRenderers;
namespace Xamarin.Forms.Platform.Android
{
class EntryAccessibilityDelegate : global::Android.Views.View.AccessibilityDelegate
class EntryAccessibilityDelegate : AccessibilityDelegateAutomationId
{
BindableObject _element;
public EntryAccessibilityDelegate(BindableObject Element) : base()
public EntryAccessibilityDelegate(BindableObject element) : base(element)
{
_element = Element;
_element = element;
}
protected override void Dispose(bool disposing)
@ -22,7 +23,7 @@ namespace Xamarin.Forms.Platform.Android
public string ClassName { get; set; } = "android.widget.Button";
public override void OnInitializeAccessibilityNodeInfo(global::Android.Views.View host, AccessibilityNodeInfo info)
public override void OnInitializeAccessibilityNodeInfo(global::Android.Views.View host, AccessibilityNodeInfoCompat info)
{
base.OnInitializeAccessibilityNodeInfo(host, info);
info.ClassName = ClassName;

Просмотреть файл

@ -1,7 +1,9 @@
using System;
using System.ComponentModel;
using System.Runtime.InteropServices.WindowsRuntime;
using Android.Views;
using Android.Widget;
using AndroidX.Core.View;
using AView = Android.Views.View;
namespace Xamarin.Forms.Platform.Android.FastRenderers
@ -30,52 +32,102 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
resourceIdClose = context.Resources.GetIdentifier($"{automationIdParent}{s_defaultDrawerIdCloseSuffix}", "string", context.ApplicationInfo.PackageName);
}
static bool ShoudISetImportantForAccessibilityToNoIfAutomationIdIsSet(AView control, Element element)
{
if (!Flags.IsAccessibilityExperimentalSet())
return false;
internal static void SetAutomationId(AView control, Element element, string value = null)
{
if (!control.IsAlive() || element == null)
if (element == null)
return false;
if (String.IsNullOrWhiteSpace(element.AutomationId))
return false;
// User has specifically said what they want
if (AutomationProperties.GetIsInAccessibleTree(element) == true)
return false;
// User has explicitly set name and help text so we honor that
if (!String.IsNullOrWhiteSpace(ConcatenateNameAndHelpText(element)))
return false;
if (element is Layout ||
element is ItemsView ||
element is BoxView ||
element is ScrollView ||
element is TableView ||
element is WebView ||
element is Page ||
element is Shapes.Path ||
element is Frame ||
element is ListView ||
element is Image)
{
return;
return true;
}
SetAutomationId(control, element.AutomationId, value);
return false;
}
internal static void SetAutomationId(AView control, string automationId, string value = null)
internal static void SetAutomationId(AView control, Element element, string value = null)
{
if (!control.IsAlive())
{
return;
}
automationId = value ?? automationId;
string automationId = value ?? element?.AutomationId;
if (!string.IsNullOrEmpty(automationId))
{
control.ContentDescription = automationId;
if (ShoudISetImportantForAccessibilityToNoIfAutomationIdIsSet(control, element))
{
control.ImportantForAccessibility = ImportantForAccessibility.No;
}
else if (Flags.IsAccessibilityExperimentalSet() && control.GetAccessibilityDelegate() == null)
ViewCompat.SetAccessibilityDelegate(control, new AccessibilityDelegateAutomationId(element));
}
}
internal static void SetBasicContentDescription(
AView control,
BindableObject bindableObject,
Element element,
string defaultContentDescription)
{
if (bindableObject == null || control == null)
if (element == null || control == null)
return;
string value = ConcatenateNameAndHelpText(bindableObject);
string value = ConcatenateNameAndHelpText(element);
var contentDescription = !string.IsNullOrWhiteSpace(value) ? value : defaultContentDescription;
if (String.IsNullOrWhiteSpace(contentDescription) && bindableObject is Element element)
if (String.IsNullOrWhiteSpace(contentDescription))
{
if(Flags.IsAccessibilityExperimentalSet())
{
SetAutomationId(control, element, element.AutomationId);
return;
}
contentDescription = element.AutomationId;
}
if (Flags.IsAccessibilityExperimentalSet())
{
if (!AutomationProperties.GetIsInAccessibleTree(element).HasValue)
{
control.ImportantForAccessibility = ImportantForAccessibility.Auto;
}
}
control.ContentDescription = contentDescription;
}
internal static void SetContentDescription(
AView control,
BindableObject element,
Element element,
string defaultContentDescription,
string defaultHint)
{
@ -96,12 +148,21 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
{
defaultFocusable = control.Focusable;
}
if (!defaultImportantForAccessibility.HasValue)
{
defaultImportantForAccessibility = control.ImportantForAccessibility;
// Auto is the default just use that
if (Flags.IsAccessibilityExperimentalSet())
defaultImportantForAccessibility = ImportantForAccessibility.Auto;
else
defaultImportantForAccessibility = control.ImportantForAccessibility;
}
bool? isInAccessibleTree = (bool?)element.GetValue(AutomationProperties.IsInAccessibleTreeProperty);
if (!isInAccessibleTree.HasValue && Flags.IsAccessibilityExperimentalSet())
return;
control.Focusable = (bool)(isInAccessibleTree ?? defaultFocusable);
control.ImportantForAccessibility = !isInAccessibleTree.HasValue ? (ImportantForAccessibility)defaultImportantForAccessibility : (bool)isInAccessibleTree ? ImportantForAccessibility.Yes : ImportantForAccessibility.No;
}
@ -253,12 +314,16 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
internal static void SetupDefaults(AView control, ref string defaultContentDescription, ref string defaultHint)
{
if (defaultContentDescription == null)
defaultContentDescription = control.ContentDescription;
if (control is TextView textView && defaultHint == null)
// Setting defaults for these values makes no sense
if (!Flags.IsAccessibilityExperimentalSet())
{
defaultHint = textView.Hint;
if (defaultContentDescription == null)
defaultContentDescription = control.ContentDescription;
if (control is TextView textView && defaultHint == null)
{
defaultHint = textView.Hint;
}
}
}

Просмотреть файл

@ -1,7 +1,21 @@
using System.Linq;
namespace Xamarin.Forms
{
internal static class Flags
{
internal const string UseLegacyRenderers = "UseLegacyRenderers";
internal const string AccessibilityExperimental = "Accessibility_Experimental";
public static bool IsFlagSet(string flagName)
{
return Device.Flags != null && Device.Flags.Contains(flagName);
}
public static bool IsAccessibilityExperimentalSet()
{
return IsFlagSet(AccessibilityExperimental);
}
}
}

Просмотреть файл

@ -17,8 +17,11 @@ namespace Xamarin.Forms.Platform.Android
public PlatformRenderer(Context context, IPlatformLayout canvas) : base(context)
{
_canvas = canvas;
Focusable = true;
FocusableInTouchMode = true;
if (!Flags.IsAccessibilityExperimentalSet())
{
Focusable = true;
FocusableInTouchMode = true;
}
}
public override bool DispatchTouchEvent(MotionEvent e)

Просмотреть файл

@ -80,7 +80,8 @@ namespace Xamarin.Forms.Platform.Android
UpdateBackground(false);
Clickable = true;
if(!Flags.IsAccessibilityExperimentalSet())
Clickable = true;
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)

Просмотреть файл

@ -9,6 +9,7 @@ using Android.Text.Style;
using Android.Util;
using Android.Views;
using Android.Widget;
using AndroidX.Core.View;
using AColor = Android.Graphics.Color;
using Orientation = Android.Widget.Orientation;
@ -67,7 +68,7 @@ namespace Xamarin.Forms.Platform.Android
{
var textField = CreateNativeControl();
textField.SetAccessibilityDelegate(_pickerAccessibilityDelegate = new EntryAccessibilityDelegate(Element));
ViewCompat.SetAccessibilityDelegate(textField, _pickerAccessibilityDelegate = new EntryAccessibilityDelegate(Element));
var useLegacyColorManagement = e.NewElement.UseLegacyColorManagement();
_textColorSwitcher = new TextColorSwitcher(textField.TextColors, useLegacyColorManagement);

Просмотреть файл

@ -117,8 +117,7 @@ namespace Xamarin.Forms.Platform.Android
{
FastRenderers
.AutomationPropertiesProvider
.SetAutomationId(_editText, _searchHandler?.AutomationId);
.SetAutomationId(_editText, null, _searchHandler?.AutomationId);
}
void UpdateFont()

Просмотреть файл

@ -144,6 +144,7 @@ namespace Xamarin.Forms.Platform.Android
_toolbar = root.FindViewById<Toolbar>(Resource.Id.main_toolbar);
_viewPager = root.FindViewById<FormsViewPager>(Resource.Id.main_viewpager);
_tablayout = root.FindViewById<TabLayout>(Resource.Id.main_tablayout);
_viewPager.EnableGesture = false;
@ -177,7 +178,7 @@ namespace Xamarin.Forms.Platform.Android
if (SectionController.GetItems().Count == 1)
{
_tablayout.Visibility = ViewStates.Gone;
UpdateTablayoutVisibility();
}
_tablayout.LayoutChange += OnTabLayoutChange;
@ -264,8 +265,22 @@ namespace Xamarin.Forms.Platform.Android
AnimationFinished?.Invoke(this, e);
}
protected virtual void OnItemsCollectionChagned(object sender, NotifyCollectionChangedEventArgs e) =>
protected virtual void OnItemsCollectionChagned(object sender, NotifyCollectionChangedEventArgs e)
{
UpdateTablayoutVisibility();
}
void UpdateTablayoutVisibility()
{
_tablayout.Visibility = (SectionController.GetItems().Count > 1) ? ViewStates.Visible : ViewStates.Gone;
if (Flags.IsAccessibilityExperimentalSet())
{
if (_tablayout.Visibility == ViewStates.Gone)
_viewPager.ImportantForAccessibility = ImportantForAccessibility.No;
else
_viewPager.ImportantForAccessibility = ImportantForAccessibility.Auto;
}
}
protected virtual void OnShellItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{

Просмотреть файл

@ -333,7 +333,11 @@ namespace Xamarin.Forms.Platform.Android
{
if (_drawerToggle == null && !context.IsDesignerContext())
{
_drawerToggle = new ActionBarDrawerToggle(context.GetActivity(), drawerLayout, toolbar, R.String.Ok, R.String.Ok)
var openId = R.String.Ok;
if (Flags.IsAccessibilityExperimentalSet())
openId = Resource.String.nav_app_bar_open_drawer_description;
_drawerToggle = new ActionBarDrawerToggle(context.GetActivity(), drawerLayout, toolbar, openId, R.String.Ok)
{
ToolbarNavigationClickListener = this,
};
@ -451,7 +455,15 @@ namespace Xamarin.Forms.Platform.Android
else if (image == null ||
toolbar.SetNavigationContentDescription(image) == null)
{
toolbar.SetNavigationContentDescription(R.String.Ok);
if (Flags.IsAccessibilityExperimentalSet())
{
if(CanNavigateBack)
toolbar.SetNavigationContentDescription(Resource.String.nav_app_bar_navigate_up_description);
else
toolbar.SetNavigationContentDescription(Resource.String.nav_app_bar_open_drawer_description);
}
else
toolbar.SetNavigationContentDescription(R.String.Ok);
}
}

Просмотреть файл

@ -45,6 +45,7 @@
<ItemGroup>
<PackageReference Include="Xamarin.AndroidX.Lifecycle.LiveData" Version="2.2.0.4" />
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.2.1.1" />
<PackageReference Include="Xamarin.AndroidX.Navigation.UI" Version="2.3.2.1" />
<PackageReference Include="Xamarin.AndroidX.Legacy.Support.V4" Version="1.0.0.6" />
</ItemGroup>
<ItemGroup>