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