1/*
2 * Copyright (C) 2017 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
17package android.support.v7.widget;
18
19import android.support.annotation.IntDef;
20import android.view.View;
21
22import java.lang.annotation.Retention;
23import java.lang.annotation.RetentionPolicy;
24
25/**
26 * A utility class used to check the boundaries of a given view within its parent view based on
27 * a set of boundary flags.
28 */
29class ViewBoundsCheck {
30
31    static final int GT = 1 << 0;
32    static final int EQ = 1 << 1;
33    static final int LT = 1 << 2;
34
35
36    static final int CVS_PVS_POS = 0;
37    /**
38     * The child view's start should be strictly greater than parent view's start.
39     */
40    static final int FLAG_CVS_GT_PVS = GT << CVS_PVS_POS;
41
42    /**
43     * The child view's start can be equal to its parent view's start. This flag follows with GT
44     * or LT indicating greater (less) than or equal relation.
45     */
46    static final int FLAG_CVS_EQ_PVS = EQ << CVS_PVS_POS;
47
48    /**
49     * The child view's start should be strictly less than parent view's start.
50     */
51    static final int FLAG_CVS_LT_PVS = LT << CVS_PVS_POS;
52
53
54    static final int CVS_PVE_POS = 4;
55    /**
56     * The child view's start should be strictly greater than parent view's end.
57     */
58    static final int FLAG_CVS_GT_PVE = GT << CVS_PVE_POS;
59
60    /**
61     * The child view's start can be equal to its parent view's end. This flag follows with GT
62     * or LT indicating greater (less) than or equal relation.
63     */
64    static final int FLAG_CVS_EQ_PVE = EQ << CVS_PVE_POS;
65
66    /**
67     * The child view's start should be strictly less than parent view's end.
68     */
69    static final int FLAG_CVS_LT_PVE = LT << CVS_PVE_POS;
70
71
72    static final int CVE_PVS_POS = 8;
73    /**
74     * The child view's end should be strictly greater than parent view's start.
75     */
76    static final int FLAG_CVE_GT_PVS = GT << CVE_PVS_POS;
77
78    /**
79     * The child view's end can be equal to its parent view's start. This flag follows with GT
80     * or LT indicating greater (less) than or equal relation.
81     */
82    static final int FLAG_CVE_EQ_PVS = EQ << CVE_PVS_POS;
83
84    /**
85     * The child view's end should be strictly less than parent view's start.
86     */
87    static final int FLAG_CVE_LT_PVS = LT << CVE_PVS_POS;
88
89
90    static final int CVE_PVE_POS = 12;
91    /**
92     * The child view's end should be strictly greater than parent view's end.
93     */
94    static final int FLAG_CVE_GT_PVE = GT << CVE_PVE_POS;
95
96    /**
97     * The child view's end can be equal to its parent view's end. This flag follows with GT
98     * or LT indicating greater (less) than or equal relation.
99     */
100    static final int FLAG_CVE_EQ_PVE = EQ << CVE_PVE_POS;
101
102    /**
103     * The child view's end should be strictly less than parent view's end.
104     */
105    static final int FLAG_CVE_LT_PVE = LT << CVE_PVE_POS;
106
107    static final int MASK = GT | EQ | LT;
108
109    final Callback mCallback;
110    BoundFlags mBoundFlags;
111    /**
112     * The set of flags that can be passed for checking the view boundary conditions.
113     * CVS in the flag name indicates the child view, and PV indicates the parent view.\
114     * The following S, E indicate a view's start and end points, respectively.
115     * GT and LT indicate a strictly greater and less than relationship.
116     * Greater than or equal (or less than or equal) can be specified by setting both GT and EQ (or
117     * LT and EQ) flags.
118     * For instance, setting both {@link #FLAG_CVS_GT_PVS} and {@link #FLAG_CVS_EQ_PVS} indicate the
119     * child view's start should be greater than or equal to its parent start.
120     */
121    @IntDef(flag = true, value = {
122            FLAG_CVS_GT_PVS, FLAG_CVS_EQ_PVS, FLAG_CVS_LT_PVS,
123            FLAG_CVS_GT_PVE, FLAG_CVS_EQ_PVE, FLAG_CVS_LT_PVE,
124            FLAG_CVE_GT_PVS, FLAG_CVE_EQ_PVS, FLAG_CVE_LT_PVS,
125            FLAG_CVE_EQ_PVE, FLAG_CVE_EQ_PVE, FLAG_CVE_LT_PVE
126    })
127    @Retention(RetentionPolicy.SOURCE)
128    public @interface ViewBounds {}
129
130    ViewBoundsCheck(Callback callback) {
131        mCallback = callback;
132        mBoundFlags = new BoundFlags();
133    }
134
135    static class BoundFlags {
136        int mBoundFlags = 0;
137        int mRvStart, mRvEnd, mChildStart, mChildEnd;
138
139        void setBounds(int rvStart, int rvEnd, int childStart, int childEnd) {
140            mRvStart = rvStart;
141            mRvEnd = rvEnd;
142            mChildStart = childStart;
143            mChildEnd = childEnd;
144        }
145
146        void setFlags(@ViewBounds int flags, int mask) {
147            mBoundFlags = (mBoundFlags & ~mask) | (flags & mask);
148        }
149
150        void addFlags(@ViewBounds int flags) {
151            mBoundFlags |= flags;
152        }
153
154        void resetFlags() {
155            mBoundFlags = 0;
156        }
157
158        int compare(int x, int y) {
159            if (x > y) {
160                return GT;
161            }
162            if (x == y) {
163                return EQ;
164            }
165            return LT;
166        }
167
168        boolean boundsMatch() {
169            if ((mBoundFlags & (MASK << CVS_PVS_POS)) != 0) {
170                if ((mBoundFlags & (compare(mChildStart, mRvStart) << CVS_PVS_POS)) == 0) {
171                    return false;
172                }
173            }
174
175            if ((mBoundFlags & (MASK << CVS_PVE_POS)) != 0) {
176                if ((mBoundFlags & (compare(mChildStart, mRvEnd) << CVS_PVE_POS)) == 0) {
177                    return false;
178                }
179            }
180
181            if ((mBoundFlags & (MASK << CVE_PVS_POS)) != 0) {
182                if ((mBoundFlags & (compare(mChildEnd, mRvStart) << CVE_PVS_POS)) == 0) {
183                    return false;
184                }
185            }
186
187            if ((mBoundFlags & (MASK << CVE_PVE_POS)) != 0) {
188                if ((mBoundFlags & (compare(mChildEnd, mRvEnd) << CVE_PVE_POS)) == 0) {
189                    return false;
190                }
191            }
192            return true;
193        }
194    };
195
196    /**
197     * Returns the first view starting from fromIndex to toIndex in views whose bounds lie within
198     * its parent bounds based on the provided preferredBoundFlags. If no match is found based on
199     * the preferred flags, and a nonzero acceptableBoundFlags is specified, the last view whose
200     * bounds lie within its parent view based on the acceptableBoundFlags is returned. If no such
201     * view is found based on either of these two flags, null is returned.
202     * @param fromIndex The view position index to start the search from.
203     * @param toIndex The view position index to end the search at.
204     * @param preferredBoundFlags The flags indicating the preferred match. Once a match is found
205     *                            based on this flag, that view is returned instantly.
206     * @param acceptableBoundFlags The flags indicating the acceptable match if no preferred match
207     *                             is found. If so, and if acceptableBoundFlags is non-zero, the
208     *                             last matching acceptable view is returned. Otherwise, null is
209     *                             returned.
210     * @return The first view that satisfies acceptableBoundFlags or the last view satisfying
211     * acceptableBoundFlags boundary conditions.
212     */
213    View findOneViewWithinBoundFlags(int fromIndex, int toIndex,
214            @ViewBounds int preferredBoundFlags,
215            @ViewBounds int acceptableBoundFlags) {
216        final int start = mCallback.getParentStart();
217        final int end = mCallback.getParentEnd();
218        final int next = toIndex > fromIndex ? 1 : -1;
219        View acceptableMatch = null;
220        for (int i = fromIndex; i != toIndex; i += next) {
221            final View child = mCallback.getChildAt(i);
222            final int childStart = mCallback.getChildStart(child);
223            final int childEnd = mCallback.getChildEnd(child);
224            mBoundFlags.setBounds(start, end, childStart, childEnd);
225            if (preferredBoundFlags != 0) {
226                mBoundFlags.resetFlags();
227                mBoundFlags.addFlags(preferredBoundFlags);
228                if (mBoundFlags.boundsMatch()) {
229                    // found a perfect match
230                    return child;
231                }
232            }
233            if (acceptableBoundFlags != 0) {
234                mBoundFlags.resetFlags();
235                mBoundFlags.addFlags(acceptableBoundFlags);
236                if (mBoundFlags.boundsMatch()) {
237                    acceptableMatch = child;
238                }
239            }
240        }
241        return acceptableMatch;
242    }
243
244    /**
245     * Returns whether the specified view lies within the boundary condition of its parent view.
246     * @param child The child view to be checked.
247     * @param boundsFlags The flag against which the child view and parent view are matched.
248     * @return True if the view meets the boundsFlag, false otherwise.
249     */
250    boolean isViewWithinBoundFlags(View child, @ViewBounds int boundsFlags) {
251        mBoundFlags.setBounds(mCallback.getParentStart(), mCallback.getParentEnd(),
252                mCallback.getChildStart(child), mCallback.getChildEnd(child));
253        if (boundsFlags != 0) {
254            mBoundFlags.resetFlags();
255            mBoundFlags.addFlags(boundsFlags);
256            return mBoundFlags.boundsMatch();
257        }
258        return false;
259    }
260
261    /**
262     * Callback provided by the user of this class in order to retrieve information about child and
263     * parent boundaries.
264     */
265    interface Callback {
266        int getChildCount();
267        View getParent();
268        View getChildAt(int index);
269        int getParentStart();
270        int getParentEnd();
271        int getChildStart(View view);
272        int getChildEnd(View view);
273    }
274}
275