admin
2024-07-03 a40e0e51331e5e6f69e8bed5940512b29150c7a9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
/*
 * 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.
     * <p>
     * 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.
     * <p>
     * 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.
     * <p>
     * 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.
     * <p>
     * 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.
     * <p>
     * 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.
     * <p>
     * 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.
     * <p>
     * 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.
     * <p>
     * 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);
    }
}