1 /*
2  * Copyright (C) 2022 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.intentresolver;
18 
19 import android.graphics.Rect;
20 import android.view.View;
21 import android.view.ViewGroup;
22 import android.view.accessibility.AccessibilityEvent;
23 
24 import androidx.annotation.NonNull;
25 import androidx.recyclerview.widget.RecyclerView;
26 import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
27 
28 public class ChooserRecyclerViewAccessibilityDelegate extends RecyclerViewAccessibilityDelegate {
29     private final Rect mTempRect = new Rect();
30     private final int[] mConsumed = new int[2];
31 
ChooserRecyclerViewAccessibilityDelegate(RecyclerView recyclerView)32     public ChooserRecyclerViewAccessibilityDelegate(RecyclerView recyclerView) {
33         super(recyclerView);
34     }
35 
36     @Override
onRequestSendAccessibilityEvent( @onNull ViewGroup host, @NonNull View view, @NonNull AccessibilityEvent event)37     public boolean onRequestSendAccessibilityEvent(
38             @NonNull ViewGroup host,
39             @NonNull View view,
40             @NonNull AccessibilityEvent event) {
41         boolean result = super.onRequestSendAccessibilityEvent(host, view, event);
42         if (result && event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
43             ensureViewOnScreenVisibility((RecyclerView) host, view);
44         }
45         return result;
46     }
47 
48     /**
49      * Bring the view that received accessibility focus on the screen.
50      * The method's logic is based on a model where RecyclerView is a child of another scrollable
51      * component (ResolverDrawerLayout) and can be partially scrolled off the screen. In that case,
52      * RecyclerView's children that are positioned fully within RecyclerView bounds but scrolled
53      * out of the screen by the outer component, when selected by the accessibility navigation will
54      * remain off the screen (as neither components detect such specific case).
55      * If the view that receiving accessibility focus is scrolled of the screen, perform the nested
56      * scrolling to make in visible.
57      */
ensureViewOnScreenVisibility(RecyclerView recyclerView, View view)58     private void ensureViewOnScreenVisibility(RecyclerView recyclerView, View view) {
59         View child = recyclerView.findContainingItemView(view);
60         if (child == null) {
61             return;
62         }
63         recyclerView.getBoundsOnScreen(mTempRect, true);
64         int recyclerOnScreenTop = mTempRect.top;
65         int recyclerOnScreenBottom = mTempRect.bottom;
66         child.getBoundsOnScreen(mTempRect);
67         int dy = 0;
68         // if needed, do the page-length scroll instead of just a row-length scroll as
69         // ResolverDrawerLayout snaps to the compact view and the row-length scroll can be snapped
70         // back right away.
71         if (mTempRect.top < recyclerOnScreenTop) {
72             // snap to the bottom
73             dy = mTempRect.bottom - recyclerOnScreenBottom;
74         } else if (mTempRect.bottom > recyclerOnScreenBottom) {
75             // snap to the top
76             dy = mTempRect.top - recyclerOnScreenTop;
77         }
78         nestedVerticalScrollBy(recyclerView, dy);
79     }
80 
nestedVerticalScrollBy(RecyclerView recyclerView, int dy)81     private void nestedVerticalScrollBy(RecyclerView recyclerView, int dy) {
82         if (dy == 0) {
83             return;
84         }
85         recyclerView.startNestedScroll(View.SCROLL_AXIS_VERTICAL);
86         if (recyclerView.dispatchNestedPreScroll(0, dy, mConsumed, null)) {
87             dy -= mConsumed[1];
88         }
89         recyclerView.scrollBy(0, dy);
90         recyclerView.stopNestedScroll();
91     }
92 }
93