com.android.talkback.menurules.RuleSuggestions.java Source code

Java tutorial

Introduction

Here is the source code for com.android.talkback.menurules.RuleSuggestions.java

Source

/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * 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.android.talkback.menurules;

import com.google.android.marvin.talkback.TalkBackService;

import android.content.Context;
import android.support.v4.os.BuildCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;

import com.android.talkback.R;
import com.android.talkback.contextmenu.ContextMenuItem;
import com.android.talkback.contextmenu.ContextMenuItemBuilder;
import com.android.talkback.controller.CursorController;
import com.android.utils.AccessibilityNodeInfoUtils;
import com.android.utils.Role;
import com.android.utils.WindowManager;
import com.android.utils.traversal.TraversalStrategy;
import com.android.utils.traversal.TraversalStrategyUtils;

import java.util.LinkedList;
import java.util.List;

/**
 * Provides menu items for moving to the suggestions window attached to an autocomplete text view,
 * or for moving back to the original autocomplete text view that anchors the suggestions.
 */
public class RuleSuggestions implements NodeMenuRule {

    @Override
    public boolean accept(TalkBackService service, AccessibilityNodeInfoCompat node) {
        if (!BuildCompat.isAtLeastN()) {
            return false;
        }

        // Only accept for "Jump to suggestions" if we're in an edit field.
        if (Role.getRole(node) == Role.ROLE_EDIT_TEXT) {
            WindowManager windowManager = new WindowManager(service.isScreenLayoutRTL());
            windowManager.setWindows(service.getWindows());
            AccessibilityWindowInfo anchoredWindow = windowManager.getAnchoredWindow(node);
            if (anchoredWindow != null) {
                return true;
            }
        }

        // Only accept for "Return to edit field" if the anchor is an edit field.
        AccessibilityNodeInfoCompat anchor = AccessibilityNodeInfoUtils.getAnchor(node);
        if (anchor != null) {
            try {
                if (Role.getRole(anchor) == Role.ROLE_EDIT_TEXT) {
                    return true;
                }
            } finally {
                anchor.recycle();
            }
        }

        return false;
    }

    @Override
    public List<ContextMenuItem> getMenuItemsForNode(TalkBackService service,
            ContextMenuItemBuilder menuItemBuilder, AccessibilityNodeInfoCompat node) {
        final LinkedList<ContextMenuItem> items = new LinkedList<>();

        if (Role.getRole(node) == Role.ROLE_EDIT_TEXT) {
            WindowManager windowManager = new WindowManager(service.isScreenLayoutRTL());
            windowManager.setWindows(service.getWindows());
            AccessibilityWindowInfo anchoredWindow = windowManager.getAnchoredWindow(node);
            if (anchoredWindow != null) {
                final ContextMenuItem viewSuggestions = menuItemBuilder.createMenuItem(service, Menu.NONE,
                        R.id.suggestions_breakout_suggestions, Menu.NONE,
                        service.getString(R.string.title_suggestions_breakout_suggestions));
                viewSuggestions.setOnMenuItemClickListener(
                        new ViewSuggestionsItemClickListener(AccessibilityNodeInfoCompat.obtain(node), service));
                viewSuggestions.setSkipRefocusEvents(true);
                items.add(viewSuggestions);
            }
        }

        AccessibilityNodeInfoCompat anchor = AccessibilityNodeInfoUtils.getAnchor(node);
        if (anchor != null) {
            if (Role.getRole(anchor) == Role.ROLE_EDIT_TEXT) {
                final ContextMenuItem returnToAnchor = menuItemBuilder.createMenuItem(service, Menu.NONE,
                        R.id.suggestions_breakout_anchor, Menu.NONE,
                        service.getString(R.string.title_suggestions_breakout_anchor));
                returnToAnchor.setOnMenuItemClickListener(new ReturnToAnchorItemClickListener(anchor, service));
                returnToAnchor.setSkipRefocusEvents(true);
                items.add(returnToAnchor);
                // Don't recycle anchor here because we gave it to the listener.
            } else {
                anchor.recycle();
            }
        }

        return items;
    }

    @Override
    public CharSequence getUserFriendlyMenuName(Context context) {
        return context.getString(R.string.title_suggestions_controls);
    }

    @Override
    public boolean canCollapseMenu() {
        return true;
    }

    private class ViewSuggestionsItemClickListener implements OnMenuItemClickListener {
        private AccessibilityNodeInfoCompat mAnchor;
        private final TalkBackService mService;

        public ViewSuggestionsItemClickListener(AccessibilityNodeInfoCompat anchor, TalkBackService service) {
            mAnchor = anchor;
            mService = service;
        }

        @Override
        public boolean onMenuItemClick(MenuItem item) {
            if (mAnchor != null) {
                WindowManager windowManager = new WindowManager(mService.isScreenLayoutRTL());
                windowManager.setWindows(mService.getWindows());
                AccessibilityWindowInfo anchoredWindow = windowManager.getAnchoredWindow(mAnchor);
                if (anchoredWindow != null) {
                    AccessibilityNodeInfoCompat firstNode = getFirstNode(anchoredWindow);
                    if (firstNode != null) {
                        CursorController cursorController = mService.getCursorController();
                        cursorController.setCursor(firstNode);
                        firstNode.recycle();
                    }
                }

                mAnchor.recycle();
                mAnchor = null;
            }
            return true;
        }

        private AccessibilityNodeInfoCompat getFirstNode(AccessibilityWindowInfo window) {
            AccessibilityNodeInfo root = window.getRoot();
            if (root != null) {
                AccessibilityNodeInfoCompat compatRoot = new AccessibilityNodeInfoCompat(root);
                TraversalStrategy traversalStrategy = TraversalStrategyUtils.getTraversalStrategy(compatRoot,
                        TraversalStrategy.SEARCH_FOCUS_FORWARD);

                AccessibilityNodeInfoCompat firstNode = AccessibilityNodeInfoUtils.searchFocus(traversalStrategy,
                        compatRoot, TraversalStrategy.SEARCH_FOCUS_FORWARD,
                        AccessibilityNodeInfoUtils.FILTER_SHOULD_FOCUS);

                compatRoot.recycle(); // This will also recycle the underlying node (root).
                return firstNode;
            }

            return null;
        }
    }

    private static class ReturnToAnchorItemClickListener implements OnMenuItemClickListener {
        private AccessibilityNodeInfoCompat mAnchor;
        private final TalkBackService mService;

        public ReturnToAnchorItemClickListener(AccessibilityNodeInfoCompat anchor, TalkBackService service) {
            mAnchor = anchor;
            mService = service;
        }

        @Override
        public boolean onMenuItemClick(MenuItem item) {
            if (mAnchor != null) {
                CursorController cursorController = mService.getCursorController();
                cursorController.setCursor(mAnchor);
                mAnchor.recycle();
                mAnchor = null;
            }

            return true;
        }
    }
}