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