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

Commit f6bf0054 authored by Tony Huang's avatar Tony Huang
Browse files

Fix broken a11y function

Due some a11y delegate change on Androidx RecyclerView, some a11y
founctions on docsui are broken.
Remove using RecyclerViewAccessibityDelegate and set a11y delegate
on each ViewHolder on RecyclerView.

Fine tune HorizontalBreadcrumb layout of text also during checking
function normally.

Fix: 139101197
Test: atest DocumentsUIGoogle
Change-Id: I22345a117849167093fd21d5603de3161ab2219a
parent 04b3ea89
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -57,7 +57,7 @@
                android:id="@+id/horizontal_breadcrumb"
                android:layout_marginRight="20dp"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />
                android:layout_height="wrap_content" />

        </androidx.appcompat.widget.Toolbar>

+1 −1
Original line number Diff line number Diff line
@@ -40,7 +40,7 @@
        android:layout_height="match_parent"
        android:gravity="center_vertical"
        android:duplicateParentState="true"
        android:textAppearance="@android:style/TextAppearance.Material.Widget.ActionBar.Title"
        android:textAppearance="@style/ToolbarTitle"
        android:background="@drawable/breadcrumb_item_background" />

    <ImageView
+12 −9
Original line number Diff line number Diff line
@@ -71,14 +71,8 @@ public final class HorizontalBreadcrumb extends RecyclerView
        mLayoutManager = new LinearLayoutManager(
                getContext(), LinearLayoutManager.HORIZONTAL, false);
        mAdapter = new BreadcrumbAdapter(
                state, env, new ItemDragListener<>(this), this::onKey);
        // Since we are using GestureDetector to detect click events, a11y services don't know which views
        // are clickable because we aren't using View.OnClickListener. Thus, we need to use a custom
        // accessibility delegate to route click events correctly. See AccessibilityClickEventRouter
        // for more details on how we are routing these a11y events.
        setAccessibilityDelegateCompat(
                new AccessibilityEventRouter(this,
                        (View child) -> onAccessibilityClick(child), null));
                state, env, new ItemDragListener<>(this), this::onKey,
                new AccessibilityEventRouter(this::onAccessibilityClick, null));

        setLayoutManager(mLayoutManager);
        addOnItemTouchListener(new ClickListener(getContext(), this::onSingleTapUp));
@@ -192,17 +186,20 @@ public final class HorizontalBreadcrumb extends RecyclerView
        private final com.android.documentsui.base.State mState;
        private final OnDragListener mDragListener;
        private final View.OnKeyListener mClickListener;
        private final View.AccessibilityDelegate mAccessibilityDelegate;
        // We keep the old item size so the breadcrumb will only re-render views that are necessary
        private int mLastItemSize;

        public BreadcrumbAdapter(com.android.documentsui.base.State state,
                Environment env,
                OnDragListener dragListener,
                View.OnKeyListener clickListener) {
                View.OnKeyListener clickListener,
                View.AccessibilityDelegate accessibilityDelegate) {
            mState = state;
            mEnv = env;
            mDragListener = dragListener;
            mClickListener = clickListener;
            mAccessibilityDelegate = accessibilityDelegate;
            mLastItemSize = mState.stack.size();
        }

