1 /*
2  * Copyright (C) 2020 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.internal.view;
18 
19 import android.annotation.Nullable;
20 import android.graphics.Point;
21 import android.graphics.Rect;
22 import android.view.ScrollCaptureCallback;
23 import android.view.View;
24 import android.view.ViewGroup;
25 
26 /**
27  * Provides built-in framework level Scroll Capture support for standard scrolling Views.
28  */
29 public class ScrollCaptureInternal {
30     private static final String TAG = "ScrollCaptureInternal";
31 
32     private static final int UP = -1;
33     private static final int DOWN = 1;
34 
35     /**
36      * Not a ViewGroup, or cannot scroll according to View APIs.
37      */
38     public static final int TYPE_FIXED = 0;
39 
40     /**
41      * Slides a single child view using mScrollX/mScrollY.
42      */
43     public static final int TYPE_SCROLLING = 1;
44 
45     /**
46      * Slides child views through the viewport by translating their layout positions with {@link
47      * View#offsetTopAndBottom(int)}. Manages Child view lifecycle, creating as needed and
48      * binding views to data from an adapter. Views are reused whenever possible.
49      */
50     public static final int TYPE_RECYCLING = 2;
51 
52     /**
53      * Performs tests on the given View and determines:
54      * 1. If scrolling is possible
55      * 2. What mechanisms are used for scrolling.
56      * <p>
57      * This needs to be fast and not alloc memory. It's called on everything in the tree not marked
58      * as excluded during scroll capture search.
59      */
detectScrollingType(View view)60     public static int detectScrollingType(View view) {
61         // Must be a ViewGroup
62         if (!(view instanceof ViewGroup)) {
63             return TYPE_FIXED;
64         }
65         // Confirm that it can scroll.
66         if (!(view.canScrollVertically(DOWN) || view.canScrollVertically(UP))) {
67             // Nothing to scroll here, move along.
68             return TYPE_FIXED;
69         }
70         // ScrollViews accept only a single child.
71         if (((ViewGroup) view).getChildCount() > 1) {
72             return TYPE_RECYCLING;
73         }
74         //Because recycling containers don't use scrollY, a non-zero value means Scroll view.
75         if (view.getScrollY() != 0) {
76             return TYPE_SCROLLING;
77         }
78         // Since scrollY cannot be negative, this means a Recycling view.
79         if (view.canScrollVertically(UP)) {
80             return TYPE_RECYCLING;
81         }
82         // canScrollVertically(UP) == false, getScrollY() == 0, getChildCount() == 1.
83 
84         // For Recycling containers, this should be a no-op (RecyclerView logs a warning)
85         view.scrollTo(view.getScrollX(), 1);
86 
87         // A scrolling container would have moved by 1px.
88         if (view.getScrollY() == 1) {
89             view.scrollTo(view.getScrollX(), 0);
90             return TYPE_SCROLLING;
91         }
92         return TYPE_RECYCLING;
93     }
94 
95     /**
96      * Creates a scroll capture callback for the given view if possible.
97      *
98      * @param view             the view to capture
99      * @param localVisibleRect the visible area of the given view in local coordinates, as supplied
100      *                         by the view parent
101      * @param positionInWindow the offset of localVisibleRect within the window
102      *
103      * @return a new callback or null if the View isn't supported
104      */
105     @Nullable
requestCallback(View view, Rect localVisibleRect, Point positionInWindow)106     public ScrollCaptureCallback requestCallback(View view, Rect localVisibleRect,
107             Point positionInWindow) {
108         // Nothing to see here yet.
109         int i = detectScrollingType(view);
110         switch (i) {
111             case TYPE_SCROLLING:
112                 return new ScrollCaptureViewSupport<>((ViewGroup) view,
113                         new ScrollViewCaptureHelper());
114         }
115         return null;
116     }
117 }
118