• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.documentsui.dirlist;
18 
19 import android.os.Bundle;
20 import android.view.View;
21 
22 import androidx.annotation.NonNull;
23 import androidx.annotation.Nullable;
24 import androidx.core.view.AccessibilityDelegateCompat;
25 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
26 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
27 import androidx.recyclerview.widget.RecyclerView;
28 import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
29 
30 import java.util.function.Function;
31 
32 /**
33  * Custom Accessibility Delegate for RecyclerViews to route click events on its child views to
34  * proper handlers, and to surface selection state to a11y events.
35  * <p>
36  * The majority of event handling isdone using TouchDetector instead of View.OnCLickListener, which
37  * most a11y services use to understand whether a particular view is clickable or not. Thus, we need
38  * to use a custom accessibility delegate to manually add ACTION_CLICK to clickable child views'
39  * accessibility node, and then correctly route these clicks done by a11y services to responsible
40  * click callbacks.
41  * <p>
42  * DocumentsUI uses {@link View#setActivated(boolean)} instead of {@link View#setSelected(boolean)}
43  * for marking a view as selected. We will surface that selection state to a11y services in this
44  * class.
45  */
46 public class AccessibilityEventRouter extends RecyclerViewAccessibilityDelegate {
47 
48     private final ItemDelegate mItemDelegate;
49     private final Function<View, Boolean> mClickCallback;
50     private final Function<View, Boolean> mLongClickCallback;
51 
AccessibilityEventRouter( RecyclerView recyclerView, @NonNull Function<View, Boolean> clickCallback, @Nullable Function<View, Boolean> longClickCallback)52     public AccessibilityEventRouter(
53             RecyclerView recyclerView, @NonNull Function<View, Boolean> clickCallback,
54             @Nullable Function<View, Boolean> longClickCallback) {
55         super(recyclerView);
56         mClickCallback = clickCallback;
57         mLongClickCallback = longClickCallback;
58         mItemDelegate = new ItemDelegate(this) {
59             @Override
60             public void onInitializeAccessibilityNodeInfo(View host,
61                     AccessibilityNodeInfoCompat info) {
62                 super.onInitializeAccessibilityNodeInfo(host, info);
63                 final RecyclerView.ViewHolder holder = recyclerView.getChildViewHolder(host);
64                 // if the viewHolder is a DocumentsHolder instance and the ItemDetails
65                 // is null, it can't be clicked
66                 if (holder instanceof DocumentHolder) {
67                     if (((DocumentHolder)holder).getItemDetails() != null) {
68                         addAction(info);
69                     }
70                 } else {
71                     addAction(info);
72                 }
73                 info.setSelected(host.isActivated());
74             }
75 
76             @Override
77             public boolean performAccessibilityAction(View host, int action, Bundle args) {
78                 // We are only handling click events; route all other to default implementation
79                 if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) {
80                     return mClickCallback.apply(host);
81                 } else if (action == AccessibilityNodeInfoCompat.ACTION_LONG_CLICK
82                         && mLongClickCallback != null) {
83                     return mLongClickCallback.apply(host);
84                 }
85                 return super.performAccessibilityAction(host, action, args);
86             }
87         };
88     }
89 
90     @Override
getItemDelegate()91     public AccessibilityDelegateCompat getItemDelegate() {
92         return mItemDelegate;
93     }
94 
addAction(AccessibilityNodeInfoCompat info)95     private void addAction(AccessibilityNodeInfoCompat info) {
96         info.addAction(AccessibilityActionCompat.ACTION_CLICK);
97         if (mLongClickCallback != null) {
98             info.addAction(AccessibilityNodeInfoCompat.ACTION_LONG_CLICK);
99         }
100     }
101 }