Skip to content
This repository has been archived by the owner on May 1, 2024. It is now read-only.

Fix up Android Accessibility behind a flag #14089

Merged
merged 8 commits into from
Jun 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .nuspec/Xamarin.Forms.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -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"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,11 @@ protected override void OnCreate(Bundle bundle)
#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
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}

}
}
5 changes: 3 additions & 2 deletions Xamarin.Forms.Platform.Android/AppCompat/PickerRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Android.Text.Style;
using Android.Util;
using Android.Widget;
using AndroidX.Core.View;

namespace Xamarin.Forms.Platform.Android.AppCompat
{
Expand Down Expand Up @@ -60,8 +61,8 @@ protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
var textField = CreateNativeControl();

SetNativeControl(textField);

ControlUsedForAutomation.SetAccessibilityDelegate(_pickerAccessibilityDelegate = new EntryAccessibilityDelegate(Element));
ViewCompat.SetAccessibilityDelegate(
ControlUsedForAutomation, _pickerAccessibilityDelegate = new EntryAccessibilityDelegate(Element));
}
UpdateFont();
UpdatePicker();
Expand Down
9 changes: 5 additions & 4 deletions Xamarin.Forms.Platform.Android/EntryAccessibilityDelegate.cs
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -22,7 +23,7 @@ protected override void Dispose(bool disposing)

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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -30,52 +32,102 @@ internal static void GetDrawerAccessibilityResources(global::Android.Content.Con
resourceIdClose = context.Resources.GetIdentifier($"{automationIdParent}{s_defaultDrawerIdCloseSuffix}", "string", context.ApplicationInfo.PackageName);
}

static bool ShoudISetImportantForAccessibilityToNoIfAutomationIdIsSet(AView control, Element element)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we come up with a better, shorter name for this bool?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the name is clear about what it's doing then no
If it's not clear then yes

It's better to be verbose and clear with methods names then short/clever/and confusing

{
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 ||
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This set of controls isn't going to change

This is a bug that's very specific to XF 5 and won't surface in MAUI as we won't be using ContentDescription

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)
{
Expand All @@ -96,12 +148,21 @@ internal static void SetFocusable(AView control, Element element, ref bool? defa
{
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;
}
Expand Down Expand Up @@ -253,12 +314,16 @@ internal static void SetupDefaults(AView control, ref string defaultContentDescr

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;
}
}
}

Expand Down
14 changes: 14 additions & 0 deletions Xamarin.Forms.Platform.Android/Flags.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
7 changes: 5 additions & 2 deletions Xamarin.Forms.Platform.Android/PlatformRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ internal class PlatformRenderer : ViewGroup
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)
Expand Down
3 changes: 2 additions & 1 deletion Xamarin.Forms.Platform.Android/Renderers/PageRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ protected override void OnElementChanged(ElementChangedEventArgs<Page> e)

UpdateBackground(false);

Clickable = true;
if(!Flags.IsAccessibilityExperimentalSet())
Clickable = true;
}

protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
Expand Down
3 changes: 2 additions & 1 deletion Xamarin.Forms.Platform.Android/Renderers/PickerRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Android.Util;
using Android.Views;
using Android.Widget;
using AndroidX.Core.View;
using AColor = Android.Graphics.Color;
using Orientation = Android.Widget.Orientation;

Expand Down Expand Up @@ -67,7 +68,7 @@ protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,7 @@ void UpdateAutomationId()
{
FastRenderers
.AutomationPropertiesProvider
.SetAutomationId(_editText, _searchHandler?.AutomationId);

.SetAutomationId(_editText, null, _searchHandler?.AutomationId);
}

void UpdateFont()
Expand Down
Loading