|
25 | 25 | import android.graphics.Rect; |
26 | 26 | import android.graphics.drawable.Drawable; |
27 | 27 | import android.graphics.drawable.TransitionDrawable; |
| 28 | +import android.os.Bundle; |
28 | 29 | import android.os.Debug; |
29 | 30 | import android.os.Handler; |
30 | 31 | import android.os.Parcel; |
|
57 | 58 | import android.view.ViewParent; |
58 | 59 | import android.view.ViewTreeObserver; |
59 | 60 | import android.view.accessibility.AccessibilityEvent; |
| 61 | +import android.view.accessibility.AccessibilityManager; |
60 | 62 | import android.view.accessibility.AccessibilityNodeInfo; |
61 | 63 | import android.view.animation.Interpolator; |
62 | 64 | import android.view.animation.LinearInterpolator; |
@@ -648,6 +650,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te |
648 | 650 | private int mGlowPaddingLeft; |
649 | 651 | private int mGlowPaddingRight; |
650 | 652 |
|
| 653 | + /** |
| 654 | + * Used for interacting with list items from an accessibility service. |
| 655 | + */ |
| 656 | + private ListItemAccessibilityDelegate mAccessibilityDelegate; |
| 657 | + |
651 | 658 | private int mLastAccessibilityScrollEventFromIndex; |
652 | 659 | private int mLastAccessibilityScrollEventToIndex; |
653 | 660 |
|
@@ -2121,9 +2128,77 @@ View obtainView(int position, boolean[] isScrap) { |
2121 | 2128 | child.setLayoutParams(lp); |
2122 | 2129 | } |
2123 | 2130 |
|
| 2131 | + if (AccessibilityManager.getInstance(mContext).isEnabled()) { |
| 2132 | + if (mAccessibilityDelegate == null) { |
| 2133 | + mAccessibilityDelegate = new ListItemAccessibilityDelegate(); |
| 2134 | + } |
| 2135 | + child.setAccessibilityDelegate(mAccessibilityDelegate); |
| 2136 | + } |
| 2137 | + |
2124 | 2138 | return child; |
2125 | 2139 | } |
2126 | 2140 |
|
| 2141 | + class ListItemAccessibilityDelegate extends AccessibilityDelegate { |
| 2142 | + @Override |
| 2143 | + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { |
| 2144 | + super.onInitializeAccessibilityNodeInfo(host, info); |
| 2145 | + |
| 2146 | + final int position = getPositionForView(host); |
| 2147 | + |
| 2148 | + if (position == INVALID_POSITION) { |
| 2149 | + return; |
| 2150 | + } |
| 2151 | + |
| 2152 | + if (isClickable()) { |
| 2153 | + info.addAction(AccessibilityNodeInfo.ACTION_CLICK); |
| 2154 | + info.setClickable(true); |
| 2155 | + } |
| 2156 | + |
| 2157 | + if (isLongClickable()) { |
| 2158 | + info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK); |
| 2159 | + info.setLongClickable(true); |
| 2160 | + } |
| 2161 | + |
| 2162 | + info.addAction(AccessibilityNodeInfo.ACTION_SELECT); |
| 2163 | + |
| 2164 | + if (position == getSelectedItemPosition()) { |
| 2165 | + info.setSelected(true); |
| 2166 | + } |
| 2167 | + } |
| 2168 | + |
| 2169 | + @Override |
| 2170 | + public boolean performAccessibilityAction(View host, int action, Bundle arguments) { |
| 2171 | + final int position = getPositionForView(host); |
| 2172 | + |
| 2173 | + if (position == INVALID_POSITION) { |
| 2174 | + return false; |
| 2175 | + } |
| 2176 | + |
| 2177 | + final long id = getItemIdAtPosition(position); |
| 2178 | + |
| 2179 | + switch (action) { |
| 2180 | + case AccessibilityNodeInfo.ACTION_SELECT: |
| 2181 | + setSelection(position); |
| 2182 | + return true; |
| 2183 | + case AccessibilityNodeInfo.ACTION_CLICK: |
| 2184 | + if (!super.performAccessibilityAction(host, action, arguments)) { |
| 2185 | + return performItemClick(host, position, id); |
| 2186 | + } |
| 2187 | + return true; |
| 2188 | + case AccessibilityNodeInfo.ACTION_LONG_CLICK: |
| 2189 | + if (!super.performAccessibilityAction(host, action, arguments)) { |
| 2190 | + return performLongPress(host, position, id); |
| 2191 | + } |
| 2192 | + return true; |
| 2193 | + case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: |
| 2194 | + smoothScrollToPosition(position); |
| 2195 | + break; |
| 2196 | + } |
| 2197 | + |
| 2198 | + return super.performAccessibilityAction(host, action, arguments); |
| 2199 | + } |
| 2200 | + } |
| 2201 | + |
2127 | 2202 | void positionSelector(int position, View sel) { |
2128 | 2203 | if (position != INVALID_POSITION) { |
2129 | 2204 | mSelectorPosition = position; |
@@ -5651,6 +5726,7 @@ public void reclaimViews(List<View> views) { |
5651 | 5726 | // Don't reclaim header or footer views, or views that should be ignored |
5652 | 5727 | if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) { |
5653 | 5728 | views.add(child); |
| 5729 | + child.setAccessibilityDelegate(null); |
5654 | 5730 | if (listener != null) { |
5655 | 5731 | // Pretend they went through the scrap heap |
5656 | 5732 | listener.onMovedToScrapHeap(child); |
@@ -6206,6 +6282,7 @@ void addScrapView(View scrap, int position) { |
6206 | 6282 | mScrapViews[viewType].add(scrap); |
6207 | 6283 | } |
6208 | 6284 |
|
| 6285 | + scrap.setAccessibilityDelegate(null); |
6209 | 6286 | if (mRecyclerListener != null) { |
6210 | 6287 | mRecyclerListener.onMovedToScrapHeap(scrap); |
6211 | 6288 | } |
@@ -6267,6 +6344,7 @@ void scrapActiveViews() { |
6267 | 6344 | lp.scrappedFromPosition = mFirstActivePosition + i; |
6268 | 6345 | scrapViews.add(victim); |
6269 | 6346 |
|
| 6347 | + victim.setAccessibilityDelegate(null); |
6270 | 6348 | if (hasListener) { |
6271 | 6349 | mRecyclerListener.onMovedToScrapHeap(victim); |
6272 | 6350 | } |
|
0 commit comments