PageRenderTime 21ms CodeModel.GetById 6ms app.highlight 11ms RepoModel.GetById 1ms app.codeStats 0ms

/TalkBack/src/com/google/android/marvin/talkback/AccessibilityNodeInfoUtils.java

http://eyes-free.googlecode.com/
Java | 213 lines | 90 code | 31 blank | 92 comment | 31 complexity | 527dd203a87ca32bdbb9096f9a99bd91 MD5 | raw file
  1/*
  2 * Copyright (C) 2011 The Android Open Source Project
  3 *
  4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5 * use this file except in compliance with the License. You may obtain a copy of
  6 * the License at
  7 *
  8 * http://www.apache.org/licenses/LICENSE-2.0
  9 *
 10 * Unless required by applicable law or agreed to in writing, software
 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 13 * License for the specific language governing permissions and limitations under
 14 * the License.
 15 */
 16
 17package com.google.android.marvin.talkback;
 18
 19import android.content.Context;
 20import android.view.accessibility.AccessibilityNodeInfo;
 21
 22import java.util.ArrayList;
 23import java.util.List;
 24
 25/**
 26 * Provides a series of utilities for interacting with AccessibilityNodeInfo
 27 * objects. NOTE: This class only recycles unused nodes that were collected
 28 * internally. Any node passed into or returned from a public method is retained
 29 * and TalkBack should recycle it when appropriate.
 30 * 
 31 * @author caseyburkhardt@google.com (Casey Burkhardt)
 32 */
 33public class AccessibilityNodeInfoUtils {
 34    /**
 35     * Filters {@link AccessibilityNodeInfo}s.
 36     */
 37    public static interface NodeFilter {
 38        /**
 39         * @param node The node info to filter.
 40         * @return {@code true} if the node is accepted
 41         */
 42        public boolean accept(AccessibilityNodeInfo node);
 43    }
 44
 45    private static final Context CONTEXT = TalkBackService.asContext();
 46
 47    private AccessibilityNodeInfoUtils() {
 48        // This class is not instantiable.
 49    }
 50
 51    /**
 52     * Returns whether a node is actionable. That is, the node supports one of
 53     * the following actions:
 54     * <ul>
 55     * <li>{@link AccessibilityNodeInfo#isCheckable()}
 56     * <li>{@link AccessibilityNodeInfo#isClickable()}
 57     * <li>{@link AccessibilityNodeInfo#isFocusable()}
 58     * <li>{@link AccessibilityNodeInfo#isLongClickable()}
 59     * </ul>
 60     * 
 61     * @param node The node to check.
 62     * @return Whether the node is actionable.
 63     */
 64    public static boolean isActionable(AccessibilityNodeInfo node) {
 65        return node.isCheckable() || node.isClickable() || node.isFocusable()
 66                || node.isLongClickable();
 67    }
 68
 69    /**
 70     * Determines if the generating class of an {@link AccessibilityNodeInfo}
 71     * matches a given {@link Class} by type.
 72     * 
 73     * @param node A sealed {@link AccessibilityNodeInfo} dispatched by the
 74     *            accessibility framework.
 75     * @param clazz A {@link Class} to match by type or inherited type.
 76     * @return {@code true} if the {@link AccessibilityNodeInfo} object matches
 77     *         the {@link Class} by type or inherited type, {@code false}
 78     *         otherwise.
 79     */
 80    public static boolean nodeMatchesClassByType(AccessibilityNodeInfo node, Class<?> clazz) {
 81        if (node == null || clazz == null) {
 82            return false;
 83        }
 84
 85        final ClassLoadingManager classLoader = ClassLoadingManager.getInstance();
 86        final CharSequence className = node.getClassName();
 87        final CharSequence packageName = node.getPackageName();
 88        final Class<?> nodeClass =
 89                classLoader.loadOrGetCachedClass(CONTEXT, className, packageName);
 90
 91        if (nodeClass == null) {
 92            return false;
 93        }
 94
 95        return clazz.isAssignableFrom(nodeClass);
 96    }
 97
 98    /**
 99     * Gets all the parent {@link AccessibilityNodeInfo} nodes, up to the view
100     * root.
101     * 
102     * @param node The {@link AccessibilityNodeInfo} child from which to begin
103     *            collecting parents.
104     * @param parentList The list to populate with predecessor nodes.
105     */
106    public static void getPredecessors(AccessibilityNodeInfo node,
107            List<AccessibilityNodeInfo> parentList) {
108        if (node == null || parentList == null) {
109            return;
110        }
111
112        node = node.getParent();
113
114        while (node != null) {
115            parentList.add(node);
116            node = node.getParent();
117        }
118    }
119
120    /**
121     * Recycles the given nodes.
122     * 
123     * @param nodes The nodes to recycle.
124     */
125    public static void recycleNodeList(ArrayList<AccessibilityNodeInfo> nodes) {
126        if (nodes == null) {
127            return;
128        }
129
130        for (AccessibilityNodeInfo node : nodes) {
131            node.recycle();
132        }
133
134        nodes.clear();
135    }
136
137    /**
138     * Recycles the given nodes.
139     * 
140     * @param nodes The nodes to recycle.
141     */
142    public static void recycleNodes(AccessibilityNodeInfo... nodes) {
143        if (nodes == null) {
144            return;
145        }
146
147        for (AccessibilityNodeInfo node : nodes) {
148            if (node != null) {
149                node.recycle();
150            }
151        }
152    }
153
154    /**
155     * Adds all descendant nodes of the given {@link AccessibilityNodeInfo} in
156     * breadth first order.
157     * 
158     * @param root {@link AccessibilityNodeInfo} for which to add descendants.
159     * @param outDescendants The list to which to add descendants.
160     * @param filter Optional filter for selecting sub-set of nodes, null for
161     *            all.
162     * @return The number of nodes that failed to match the filter.
163     */
164    public static int addDescendantsBfs(AccessibilityNodeInfo root,
165            ArrayList<AccessibilityNodeInfo> outDescendants, NodeFilter filter) {
166        final int oldOutDescendantsSize = outDescendants.size();
167
168        int failedFilter = addChildren(root, outDescendants, filter);
169
170        final int newOutDescendantsSize = outDescendants.size();
171
172        for (int i = oldOutDescendantsSize; i < newOutDescendantsSize; i++) {
173            final AccessibilityNodeInfo child = outDescendants.get(i);
174
175            failedFilter += addDescendantsBfs(child, outDescendants, filter);
176        }
177
178        return failedFilter;
179    }
180
181    /**
182     * Adds only the children of the given {@link AccessibilityNodeInfo}.
183     * 
184     * @param node {@link AccessibilityNodeInfo} for which to add children.
185     * @param outChildren The list to which to add the children.
186     * @param filter Optional filter for selecting sub-set of nodes, null for
187     *            all.
188     * @return The number of nodes that failed to match the filter.
189     */
190    private static int addChildren(AccessibilityNodeInfo node,
191            ArrayList<AccessibilityNodeInfo> outChildren, NodeFilter filter) {
192        final int childCount = node.getChildCount();
193
194        int failedFilter = 0;
195
196        for (int i = 0; i < childCount; i++) {
197            final AccessibilityNodeInfo child = node.getChild(i);
198
199            if (child == null) {
200                continue;
201            }
202
203            if (filter == null || filter.accept(child)) {
204                outChildren.add(child);
205            } else {
206                child.recycle();
207                failedFilter++;
208            }
209        }
210
211        return failedFilter;
212    }
213}