Loading res/layout/fixed_layout.xml +1 −1 Original line number Diff line number Diff line Loading @@ -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> Loading res/layout/navigation_breadcrumb_item.xml +1 −1 Original line number Diff line number Diff line Loading @@ -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 Loading src/com/android/documentsui/HorizontalBreadcrumb.java +12 −9 Original line number Diff line number Diff line Loading @@ -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)); Loading Loading @@ -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(); } Loading Loading @@ -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) { Loading src/com/android/documentsui/dirlist/AccessibilityEventRouter.java +22 −44 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 src/com/android/documentsui/dirlist/DirectoryFragment.java +4 −4 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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 Loading
res/layout/fixed_layout.xml +1 −1 Original line number Diff line number Diff line Loading @@ -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> Loading
res/layout/navigation_breadcrumb_item.xml +1 −1 Original line number Diff line number Diff line Loading @@ -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 Loading
src/com/android/documentsui/HorizontalBreadcrumb.java +12 −9 Original line number Diff line number Diff line Loading @@ -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)); Loading Loading @@ -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(); } Loading Loading @@ -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) { Loading
src/com/android/documentsui/dirlist/AccessibilityEventRouter.java +22 −44 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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
src/com/android/documentsui/dirlist/DirectoryFragment.java +4 −4 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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