1 /*
2  * Copyright (C) 2021 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 android.view;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.graphics.Rect;
22 
23 import com.android.internal.util.Preconditions;
24 
25 import java.lang.ref.WeakReference;
26 import java.util.ArrayList;
27 import java.util.Collections;
28 import java.util.Iterator;
29 import java.util.List;
30 import java.util.function.Function;
31 
32 /**
33  * Abstract class to track a collection of rects reported by the views under the same
34  * {@link ViewRootImpl}.
35  */
36 class ViewRootRectTracker {
37     private final Function<View, List<Rect>> mRectCollector;
38     private boolean mViewsChanged = false;
39     private boolean mRootRectsChanged = false;
40     private List<Rect> mRootRects = Collections.emptyList();
41     private List<ViewInfo> mViewInfos = new ArrayList<>();
42     private List<Rect> mRects = Collections.emptyList();
43 
44     /**
45      * @param rectCollector given a view returns a list of the rects of interest for this
46      *                      ViewRootRectTracker
47      */
ViewRootRectTracker(Function<View, List<Rect>> rectCollector)48     ViewRootRectTracker(Function<View, List<Rect>> rectCollector) {
49         mRectCollector = rectCollector;
50     }
51 
updateRectsForView(@onNull View view)52     public void updateRectsForView(@NonNull View view) {
53         boolean found = false;
54         final Iterator<ViewInfo> i = mViewInfos.iterator();
55         while (i.hasNext()) {
56             final ViewInfo info = i.next();
57             final View v = info.getView();
58             if (v == null || !v.isAttachedToWindow() || !v.isAggregatedVisible()) {
59                 mViewsChanged = true;
60                 i.remove();
61                 continue;
62             }
63             if (v == view) {
64                 found = true;
65                 info.mDirty = true;
66                 break;
67             }
68         }
69         if (!found && view.isAttachedToWindow()) {
70             mViewInfos.add(new ViewInfo(view));
71             mViewsChanged = true;
72         }
73     }
74 
75     /**
76      * @return all Rects from all visible Views in the global (root) coordinate system,
77      * or {@code null} if Rects are unchanged since the last call to this method.
78      */
79     @Nullable
computeChangedRects()80     public List<Rect> computeChangedRects() {
81         if (computeChanges()) {
82             return mRects;
83         }
84         return null;
85     }
86 
87     /**
88      * Computes changes to all Rects from all Views.
89      * After calling this method, the updated list of Rects can be retrieved
90      * with {@link #getLastComputedRects()}.
91      *
92      * @return {@code true} if there were changes, {@code false} otherwise.
93      */
computeChanges()94     public boolean computeChanges() {
95         boolean changed = mRootRectsChanged;
96         final Iterator<ViewInfo> i = mViewInfos.iterator();
97         final List<Rect> rects = new ArrayList<>(mRootRects);
98         while (i.hasNext()) {
99             final ViewInfo info = i.next();
100             switch (info.update()) {
101                 case ViewInfo.CHANGED:
102                     changed = true;
103                     // Deliberate fall-through
104                 case ViewInfo.UNCHANGED:
105                     rects.addAll(info.mRects);
106                     break;
107                 case ViewInfo.GONE:
108                     mViewsChanged = true;
109                     i.remove();
110                     break;
111             }
112         }
113         if (changed || mViewsChanged) {
114             mViewsChanged = false;
115             mRootRectsChanged = false;
116             if (!mRects.equals(rects)) {
117                 mRects = rects;
118                 return true;
119             }
120         }
121         return false;
122     }
123 
124     /**
125      * Returns a List of all Rects from all visible Views in the global (root) coordinate system.
126      * This list is only updated when calling {@link #computeChanges()} or
127      * {@link #computeChangedRects()}.
128      *
129      * @return all Rects from all visible Views in the global (root) coordinate system
130      */
131     @NonNull
getLastComputedRects()132     public List<Rect> getLastComputedRects() {
133         return mRects;
134     }
135 
136     /**
137      * Sets rects defined in the global (root) coordinate system, i.e. not for a specific view.
138      */
setRootRects(@onNull List<Rect> rects)139     public void setRootRects(@NonNull List<Rect> rects) {
140         Preconditions.checkNotNull(rects, "rects must not be null");
141         mRootRects = rects;
142         mRootRectsChanged = true;
143     }
144 
145     @NonNull
getRootRects()146     public List<Rect> getRootRects() {
147         return mRootRects;
148     }
149 
150     @NonNull
getTrackedRectsForView(@onNull View v)151     private List<Rect> getTrackedRectsForView(@NonNull View v) {
152         final List<Rect> rects = mRectCollector.apply(v);
153         return rects == null ? Collections.emptyList() : rects;
154     }
155 
156     private class ViewInfo {
157         public static final int CHANGED = 0;
158         public static final int UNCHANGED = 1;
159         public static final int GONE = 2;
160 
161         private final WeakReference<View> mView;
162         boolean mDirty = true;
163         List<Rect> mRects = Collections.emptyList();
164 
ViewInfo(View view)165         ViewInfo(View view) {
166             mView = new WeakReference<>(view);
167         }
168 
getView()169         public View getView() {
170             return mView.get();
171         }
172 
update()173         public int update() {
174             final View view = getView();
175             if (view == null || !view.isAttachedToWindow()
176                     || !view.isAggregatedVisible()) return GONE;
177             final List<Rect> localRects = getTrackedRectsForView(view);
178             final List<Rect> newRects = new ArrayList<>(localRects.size());
179             for (Rect src : localRects) {
180                 Rect mappedRect = new Rect(src);
181                 ViewParent p = view.getParent();
182                 if (p != null && p.getChildVisibleRect(view, mappedRect, null)) {
183                     newRects.add(mappedRect);
184                 }
185             }
186 
187             if (mRects.equals(localRects)) return UNCHANGED;
188             mRects = newRects;
189             return CHANGED;
190         }
191     }
192 }
193