Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8c1d15d4 authored by Ben Lin's avatar Ben Lin Committed by Android (Google) Code Review
Browse files

Merge "Communicate selection state to a11y services." into arc-apps

parents ac1554dd a355d190
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -32,7 +32,7 @@ import com.android.documentsui.NavigationViewManager.Breadcrumb;
import com.android.documentsui.NavigationViewManager.Environment;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.dirlist.AccessibilityClickEventRouter;
import com.android.documentsui.dirlist.AccessibilityEventRouter;

import java.util.function.Consumer;
import java.util.function.IntConsumer;
@@ -76,7 +76,7 @@ public final class HorizontalBreadcrumb extends RecyclerView
        // accessibility delegate to route click events correctly. See AccessibilityClickEventRouter
        // for more details on how we are routing these a11y events.
        setAccessibilityDelegateCompat(
                new AccessibilityClickEventRouter(this,
                new AccessibilityEventRouter(this,
                        (View child) -> onAccessibilityClick(child)));

        setLayoutManager(mLayoutManager);
+13 −8
Original line number Diff line number Diff line
@@ -28,20 +28,24 @@ import java.util.function.Function;

/**
 * Custom Accessibility Delegate for RecyclerViews to route click events on its child views to
 * proper handlers.
 *
 * The majority of event handling is done using TouchDetector instead of View.OnCLickListener,
 * which most a11y services use to understand whether a particular view is clickable or not.
 * Thus, we need to use a custom accessibility delegate to manually add ACTION_CLICK to clickable child
 * views' accessibility node, and then correctly route these clicks done by a11y services to responsible
 * proper handlers, and to surface selection state to a11y events.
 * <p>
 * The majority of event handling isdone using TouchDetector instead of View.OnCLickListener, which
 * most a11y services use to understand whether a particular view is clickable or not. Thus, we need
 * to use a custom accessibility delegate to manually add ACTION_CLICK to clickable child views'
 * accessibility node, and then correctly route these clicks done by a11y services to responsible
 * click callbacks.
 * <p>
 * DocumentsUI uses {@link View#setActivated(boolean)} instead of {@link View#setSelected(boolean)}
 * for marking a view as selected. We will surface that selection state to a11y services in this
 * class.
 */
public class AccessibilityClickEventRouter extends RecyclerViewAccessibilityDelegate {
public class AccessibilityEventRouter extends RecyclerViewAccessibilityDelegate {

    private final ItemDelegate mItemDelegate;
    private final Function<View, Boolean> mClickCallback;

    public AccessibilityClickEventRouter(
    public AccessibilityEventRouter(
            RecyclerView recyclerView, Function<View, Boolean> clickCallback) {
        super(recyclerView);
        mClickCallback = clickCallback;
@@ -51,6 +55,7 @@ public class AccessibilityClickEventRouter extends RecyclerViewAccessibilityDele
                    AccessibilityNodeInfoCompat info) {
                super.onInitializeAccessibilityNodeInfo(host, info);
                info.addAction(AccessibilityActionCompat.ACTION_CLICK);
                info.setSelected(host.isActivated());
            }

            @Override
+1 −1
Original line number Diff line number Diff line
@@ -315,7 +315,7 @@ public class DirectoryFragment extends Fragment
        mActions = mInjector.getActionHandler(mModel);

        mRecView.setAccessibilityDelegateCompat(
                new AccessibilityClickEventRouter(mRecView,
                new AccessibilityEventRouter(mRecView,
                        (View child) -> onAccessibilityClick(child)));
        mSelectionMetadata = new SelectionMetadata(mModel::getItem);
        mSelectionMgr.addItemCallback(mSelectionMetadata);
+7 −0
Original line number Diff line number Diff line
@@ -44,6 +44,13 @@ public final class Views {
        return view;
    }

    public static View createTestView(boolean activated) {
        View view = createTestView();
        Mockito.when(view.isActivated()).thenReturn(activated);

        return view;
    }

    public static void setBackground(View testView, Drawable background) {
        Mockito.when(testView.getBackground()).thenReturn(background);
    }
+63 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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.documentsui.dirlist;

import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;

import com.android.documentsui.testing.TestRecyclerView;
import com.android.documentsui.testing.Views;

import java.util.List;

@SmallTest
public class AccessibilityTest extends AndroidTestCase {

    private static final List<String> ITEMS = TestData.create(10);

    private TestRecyclerView mView;
    private AccessibilityEventRouter mAccessibilityDelegate;
    private boolean mCallbackCalled = false;

    @Override
    public void setUp() throws Exception {
        mView = TestRecyclerView.create(ITEMS);
        mAccessibilityDelegate = new AccessibilityEventRouter(mView, (View v) -> {
            mCallbackCalled = true;
            return true;
        });
        mView.setAccessibilityDelegateCompat(mAccessibilityDelegate);
    }

    public void test_announceSelected() throws Exception {
        View item = Views.createTestView(true);
        AccessibilityNodeInfoCompat info = new AccessibilityNodeInfoCompat(AccessibilityNodeInfo.obtain());
        mAccessibilityDelegate.getItemDelegate().onInitializeAccessibilityNodeInfo(item, info);
        assertTrue(info.isSelected());
    }

    public void test_routesAccessibilityClicks() throws Exception {
        View item = Views.createTestView(true);
        AccessibilityNodeInfoCompat info = new AccessibilityNodeInfoCompat(AccessibilityNodeInfo.obtain());
        mAccessibilityDelegate.getItemDelegate().onInitializeAccessibilityNodeInfo(item, info);
        mAccessibilityDelegate.getItemDelegate().performAccessibilityAction(item, AccessibilityNodeInfoCompat.ACTION_CLICK, null);
        assertTrue(mCallbackCalled);
    }
}