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