@@ -235,6 +232,12 @@ public final class HorizontalBreadcrumb extends RecyclerView
            }
            holder.itemView.setOnDragListener(mDragListener);
            holder.itemView.setOnKeyListener(mClickListener);
            // Since we are using GestureDetector to detect click events, a11y services don't know
            // which views are clickable because we aren't using View.OnClickListener. Thus, we
            // need to use a custom accessibility delegate to route click events correctly.
            // See AccessibilityClickEventRouter for more details on how we are routing these
            // a11y events.
            holder.itemView.setAccessibilityDelegate(mAccessibilityDelegate);
        }

        private DocumentInfo getItem(int position) {
+22 −44
Original line number Diff line number Diff line
@@ -18,19 +18,15 @@ package com.android.documentsui.dirlist;

import android.os.Bundle;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.AccessibilityDelegateCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;

import java.util.function.Function;

/**
 * Custom Accessibility Delegate for RecyclerViews to route click events on its child views to
 * Custom Accessibility Delegate for item views in RecyclerViews to route click events to
 * proper handlers, and to surface selection state to a11y events.
 * <p>
 * The majority of event handling isdone using TouchDetector instead of View.OnCLickListener, which
@@ -43,59 +39,41 @@ import java.util.function.Function;
 * for marking a view as selected. We will surface that selection state to a11y services in this
 * class.
 */
public class AccessibilityEventRouter extends RecyclerViewAccessibilityDelegate {
public class AccessibilityEventRouter extends View.AccessibilityDelegate {

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

    public AccessibilityEventRouter(
            RecyclerView recyclerView, @NonNull Function<View, Boolean> clickCallback,
            @NonNull Function<View, Boolean> clickCallback,
            @Nullable Function<View, Boolean> longClickCallback) {
        super(recyclerView);
        super();
        mClickCallback = clickCallback;
        mLongClickCallback = longClickCallback;
        mItemDelegate = new ItemDelegate(this) {
            @Override
            public void onInitializeAccessibilityNodeInfo(View host,
                    AccessibilityNodeInfoCompat info) {
                super.onInitializeAccessibilityNodeInfo(host, info);
                final RecyclerView.ViewHolder holder = recyclerView.getChildViewHolder(host);
                // if the viewHolder is a DocumentsHolder instance and the ItemDetails
                // is null, it can't be clicked
                if (holder instanceof DocumentHolder) {
                    if (((DocumentHolder)holder).getItemDetails() != null) {
                        addAction(info);
    }
                } else {

    @Override
    public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
        addAction(info);
                }
        info.setSelected(host.isActivated());
    }

    @Override
    public boolean performAccessibilityAction(View host, int action, Bundle args) {
        // We are only handling click events; route all other to default implementation
                if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) {
        if (action == AccessibilityNodeInfo.ACTION_CLICK) {
            return mClickCallback.apply(host);
                } else if (action == AccessibilityNodeInfoCompat.ACTION_LONG_CLICK
        } else if (action == AccessibilityNodeInfo.ACTION_LONG_CLICK
                && mLongClickCallback != null) {
            return mLongClickCallback.apply(host);
        }
        return super.performAccessibilityAction(host, action, args);
    }
        };
    }

    @Override
    public AccessibilityDelegateCompat getItemDelegate() {
        return mItemDelegate;
    }

    private void addAction(AccessibilityNodeInfoCompat info) {
        info.addAction(AccessibilityActionCompat.ACTION_CLICK);
    private void addAction(AccessibilityNodeInfo info) {
        info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
        if (mLongClickCallback != null) {
            info.addAction(AccessibilityNodeInfoCompat.ACTION_LONG_CLICK);
            info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
        }
    }
}
 No newline at end of file
+4 −4
Original line number Diff line number Diff line
@@ -325,10 +325,6 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On
        mFocusManager = mInjector.getFocusManager(mRecView, mModel);
        mActions = mInjector.getActionHandler(mContentLock);

        mRecView.setAccessibilityDelegateCompat(
                new AccessibilityEventRouter(mRecView,
                        (View child) -> onAccessibilityClick(child),
                        (View child) -> onAccessibilityLongClick(child)));
        mSelectionMetadata = new SelectionMetadata(mModel::getItem);
        mDetailsLookup = new DocsItemDetailsLookup(mRecView);

@@ -1270,6 +1266,10 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On
        @Override
        public void onBindDocumentHolder(DocumentHolder holder, Cursor cursor) {
            setupDragAndDropOnDocumentView(holder.itemView, cursor);
            holder.itemView.setAccessibilityDelegate(new AccessibilityEventRouter(
                    DirectoryFragment.this::onAccessibilityClick,
                    DirectoryFragment.this::onAccessibilityLongClick
            ));
        }

        @Override
Loading