Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
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;
Expand Down Expand Up @@ -174,11 +173,7 @@ public BaseViewManager(@Nullable ReactApplicationContext reactContext) {
view.setFocusableInTouchMode(false);

// Accessibility state the props apply to the view itself, as a new view has it. The delegate
// stays, since a view's constructor may have installed it, and forgets what it cached.
AccessibilityDelegateCompat accessibilityDelegate = ViewCompat.getAccessibilityDelegate(view);
if (accessibilityDelegate instanceof ReactAccessibilityDelegate) {
((ReactAccessibilityDelegate) accessibilityDelegate).internal_prepareToRecycle();
}
// stays, since a view's constructor may have installed it.
view.setContentDescription(null);
view.setSelected(false);
view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
Expand Down Expand Up @@ -498,11 +493,7 @@ private void updateViewContentDescription(@NonNull T view) {
}

@ReactProp(name = ViewProps.ACCESSIBILITY_ACTIONS)
public void setAccessibilityActions(T view, ReadableArray accessibilityActions) {
if (accessibilityActions == null) {
return;
}

public void setAccessibilityActions(T view, @Nullable ReadableArray accessibilityActions) {
view.setTag(R.id.accessibility_actions, accessibilityActions);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ public open class ReactAccessibilityDelegate( // The View this delegate is attac
host?.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED)
}
}
private val accessibilityActionsMap = HashMap<Int, String?>()

init {
// We need to reset these two properties, as ExploreByTouchHelper sets focusable to "true" and
Expand All @@ -68,12 +67,6 @@ public open class ReactAccessibilityDelegate( // The View this delegate is attac
hostView.importantForAccessibility = originalImportantForAccessibility
}

/** Forgets the actions cached from the props of a view that is about to be recycled. */
@JvmName("internal_prepareToRecycle")
internal fun prepareToRecycle() {
accessibilityActionsMap.clear()
}

override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
super.onInitializeAccessibilityNodeInfo(host, info)

Expand Down Expand Up @@ -135,7 +128,6 @@ public open class ReactAccessibilityDelegate( // The View this delegate is attac
val actionId: Int =
actionIdMap.get(actionName)
?: customActionIdMap.getOrPut(actionName) { customActionCounter++ }
accessibilityActionsMap[actionId] = actionName
val accessibilityAction = AccessibilityActionCompat(actionId, actionLabel)
info.addAction(accessibilityAction)
}
Expand Down Expand Up @@ -230,9 +222,10 @@ public open class ReactAccessibilityDelegate( // The View this delegate is attac
if (action == AccessibilityNodeInfoCompat.ACTION_EXPAND) {
host.setTag(R.id.accessibility_state_expanded, true)
}
if (accessibilityActionsMap.containsKey(action)) {
val actionName = findDeclaredActionName(host, action)
if (actionName != null) {
val eventData = createMap()
eventData.putString("actionName", accessibilityActionsMap[action])
eventData.putString("actionName", actionName)
if (
action == AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_PROGRESS.id &&
args?.containsKey(AccessibilityNodeInfo.ACTION_ARGUMENT_PROGRESS_VALUE) == true
Expand Down Expand Up @@ -278,6 +271,22 @@ public open class ReactAccessibilityDelegate( // The View this delegate is attac
return super.performAccessibilityAction(host, action, args)
}

private fun findDeclaredActionName(host: View, actionId: Int): String? {
val accessibilityActions =
host.getTag(R.id.accessibility_actions) as ReadableArray? ?: return null
for (i in 0..<accessibilityActions.size()) {
val action = accessibilityActions.getMap(i)
if (action == null || !action.hasKey("name")) {
continue
}
val actionName = action.getString("name")
if ((actionIdMap[actionName] ?: customActionIdMap[actionName]) == actionId) {
return actionName
}
}
return null
}

/**
* Schedule a command for sending an accessibility event. Note: A command is used to ensure that
* accessibility events are sent at most one in a given time frame to save system resources while
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,56 @@ class BaseViewManagerTest {
Assertions.assertThat(view.elevation).isEqualTo(0f)
}

@Test
fun testDeclaredActivateActionTakesTheAccessibilityClick() {
applyAccessibilityActions(JavaOnlyArray.of(JavaOnlyMap.of("name", "activate")))
view.createAccessibilityNodeInfo()

Assertions.assertThat(performAccessibilityClick()).isEqualTo(0)
}

@Test
fun testRemovedAccessibilityActionsNoLongerTakeTheAccessibilityClick() {
applyAccessibilityActions(JavaOnlyArray.of(JavaOnlyMap.of("name", "activate")))
view.createAccessibilityNodeInfo()
applyAccessibilityActions(null)

Assertions.assertThat(performAccessibilityClick()).isEqualTo(1)
}

@Test
fun testEmptiedAccessibilityActionsNoLongerTakeTheAccessibilityClick() {
applyAccessibilityActions(JavaOnlyArray.of(JavaOnlyMap.of("name", "activate")))
view.createAccessibilityNodeInfo()
applyAccessibilityActions(JavaOnlyArray())

Assertions.assertThat(performAccessibilityClick()).isEqualTo(1)
}

@Test
fun testRemainingAccessibilityActionsDoNotTakeTheAccessibilityClick() {
applyAccessibilityActions(
JavaOnlyArray.of(JavaOnlyMap.of("name", "activate"), JavaOnlyMap.of("name", "longpress"))
)
view.createAccessibilityNodeInfo()
applyAccessibilityActions(JavaOnlyArray.of(JavaOnlyMap.of("name", "longpress")))

Assertions.assertThat(performAccessibilityClick()).isEqualTo(1)
}

@Test
fun testRemovedAccessibilityActionsLeaveTheNodeInfo() {
applyAccessibilityActions(
JavaOnlyArray.of(JavaOnlyMap.of("name", "openMenu", "label", "Open menu"))
)
applyAccessibilityActions(null)

Assertions.assertThat(
view.createAccessibilityNodeInfo().actionList.map { it.label?.toString() }
)
.doesNotContain("Open menu")
}

private fun recycleView(
previousProps: JavaOnlyMap,
beforeDrop: (ReactViewGroup) -> Unit = {},
Expand Down Expand Up @@ -447,4 +497,18 @@ class BaseViewManagerTest {
null,
JSResponderHandler(),
)

private fun applyAccessibilityActions(accessibilityActions: JavaOnlyArray?) {
viewManager.updateProperties(
view,
ReactStylesDiffMap(JavaOnlyMap.of(ViewProps.ACCESSIBILITY_ACTIONS, accessibilityActions)),
)
}

private fun performAccessibilityClick(): Int {
var clicks = 0
view.setOnClickListener { clicks++ }
view.performAccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, null)
return clicks
}
}
Loading