mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
84f5ebe4f9
Summary: Fix typos mostly in comments and some string literals. ## Changelog [General] [Fixed] - Fix typos Pull Request resolved: https://github.com/facebook/react-native/pull/25770 Differential Revision: D16437857 Pulled By: cpojer fbshipit-source-id: ffeb4d6b175e341381352091134f7c97d78c679f
342 lines
13 KiB
Java
342 lines
13 KiB
Java
// Copyright (c) Facebook, Inc. and its 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.content.Context;
|
|
import android.os.Bundle;
|
|
import android.text.SpannableString;
|
|
import android.text.style.URLSpan;
|
|
import android.util.Log;
|
|
import android.view.View;
|
|
import androidx.annotation.Nullable;
|
|
import androidx.core.view.AccessibilityDelegateCompat;
|
|
import androidx.core.view.ViewCompat;
|
|
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
|
|
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
|
|
import com.facebook.react.R;
|
|
import com.facebook.react.bridge.Arguments;
|
|
import com.facebook.react.bridge.Dynamic;
|
|
import com.facebook.react.bridge.ReactContext;
|
|
import com.facebook.react.bridge.ReadableArray;
|
|
import com.facebook.react.bridge.ReadableMap;
|
|
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
|
import com.facebook.react.bridge.ReadableType;
|
|
import com.facebook.react.bridge.WritableMap;
|
|
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
|
import java.util.HashMap;
|
|
|
|
/**
|
|
* Utility class that handles the addition of a "role" for accessibility to either a View or
|
|
* AccessibilityNodeInfo.
|
|
*/
|
|
public class ReactAccessibilityDelegate extends AccessibilityDelegateCompat {
|
|
|
|
private static final String TAG = "ReactAccessibilityDelegate";
|
|
private static int sCounter = 0x3f000000;
|
|
|
|
public static final HashMap<String, Integer> sActionIdMap = new HashMap<>();
|
|
|
|
static {
|
|
sActionIdMap.put("activate", AccessibilityActionCompat.ACTION_CLICK.getId());
|
|
sActionIdMap.put("longpress", AccessibilityActionCompat.ACTION_LONG_CLICK.getId());
|
|
sActionIdMap.put("increment", AccessibilityActionCompat.ACTION_SCROLL_FORWARD.getId());
|
|
sActionIdMap.put("decrement", AccessibilityActionCompat.ACTION_SCROLL_BACKWARD.getId());
|
|
}
|
|
|
|
/**
|
|
* These roles are defined by Google's TalkBack screen reader, and this list should be kept up to
|
|
* date with their implementation. Details can be seen in their source code here:
|
|
*
|
|
* <p>https://github.com/google/talkback/blob/master/utils/src/main/java/Role.java
|
|
*/
|
|
public enum AccessibilityRole {
|
|
NONE,
|
|
BUTTON,
|
|
LINK,
|
|
SEARCH,
|
|
IMAGE,
|
|
IMAGEBUTTON,
|
|
KEYBOARDKEY,
|
|
TEXT,
|
|
ADJUSTABLE,
|
|
SUMMARY,
|
|
HEADER,
|
|
ALERT,
|
|
CHECKBOX,
|
|
COMBOBOX,
|
|
MENU,
|
|
MENUBAR,
|
|
MENUITEM,
|
|
PROGRESSBAR,
|
|
RADIO,
|
|
RADIOGROUP,
|
|
SCROLLBAR,
|
|
SPINBUTTON,
|
|
SWITCH,
|
|
TAB,
|
|
TABLIST,
|
|
TIMER,
|
|
TOOLBAR;
|
|
|
|
public static String getValue(AccessibilityRole role) {
|
|
switch (role) {
|
|
case BUTTON:
|
|
return "android.widget.Button";
|
|
case SEARCH:
|
|
return "android.widget.EditText";
|
|
case IMAGE:
|
|
return "android.widget.ImageView";
|
|
case IMAGEBUTTON:
|
|
return "android.widget.ImageButon";
|
|
case KEYBOARDKEY:
|
|
return "android.inputmethodservice.Keyboard$Key";
|
|
case TEXT:
|
|
return "android.widget.TextView";
|
|
case ADJUSTABLE:
|
|
return "android.widget.SeekBar";
|
|
case CHECKBOX:
|
|
return "android.widget.CheckBox";
|
|
case RADIO:
|
|
return "android.widget.RadioButton";
|
|
case SPINBUTTON:
|
|
return "android.widget.SpinButton";
|
|
case SWITCH:
|
|
return "android.widget.Switch";
|
|
case NONE:
|
|
case LINK:
|
|
case SUMMARY:
|
|
case HEADER:
|
|
case ALERT:
|
|
case COMBOBOX:
|
|
case MENU:
|
|
case MENUBAR:
|
|
case MENUITEM:
|
|
case PROGRESSBAR:
|
|
case RADIOGROUP:
|
|
case SCROLLBAR:
|
|
case TAB:
|
|
case TABLIST:
|
|
case TIMER:
|
|
case TOOLBAR:
|
|
return "android.view.View";
|
|
default:
|
|
throw new IllegalArgumentException("Invalid accessibility role value: " + role);
|
|
}
|
|
}
|
|
|
|
public static AccessibilityRole fromValue(@Nullable String value) {
|
|
for (AccessibilityRole role : AccessibilityRole.values()) {
|
|
if (role.name().equalsIgnoreCase(value)) {
|
|
return role;
|
|
}
|
|
}
|
|
throw new IllegalArgumentException("Invalid accessibility role value: " + value);
|
|
}
|
|
}
|
|
|
|
private final HashMap<Integer, String> mAccessibilityActionsMap;
|
|
|
|
// State constants for states which have analogs in AccessibilityNodeInfo
|
|
|
|
private static final String STATE_DISABLED = "disabled";
|
|
private static final String STATE_SELECTED = "selected";
|
|
private static final String STATE_CHECKED = "checked";
|
|
|
|
public ReactAccessibilityDelegate() {
|
|
super();
|
|
mAccessibilityActionsMap = new HashMap<Integer, String>();
|
|
}
|
|
|
|
@Override
|
|
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
|
|
super.onInitializeAccessibilityNodeInfo(host, info);
|
|
final AccessibilityRole accessibilityRole =
|
|
(AccessibilityRole) host.getTag(R.id.accessibility_role);
|
|
if (accessibilityRole != null) {
|
|
setRole(info, accessibilityRole, host.getContext());
|
|
}
|
|
|
|
// states are changeable.
|
|
final ReadableArray accessibilityStates =
|
|
(ReadableArray) host.getTag(R.id.accessibility_states);
|
|
final ReadableMap accessibilityState = (ReadableMap) host.getTag(R.id.accessibility_state);
|
|
if (accessibilityStates != null) {
|
|
setStates(info, accessibilityStates, host.getContext());
|
|
}
|
|
if (accessibilityState != null) {
|
|
setState(info, accessibilityState, host.getContext());
|
|
}
|
|
final ReadableArray accessibilityActions =
|
|
(ReadableArray) host.getTag(R.id.accessibility_actions);
|
|
if (accessibilityActions != null) {
|
|
for (int i = 0; i < accessibilityActions.size(); i++) {
|
|
final ReadableMap action = accessibilityActions.getMap(i);
|
|
if (!action.hasKey("name")) {
|
|
throw new IllegalArgumentException("Unknown accessibility action.");
|
|
}
|
|
int actionId = sCounter;
|
|
String actionLabel = action.hasKey("label") ? action.getString("label") : null;
|
|
if (sActionIdMap.containsKey(action.getString("name"))) {
|
|
actionId = sActionIdMap.get(action.getString("name"));
|
|
} else {
|
|
sCounter++;
|
|
}
|
|
mAccessibilityActionsMap.put(actionId, action.getString("name"));
|
|
final AccessibilityActionCompat accessibilityAction =
|
|
new AccessibilityActionCompat(actionId, actionLabel);
|
|
info.addAction(accessibilityAction);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean performAccessibilityAction(View host, int action, Bundle args) {
|
|
if (mAccessibilityActionsMap.containsKey(action)) {
|
|
final WritableMap event = Arguments.createMap();
|
|
event.putString("actionName", mAccessibilityActionsMap.get(action));
|
|
ReactContext reactContext = (ReactContext) host.getContext();
|
|
reactContext
|
|
.getJSModule(RCTEventEmitter.class)
|
|
.receiveEvent(host.getId(), "topAccessibilityAction", event);
|
|
return true;
|
|
}
|
|
return super.performAccessibilityAction(host, action, args);
|
|
}
|
|
|
|
private static void setStates(
|
|
AccessibilityNodeInfoCompat info, ReadableArray accessibilityStates, Context context) {
|
|
for (int i = 0; i < accessibilityStates.size(); i++) {
|
|
String state = accessibilityStates.getString(i);
|
|
switch (state) {
|
|
case "selected":
|
|
info.setSelected(true);
|
|
break;
|
|
case "disabled":
|
|
info.setEnabled(false);
|
|
break;
|
|
case "checked":
|
|
info.setCheckable(true);
|
|
info.setChecked(true);
|
|
if (info.getClassName().equals(AccessibilityRole.getValue(AccessibilityRole.SWITCH))) {
|
|
info.setText(context.getString(R.string.state_on_description));
|
|
}
|
|
break;
|
|
case "unchecked":
|
|
info.setCheckable(true);
|
|
info.setChecked(false);
|
|
if (info.getClassName().equals(AccessibilityRole.getValue(AccessibilityRole.SWITCH))) {
|
|
info.setText(context.getString(R.string.state_off_description));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void setState(
|
|
AccessibilityNodeInfoCompat info, ReadableMap accessibilityState, Context context) {
|
|
Log.d(TAG, "setState " + accessibilityState);
|
|
final ReadableMapKeySetIterator i = accessibilityState.keySetIterator();
|
|
while (i.hasNextKey()) {
|
|
final String state = i.nextKey();
|
|
final Dynamic value = accessibilityState.getDynamic(state);
|
|
if (state.equals(STATE_SELECTED) && value.getType() == ReadableType.Boolean) {
|
|
info.setSelected(value.asBoolean());
|
|
} else if (state.equals(STATE_DISABLED) && value.getType() == ReadableType.Boolean) {
|
|
info.setEnabled(!value.asBoolean());
|
|
} else if (state.equals(STATE_CHECKED) && value.getType() == ReadableType.Boolean) {
|
|
final boolean boolValue = value.asBoolean();
|
|
info.setCheckable(true);
|
|
info.setChecked(boolValue);
|
|
if (info.getClassName().equals(AccessibilityRole.getValue(AccessibilityRole.SWITCH))) {
|
|
info.setText(
|
|
context.getString(
|
|
boolValue ? R.string.state_on_description : R.string.state_off_description));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Strings for setting the Role Description in english */
|
|
|
|
// TODO: Eventually support for other languages on talkback
|
|
|
|
public static void setRole(
|
|
AccessibilityNodeInfoCompat nodeInfo, AccessibilityRole role, final Context context) {
|
|
if (role == null) {
|
|
role = AccessibilityRole.NONE;
|
|
}
|
|
nodeInfo.setClassName(AccessibilityRole.getValue(role));
|
|
if (role.equals(AccessibilityRole.LINK)) {
|
|
nodeInfo.setRoleDescription(context.getString(R.string.link_description));
|
|
|
|
if (nodeInfo.getContentDescription() != null) {
|
|
SpannableString spannable = new SpannableString(nodeInfo.getContentDescription());
|
|
spannable.setSpan(new URLSpan(""), 0, spannable.length(), 0);
|
|
nodeInfo.setContentDescription(spannable);
|
|
}
|
|
|
|
if (nodeInfo.getText() != null) {
|
|
SpannableString spannable = new SpannableString(nodeInfo.getText());
|
|
spannable.setSpan(new URLSpan(""), 0, spannable.length(), 0);
|
|
nodeInfo.setText(spannable);
|
|
}
|
|
} else if (role.equals(AccessibilityRole.SEARCH)) {
|
|
nodeInfo.setRoleDescription(context.getString(R.string.search_description));
|
|
} else if (role.equals(AccessibilityRole.IMAGE)) {
|
|
nodeInfo.setRoleDescription(context.getString(R.string.image_description));
|
|
} else if (role.equals(AccessibilityRole.IMAGEBUTTON)) {
|
|
nodeInfo.setRoleDescription(context.getString(R.string.imagebutton_description));
|
|
nodeInfo.setClickable(true);
|
|
} else if (role.equals(AccessibilityRole.SUMMARY)) {
|
|
nodeInfo.setRoleDescription(context.getString(R.string.summary_description));
|
|
} else if (role.equals(AccessibilityRole.HEADER)) {
|
|
nodeInfo.setRoleDescription(context.getString(R.string.header_description));
|
|
final AccessibilityNodeInfoCompat.CollectionItemInfoCompat itemInfo =
|
|
AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(0, 1, 0, 1, true);
|
|
nodeInfo.setCollectionItemInfo(itemInfo);
|
|
} else if (role.equals(AccessibilityRole.ALERT)) {
|
|
nodeInfo.setRoleDescription(context.getString(R.string.alert_description));
|
|
} else if (role.equals(AccessibilityRole.COMBOBOX)) {
|
|
nodeInfo.setRoleDescription(context.getString(R.string.combobox_description));
|
|
} else if (role.equals(AccessibilityRole.MENU)) {
|
|
nodeInfo.setRoleDescription(context.getString(R.string.menu_description));
|
|
} else if (role.equals(AccessibilityRole.MENUBAR)) {
|
|
nodeInfo.setRoleDescription(context.getString(R.string.menubar_description));
|
|
} else if (role.equals(AccessibilityRole.MENUITEM)) {
|
|
nodeInfo.setRoleDescription(context.getString(R.string.menuitem_description));
|
|
} else if (role.equals(AccessibilityRole.PROGRESSBAR)) {
|
|
nodeInfo.setRoleDescription(context.getString(R.string.progressbar_description));
|
|
} else if (role.equals(AccessibilityRole.RADIOGROUP)) {
|
|
nodeInfo.setRoleDescription(context.getString(R.string.radiogroup_description));
|
|
} else if (role.equals(AccessibilityRole.SCROLLBAR)) {
|
|
nodeInfo.setRoleDescription(context.getString(R.string.scrollbar_description));
|
|
} else if (role.equals(AccessibilityRole.SPINBUTTON)) {
|
|
nodeInfo.setRoleDescription(context.getString(R.string.spinbutton_description));
|
|
} else if (role.equals(AccessibilityRole.TAB)) {
|
|
nodeInfo.setRoleDescription(context.getString(R.string.rn_tab_description));
|
|
} else if (role.equals(AccessibilityRole.TABLIST)) {
|
|
nodeInfo.setRoleDescription(context.getString(R.string.tablist_description));
|
|
} else if (role.equals(AccessibilityRole.TIMER)) {
|
|
nodeInfo.setRoleDescription(context.getString(R.string.timer_description));
|
|
} else if (role.equals(AccessibilityRole.TOOLBAR)) {
|
|
nodeInfo.setRoleDescription(context.getString(R.string.toolbar_description));
|
|
}
|
|
}
|
|
|
|
public static void setDelegate(final View view) {
|
|
// if a view already has an accessibility delegate, replacing it could cause
|
|
// problems,
|
|
// so leave it alone.
|
|
if (!ViewCompat.hasAccessibilityDelegate(view)
|
|
&& (view.getTag(R.id.accessibility_role) != null
|
|
|| view.getTag(R.id.accessibility_states) != null
|
|
|| view.getTag(R.id.accessibility_state) != null
|
|
|| view.getTag(R.id.accessibility_actions) != null)) {
|
|
ViewCompat.setAccessibilityDelegate(view, new ReactAccessibilityDelegate());
|
|
}
|
|
}
|
|
}
|