/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.view; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Rect; import com.android.internal.util.Preconditions; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.function.Function; /** * Abstract class to track a collection of rects reported by the views under the same * {@link ViewRootImpl}. */ class ViewRootRectTracker { private final Function> mRectCollector; private boolean mViewsChanged = false; private boolean mRootRectsChanged = false; private List mRootRects = Collections.emptyList(); private List mViewInfos = new ArrayList<>(); private List mRects = Collections.emptyList(); /** * @param rectCollector given a view returns a list of the rects of interest for this * ViewRootRectTracker */ ViewRootRectTracker(Function> rectCollector) { mRectCollector = rectCollector; } public void updateRectsForView(@NonNull View view) { boolean found = false; final Iterator i = mViewInfos.iterator(); while (i.hasNext()) { final ViewInfo info = i.next(); final View v = info.getView(); if (v == null || !v.isAttachedToWindow() || !v.isAggregatedVisible()) { mViewsChanged = true; i.remove(); continue; } if (v == view) { found = true; info.mDirty = true; break; } } if (!found && view.isAttachedToWindow()) { mViewInfos.add(new ViewInfo(view)); mViewsChanged = true; } } /** * @return all Rects from all visible Views in the global (root) coordinate system, * or {@code null} if Rects are unchanged since the last call to this method. */ @Nullable public List computeChangedRects() { if (computeChanges()) { return mRects; } return null; } /** * Computes changes to all Rects from all Views. * After calling this method, the updated list of Rects can be retrieved * with {@link #getLastComputedRects()}. * * @return {@code true} if there were changes, {@code false} otherwise. */ public boolean computeChanges() { boolean changed = mRootRectsChanged; final Iterator i = mViewInfos.iterator(); final List rects = new ArrayList<>(mRootRects); while (i.hasNext()) { final ViewInfo info = i.next(); switch (info.update()) { case ViewInfo.CHANGED: changed = true; // Deliberate fall-through case ViewInfo.UNCHANGED: rects.addAll(info.mRects); break; case ViewInfo.GONE: mViewsChanged = true; i.remove(); break; } } if (changed || mViewsChanged) { mViewsChanged = false; mRootRectsChanged = false; if (!mRects.equals(rects)) { mRects = rects; return true; } } return false; } /** * Returns a List of all Rects from all visible Views in the global (root) coordinate system. * This list is only updated when calling {@link #computeChanges()} or * {@link #computeChangedRects()}. * * @return all Rects from all visible Views in the global (root) coordinate system */ @NonNull public List getLastComputedRects() { return mRects; } /** * Sets rects defined in the global (root) coordinate system, i.e. not for a specific view. */ public void setRootRects(@NonNull List rects) { Preconditions.checkNotNull(rects, "rects must not be null"); mRootRects = rects; mRootRectsChanged = true; } @NonNull public List getRootRects() { return mRootRects; } @NonNull private List getTrackedRectsForView(@NonNull View v) { final List rects = mRectCollector.apply(v); return rects == null ? Collections.emptyList() : rects; } private class ViewInfo { public static final int CHANGED = 0; public static final int UNCHANGED = 1; public static final int GONE = 2; private final WeakReference mView; boolean mDirty = true; List mRects = Collections.emptyList(); ViewInfo(View view) { mView = new WeakReference<>(view); } public View getView() { return mView.get(); } public int update() { final View view = getView(); if (view == null || !view.isAttachedToWindow() || !view.isAggregatedVisible()) return GONE; final List localRects = getTrackedRectsForView(view); final List newRects = new ArrayList<>(localRects.size()); for (Rect src : localRects) { Rect mappedRect = new Rect(src); ViewParent p = view.getParent(); if (p != null && p.getChildVisibleRect(view, mappedRect, null)) { newRects.add(mappedRect); } } if (mRects.equals(localRects)) return UNCHANGED; mRects = newRects; return CHANGED; } } }