com.googlecode.eyesfree.brailleback.FocusFinder.java Source code

Java tutorial

Introduction

Here is the source code for com.googlecode.eyesfree.brailleback.FocusFinder.java

Source

/*
 * Copyright (C) 2012 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.googlecode.eyesfree.brailleback;

import com.googlecode.eyesfree.utils.AccessibilityNodeInfoUtils;
import com.googlecode.eyesfree.utils.LogUtils;
import com.googlecode.eyesfree.utils.NodeFocusFinder;

import android.accessibilityservice.AccessibilityService;
import android.content.Context;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.util.Log;
import android.view.accessibility.AccessibilityNodeInfo;

import java.util.HashSet;

/**
 * Functions to find focus.
 *
 * NOTE: To give a consistent behaviour, this code should be kept in sync
 * with the relevant subset of code in the {@code CursorController}
 * class in TalkBack.
 */
public class FocusFinder {

    public static final int SEARCH_FORWARD = NodeFocusFinder.SEARCH_FORWARD;
    public static final int SEARCH_BACKWARD = NodeFocusFinder.SEARCH_BACKWARD;

    private Context mContext;

    private final HashSet<AccessibilityNodeInfoCompat> mTmpNodeHash = new HashSet<AccessibilityNodeInfoCompat>();

    public FocusFinder(Context context) {
        mContext = context;
    }

    public AccessibilityNodeInfoCompat linear(AccessibilityNodeInfoCompat source, int direction) {
        if (source == null) {
            return null;
        }
        AccessibilityNodeInfoCompat next = NodeFocusFinder.focusSearch(source, direction);

        HashSet<AccessibilityNodeInfoCompat> seenNodes = mTmpNodeHash;
        seenNodes.clear();

        while ((next != null) && !AccessibilityNodeInfoUtils.shouldFocusNode(mContext, next)) {
            if (seenNodes.contains(next)) {
                LogUtils.log(this, Log.ERROR, "Found duplicate node during traversal: %s", next);
                break;
            }

            LogUtils.log(this, Log.VERBOSE, "Search strategy rejected node: %s", next.getInfo());
            seenNodes.add(next);
            next = NodeFocusFinder.focusSearch(next, direction);
        }

        // Clear the list of seen nodes.
        AccessibilityNodeInfoUtils.recycleNodes(seenNodes);

        if (next == null) {
            LogUtils.log(this, Log.VERBOSE, "Failed to find the next node");
        }
        return next;
    }

    public static AccessibilityNodeInfoCompat findFirstFocusableDescendant(AccessibilityNodeInfoCompat root,
            Context context) {
        // null guard and shortcut for leaf nodes.
        if (root == null || root.getChildCount() <= 0) {
            return null;
        }
        HashSet<AccessibilityNodeInfoCompat> seenNodes = new HashSet<AccessibilityNodeInfoCompat>();
        seenNodes.add(root);
        try {
            return findFirstFocusableDescendantInternal(root, context, seenNodes);
        } finally {
            seenNodes.remove(root); // Not owned by us.
            AccessibilityNodeInfoUtils.recycleNodes(seenNodes);
        }
    }

    private static AccessibilityNodeInfoCompat findFirstFocusableDescendantInternal(
            AccessibilityNodeInfoCompat root, Context context, HashSet<AccessibilityNodeInfoCompat> seenNodes) {
        for (int i = 0, end = root.getChildCount(); i < end; ++i) {
            AccessibilityNodeInfoCompat child = root.getChild(i);
            if (child == null) {
                continue;
            }
            if (AccessibilityNodeInfoUtils.shouldFocusNode(context, child)) {
                return child;
            }
            if (!seenNodes.add(child)) {
                LogUtils.log(FocusFinder.class, Log.ERROR, "Cycle in node tree");
                child.recycle();
                return null;
            }
            AccessibilityNodeInfoCompat n = findFirstFocusableDescendantInternal(child, context, seenNodes);
            if (n != null) {
                return n;
            }
        }
        return null;
    }

    public static AccessibilityNodeInfoCompat findLastFocusableDescendant(AccessibilityNodeInfoCompat root,
            Context context) {
        // null guard and shortcut for leaf nodes.
        if (root == null || root.getChildCount() <= 0) {
            return null;
        }
        HashSet<AccessibilityNodeInfoCompat> seenNodes = new HashSet<AccessibilityNodeInfoCompat>();
        seenNodes.add(root);
        try {
            return findLastFocusableDescendantInternal(root, context, seenNodes);
        } finally {
            seenNodes.remove(root); // Not owned by us.
            AccessibilityNodeInfoUtils.recycleNodes(seenNodes);
        }
    }

    private static AccessibilityNodeInfoCompat findLastFocusableDescendantInternal(AccessibilityNodeInfoCompat root,
            Context context, HashSet<AccessibilityNodeInfoCompat> seenNodes) {
        for (int end = root.getChildCount(), i = end - 1; i >= 0; --i) {
            AccessibilityNodeInfoCompat child = root.getChild(i);
            if (child == null) {
                continue;
            }

            AccessibilityNodeInfoCompat n = findLastFocusableDescendantInternal(child, context, seenNodes);
            if (n != null) {
                return n;
            }

            if (AccessibilityNodeInfoUtils.shouldFocusNode(context, child)) {
                return child;
            }
            if (!seenNodes.add(child)) {
                LogUtils.log(FocusFinder.class, Log.ERROR, "Cycle in node tree");
                child.recycle();
                return null;
            }
        }
        return null;
    }

    public static AccessibilityNodeInfoCompat getFocusedNode(AccessibilityService service, boolean fallbackOnRoot) {
        AccessibilityNodeInfo root = service.getRootInActiveWindow();
        AccessibilityNodeInfo focused = null;
        try {
            AccessibilityNodeInfo ret = null;
            if (root != null) {
                focused = root.findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY);
                if (focused != null && focused.isVisibleToUser()) {
                    ret = focused;
                    focused = null;
                } else if (fallbackOnRoot) {
                    ret = root;
                    root = null;
                }
            } else {
                LogUtils.log(service, Log.ERROR, "No current window root");
            }
            if (ret != null) {
                return new AccessibilityNodeInfoCompat(ret);
            }
        } finally {
            if (root != null) {
                root.recycle();
            }
            if (focused != null) {
                focused.recycle();
            }
        }
        return null;
    }
}