diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 9aab9261e2c..158530563dc 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -3659,7 +3659,6 @@ public class com/facebook/react/uimanager/ReactAccessibilityDelegate : androidx/ public static final field TOP_ACCESSIBILITY_ACTION_EVENT Ljava/lang/String; public static final field sActionIdMap Ljava/util/HashMap; public fun (Landroid/view/View;ZI)V - public fun cleanUp ()V public static fun createNodeInfoFromView (Landroid/view/View;)Landroidx/core/view/accessibility/AccessibilityNodeInfoCompat; public fun getAccessibilityNodeProvider (Landroid/view/View;)Landroidx/core/view/accessibility/AccessibilityNodeProviderCompat; protected fun getHostView ()Landroid/view/View; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java index 6fccc44a85e..07ac1cad632 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java @@ -19,7 +19,6 @@ import android.view.accessibility.AccessibilityEvent; import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.core.view.AccessibilityDelegateCompat; import androidx.core.view.ViewCompat; import com.facebook.common.logging.FLog; import com.facebook.react.R; @@ -188,12 +187,6 @@ public abstract class BaseViewManager mAxOrderViews; private Handler mHandler; private final HashMap mAccessibilityActionsMap; - @Nullable - private AccessibilityManager.AccessibilityStateChangeListener accessibilityStateChangeListener = - null; - @Nullable View mAccessibilityLabelledBy; static { @@ -116,7 +107,6 @@ public class ReactAccessibilityDelegate extends ExploreByTouchHelper { // problems, so leave it alone. if (!ViewCompat.hasAccessibilityDelegate(view) && (view.getTag(R.id.accessibility_role) != null - || view.getTag(R.id.accessibility_order) != null || view.getTag(R.id.accessibility_state) != null || view.getTag(R.id.accessibility_actions) != null || view.getTag(R.id.react_test_id) != null @@ -142,7 +132,10 @@ public class ReactAccessibilityDelegate extends ExploreByTouchHelper { return mView; } - private void populateAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { + super.onInitializeAccessibilityNodeInfo(host, info); + if (host.getTag(R.id.accessibility_state_expanded) != null) { final boolean accessibilityStateExpanded = (boolean) host.getTag(R.id.accessibility_state_expanded); @@ -259,52 +252,6 @@ public class ReactAccessibilityDelegate extends ExploreByTouchHelper { } } - @Override - public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { - super.onInitializeAccessibilityNodeInfo(host, info); - // If we set an accessibility order then all the focusing logic should go through our custom - // virtual view tree hierarchy and ignore the default path - ReadableArray axOrderIds = (ReadableArray) mView.getTag(R.id.accessibility_order); - if (axOrderIds != null && axOrderIds.size() != 0) { - info.setContentDescription(""); - info.setFocusable(false); - - AccessibilityManager am = - (AccessibilityManager) host.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); - - if (accessibilityStateChangeListener == null && am != null) { - AccessibilityManager.AccessibilityStateChangeListener newAccessibilityStateChangeListener = - enabled -> { - if (!enabled) { - ReactAxOrderHelper.restoreSubtreeFocusability(host); - host.setTag(R.id.accessibility_order_dirty, true); - } - }; - - am.addAccessibilityStateChangeListener(newAccessibilityStateChangeListener); - accessibilityStateChangeListener = newAccessibilityStateChangeListener; - } - - Boolean isAxOrderDirty = (Boolean) mView.getTag(R.id.accessibility_order_dirty); - if (isAxOrderDirty != null && isAxOrderDirty) { - List axOrderIdsList = new ArrayList<>(); - Set axOrderSet = new HashSet<>(); - for (int i = 0; i < axOrderIds.size(); i++) { - String id = axOrderIds.getString(i); - if (id != null) { - axOrderIdsList.add(id); - axOrderSet.add(id); - } - } - - mAxOrderViews = ReactAxOrderHelper.processAxOrderTree(mView, axOrderIdsList, axOrderSet); - } - return; - } - - populateAccessibilityNodeInfo(host, info); - } - @Override public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { super.onInitializeAccessibilityEvent(host, event); @@ -475,61 +422,17 @@ public class ReactAccessibilityDelegate extends ExploreByTouchHelper { @Override protected int getVirtualViewAt(float x, float y) { - if (mAxOrderViews == null) { - return HOST_ID; - } - - int closestViewId = HOST_ID; - int smallestArea = Integer.MAX_VALUE; - - for (int i = 0; i < mAxOrderViews.size(); i++) { - Rect bounds = ReactAxOrderHelper.getVirtualViewBounds(mView, mAxOrderViews.get(i)); - if (bounds.contains((int) x, (int) y)) { - int area = bounds.width() * bounds.height(); - if (area < smallestArea) { - smallestArea = area; - closestViewId = i; - } - } - } - - return closestViewId; + return INVALID_ID; } @Override - protected void getVisibleVirtualViews(List virtualViewIds) { - if (mAxOrderViews != null && !mAxOrderViews.isEmpty()) { - for (int i = 0; i < mAxOrderViews.size(); i++) { - virtualViewIds.add(i); - } - } - } + protected void getVisibleVirtualViews(List virtualViewIds) {} @Override protected void onPopulateNodeForVirtualView( int virtualViewId, @NonNull AccessibilityNodeInfoCompat node) { - if (mView.getTag(R.id.accessibility_order) != null) { - if (mAxOrderViews.size() <= virtualViewId) { - node.setContentDescription(""); - node.setBoundsInParent(new Rect(0, 0, 1, 1)); - return; - } - - View virtualView = mAxOrderViews.get(virtualViewId); - - node.setContentDescription(""); - if (virtualView == mView) { - if (mView.getContentDescription() != null) { - node.setContentDescription(mView.getContentDescription()); - } - - populateAccessibilityNodeInfo(mView, node); - node.setBoundsInParent(new Rect(0, 0, mView.getWidth(), mView.getHeight())); - } else { - node.setBoundsInParent(ReactAxOrderHelper.getVirtualViewBounds(mView, virtualView)); - } - node.addChild(virtualView); - } + node.setContentDescription(""); + node.setBoundsInParent(new Rect(0, 0, 1, 1)); } @Override @@ -540,10 +443,6 @@ public class ReactAccessibilityDelegate extends ExploreByTouchHelper { @Override public @Nullable AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View host) { - if (mView.getTag(R.id.accessibility_order) != null) { - return super.getAccessibilityNodeProvider(host); - } - return null; } @@ -1121,17 +1020,4 @@ public class ReactAccessibilityDelegate extends ExploreByTouchHelper { } } } - - // In case a view with accessibilityOrder is unmounted we need a way to clean up the listener on - // this delegate - public void cleanUp() { - if (accessibilityStateChangeListener != null) { - AccessibilityManager am = - (AccessibilityManager) mView.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); - if (am != null) { - am.removeAccessibilityStateChangeListener(accessibilityStateChangeListener); - } - accessibilityStateChangeListener = null; - } - } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAxOrderHelper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAxOrderHelper.kt deleted file mode 100644 index 12c139c8a50..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactAxOrderHelper.kt +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.uimanager - -import android.graphics.Rect -import android.view.View -import android.view.ViewGroup -import com.facebook.react.R -import com.facebook.react.bridge.ReadableArray - -private object ReactAxOrderHelper { - /** - * Processes the View tree that begins at the View with AccessibilityOrder set - * - * Disables accessibility for views not included in the specified accessibility order. - * - * This method emulates iOS's focusing order behavior to facilitate cross-platform code sharing. - * It disables accessibility for views that are either not part of the accessibility order or - * don't have a container that belongs to the accessibility order. - * - * The container/element concept is borrowed from iOS, where a "container" is a non-accessible - * view with children, and an "element" is any accessible view. - * - * @return an array of views following the accessibility order - */ - @JvmStatic - fun processAxOrderTree( - root: View, - axOrderIds: MutableList, - axOrderSet: MutableSet - ): List { - val axOrderViews = Array(axOrderIds.size) { mutableListOf() }.toMutableList() - - fun traverseAndBuildAxOrder(parent: View, view: View, containerId: String?) { - val nativeId = view.getTag(R.id.view_tag_native_id) as String? - - val isContained = (containerId != null && axOrderSet.contains(containerId)) - val isIncluded = (nativeId != null && axOrderSet.contains(nativeId)) - - val isNestedAxOrder = view.getTag(R.id.accessibility_order) != null && view != parent - - if (isIncluded && view.isFocusable) { - axOrderViews[axOrderIds.indexOf(nativeId)].add(view) - if (parent != view) { - view.setTag(R.id.accessibility_order_parent, parent) - } - } else if (isContained && view.isFocusable) { - axOrderViews[axOrderIds.indexOf(containerId)].add(view) - if (parent != view) { - view.setTag(R.id.accessibility_order_parent, parent) - } - } - - if (isNestedAxOrder) { - val nestedOrder = view.getTag(R.id.accessibility_order) as ReadableArray - for (i in 0 until nestedOrder.size()) { - val id = nestedOrder.getString(i) - if (id != null) { - val insertIdx = axOrderIds.indexOf(nativeId) + 1 - if (insertIdx < axOrderIds.size) { - axOrderIds.add(axOrderIds.indexOf(nativeId) + 1 + i, id) - axOrderViews.add(axOrderIds.indexOf(nativeId) + 1 + i, mutableListOf()) - } else { - axOrderIds.add(id) - axOrderViews.add(mutableListOf()) - } - - axOrderSet.add(id) - } - } - } - - // Don't traverse the children of a nested accessibility order - if (view is ViewGroup) { - val axChildren: ArrayList = getAxChildren(view) - - // If the View is a "container" (Not focusable but is included in the order) We add all its - // children to the order. - if (containerId != null) { - for (i in 0 until axChildren.size) { - traverseAndBuildAxOrder(parent, axChildren[i], containerId) - } - } else if (!view.isFocusable && isIncluded) { - for (i in 0 until axChildren.size) { - traverseAndBuildAxOrder(parent, axChildren[i], nativeId) - } - } else { - for (i in 0 until axChildren.size) { - traverseAndBuildAxOrder(parent, axChildren[i], null) - } - } - } - - if (!isIncluded && !isContained && parent != view) { - if (view.getTag(R.id.original_focusability) == null) { - view.setTag(R.id.original_focusability, view.isFocusable) - } - view.isFocusable = false - } - } - - traverseAndBuildAxOrder( - root, - root, - null, - ) - - val result = mutableListOf() - for (viewList in axOrderViews) { - for (view in viewList) { - if (view != null) { - result.add(view) - } - } - } - - root.setTag(R.id.accessibility_order_dirty, false) - - return result - } - - @JvmStatic - fun getVirtualViewBounds(host: View, virtualView: View): Rect { - var currentView: View = virtualView - val viewBoundsInParent = - Rect(virtualView.left, virtualView.top, virtualView.right, virtualView.bottom) - while (currentView.parent != host && currentView != host) { - val parent = currentView.parent as View - viewBoundsInParent.top += parent.top - viewBoundsInParent.bottom += parent.top - viewBoundsInParent.left += parent.left - viewBoundsInParent.right += parent.left - currentView = parent - } - - return viewBoundsInParent - } - - private fun getAxChildren(host: ViewGroup): ArrayList { - val axChildren: ArrayList = ArrayList() - - // When host has an accessibilityNodeProvider it means the order is not default so ViewGroup's - // method bails, in this case we should just add the children to the node in the edge case where - // we need to coopt at accessibilityOrderParent level - if (host.accessibilityNodeProvider != null) { - for (i in 0 until host.childCount) { - val child = host.getChildAt(i) - if (child != null) { - axChildren.add(child) - } - } - } else { - // This extracts the children of host sorted in accessibility order, this is by layout - // top to bottom, left to right - host.addChildrenForAccessibility(axChildren) - } - return axChildren - } - - @JvmStatic - public fun restoreSubtreeFocusability(view: View) { - val originalFocusability = view.getTag(R.id.original_focusability) - if (originalFocusability is Boolean) { - view.isFocusable = originalFocusability - } - - if (view is ViewGroup) { - for (i in 0 until view.childCount) { - val child = view.getChildAt(i) - if (child != null) { - restoreSubtreeFocusability(child) - } - } - } - } -}