/* * Copyright (C) 2014 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.taoke.autopay.android.utils.accessibility; import android.content.Context; import android.os.Bundle; import android.provider.Settings; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; /** * Utility class for sending commands to ChromeVox. * * @author caseyburkhardt@google.com (Casey Burkhardt) */ public class WebInterfaceUtils { /** * If injection of accessibility enhancing JavaScript screen-reader is * enabled. *
* This property represents a boolean value encoded as an integer (1 is * true, 0 is false). */ private static final String ACCESSIBILITY_SCRIPT_INJECTION = "accessibility_script_injection"; /** * Direction constant for forward movement within a page. */ public static final int DIRECTION_FORWARD = 1; /** * Direction constant for backward movement within a page. */ public static final int DIRECTION_BACKWARD = -1; /** * Action argument to use with * {@link #performSpecialAction(AccessibilityNodeInfoCompat, int)} to * instruct ChromeVox to read the currently focused element within the node. * within the page. */ public static final int ACTION_READ_CURRENT_HTML_ELEMENT = -1; /** * Action argument to use with * {@link #performSpecialAction(AccessibilityNodeInfoCompat, int)} to * instruct ChromeVox to read the title of the page within the node. */ public static final int ACTION_READ_PAGE_TITLE_ELEMENT = -2; /** * Action argument to use with * {@link #performSpecialAction(AccessibilityNodeInfoCompat, int)} to * instruct ChromeVox to stop all speech and automatic actions. */ public static final int ACTION_STOP_SPEECH = -3; /** * Action argument to use with * {@link #performSpecialAction(AccessibilityNodeInfoCompat, int, int)} to * instruct ChromeVox to move into or out of the special content navigation * mode. *
* Using this constant also requires specifying a direction. * {@link #DIRECTION_FORWARD} indicates ChromeVox should move into this * content navigation mode, {@link #DIRECTION_BACKWARD} indicates ChromeVox * should move out of this mode. */ private static final int ACTION_TOGGLE_SPECIAL_CONTENT = -4; /** * Action argument to use with * {@link #performSpecialAction(AccessibilityNodeInfoCompat, int, int)} to * instruct ChromeVox to move into or out of the incremental search mode. *
* Using this constant does not require a direction as it only toggles * the state. */ public static final int ACTION_TOGGLE_INCREMENTAL_SEARCH = -5; /** * HTML element argument to use with * {@link #performNavigationToHtmlElementAction(AccessibilityNodeInfoCompat, * int, String)} to instruct ChromeVox to move to the next or previous page * section. */ public static final String HTML_ELEMENT_MOVE_BY_SECTION = "SECTION"; /** * HTML element argument to use with * {@link #performNavigationToHtmlElementAction(AccessibilityNodeInfoCompat, * int, String)} to instruct ChromeVox to move to the next or previous list. */ public static final String HTML_ELEMENT_MOVE_BY_LIST = "LIST"; /** * HTML element argument to use with * {@link #performNavigationToHtmlElementAction(AccessibilityNodeInfoCompat, * int, String)} to instruct ChromeVox to move to the next or previous * control. */ public static final String HTML_ELEMENT_MOVE_BY_CONTROL = "CONTROL"; /** * Sends an instruction to ChromeVox to read the specified HTML element in * the given direction within a node. *
* WARNING: Calling this method with a source node of * {@link android.webkit.WebView} has the side effect of closing the IME * if currently displayed. * * @param node The node containing web content with ChromeVox to which the * message should be sent * @param direction {@link #DIRECTION_FORWARD} or * {@link #DIRECTION_BACKWARD} * @param htmlElement The HTML tag to send * @return {@code true} if the action was performed, {@code false} * otherwise. */ public static boolean performNavigationToHtmlElementAction( AccessibilityNodeInfoCompat node, int direction, String htmlElement) { final int action = (direction == DIRECTION_FORWARD) ? AccessibilityNodeInfoCompat.ACTION_NEXT_HTML_ELEMENT : AccessibilityNodeInfoCompat.ACTION_PREVIOUS_HTML_ELEMENT; final Bundle args = new Bundle(); args.putString( AccessibilityNodeInfoCompat.ACTION_ARGUMENT_HTML_ELEMENT_STRING, htmlElement); return node.performAction(action, args); } /** * Sends an instruction to ChromeVox to navigate by DOM object in * the given direction within a node. * * @param node The node containing web content with ChromeVox to which the * message should be sent * @param direction {@link #DIRECTION_FORWARD} or * {@link #DIRECTION_BACKWARD} * @return {@code true} if the action was performed, {@code false} * otherwise. */ public static boolean performNavigationByDOMObject( AccessibilityNodeInfoCompat node, int direction) { final int action = (direction == DIRECTION_FORWARD) ? AccessibilityNodeInfoCompat.ACTION_NEXT_HTML_ELEMENT : AccessibilityNodeInfoCompat.ACTION_PREVIOUS_HTML_ELEMENT; return node.performAction(action); } /** * Sends an instruction to ChromeVox to move within a page at a specified * granularity in a given direction. *
* WARNING: Calling this method with a source node of * {@link android.webkit.WebView} has the side effect of closing the IME * if currently displayed. * * @param node The node containing web content with ChromeVox to which the * message should be sent * @param direction {@link #DIRECTION_FORWARD} or * {@link #DIRECTION_BACKWARD} * @param granularity The granularity with which to move or a special case argument. * @return {@code true} if the action was performed, {@code false} otherwise. */ public static boolean performNavigationAtGranularityAction( AccessibilityNodeInfoCompat node, int direction, int granularity) { final int action = (direction == DIRECTION_FORWARD) ? AccessibilityNodeInfoCompat.ACTION_NEXT_AT_MOVEMENT_GRANULARITY : AccessibilityNodeInfoCompat.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY; final Bundle args = new Bundle(); args.putInt( AccessibilityNodeInfoCompat.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT, granularity); return node.performAction(action, args); } /** * Sends instruction to ChromeVox to perform one of the special actions * defined by the ACTION constants in this class. *
* WARNING: Calling this method with a source node of * {@link android.webkit.WebView} has the side effect of closing the IME if * currently displayed. * * @param node The node containing web content with ChromeVox to which the * message should be sent * @param action The ACTION constant in this class match the special action * that ChromeVox should perform. * @return {@code true} if the action was performed, {@code false} * otherwise. */ public static boolean performSpecialAction(AccessibilityNodeInfoCompat node, int action) { return performSpecialAction(node, action, DIRECTION_FORWARD); } /** * Sends instruction to ChromeVox to perform one of the special actions * defined by the ACTION constants in this class. *
* WARNING: Calling this method with a source node of * {@link android.webkit.WebView} has the side effect of closing the IME if * currently displayed. * * @param node The node containing web content with ChromeVox to which the * message should be sent * @param action The ACTION constant in this class match the special action * that ChromeVox should perform. * @param direction The DIRECTION constant in this class to add as an extra * argument to the special action. * @return {@code true} if the action was performed, {@code false} * otherwise. */ public static boolean performSpecialAction( AccessibilityNodeInfoCompat node, int action, int direction) { /* * We use performNavigationAtGranularity to communicate with ChromeVox * for these actions because it is side-effect-free. If we use * performNavigationToHtmlElementAction and ChromeVox isn't injected, * we'll actually move selection within the fallback implementation. We * use the granularity field to hold a value that ChromeVox interprets * as a special command. */ return performNavigationAtGranularityAction(node, direction, action /* fake granularity */); } /** * Sends a message to ChromeVox indicating that it should enter or exit * special content navigation. This is applicable for things like tables and * math expressions. *
* NOTE: further navigation should occur at the default movement * granularity. * * @param node The node representing the web content * @param enabled Whether this mode should be entered or exited * @return {@code true} if the action was performed, {@code false} * otherwise. */ public static boolean setSpecialContentModeEnabled( AccessibilityNodeInfoCompat node, boolean enabled) { final int direction = (enabled) ? DIRECTION_FORWARD : DIRECTION_BACKWARD; return performSpecialAction(node, ACTION_TOGGLE_SPECIAL_CONTENT, direction); } /** * Determines whether or not the given node contains web content. * * @param node The node to evaluate * @return {@code true} if the node contains web content, {@code false} otherwise */ public static boolean supportsWebActions(AccessibilityNodeInfoCompat node) { return AccessibilityNodeInfoUtils.supportsAnyAction(node, AccessibilityNodeInfoCompat.ACTION_NEXT_HTML_ELEMENT, AccessibilityNodeInfoCompat.ACTION_PREVIOUS_HTML_ELEMENT); } /** * Determines whether or not the given node contains native web content (and not ChromeVox). * * @param node The node to evaluate * @return {@code true} if the node contains native web content, {@code false} otherwise */ public static boolean hasNativeWebContent(AccessibilityNodeInfoCompat node) { if (node == null) { return false; } if (!supportsWebActions(node)) { return false; } // ChromeVox does not have sub elements, so if the parent element also has web content // this cannot be ChromeVox. AccessibilityNodeInfoCompat parent = node.getParent(); if (supportsWebActions(parent)) { if (parent != null) { parent.recycle(); } return true; } if (parent != null) { parent.recycle(); } // ChromeVox never has child elements return node.getChildCount() > 0; } /** * Determines whether or not the given node contains ChromeVox content. * * @param node The node to evaluate * @return {@code true} if the node contains ChromeVox content, {@code false} otherwise */ public static boolean hasLegacyWebContent(AccessibilityNodeInfoCompat node) { if (node == null) { return false; } if (!supportsWebActions(node)) { return false; } // ChromeVox does not have sub elements, so if the parent element also has web content // this cannot be ChromeVox. AccessibilityNodeInfoCompat parent = node.getParent(); if (supportsWebActions(parent)) { if (parent != null) { parent.recycle(); } return false; } if (parent != null) { parent.recycle(); } // ChromeVox never has child elements return node.getChildCount() == 0; } /** * @return {@code true} if the user has explicitly enabled injection of * accessibility scripts into web content. */ public static boolean isScriptInjectionEnabled(Context context) { final int injectionSetting = Settings.Secure.getInt( context.getContentResolver(), ACCESSIBILITY_SCRIPT_INJECTION, 0); return (injectionSetting == 1); } /** * Returns whether the given node has navigable web content, either legacy (ChromeVox) or native * web content. * * @param context The parent context. * @param node The node to check for web content. * @return Whether the given node has navigable web content. */ public static boolean hasNavigableWebContent( Context context, AccessibilityNodeInfoCompat node) { return (supportsWebActions(node) && isScriptInjectionEnabled(context)) || hasNativeWebContent(node); } }