1/*
2 * Copyright (C) 2006 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.widget;
18
19import com.android.internal.R;
20
21import java.util.ArrayDeque;
22import java.util.ArrayList;
23import java.util.Comparator;
24import java.util.HashMap;
25import java.util.SortedSet;
26import java.util.TreeSet;
27
28import android.content.Context;
29import android.content.res.Resources;
30import android.content.res.TypedArray;
31import android.graphics.Rect;
32import android.util.AttributeSet;
33import android.util.Pool;
34import android.util.Poolable;
35import android.util.PoolableManager;
36import android.util.Pools;
37import android.util.SparseArray;
38import android.view.Gravity;
39import android.view.View;
40import android.view.ViewDebug;
41import android.view.ViewGroup;
42import android.view.accessibility.AccessibilityEvent;
43import android.view.accessibility.AccessibilityNodeInfo;
44import android.widget.RemoteViews.RemoteView;
45
46import static android.util.Log.d;
47
48/**
49 * A Layout where the positions of the children can be described in relation to each other or to the
50 * parent.
51 *
52 * <p>
53 * Note that you cannot have a circular dependency between the size of the RelativeLayout and the
54 * position of its children. For example, you cannot have a RelativeLayout whose height is set to
55 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT WRAP_CONTENT} and a child set to
56 * {@link #ALIGN_PARENT_BOTTOM}.
57 * </p>
58 *
59 * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-relativelayout.html">Relative
60 * Layout tutorial</a>.</p>
61 *
62 * <p>
63 * Also see {@link android.widget.RelativeLayout.LayoutParams RelativeLayout.LayoutParams} for
64 * layout attributes
65 * </p>
66 *
67 * @attr ref android.R.styleable#RelativeLayout_gravity
68 * @attr ref android.R.styleable#RelativeLayout_ignoreGravity
69 */
70@RemoteView
71public class RelativeLayout extends ViewGroup {
72    private static final String LOG_TAG = "RelativeLayout";
73
74    private static final boolean DEBUG_GRAPH = false;
75
76    public static final int TRUE = -1;
77
78    /**
79     * Rule that aligns a child's right edge with another child's left edge.
80     */
81    public static final int LEFT_OF                  = 0;
82    /**
83     * Rule that aligns a child's left edge with another child's right edge.
84     */
85    public static final int RIGHT_OF                 = 1;
86    /**
87     * Rule that aligns a child's bottom edge with another child's top edge.
88     */
89    public static final int ABOVE                    = 2;
90    /**
91     * Rule that aligns a child's top edge with another child's bottom edge.
92     */
93    public static final int BELOW                    = 3;
94
95    /**
96     * Rule that aligns a child's baseline with another child's baseline.
97     */
98    public static final int ALIGN_BASELINE           = 4;
99    /**
100     * Rule that aligns a child's left edge with another child's left edge.
101     */
102    public static final int ALIGN_LEFT               = 5;
103    /**
104     * Rule that aligns a child's top edge with another child's top edge.
105     */
106    public static final int ALIGN_TOP                = 6;
107    /**
108     * Rule that aligns a child's right edge with another child's right edge.
109     */
110    public static final int ALIGN_RIGHT              = 7;
111    /**
112     * Rule that aligns a child's bottom edge with another child's bottom edge.
113     */
114    public static final int ALIGN_BOTTOM             = 8;
115
116    /**
117     * Rule that aligns the child's left edge with its RelativeLayout
118     * parent's left edge.
119     */
120    public static final int ALIGN_PARENT_LEFT        = 9;
121    /**
122     * Rule that aligns the child's top edge with its RelativeLayout
123     * parent's top edge.
124     */
125    public static final int ALIGN_PARENT_TOP         = 10;
126    /**
127     * Rule that aligns the child's right edge with its RelativeLayout
128     * parent's right edge.
129     */
130    public static final int ALIGN_PARENT_RIGHT       = 11;
131    /**
132     * Rule that aligns the child's bottom edge with its RelativeLayout
133     * parent's bottom edge.
134     */
135    public static final int ALIGN_PARENT_BOTTOM      = 12;
136
137    /**
138     * Rule that centers the child with respect to the bounds of its
139     * RelativeLayout parent.
140     */
141    public static final int CENTER_IN_PARENT         = 13;
142    /**
143     * Rule that centers the child horizontally with respect to the
144     * bounds of its RelativeLayout parent.
145     */
146    public static final int CENTER_HORIZONTAL        = 14;
147    /**
148     * Rule that centers the child vertically with respect to the
149     * bounds of its RelativeLayout parent.
150     */
151    public static final int CENTER_VERTICAL          = 15;
152
153    private static final int VERB_COUNT              = 16;
154
155
156    private static final int[] RULES_VERTICAL = {
157            ABOVE, BELOW, ALIGN_BASELINE, ALIGN_TOP, ALIGN_BOTTOM
158    };
159
160    private static final int[] RULES_HORIZONTAL = {
161            LEFT_OF, RIGHT_OF, ALIGN_LEFT, ALIGN_RIGHT
162    };
163
164    private View mBaselineView = null;
165    private boolean mHasBaselineAlignedChild;
166
167    private int mGravity = Gravity.LEFT | Gravity.TOP;
168    private final Rect mContentBounds = new Rect();
169    private final Rect mSelfBounds = new Rect();
170    private int mIgnoreGravity;
171
172    private SortedSet<View> mTopToBottomLeftToRightSet = null;
173
174    private boolean mDirtyHierarchy;
175    private View[] mSortedHorizontalChildren = new View[0];
176    private View[] mSortedVerticalChildren = new View[0];
177    private final DependencyGraph mGraph = new DependencyGraph();
178
179    public RelativeLayout(Context context) {
180        super(context);
181    }
182
183    public RelativeLayout(Context context, AttributeSet attrs) {
184        super(context, attrs);
185        initFromAttributes(context, attrs);
186    }
187
188    public RelativeLayout(Context context, AttributeSet attrs, int defStyle) {
189        super(context, attrs, defStyle);
190        initFromAttributes(context, attrs);
191    }
192
193    private void initFromAttributes(Context context, AttributeSet attrs) {
194        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RelativeLayout);
195        mIgnoreGravity = a.getResourceId(R.styleable.RelativeLayout_ignoreGravity, View.NO_ID);
196        mGravity = a.getInt(R.styleable.RelativeLayout_gravity, mGravity);
197        a.recycle();
198    }
199
200    @Override
201    public boolean shouldDelayChildPressedState() {
202        return false;
203    }
204
205    /**
206     * Defines which View is ignored when the gravity is applied. This setting has no
207     * effect if the gravity is <code>Gravity.LEFT | Gravity.TOP</code>.
208     *
209     * @param viewId The id of the View to be ignored by gravity, or 0 if no View
210     *        should be ignored.
211     *
212     * @see #setGravity(int)
213     *
214     * @attr ref android.R.styleable#RelativeLayout_ignoreGravity
215     */
216    @android.view.RemotableViewMethod
217    public void setIgnoreGravity(int viewId) {
218        mIgnoreGravity = viewId;
219    }
220
221    /**
222     * Describes how the child views are positioned.
223     *
224     * @return the gravity.
225     *
226     * @see #setGravity(int)
227     * @see android.view.Gravity
228     *
229     * @attr ref android.R.styleable#RelativeLayout_gravity
230     */
231    public int getGravity() {
232        return mGravity;
233    }
234
235    /**
236     * Describes how the child views are positioned. Defaults to
237     * <code>Gravity.LEFT | Gravity.TOP</code>.
238     *
239     * <p>Note that since RelativeLayout considers the positioning of each child
240     * relative to one another to be significant, setting gravity will affect
241     * the positioning of all children as a single unit within the parent.
242     * This happens after children have been relatively positioned.</p>
243     *
244     * @param gravity See {@link android.view.Gravity}
245     *
246     * @see #setHorizontalGravity(int)
247     * @see #setVerticalGravity(int)
248     *
249     * @attr ref android.R.styleable#RelativeLayout_gravity
250     */
251    @android.view.RemotableViewMethod
252    public void setGravity(int gravity) {
253        if (mGravity != gravity) {
254            if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
255                gravity |= Gravity.START;
256            }
257
258            if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
259                gravity |= Gravity.TOP;
260            }
261
262            mGravity = gravity;
263            requestLayout();
264        }
265    }
266
267    @android.view.RemotableViewMethod
268    public void setHorizontalGravity(int horizontalGravity) {
269        final int gravity = horizontalGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
270        if ((mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) != gravity) {
271            mGravity = (mGravity & ~Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) | gravity;
272            requestLayout();
273        }
274    }
275
276    @android.view.RemotableViewMethod
277    public void setVerticalGravity(int verticalGravity) {
278        final int gravity = verticalGravity & Gravity.VERTICAL_GRAVITY_MASK;
279        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != gravity) {
280            mGravity = (mGravity & ~Gravity.VERTICAL_GRAVITY_MASK) | gravity;
281            requestLayout();
282        }
283    }
284
285    @Override
286    public int getBaseline() {
287        return mBaselineView != null ? mBaselineView.getBaseline() : super.getBaseline();
288    }
289
290    @Override
291    public void requestLayout() {
292        super.requestLayout();
293        mDirtyHierarchy = true;
294    }
295
296    private void sortChildren() {
297        int count = getChildCount();
298        if (mSortedVerticalChildren.length != count) mSortedVerticalChildren = new View[count];
299        if (mSortedHorizontalChildren.length != count) mSortedHorizontalChildren = new View[count];
300
301        final DependencyGraph graph = mGraph;
302        graph.clear();
303
304        for (int i = 0; i < count; i++) {
305            final View child = getChildAt(i);
306            graph.add(child);
307        }
308
309        if (DEBUG_GRAPH) {
310            d(LOG_TAG, "=== Sorted vertical children");
311            graph.log(getResources(), RULES_VERTICAL);
312            d(LOG_TAG, "=== Sorted horizontal children");
313            graph.log(getResources(), RULES_HORIZONTAL);
314        }
315
316        graph.getSortedViews(mSortedVerticalChildren, RULES_VERTICAL);
317        graph.getSortedViews(mSortedHorizontalChildren, RULES_HORIZONTAL);
318
319        if (DEBUG_GRAPH) {
320            d(LOG_TAG, "=== Ordered list of vertical children");
321            for (View view : mSortedVerticalChildren) {
322                DependencyGraph.printViewId(getResources(), view);
323            }
324            d(LOG_TAG, "=== Ordered list of horizontal children");
325            for (View view : mSortedHorizontalChildren) {
326                DependencyGraph.printViewId(getResources(), view);
327            }
328        }
329    }
330
331    // TODO: we need to find another way to implement RelativeLayout
332    // This implementation cannot handle every case
333    @Override
334    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
335        if (mDirtyHierarchy) {
336            mDirtyHierarchy = false;
337            sortChildren();
338        }
339
340        int myWidth = -1;
341        int myHeight = -1;
342
343        int width = 0;
344        int height = 0;
345
346        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
347        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
348        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
349        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
350
351        // Record our dimensions if they are known;
352        if (widthMode != MeasureSpec.UNSPECIFIED) {
353            myWidth = widthSize;
354        }
355
356        if (heightMode != MeasureSpec.UNSPECIFIED) {
357            myHeight = heightSize;
358        }
359
360        if (widthMode == MeasureSpec.EXACTLY) {
361            width = myWidth;
362        }
363
364        if (heightMode == MeasureSpec.EXACTLY) {
365            height = myHeight;
366        }
367
368        mHasBaselineAlignedChild = false;
369
370        View ignore = null;
371        int gravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
372        final boolean horizontalGravity = gravity != Gravity.LEFT && gravity != 0;
373        gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
374        final boolean verticalGravity = gravity != Gravity.TOP && gravity != 0;
375
376        int left = Integer.MAX_VALUE;
377        int top = Integer.MAX_VALUE;
378        int right = Integer.MIN_VALUE;
379        int bottom = Integer.MIN_VALUE;
380
381        boolean offsetHorizontalAxis = false;
382        boolean offsetVerticalAxis = false;
383
384        if ((horizontalGravity || verticalGravity) && mIgnoreGravity != View.NO_ID) {
385            ignore = findViewById(mIgnoreGravity);
386        }
387
388        final boolean isWrapContentWidth = widthMode != MeasureSpec.EXACTLY;
389        final boolean isWrapContentHeight = heightMode != MeasureSpec.EXACTLY;
390
391        View[] views = mSortedHorizontalChildren;
392        int count = views.length;
393        for (int i = 0; i < count; i++) {
394            View child = views[i];
395            if (child.getVisibility() != GONE) {
396                LayoutParams params = (LayoutParams) child.getLayoutParams();
397
398                applyHorizontalSizeRules(params, myWidth);
399                measureChildHorizontal(child, params, myWidth, myHeight);
400                if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
401                    offsetHorizontalAxis = true;
402                }
403            }
404        }
405
406        views = mSortedVerticalChildren;
407        count = views.length;
408
409        for (int i = 0; i < count; i++) {
410            View child = views[i];
411            if (child.getVisibility() != GONE) {
412                LayoutParams params = (LayoutParams) child.getLayoutParams();
413
414                applyVerticalSizeRules(params, myHeight);
415                measureChild(child, params, myWidth, myHeight);
416                if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
417                    offsetVerticalAxis = true;
418                }
419
420                if (isWrapContentWidth) {
421                    width = Math.max(width, params.mRight);
422                }
423
424                if (isWrapContentHeight) {
425                    height = Math.max(height, params.mBottom);
426                }
427
428                if (child != ignore || verticalGravity) {
429                    left = Math.min(left, params.mLeft - params.leftMargin);
430                    top = Math.min(top, params.mTop - params.topMargin);
431                }
432
433                if (child != ignore || horizontalGravity) {
434                    right = Math.max(right, params.mRight + params.rightMargin);
435                    bottom = Math.max(bottom, params.mBottom + params.bottomMargin);
436                }
437            }
438        }
439
440        if (mHasBaselineAlignedChild) {
441            for (int i = 0; i < count; i++) {
442                View child = getChildAt(i);
443                if (child.getVisibility() != GONE) {
444                    LayoutParams params = (LayoutParams) child.getLayoutParams();
445                    alignBaseline(child, params);
446
447                    if (child != ignore || verticalGravity) {
448                        left = Math.min(left, params.mLeft - params.leftMargin);
449                        top = Math.min(top, params.mTop - params.topMargin);
450                    }
451
452                    if (child != ignore || horizontalGravity) {
453                        right = Math.max(right, params.mRight + params.rightMargin);
454                        bottom = Math.max(bottom, params.mBottom + params.bottomMargin);
455                    }
456                }
457            }
458        }
459
460        if (isWrapContentWidth) {
461            // Width already has left padding in it since it was calculated by looking at
462            // the right of each child view
463            width += mPaddingRight;
464
465            if (mLayoutParams.width >= 0) {
466                width = Math.max(width, mLayoutParams.width);
467            }
468
469            width = Math.max(width, getSuggestedMinimumWidth());
470            width = resolveSize(width, widthMeasureSpec);
471
472            if (offsetHorizontalAxis) {
473                for (int i = 0; i < count; i++) {
474                    View child = getChildAt(i);
475                    if (child.getVisibility() != GONE) {
476                        LayoutParams params = (LayoutParams) child.getLayoutParams();
477                        final int[] rules = params.getRules();
478                        if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {
479                            centerHorizontal(child, params, width);
480                        } else if (rules[ALIGN_PARENT_RIGHT] != 0) {
481                            final int childWidth = child.getMeasuredWidth();
482                            params.mLeft = width - mPaddingRight - childWidth;
483                            params.mRight = params.mLeft + childWidth;
484                        }
485                    }
486                }
487            }
488        }
489
490        if (isWrapContentHeight) {
491            // Height already has top padding in it since it was calculated by looking at
492            // the bottom of each child view
493            height += mPaddingBottom;
494
495            if (mLayoutParams.height >= 0) {
496                height = Math.max(height, mLayoutParams.height);
497            }
498
499            height = Math.max(height, getSuggestedMinimumHeight());
500            height = resolveSize(height, heightMeasureSpec);
501
502            if (offsetVerticalAxis) {
503                for (int i = 0; i < count; i++) {
504                    View child = getChildAt(i);
505                    if (child.getVisibility() != GONE) {
506                        LayoutParams params = (LayoutParams) child.getLayoutParams();
507                        final int[] rules = params.getRules();
508                        if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) {
509                            centerVertical(child, params, height);
510                        } else if (rules[ALIGN_PARENT_BOTTOM] != 0) {
511                            final int childHeight = child.getMeasuredHeight();
512                            params.mTop = height - mPaddingBottom - childHeight;
513                            params.mBottom = params.mTop + childHeight;
514                        }
515                    }
516                }
517            }
518        }
519
520        if (horizontalGravity || verticalGravity) {
521            final Rect selfBounds = mSelfBounds;
522            selfBounds.set(mPaddingLeft, mPaddingTop, width - mPaddingRight,
523                    height - mPaddingBottom);
524
525            final Rect contentBounds = mContentBounds;
526            final int layoutDirection = getResolvedLayoutDirection();
527            Gravity.apply(mGravity, right - left, bottom - top, selfBounds, contentBounds,
528                    layoutDirection);
529
530            final int horizontalOffset = contentBounds.left - left;
531            final int verticalOffset = contentBounds.top - top;
532            if (horizontalOffset != 0 || verticalOffset != 0) {
533                for (int i = 0; i < count; i++) {
534                    View child = getChildAt(i);
535                    if (child.getVisibility() != GONE && child != ignore) {
536                        LayoutParams params = (LayoutParams) child.getLayoutParams();
537                        if (horizontalGravity) {
538                            params.mLeft += horizontalOffset;
539                            params.mRight += horizontalOffset;
540                        }
541                        if (verticalGravity) {
542                            params.mTop += verticalOffset;
543                            params.mBottom += verticalOffset;
544                        }
545                    }
546                }
547            }
548        }
549
550        setMeasuredDimension(width, height);
551    }
552
553    private void alignBaseline(View child, LayoutParams params) {
554        int[] rules = params.getRules();
555        int anchorBaseline = getRelatedViewBaseline(rules, ALIGN_BASELINE);
556
557        if (anchorBaseline != -1) {
558            LayoutParams anchorParams = getRelatedViewParams(rules, ALIGN_BASELINE);
559            if (anchorParams != null) {
560                int offset = anchorParams.mTop + anchorBaseline;
561                int baseline = child.getBaseline();
562                if (baseline != -1) {
563                    offset -= baseline;
564                }
565                int height = params.mBottom - params.mTop;
566                params.mTop = offset;
567                params.mBottom = params.mTop + height;
568            }
569        }
570
571        if (mBaselineView == null) {
572            mBaselineView = child;
573        } else {
574            LayoutParams lp = (LayoutParams) mBaselineView.getLayoutParams();
575            if (params.mTop < lp.mTop || (params.mTop == lp.mTop && params.mLeft < lp.mLeft)) {
576                mBaselineView = child;
577            }
578        }
579    }
580
581    /**
582     * Measure a child. The child should have left, top, right and bottom information
583     * stored in its LayoutParams. If any of these values is -1 it means that the view
584     * can extend up to the corresponding edge.
585     *
586     * @param child Child to measure
587     * @param params LayoutParams associated with child
588     * @param myWidth Width of the the RelativeLayout
589     * @param myHeight Height of the RelativeLayout
590     */
591    private void measureChild(View child, LayoutParams params, int myWidth, int myHeight) {
592        int childWidthMeasureSpec = getChildMeasureSpec(params.mLeft,
593                params.mRight, params.width,
594                params.leftMargin, params.rightMargin,
595                mPaddingLeft, mPaddingRight,
596                myWidth);
597        int childHeightMeasureSpec = getChildMeasureSpec(params.mTop,
598                params.mBottom, params.height,
599                params.topMargin, params.bottomMargin,
600                mPaddingTop, mPaddingBottom,
601                myHeight);
602        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
603    }
604
605    private void measureChildHorizontal(View child, LayoutParams params, int myWidth, int myHeight) {
606        int childWidthMeasureSpec = getChildMeasureSpec(params.mLeft,
607                params.mRight, params.width,
608                params.leftMargin, params.rightMargin,
609                mPaddingLeft, mPaddingRight,
610                myWidth);
611        int childHeightMeasureSpec;
612        if (params.width == LayoutParams.MATCH_PARENT) {
613            childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.EXACTLY);
614        } else {
615            childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.AT_MOST);
616        }
617        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
618    }
619
620    /**
621     * Get a measure spec that accounts for all of the constraints on this view.
622     * This includes size contstraints imposed by the RelativeLayout as well as
623     * the View's desired dimension.
624     *
625     * @param childStart The left or top field of the child's layout params
626     * @param childEnd The right or bottom field of the child's layout params
627     * @param childSize The child's desired size (the width or height field of
628     *        the child's layout params)
629     * @param startMargin The left or top margin
630     * @param endMargin The right or bottom margin
631     * @param startPadding mPaddingLeft or mPaddingTop
632     * @param endPadding mPaddingRight or mPaddingBottom
633     * @param mySize The width or height of this view (the RelativeLayout)
634     * @return MeasureSpec for the child
635     */
636    private int getChildMeasureSpec(int childStart, int childEnd,
637            int childSize, int startMargin, int endMargin, int startPadding,
638            int endPadding, int mySize) {
639        int childSpecMode = 0;
640        int childSpecSize = 0;
641
642        // Figure out start and end bounds.
643        int tempStart = childStart;
644        int tempEnd = childEnd;
645
646        // If the view did not express a layout constraint for an edge, use
647        // view's margins and our padding
648        if (tempStart < 0) {
649            tempStart = startPadding + startMargin;
650        }
651        if (tempEnd < 0) {
652            tempEnd = mySize - endPadding - endMargin;
653        }
654
655        // Figure out maximum size available to this view
656        int maxAvailable = tempEnd - tempStart;
657
658        if (childStart >= 0 && childEnd >= 0) {
659            // Constraints fixed both edges, so child must be an exact size
660            childSpecMode = MeasureSpec.EXACTLY;
661            childSpecSize = maxAvailable;
662        } else {
663            if (childSize >= 0) {
664                // Child wanted an exact size. Give as much as possible
665                childSpecMode = MeasureSpec.EXACTLY;
666
667                if (maxAvailable >= 0) {
668                    // We have a maxmum size in this dimension.
669                    childSpecSize = Math.min(maxAvailable, childSize);
670                } else {
671                    // We can grow in this dimension.
672                    childSpecSize = childSize;
673                }
674            } else if (childSize == LayoutParams.MATCH_PARENT) {
675                // Child wanted to be as big as possible. Give all availble
676                // space
677                childSpecMode = MeasureSpec.EXACTLY;
678                childSpecSize = maxAvailable;
679            } else if (childSize == LayoutParams.WRAP_CONTENT) {
680                // Child wants to wrap content. Use AT_MOST
681                // to communicate available space if we know
682                // our max size
683                if (maxAvailable >= 0) {
684                    // We have a maxmum size in this dimension.
685                    childSpecMode = MeasureSpec.AT_MOST;
686                    childSpecSize = maxAvailable;
687                } else {
688                    // We can grow in this dimension. Child can be as big as it
689                    // wants
690                    childSpecMode = MeasureSpec.UNSPECIFIED;
691                    childSpecSize = 0;
692                }
693            }
694        }
695
696        return MeasureSpec.makeMeasureSpec(childSpecSize, childSpecMode);
697    }
698
699    private boolean positionChildHorizontal(View child, LayoutParams params, int myWidth,
700            boolean wrapContent) {
701
702        int[] rules = params.getRules();
703
704        if (params.mLeft < 0 && params.mRight >= 0) {
705            // Right is fixed, but left varies
706            params.mLeft = params.mRight - child.getMeasuredWidth();
707        } else if (params.mLeft >= 0 && params.mRight < 0) {
708            // Left is fixed, but right varies
709            params.mRight = params.mLeft + child.getMeasuredWidth();
710        } else if (params.mLeft < 0 && params.mRight < 0) {
711            // Both left and right vary
712            if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {
713                if (!wrapContent) {
714                    centerHorizontal(child, params, myWidth);
715                } else {
716                    params.mLeft = mPaddingLeft + params.leftMargin;
717                    params.mRight = params.mLeft + child.getMeasuredWidth();
718                }
719                return true;
720            } else {
721                params.mLeft = mPaddingLeft + params.leftMargin;
722                params.mRight = params.mLeft + child.getMeasuredWidth();
723            }
724        }
725        return rules[ALIGN_PARENT_RIGHT] != 0;
726    }
727
728    private boolean positionChildVertical(View child, LayoutParams params, int myHeight,
729            boolean wrapContent) {
730
731        int[] rules = params.getRules();
732
733        if (params.mTop < 0 && params.mBottom >= 0) {
734            // Bottom is fixed, but top varies
735            params.mTop = params.mBottom - child.getMeasuredHeight();
736        } else if (params.mTop >= 0 && params.mBottom < 0) {
737            // Top is fixed, but bottom varies
738            params.mBottom = params.mTop + child.getMeasuredHeight();
739        } else if (params.mTop < 0 && params.mBottom < 0) {
740            // Both top and bottom vary
741            if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) {
742                if (!wrapContent) {
743                    centerVertical(child, params, myHeight);
744                } else {
745                    params.mTop = mPaddingTop + params.topMargin;
746                    params.mBottom = params.mTop + child.getMeasuredHeight();
747                }
748                return true;
749            } else {
750                params.mTop = mPaddingTop + params.topMargin;
751                params.mBottom = params.mTop + child.getMeasuredHeight();
752            }
753        }
754        return rules[ALIGN_PARENT_BOTTOM] != 0;
755    }
756
757    private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth) {
758        int[] rules = childParams.getRules();
759        RelativeLayout.LayoutParams anchorParams;
760
761        // -1 indicated a "soft requirement" in that direction. For example:
762        // left=10, right=-1 means the view must start at 10, but can go as far as it wants to the right
763        // left =-1, right=10 means the view must end at 10, but can go as far as it wants to the left
764        // left=10, right=20 means the left and right ends are both fixed
765        childParams.mLeft = -1;
766        childParams.mRight = -1;
767
768        anchorParams = getRelatedViewParams(rules, LEFT_OF);
769        if (anchorParams != null) {
770            childParams.mRight = anchorParams.mLeft - (anchorParams.leftMargin +
771                    childParams.rightMargin);
772        } else if (childParams.alignWithParent && rules[LEFT_OF] != 0) {
773            if (myWidth >= 0) {
774                childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
775            } else {
776                // FIXME uh oh...
777            }
778        }
779
780        anchorParams = getRelatedViewParams(rules, RIGHT_OF);
781        if (anchorParams != null) {
782            childParams.mLeft = anchorParams.mRight + (anchorParams.rightMargin +
783                    childParams.leftMargin);
784        } else if (childParams.alignWithParent && rules[RIGHT_OF] != 0) {
785            childParams.mLeft = mPaddingLeft + childParams.leftMargin;
786        }
787
788        anchorParams = getRelatedViewParams(rules, ALIGN_LEFT);
789        if (anchorParams != null) {
790            childParams.mLeft = anchorParams.mLeft + childParams.leftMargin;
791        } else if (childParams.alignWithParent && rules[ALIGN_LEFT] != 0) {
792            childParams.mLeft = mPaddingLeft + childParams.leftMargin;
793        }
794
795        anchorParams = getRelatedViewParams(rules, ALIGN_RIGHT);
796        if (anchorParams != null) {
797            childParams.mRight = anchorParams.mRight - childParams.rightMargin;
798        } else if (childParams.alignWithParent && rules[ALIGN_RIGHT] != 0) {
799            if (myWidth >= 0) {
800                childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
801            } else {
802                // FIXME uh oh...
803            }
804        }
805
806        if (0 != rules[ALIGN_PARENT_LEFT]) {
807            childParams.mLeft = mPaddingLeft + childParams.leftMargin;
808        }
809
810        if (0 != rules[ALIGN_PARENT_RIGHT]) {
811            if (myWidth >= 0) {
812                childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
813            } else {
814                // FIXME uh oh...
815            }
816        }
817    }
818
819    private void applyVerticalSizeRules(LayoutParams childParams, int myHeight) {
820        int[] rules = childParams.getRules();
821        RelativeLayout.LayoutParams anchorParams;
822
823        childParams.mTop = -1;
824        childParams.mBottom = -1;
825
826        anchorParams = getRelatedViewParams(rules, ABOVE);
827        if (anchorParams != null) {
828            childParams.mBottom = anchorParams.mTop - (anchorParams.topMargin +
829                    childParams.bottomMargin);
830        } else if (childParams.alignWithParent && rules[ABOVE] != 0) {
831            if (myHeight >= 0) {
832                childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin;
833            } else {
834                // FIXME uh oh...
835            }
836        }
837
838        anchorParams = getRelatedViewParams(rules, BELOW);
839        if (anchorParams != null) {
840            childParams.mTop = anchorParams.mBottom + (anchorParams.bottomMargin +
841                    childParams.topMargin);
842        } else if (childParams.alignWithParent && rules[BELOW] != 0) {
843            childParams.mTop = mPaddingTop + childParams.topMargin;
844        }
845
846        anchorParams = getRelatedViewParams(rules, ALIGN_TOP);
847        if (anchorParams != null) {
848            childParams.mTop = anchorParams.mTop + childParams.topMargin;
849        } else if (childParams.alignWithParent && rules[ALIGN_TOP] != 0) {
850            childParams.mTop = mPaddingTop + childParams.topMargin;
851        }
852
853        anchorParams = getRelatedViewParams(rules, ALIGN_BOTTOM);
854        if (anchorParams != null) {
855            childParams.mBottom = anchorParams.mBottom - childParams.bottomMargin;
856        } else if (childParams.alignWithParent && rules[ALIGN_BOTTOM] != 0) {
857            if (myHeight >= 0) {
858                childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin;
859            } else {
860                // FIXME uh oh...
861            }
862        }
863
864        if (0 != rules[ALIGN_PARENT_TOP]) {
865            childParams.mTop = mPaddingTop + childParams.topMargin;
866        }
867
868        if (0 != rules[ALIGN_PARENT_BOTTOM]) {
869            if (myHeight >= 0) {
870                childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin;
871            } else {
872                // FIXME uh oh...
873            }
874        }
875
876        if (rules[ALIGN_BASELINE] != 0) {
877            mHasBaselineAlignedChild = true;
878        }
879    }
880
881    private View getRelatedView(int[] rules, int relation) {
882        int id = rules[relation];
883        if (id != 0) {
884            DependencyGraph.Node node = mGraph.mKeyNodes.get(id);
885            if (node == null) return null;
886            View v = node.view;
887
888            // Find the first non-GONE view up the chain
889            while (v.getVisibility() == View.GONE) {
890                rules = ((LayoutParams) v.getLayoutParams()).getRules();
891                node = mGraph.mKeyNodes.get((rules[relation]));
892                if (node == null) return null;
893                v = node.view;
894            }
895
896            return v;
897        }
898
899        return null;
900    }
901
902    private LayoutParams getRelatedViewParams(int[] rules, int relation) {
903        View v = getRelatedView(rules, relation);
904        if (v != null) {
905            ViewGroup.LayoutParams params = v.getLayoutParams();
906            if (params instanceof LayoutParams) {
907                return (LayoutParams) v.getLayoutParams();
908            }
909        }
910        return null;
911    }
912
913    private int getRelatedViewBaseline(int[] rules, int relation) {
914        View v = getRelatedView(rules, relation);
915        if (v != null) {
916            return v.getBaseline();
917        }
918        return -1;
919    }
920
921    private void centerHorizontal(View child, LayoutParams params, int myWidth) {
922        int childWidth = child.getMeasuredWidth();
923        int left = (myWidth - childWidth) / 2;
924
925        params.mLeft = left;
926        params.mRight = left + childWidth;
927    }
928
929    private void centerVertical(View child, LayoutParams params, int myHeight) {
930        int childHeight = child.getMeasuredHeight();
931        int top = (myHeight - childHeight) / 2;
932
933        params.mTop = top;
934        params.mBottom = top + childHeight;
935    }
936
937    @Override
938    protected void onLayout(boolean changed, int l, int t, int r, int b) {
939        //  The layout has actually already been performed and the positions
940        //  cached.  Apply the cached values to the children.
941        int count = getChildCount();
942
943        for (int i = 0; i < count; i++) {
944            View child = getChildAt(i);
945            if (child.getVisibility() != GONE) {
946                RelativeLayout.LayoutParams st =
947                        (RelativeLayout.LayoutParams) child.getLayoutParams();
948                child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
949
950            }
951        }
952    }
953
954    @Override
955    public LayoutParams generateLayoutParams(AttributeSet attrs) {
956        return new RelativeLayout.LayoutParams(getContext(), attrs);
957    }
958
959    /**
960     * Returns a set of layout parameters with a width of
961     * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT},
962     * a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and no spanning.
963     */
964    @Override
965    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
966        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
967    }
968
969    // Override to allow type-checking of LayoutParams.
970    @Override
971    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
972        return p instanceof RelativeLayout.LayoutParams;
973    }
974
975    @Override
976    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
977        return new LayoutParams(p);
978    }
979
980    @Override
981    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
982        if (mTopToBottomLeftToRightSet == null) {
983            mTopToBottomLeftToRightSet = new TreeSet<View>(new TopToBottomLeftToRightComparator());
984        }
985
986        // sort children top-to-bottom and left-to-right
987        for (int i = 0, count = getChildCount(); i < count; i++) {
988            mTopToBottomLeftToRightSet.add(getChildAt(i));
989        }
990
991        for (View view : mTopToBottomLeftToRightSet) {
992            if (view.getVisibility() == View.VISIBLE
993                    && view.dispatchPopulateAccessibilityEvent(event)) {
994                mTopToBottomLeftToRightSet.clear();
995                return true;
996            }
997        }
998
999        mTopToBottomLeftToRightSet.clear();
1000        return false;
1001    }
1002
1003    @Override
1004    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1005        super.onInitializeAccessibilityEvent(event);
1006        event.setClassName(RelativeLayout.class.getName());
1007    }
1008
1009    @Override
1010    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1011        super.onInitializeAccessibilityNodeInfo(info);
1012        info.setClassName(RelativeLayout.class.getName());
1013    }
1014
1015    /**
1016     * Compares two views in left-to-right and top-to-bottom fashion.
1017     */
1018     private class TopToBottomLeftToRightComparator implements Comparator<View> {
1019        public int compare(View first, View second) {
1020            // top - bottom
1021            int topDifference = first.getTop() - second.getTop();
1022            if (topDifference != 0) {
1023                return topDifference;
1024            }
1025            // left - right
1026            int leftDifference = first.getLeft() - second.getLeft();
1027            if (leftDifference != 0) {
1028                return leftDifference;
1029            }
1030            // break tie by height
1031            int heightDiference = first.getHeight() - second.getHeight();
1032            if (heightDiference != 0) {
1033                return heightDiference;
1034            }
1035            // break tie by width
1036            int widthDiference = first.getWidth() - second.getWidth();
1037            if (widthDiference != 0) {
1038                return widthDiference;
1039            }
1040            return 0;
1041        }
1042    }
1043
1044    /**
1045     * Per-child layout information associated with RelativeLayout.
1046     *
1047     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignWithParentIfMissing
1048     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toLeftOf
1049     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toRightOf
1050     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_above
1051     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_below
1052     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignBaseline
1053     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignLeft
1054     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignTop
1055     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignRight
1056     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignBottom
1057     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentLeft
1058     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentTop
1059     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentRight
1060     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentBottom
1061     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerInParent
1062     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerHorizontal
1063     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerVertical
1064     */
1065    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
1066        @ViewDebug.ExportedProperty(category = "layout", resolveId = true, indexMapping = {
1067            @ViewDebug.IntToString(from = ABOVE,               to = "above"),
1068            @ViewDebug.IntToString(from = ALIGN_BASELINE,      to = "alignBaseline"),
1069            @ViewDebug.IntToString(from = ALIGN_BOTTOM,        to = "alignBottom"),
1070            @ViewDebug.IntToString(from = ALIGN_LEFT,          to = "alignLeft"),
1071            @ViewDebug.IntToString(from = ALIGN_PARENT_BOTTOM, to = "alignParentBottom"),
1072            @ViewDebug.IntToString(from = ALIGN_PARENT_LEFT,   to = "alignParentLeft"),
1073            @ViewDebug.IntToString(from = ALIGN_PARENT_RIGHT,  to = "alignParentRight"),
1074            @ViewDebug.IntToString(from = ALIGN_PARENT_TOP,    to = "alignParentTop"),
1075            @ViewDebug.IntToString(from = ALIGN_RIGHT,         to = "alignRight"),
1076            @ViewDebug.IntToString(from = ALIGN_TOP,           to = "alignTop"),
1077            @ViewDebug.IntToString(from = BELOW,               to = "below"),
1078            @ViewDebug.IntToString(from = CENTER_HORIZONTAL,   to = "centerHorizontal"),
1079            @ViewDebug.IntToString(from = CENTER_IN_PARENT,    to = "center"),
1080            @ViewDebug.IntToString(from = CENTER_VERTICAL,     to = "centerVertical"),
1081            @ViewDebug.IntToString(from = LEFT_OF,             to = "leftOf"),
1082            @ViewDebug.IntToString(from = RIGHT_OF,            to = "rightOf")
1083        }, mapping = {
1084            @ViewDebug.IntToString(from = TRUE, to = "true"),
1085            @ViewDebug.IntToString(from = 0,    to = "false/NO_ID")
1086        })
1087        private int[] mRules = new int[VERB_COUNT];
1088
1089        private int mLeft, mTop, mRight, mBottom;
1090
1091        /**
1092         * When true, uses the parent as the anchor if the anchor doesn't exist or if
1093         * the anchor's visibility is GONE.
1094         */
1095        @ViewDebug.ExportedProperty(category = "layout")
1096        public boolean alignWithParent;
1097
1098        public LayoutParams(Context c, AttributeSet attrs) {
1099            super(c, attrs);
1100
1101            TypedArray a = c.obtainStyledAttributes(attrs,
1102                    com.android.internal.R.styleable.RelativeLayout_Layout);
1103
1104            final int[] rules = mRules;
1105
1106            final int N = a.getIndexCount();
1107            for (int i = 0; i < N; i++) {
1108                int attr = a.getIndex(i);
1109                switch (attr) {
1110                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignWithParentIfMissing:
1111                        alignWithParent = a.getBoolean(attr, false);
1112                        break;
1113                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toLeftOf:
1114                        rules[LEFT_OF] = a.getResourceId(attr, 0);
1115                        break;
1116                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toRightOf:
1117                        rules[RIGHT_OF] = a.getResourceId(attr, 0);
1118                        break;
1119                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_above:
1120                        rules[ABOVE] = a.getResourceId(attr, 0);
1121                        break;
1122                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_below:
1123                        rules[BELOW] = a.getResourceId(attr, 0);
1124                        break;
1125                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBaseline:
1126                        rules[ALIGN_BASELINE] = a.getResourceId(attr, 0);
1127                        break;
1128                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignLeft:
1129                        rules[ALIGN_LEFT] = a.getResourceId(attr, 0);
1130                        break;
1131                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignTop:
1132                        rules[ALIGN_TOP] = a.getResourceId(attr, 0);
1133                        break;
1134                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignRight:
1135                        rules[ALIGN_RIGHT] = a.getResourceId(attr, 0);
1136                        break;
1137                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBottom:
1138                        rules[ALIGN_BOTTOM] = a.getResourceId(attr, 0);
1139                        break;
1140                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentLeft:
1141                        rules[ALIGN_PARENT_LEFT] = a.getBoolean(attr, false) ? TRUE : 0;
1142                        break;
1143                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentTop:
1144                        rules[ALIGN_PARENT_TOP] = a.getBoolean(attr, false) ? TRUE : 0;
1145                        break;
1146                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentRight:
1147                        rules[ALIGN_PARENT_RIGHT] = a.getBoolean(attr, false) ? TRUE : 0;
1148                        break;
1149                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentBottom:
1150                        rules[ALIGN_PARENT_BOTTOM] = a.getBoolean(attr, false) ? TRUE : 0;
1151                        break;
1152                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerInParent:
1153                        rules[CENTER_IN_PARENT] = a.getBoolean(attr, false) ? TRUE : 0;
1154                        break;
1155                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerHorizontal:
1156                        rules[CENTER_HORIZONTAL] = a.getBoolean(attr, false) ? TRUE : 0;
1157                        break;
1158                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerVertical:
1159                        rules[CENTER_VERTICAL] = a.getBoolean(attr, false) ? TRUE : 0;
1160                       break;
1161                }
1162            }
1163
1164            a.recycle();
1165        }
1166
1167        public LayoutParams(int w, int h) {
1168            super(w, h);
1169        }
1170
1171        /**
1172         * {@inheritDoc}
1173         */
1174        public LayoutParams(ViewGroup.LayoutParams source) {
1175            super(source);
1176        }
1177
1178        /**
1179         * {@inheritDoc}
1180         */
1181        public LayoutParams(ViewGroup.MarginLayoutParams source) {
1182            super(source);
1183        }
1184
1185        @Override
1186        public String debug(String output) {
1187            return output + "ViewGroup.LayoutParams={ width=" + sizeToString(width) +
1188                    ", height=" + sizeToString(height) + " }";
1189        }
1190
1191        /**
1192         * Adds a layout rule to be interpreted by the RelativeLayout. This
1193         * method should only be used for constraints that don't refer to another sibling
1194         * (e.g., CENTER_IN_PARENT) or take a boolean value ({@link RelativeLayout#TRUE}
1195         * for true or - for false). To specify a verb that takes a subject, use
1196         * {@link #addRule(int, int)} instead.
1197         *
1198         * @param verb One of the verbs defined by
1199         *        {@link android.widget.RelativeLayout RelativeLayout}, such as
1200         *        ALIGN_WITH_PARENT_LEFT.
1201         * @see #addRule(int, int)
1202         */
1203        public void addRule(int verb) {
1204            mRules[verb] = TRUE;
1205        }
1206
1207        /**
1208         * Adds a layout rule to be interpreted by the RelativeLayout. Use this for
1209         * verbs that take a target, such as a sibling (ALIGN_RIGHT) or a boolean
1210         * value (VISIBLE).
1211         *
1212         * @param verb One of the verbs defined by
1213         *        {@link android.widget.RelativeLayout RelativeLayout}, such as
1214         *         ALIGN_WITH_PARENT_LEFT.
1215         * @param anchor The id of another view to use as an anchor,
1216         *        or a boolean value(represented as {@link RelativeLayout#TRUE})
1217         *        for true or 0 for false).  For verbs that don't refer to another sibling
1218         *        (for example, ALIGN_WITH_PARENT_BOTTOM) just use -1.
1219         * @see #addRule(int)
1220         */
1221        public void addRule(int verb, int anchor) {
1222            mRules[verb] = anchor;
1223        }
1224
1225        /**
1226         * Retrieves a complete list of all supported rules, where the index is the rule
1227         * verb, and the element value is the value specified, or "false" if it was never
1228         * set.
1229         *
1230         * @return the supported rules
1231         * @see #addRule(int, int)
1232         */
1233        public int[] getRules() {
1234            return mRules;
1235        }
1236    }
1237
1238    private static class DependencyGraph {
1239        /**
1240         * List of all views in the graph.
1241         */
1242        private ArrayList<Node> mNodes = new ArrayList<Node>();
1243
1244        /**
1245         * List of nodes in the graph. Each node is identified by its
1246         * view id (see View#getId()).
1247         */
1248        private SparseArray<Node> mKeyNodes = new SparseArray<Node>();
1249
1250        /**
1251         * Temporary data structure used to build the list of roots
1252         * for this graph.
1253         */
1254        private ArrayDeque<Node> mRoots = new ArrayDeque<Node>();
1255
1256        /**
1257         * Clears the graph.
1258         */
1259        void clear() {
1260            final ArrayList<Node> nodes = mNodes;
1261            final int count = nodes.size();
1262
1263            for (int i = 0; i < count; i++) {
1264                nodes.get(i).release();
1265            }
1266            nodes.clear();
1267
1268            mKeyNodes.clear();
1269            mRoots.clear();
1270        }
1271
1272        /**
1273         * Adds a view to the graph.
1274         *
1275         * @param view The view to be added as a node to the graph.
1276         */
1277        void add(View view) {
1278            final int id = view.getId();
1279            final Node node = Node.acquire(view);
1280
1281            if (id != View.NO_ID) {
1282                mKeyNodes.put(id, node);
1283            }
1284
1285            mNodes.add(node);
1286        }
1287
1288        /**
1289         * Builds a sorted list of views. The sorting order depends on the dependencies
1290         * between the view. For instance, if view C needs view A to be processed first
1291         * and view A needs view B to be processed first, the dependency graph
1292         * is: B -> A -> C. The sorted array will contain views B, A and C in this order.
1293         *
1294         * @param sorted The sorted list of views. The length of this array must
1295         *        be equal to getChildCount().
1296         * @param rules The list of rules to take into account.
1297         */
1298        void getSortedViews(View[] sorted, int... rules) {
1299            final ArrayDeque<Node> roots = findRoots(rules);
1300            int index = 0;
1301
1302            Node node;
1303            while ((node = roots.pollLast()) != null) {
1304                final View view = node.view;
1305                final int key = view.getId();
1306
1307                sorted[index++] = view;
1308
1309                final HashMap<Node, DependencyGraph> dependents = node.dependents;
1310                for (Node dependent : dependents.keySet()) {
1311                    final SparseArray<Node> dependencies = dependent.dependencies;
1312
1313                    dependencies.remove(key);
1314                    if (dependencies.size() == 0) {
1315                        roots.add(dependent);
1316                    }
1317                }
1318            }
1319
1320            if (index < sorted.length) {
1321                throw new IllegalStateException("Circular dependencies cannot exist"
1322                        + " in RelativeLayout");
1323            }
1324        }
1325
1326        /**
1327         * Finds the roots of the graph. A root is a node with no dependency and
1328         * with [0..n] dependents.
1329         *
1330         * @param rulesFilter The list of rules to consider when building the
1331         *        dependencies
1332         *
1333         * @return A list of node, each being a root of the graph
1334         */
1335        private ArrayDeque<Node> findRoots(int[] rulesFilter) {
1336            final SparseArray<Node> keyNodes = mKeyNodes;
1337            final ArrayList<Node> nodes = mNodes;
1338            final int count = nodes.size();
1339
1340            // Find roots can be invoked several times, so make sure to clear
1341            // all dependents and dependencies before running the algorithm
1342            for (int i = 0; i < count; i++) {
1343                final Node node = nodes.get(i);
1344                node.dependents.clear();
1345                node.dependencies.clear();
1346            }
1347
1348            // Builds up the dependents and dependencies for each node of the graph
1349            for (int i = 0; i < count; i++) {
1350                final Node node = nodes.get(i);
1351
1352                final LayoutParams layoutParams = (LayoutParams) node.view.getLayoutParams();
1353                final int[] rules = layoutParams.mRules;
1354                final int rulesCount = rulesFilter.length;
1355
1356                // Look only the the rules passed in parameter, this way we build only the
1357                // dependencies for a specific set of rules
1358                for (int j = 0; j < rulesCount; j++) {
1359                    final int rule = rules[rulesFilter[j]];
1360                    if (rule > 0) {
1361                        // The node this node depends on
1362                        final Node dependency = keyNodes.get(rule);
1363                        // Skip unknowns and self dependencies
1364                        if (dependency == null || dependency == node) {
1365                            continue;
1366                        }
1367                        // Add the current node as a dependent
1368                        dependency.dependents.put(node, this);
1369                        // Add a dependency to the current node
1370                        node.dependencies.put(rule, dependency);
1371                    }
1372                }
1373            }
1374
1375            final ArrayDeque<Node> roots = mRoots;
1376            roots.clear();
1377
1378            // Finds all the roots in the graph: all nodes with no dependencies
1379            for (int i = 0; i < count; i++) {
1380                final Node node = nodes.get(i);
1381                if (node.dependencies.size() == 0) roots.addLast(node);
1382            }
1383
1384            return roots;
1385        }
1386
1387        /**
1388         * Prints the dependency graph for the specified rules.
1389         *
1390         * @param resources The context's resources to print the ids.
1391         * @param rules The list of rules to take into account.
1392         */
1393        void log(Resources resources, int... rules) {
1394            final ArrayDeque<Node> roots = findRoots(rules);
1395            for (Node node : roots) {
1396                printNode(resources, node);
1397            }
1398        }
1399
1400        static void printViewId(Resources resources, View view) {
1401            if (view.getId() != View.NO_ID) {
1402                d(LOG_TAG, resources.getResourceEntryName(view.getId()));
1403            } else {
1404                d(LOG_TAG, "NO_ID");
1405            }
1406        }
1407
1408        private static void appendViewId(Resources resources, Node node, StringBuilder buffer) {
1409            if (node.view.getId() != View.NO_ID) {
1410                buffer.append(resources.getResourceEntryName(node.view.getId()));
1411            } else {
1412                buffer.append("NO_ID");
1413            }
1414        }
1415
1416        private static void printNode(Resources resources, Node node) {
1417            if (node.dependents.size() == 0) {
1418                printViewId(resources, node.view);
1419            } else {
1420                for (Node dependent : node.dependents.keySet()) {
1421                    StringBuilder buffer = new StringBuilder();
1422                    appendViewId(resources, node, buffer);
1423                    printdependents(resources, dependent, buffer);
1424                }
1425            }
1426        }
1427
1428        private static void printdependents(Resources resources, Node node, StringBuilder buffer) {
1429            buffer.append(" -> ");
1430            appendViewId(resources, node, buffer);
1431
1432            if (node.dependents.size() == 0) {
1433                d(LOG_TAG, buffer.toString());
1434            } else {
1435                for (Node dependent : node.dependents.keySet()) {
1436                    StringBuilder subBuffer = new StringBuilder(buffer);
1437                    printdependents(resources, dependent, subBuffer);
1438                }
1439            }
1440        }
1441
1442        /**
1443         * A node in the dependency graph. A node is a view, its list of dependencies
1444         * and its list of dependents.
1445         *
1446         * A node with no dependent is considered a root of the graph.
1447         */
1448        static class Node implements Poolable<Node> {
1449            /**
1450             * The view representing this node in the layout.
1451             */
1452            View view;
1453
1454            /**
1455             * The list of dependents for this node; a dependent is a node
1456             * that needs this node to be processed first.
1457             */
1458            final HashMap<Node, DependencyGraph> dependents = new HashMap<Node, DependencyGraph>();
1459
1460            /**
1461             * The list of dependencies for this node.
1462             */
1463            final SparseArray<Node> dependencies = new SparseArray<Node>();
1464
1465            /*
1466             * START POOL IMPLEMENTATION
1467             */
1468            // The pool is static, so all nodes instances are shared across
1469            // activities, that's why we give it a rather high limit
1470            private static final int POOL_LIMIT = 100;
1471            private static final Pool<Node> sPool = Pools.synchronizedPool(
1472                    Pools.finitePool(new PoolableManager<Node>() {
1473                        public Node newInstance() {
1474                            return new Node();
1475                        }
1476
1477                        public void onAcquired(Node element) {
1478                        }
1479
1480                        public void onReleased(Node element) {
1481                        }
1482                    }, POOL_LIMIT)
1483            );
1484
1485            private Node mNext;
1486            private boolean mIsPooled;
1487
1488            public void setNextPoolable(Node element) {
1489                mNext = element;
1490            }
1491
1492            public Node getNextPoolable() {
1493                return mNext;
1494            }
1495
1496            public boolean isPooled() {
1497                return mIsPooled;
1498            }
1499
1500            public void setPooled(boolean isPooled) {
1501                mIsPooled = isPooled;
1502            }
1503
1504            static Node acquire(View view) {
1505                final Node node = sPool.acquire();
1506                node.view = view;
1507
1508                return node;
1509            }
1510
1511            void release() {
1512                view = null;
1513                dependents.clear();
1514                dependencies.clear();
1515
1516                sPool.release(this);
1517            }
1518            /*
1519             * END POOL IMPLEMENTATION
1520             */
1521        }
1522    }
1523}
1524