summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author TreeHugger Robot <treehugger-gerrit@google.com> 2019-08-16 08:13:39 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2019-08-16 08:13:39 +0000
commite6771c2cb8bc8eb4e6cdf596e6f67a4a84d3f20b (patch)
treea8f2e70c6fb19f4f5dc6c59130ae46518eea69c0
parentf4e96eb8197b172b1b06909e8cf9cf8075fbcbe7 (diff)
parentf6bf0054cf471616bcf2dc715cfeeca411686a70 (diff)
Merge "Fix broken a11y function"
-rw-r--r--res/layout/fixed_layout.xml2
-rw-r--r--res/layout/navigation_breadcrumb_item.xml2
-rw-r--r--src/com/android/documentsui/HorizontalBreadcrumb.java21
-rw-r--r--src/com/android/documentsui/dirlist/AccessibilityEventRouter.java66
-rw-r--r--src/com/android/documentsui/dirlist/DirectoryFragment.java8
-rw-r--r--tests/unit/com/android/documentsui/dirlist/AccessibilityTest.java69
6 files changed, 109 insertions, 59 deletions
diff --git a/res/layout/fixed_layout.xml b/res/layout/fixed_layout.xml
index 7f7547fd4..4116df614 100644
--- a/res/layout/fixed_layout.xml
+++ b/res/layout/fixed_layout.xml
@@ -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>
diff --git a/res/layout/navigation_breadcrumb_item.xml b/res/layout/navigation_breadcrumb_item.xml
index 1dd6d921f..ef7977238 100644
--- a/res/layout/navigation_breadcrumb_item.xml
+++ b/res/layout/navigation_breadcrumb_item.xml
@@ -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
diff --git a/src/com/android/documentsui/HorizontalBreadcrumb.java b/src/com/android/documentsui/HorizontalBreadcrumb.java
index 6a7cce1ff..91480dbb5 100644
--- a/src/com/android/documentsui/HorizontalBreadcrumb.java
+++ b/src/com/android/documentsui/HorizontalBreadcrumb.java
@@ -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) {
diff --git a/src/com/android/documentsui/dirlist/AccessibilityEventRouter.java b/src/com/android/documentsui/dirlist/AccessibilityEventRouter.java
index e12bca1db..51a55b417 100644
--- a/src/com/android/documentsui/dirlist/AccessibilityEventRouter.java
+++ b/src/com/android/documentsui/dirlist/AccessibilityEventRouter.java
@@ -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 {
- 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) {
- return mClickCallback.apply(host);
- } else if (action == AccessibilityNodeInfoCompat.ACTION_LONG_CLICK
- && mLongClickCallback != null) {
- return mLongClickCallback.apply(host);
- }
- return super.performAccessibilityAction(host, action, args);
- }
- };
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ addAction(info);
+ info.setSelected(host.isActivated());
}
@Override
- public AccessibilityDelegateCompat getItemDelegate() {
- return mItemDelegate;
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ // We are only handling click events; route all other to default implementation
+ if (action == AccessibilityNodeInfo.ACTION_CLICK) {
+ return mClickCallback.apply(host);
+ } else if (action == AccessibilityNodeInfo.ACTION_LONG_CLICK
+ && mLongClickCallback != null) {
+ return mLongClickCallback.apply(host);
+ }
+ return super.performAccessibilityAction(host, action, args);
}
- 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
diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 1ff364701..18d618c79 100644
--- a/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -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
diff --git a/tests/unit/com/android/documentsui/dirlist/AccessibilityTest.java b/tests/unit/com/android/documentsui/dirlist/AccessibilityTest.java
new file mode 100644
index 000000000..99d4751a6
--- /dev/null
+++ b/tests/unit/com/android/documentsui/dirlist/AccessibilityTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2019 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.test.AndroidTestCase;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.documentsui.testing.Views;
+
+@SmallTest
+public class AccessibilityTest extends AndroidTestCase {
+
+ private AccessibilityEventRouter mAccessibilityDelegate;
+ private boolean mClickCallbackCalled = false;
+ private boolean mLongClickCallbackCalled = false;
+
+ @Override
+ public void setUp() throws Exception {
+ mAccessibilityDelegate = new AccessibilityEventRouter((View v) -> {
+ mClickCallbackCalled = true;
+ return true;
+ }, (View v) -> {
+ mLongClickCallbackCalled = true;
+ return true;
+ });
+ }
+
+ public void test_announceSelected() throws Exception {
+ View item = Views.createTestView(true);
+ AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+ mAccessibilityDelegate.onInitializeAccessibilityNodeInfo(item, info);
+ assertTrue(info.isSelected());
+ }
+
+ public void test_routesAccessibilityClicks() throws Exception {
+ View item = Views.createTestView(true);
+ AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+ mAccessibilityDelegate.onInitializeAccessibilityNodeInfo(item, info);
+ mAccessibilityDelegate.performAccessibilityAction(
+ item, AccessibilityNodeInfo.ACTION_CLICK, null);
+ assertTrue(mClickCallbackCalled);
+ }
+
+ public void test_routesAccessibilityLongClicks() throws Exception {
+ View item = Views.createTestView(true);
+ AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+ mAccessibilityDelegate.onInitializeAccessibilityNodeInfo(item, info);
+ mAccessibilityDelegate.performAccessibilityAction(
+ item, AccessibilityNodeInfo.ACTION_LONG_CLICK, null);
+ assertTrue(mLongClickCallbackCalled);
+ }
+}