Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2015 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 package android.support.v7.widget;
     17 
     18 import static android.support.v7.widget.StaggeredGridLayoutManager.HORIZONTAL;
     19 
     20 import static org.hamcrest.CoreMatchers.is;
     21 import static org.hamcrest.MatcherAssert.assertThat;
     22 import static org.junit.Assert.assertEquals;
     23 import static org.junit.Assert.assertNull;
     24 import static org.junit.Assert.assertTrue;
     25 
     26 import android.app.Activity;
     27 import android.content.Context;
     28 import android.graphics.Color;
     29 import android.graphics.Rect;
     30 import android.support.annotation.Nullable;
     31 import android.support.v4.util.LongSparseArray;
     32 import android.support.v7.widget.TestedFrameLayout.FullControlLayoutParams;
     33 import android.util.Log;
     34 import android.view.Gravity;
     35 import android.view.View;
     36 import android.view.ViewGroup;
     37 import android.widget.TextView;
     38 
     39 import org.hamcrest.CoreMatchers;
     40 import org.junit.Test;
     41 
     42 import org.hamcrest.CoreMatchers;
     43 import org.hamcrest.MatcherAssert;
     44 
     45 import java.util.ArrayList;
     46 import java.util.Collections;
     47 import java.util.List;
     48 import java.util.concurrent.CountDownLatch;
     49 import java.util.concurrent.TimeUnit;
     50 
     51 /**
     52  * Class to test any generic wrap content behavior.
     53  * It does so by running the same view scenario twice. Once with match parent setup to record all
     54  * dimensions and once with wrap_content setup. Then compares all child locations & ids +
     55  * RecyclerView size.
     56  */
     57 abstract public class BaseWrapContentTest extends BaseRecyclerViewInstrumentationTest {
     58 
     59     static final boolean DEBUG = false;
     60     static final String TAG = "WrapContentTest";
     61     RecyclerView.LayoutManager mLayoutManager;
     62 
     63     TestAdapter mTestAdapter;
     64 
     65     LoggingItemAnimator mLoggingItemAnimator;
     66 
     67     boolean mIsWrapContent;
     68 
     69     protected final WrapContentConfig mWrapContentConfig;
     70 
     71     public BaseWrapContentTest(WrapContentConfig config) {
     72         mWrapContentConfig = config;
     73     }
     74 
     75     abstract RecyclerView.LayoutManager createLayoutManager();
     76 
     77     void unspecifiedWithHintTest(boolean horizontal) throws Throwable {
     78         final int itemHeight = 20;
     79         final int itemWidth = 15;
     80         RecyclerView.LayoutManager layoutManager = createLayoutManager();
     81         WrappedRecyclerView rv = createRecyclerView(getActivity());
     82         TestAdapter testAdapter = new TestAdapter(20) {
     83             @Override
     84             public void onBindViewHolder(TestViewHolder holder,
     85                     int position) {
     86                 super.onBindViewHolder(holder, position);
     87                 holder.itemView.setLayoutParams(new ViewGroup.LayoutParams(itemWidth, itemHeight));
     88             }
     89         };
     90         rv.setLayoutManager(layoutManager);
     91         rv.setAdapter(testAdapter);
     92         TestedFrameLayout.FullControlLayoutParams lp =
     93                 new TestedFrameLayout.FullControlLayoutParams(0, 0);
     94         if (horizontal) {
     95             lp.wSpec = View.MeasureSpec.makeMeasureSpec(25, View.MeasureSpec.UNSPECIFIED);
     96             lp.hSpec = View.MeasureSpec.makeMeasureSpec(50, View.MeasureSpec.AT_MOST);
     97         } else {
     98             lp.hSpec = View.MeasureSpec.makeMeasureSpec(25, View.MeasureSpec.UNSPECIFIED);
     99             lp.wSpec = View.MeasureSpec.makeMeasureSpec(50, View.MeasureSpec.AT_MOST);
    100         }
    101         rv.setLayoutParams(lp);
    102         setRecyclerView(rv);
    103         rv.waitUntilLayout();
    104 
    105         // we don't assert against the given size hint because LM will still ask for more if it
    106         // lays out more children. This is the correct behavior because the spec is not AT_MOST,
    107         // it is UNSPECIFIED.
    108         if (horizontal) {
    109             int expectedWidth = rv.getPaddingLeft() + rv.getPaddingRight() + itemWidth;
    110             while (expectedWidth < 25) {
    111                 expectedWidth += itemWidth;
    112             }
    113             assertThat(rv.getWidth(), CoreMatchers.is(expectedWidth));
    114         } else {
    115             int expectedHeight = rv.getPaddingTop() + rv.getPaddingBottom() + itemHeight;
    116             while (expectedHeight < 25) {
    117                 expectedHeight += itemHeight;
    118             }
    119             assertThat(rv.getHeight(), CoreMatchers.is(expectedHeight));
    120         }
    121     }
    122 
    123     protected void testScenerio(Scenario scenario) throws Throwable {
    124         FullControlLayoutParams matchParent = new FullControlLayoutParams(
    125                 ViewGroup.LayoutParams.MATCH_PARENT,
    126                 ViewGroup.LayoutParams.MATCH_PARENT);
    127         FullControlLayoutParams wrapContent = new FullControlLayoutParams(
    128                 ViewGroup.LayoutParams.WRAP_CONTENT,
    129                 ViewGroup.LayoutParams.WRAP_CONTENT);
    130         if (mWrapContentConfig.isUnlimitedHeight()) {
    131             wrapContent.hSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    132         }
    133         if (mWrapContentConfig.isUnlimitedWidth()) {
    134             wrapContent.wSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    135         }
    136 
    137         mIsWrapContent = false;
    138         List<Snapshot> s1 = runScenario(scenario, matchParent, null);
    139         mIsWrapContent = true;
    140 
    141         List<Snapshot> s2 = runScenario(scenario, wrapContent, s1);
    142         assertEquals("test sanity", s1.size(), s2.size());
    143 
    144         for (int i = 0; i < s1.size(); i++) {
    145             Snapshot step1 = s1.get(i);
    146             Snapshot step2 = s2.get(i);
    147             step1.assertSame(step2, i);
    148         }
    149     }
    150 
    151     public List<Snapshot> runScenario(Scenario scenario, ViewGroup.LayoutParams lp,
    152             @Nullable List<Snapshot> compareWith)
    153             throws Throwable {
    154         removeRecyclerView();
    155         Item.idCounter.set(0);
    156         List<Snapshot> result = new ArrayList<>();
    157         RecyclerView.LayoutManager layoutManager = scenario.createLayoutManager();
    158         WrappedRecyclerView recyclerView = new WrappedRecyclerView(getActivity());
    159         recyclerView.setBackgroundColor(Color.rgb(0, 0, 255));
    160         recyclerView.setLayoutManager(layoutManager);
    161         recyclerView.setLayoutParams(lp);
    162         mLayoutManager = layoutManager;
    163         mTestAdapter = new TestAdapter(scenario.getSeedAdapterSize());
    164         recyclerView.setAdapter(mTestAdapter);
    165         mLoggingItemAnimator = new LoggingItemAnimator();
    166         recyclerView.setItemAnimator(mLoggingItemAnimator);
    167         setRecyclerView(recyclerView);
    168         recyclerView.waitUntilLayout();
    169         int stepIndex = 0;
    170         for (Step step : scenario.mStepList) {
    171             mLoggingItemAnimator.reset();
    172             step.onRun();
    173             recyclerView.waitUntilLayout();
    174             recyclerView.waitUntilAnimations();
    175             Snapshot snapshot = takeSnapshot();
    176             if (mIsWrapContent) {
    177                 snapshot.assertRvSize();
    178             }
    179             result.add(snapshot);
    180             if (compareWith != null) {
    181                 compareWith.get(stepIndex).assertSame(snapshot, stepIndex);
    182             }
    183             stepIndex++;
    184         }
    185         recyclerView.waitUntilLayout();
    186         recyclerView.waitUntilAnimations();
    187         Snapshot snapshot = takeSnapshot();
    188         if (mIsWrapContent) {
    189             snapshot.assertRvSize();
    190         }
    191         result.add(snapshot);
    192         if (compareWith != null) {
    193             compareWith.get(stepIndex).assertSame(snapshot, stepIndex);
    194         }
    195         return result;
    196     }
    197 
    198     protected WrappedRecyclerView createRecyclerView(Activity activity) {
    199         return new WrappedRecyclerView(getActivity());
    200     }
    201 
    202     void layoutAndCheck(TestedFrameLayout.FullControlLayoutParams lp,
    203             BaseWrapContentWithAspectRatioTest.WrapContentAdapter adapter, Rect[] expected,
    204             int width, int height) throws Throwable {
    205         WrappedRecyclerView recyclerView = createRecyclerView(getActivity());
    206         recyclerView.setBackgroundColor(Color.rgb(0, 0, 255));
    207         recyclerView.setLayoutManager(createLayoutManager());
    208         recyclerView.setAdapter(adapter);
    209         recyclerView.setLayoutParams(lp);
    210         Rect padding = mWrapContentConfig.padding;
    211         recyclerView.setPadding(padding.left, padding.top, padding.right, padding.bottom);
    212         setRecyclerView(recyclerView);
    213         recyclerView.waitUntilLayout();
    214         Snapshot snapshot = takeSnapshot();
    215         int index = 0;
    216         Rect tmp = new Rect();
    217         for (BaseWrapContentWithAspectRatioTest.MeasureBehavior behavior : adapter.behaviors) {
    218             tmp.set(expected[index]);
    219             tmp.offset(padding.left, padding.top);
    220             assertThat("behavior " + index, snapshot.mChildCoordinates.get(behavior.getId()),
    221                     is(tmp));
    222             index ++;
    223         }
    224         Rect boundingBox = new Rect(0, 0, 0, 0);
    225         for (Rect rect : expected) {
    226             boundingBox.union(rect);
    227         }
    228         assertThat(recyclerView.getWidth(), is(width + padding.left + padding.right));
    229         assertThat(recyclerView.getHeight(), is(height + padding.top + padding.bottom));
    230     }
    231 
    232 
    233     abstract protected int getVerticalGravity(RecyclerView.LayoutManager layoutManager);
    234 
    235     abstract protected int getHorizontalGravity(RecyclerView.LayoutManager layoutManager);
    236 
    237     protected Snapshot takeSnapshot() throws Throwable {
    238         Snapshot snapshot = new Snapshot(mRecyclerView, mLoggingItemAnimator,
    239                 getHorizontalGravity(mLayoutManager), getVerticalGravity(mLayoutManager));
    240         return snapshot;
    241     }
    242 
    243     abstract class Scenario {
    244 
    245         ArrayList<Step> mStepList = new ArrayList<>();
    246 
    247         public Scenario(Step... steps) {
    248             Collections.addAll(mStepList, steps);
    249         }
    250 
    251         public int getSeedAdapterSize() {
    252             return 10;
    253         }
    254 
    255         public RecyclerView.LayoutManager createLayoutManager() {
    256             return BaseWrapContentTest.this.createLayoutManager();
    257         }
    258     }
    259 
    260     abstract static class Step {
    261 
    262         abstract void onRun() throws Throwable;
    263     }
    264 
    265     class Snapshot {
    266 
    267         Rect mRawChildrenBox = new Rect();
    268 
    269         Rect mRvSize = new Rect();
    270 
    271         Rect mRvPadding = new Rect();
    272 
    273         Rect mRvParentSize = new Rect();
    274 
    275         LongSparseArray<Rect> mChildCoordinates = new LongSparseArray<>();
    276 
    277         LongSparseArray<String> mAppear = new LongSparseArray<>();
    278 
    279         LongSparseArray<String> mDisappear = new LongSparseArray<>();
    280 
    281         LongSparseArray<String> mPersistent = new LongSparseArray<>();
    282 
    283         LongSparseArray<String> mChanged = new LongSparseArray<>();
    284 
    285         int mVerticalGravity;
    286 
    287         int mHorizontalGravity;
    288 
    289         int mOffsetX, mOffsetY;// how much we should offset children
    290 
    291         public Snapshot(RecyclerView recyclerView, LoggingItemAnimator loggingItemAnimator,
    292                 int horizontalGravity, int verticalGravity)
    293                 throws Throwable {
    294             mRvSize = getViewBounds(recyclerView);
    295             mRvParentSize = getViewBounds((View) recyclerView.getParent());
    296             mRvPadding = new Rect(recyclerView.getPaddingLeft(), recyclerView.getPaddingTop(),
    297                     recyclerView.getPaddingRight(), recyclerView.getPaddingBottom());
    298             mVerticalGravity = verticalGravity;
    299             mHorizontalGravity = horizontalGravity;
    300             if (mVerticalGravity == Gravity.TOP) {
    301                 mOffsetY = 0;
    302             } else {
    303                 mOffsetY = mRvParentSize.bottom - mRvSize.bottom;
    304             }
    305 
    306             if (mHorizontalGravity == Gravity.LEFT) {
    307                 mOffsetX = 0;
    308             } else {
    309                 mOffsetX = mRvParentSize.right - mRvSize.right;
    310             }
    311             collectChildCoordinates(recyclerView);
    312             if (loggingItemAnimator != null) {
    313                 collectInto(mAppear, loggingItemAnimator.mAnimateAppearanceList);
    314                 collectInto(mDisappear, loggingItemAnimator.mAnimateDisappearanceList);
    315                 collectInto(mPersistent, loggingItemAnimator.mAnimatePersistenceList);
    316                 collectInto(mChanged, loggingItemAnimator.mAnimateChangeList);
    317             }
    318         }
    319 
    320         public boolean doesChildrenFitVertically() {
    321             return mRawChildrenBox.top >= mRvPadding.top
    322                     && mRawChildrenBox.bottom <= mRvSize.bottom - mRvPadding.bottom;
    323         }
    324 
    325         public boolean doesChildrenFitHorizontally() {
    326             return mRawChildrenBox.left >= mRvPadding.left
    327                     && mRawChildrenBox.right <= mRvSize.right - mRvPadding.right;
    328         }
    329 
    330         public void assertSame(Snapshot other, int step) {
    331             if (mWrapContentConfig.isUnlimitedHeight() &&
    332                     (!doesChildrenFitVertically() || !other.doesChildrenFitVertically())) {
    333                 if (DEBUG) {
    334                     Log.d(TAG, "cannot assert coordinates because it does not fit vertically");
    335                 }
    336                 return;
    337             }
    338             if (mWrapContentConfig.isUnlimitedWidth() &&
    339                     (!doesChildrenFitHorizontally() || !other.doesChildrenFitHorizontally())) {
    340                 if (DEBUG) {
    341                     Log.d(TAG, "cannot assert coordinates because it does not fit horizontally");
    342                 }
    343                 return;
    344             }
    345             assertMap("child coordinates. step:" + step, mChildCoordinates,
    346                     other.mChildCoordinates);
    347             if (mWrapContentConfig.isUnlimitedHeight() || mWrapContentConfig.isUnlimitedWidth()) {
    348                 return;//cannot assert animatinos in unlimited size
    349             }
    350             assertMap("appearing step:" + step, mAppear, other.mAppear);
    351             assertMap("disappearing step:" + step, mDisappear, other.mDisappear);
    352             assertMap("persistent step:" + step, mPersistent, other.mPersistent);
    353             assertMap("changed step:" + step, mChanged, other.mChanged);
    354         }
    355 
    356         private void assertMap(String prefix, LongSparseArray<?> map1, LongSparseArray<?> map2) {
    357             StringBuilder logBuilder = new StringBuilder();
    358             logBuilder.append(prefix).append("\n");
    359             logBuilder.append("map1").append("\n");
    360             logInto(map1, logBuilder);
    361             logBuilder.append("map2").append("\n");
    362             logInto(map2, logBuilder);
    363             final String log = logBuilder.toString();
    364             assertEquals(log + " same size", map1.size(), map2.size());
    365             for (int i = 0; i < map1.size(); i++) {
    366                 assertAtIndex(log, map1, map2, i);
    367             }
    368         }
    369 
    370         private void assertAtIndex(String prefix, LongSparseArray<?> map1, LongSparseArray<?> map2,
    371                 int index) {
    372             long key1 = map1.keyAt(index);
    373             long key2 = map2.keyAt(index);
    374             assertEquals(prefix + "key mismatch at index " + index, key1, key2);
    375             Object value1 = map1.valueAt(index);
    376             Object value2 = map2.valueAt(index);
    377             assertEquals(prefix + " value mismatch at index " + index, value1, value2);
    378         }
    379 
    380         private void logInto(LongSparseArray<?> map, StringBuilder sb) {
    381             for (int i = 0; i < map.size(); i++) {
    382                 long key = map.keyAt(i);
    383                 Object value = map.valueAt(i);
    384                 sb.append(key).append(" : ").append(value).append("\n");
    385             }
    386         }
    387 
    388         @Override
    389         public String toString() {
    390             StringBuilder sb = new StringBuilder("Snapshot{\n");
    391             sb.append("child coordinates:\n");
    392             logInto(mChildCoordinates, sb);
    393             sb.append("appear animations:\n");
    394             logInto(mAppear, sb);
    395             sb.append("disappear animations:\n");
    396             logInto(mDisappear, sb);
    397             sb.append("change animations:\n");
    398             logInto(mChanged, sb);
    399             sb.append("persistent animations:\n");
    400             logInto(mPersistent, sb);
    401             sb.append("}");
    402             return sb.toString();
    403         }
    404 
    405         @Override
    406         public int hashCode() {
    407             int result = mChildCoordinates.hashCode();
    408             result = 31 * result + mAppear.hashCode();
    409             result = 31 * result + mDisappear.hashCode();
    410             result = 31 * result + mPersistent.hashCode();
    411             result = 31 * result + mChanged.hashCode();
    412             return result;
    413         }
    414 
    415         private void collectInto(
    416                 LongSparseArray<String> target,
    417                 List<? extends BaseRecyclerViewAnimationsTest.AnimateLogBase> list) {
    418             for (BaseRecyclerViewAnimationsTest.AnimateLogBase base : list) {
    419                 long id = getItemId(base.viewHolder);
    420                 assertNull(target.get(id));
    421                 target.put(id, log(base));
    422             }
    423         }
    424 
    425         private String log(BaseRecyclerViewAnimationsTest.AnimateLogBase base) {
    426             return base.getClass().getSimpleName() +
    427                     ((TextView) base.viewHolder.itemView).getText() + ": " +
    428                     "[pre:" + log(base.postInfo) +
    429                     ", post:" + log(base.postInfo) + "]";
    430         }
    431 
    432         private String log(BaseRecyclerViewAnimationsTest.LoggingInfo postInfo) {
    433             if (postInfo == null) {
    434                 return "?";
    435             }
    436             return "PI[flags: " + postInfo.changeFlags
    437                     + ",l:" + (postInfo.left + mOffsetX)
    438                     + ",t:" + (postInfo.top + mOffsetY)
    439                     + ",r:" + (postInfo.right + mOffsetX)
    440                     + ",b:" + (postInfo.bottom + mOffsetY) + "]";
    441         }
    442 
    443         void collectChildCoordinates(RecyclerView recyclerView) throws Throwable {
    444             mRawChildrenBox = new Rect(0, 0, 0, 0);
    445             final int childCount = recyclerView.getChildCount();
    446             for (int i = 0; i < childCount; i++) {
    447                 View child = recyclerView.getChildAt(i);
    448                 Rect childBounds = getChildBounds(recyclerView, child, true);
    449                 mRawChildrenBox.union(getChildBounds(recyclerView, child, false));
    450                 RecyclerView.ViewHolder childViewHolder = recyclerView.getChildViewHolder(child);
    451                 mChildCoordinates.put(getItemId(childViewHolder), childBounds);
    452             }
    453         }
    454 
    455         private Rect getViewBounds(View view) {
    456             return new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
    457         }
    458 
    459         private Rect getChildBounds(RecyclerView recyclerView, View child, boolean offset) {
    460             RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
    461             RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
    462             Rect rect = new Rect(layoutManager.getDecoratedLeft(child) - lp.leftMargin,
    463                     layoutManager.getDecoratedTop(child) - lp.topMargin,
    464                     layoutManager.getDecoratedRight(child) + lp.rightMargin,
    465                     layoutManager.getDecoratedBottom(child) + lp.bottomMargin);
    466             if (offset) {
    467                 rect.offset(mOffsetX, mOffsetY);
    468             }
    469             return rect;
    470         }
    471 
    472         private long getItemId(RecyclerView.ViewHolder vh) {
    473             if (vh instanceof TestViewHolder) {
    474                 return ((TestViewHolder) vh).mBoundItem.mId;
    475             } else if (vh instanceof BaseWrapContentWithAspectRatioTest.WrapContentViewHolder) {
    476                 BaseWrapContentWithAspectRatioTest.WrapContentViewHolder casted =
    477                         (BaseWrapContentWithAspectRatioTest.WrapContentViewHolder) vh;
    478                 return casted.mView.mBehavior.getId();
    479             } else {
    480                 throw new IllegalArgumentException("i don't support any VH");
    481             }
    482         }
    483 
    484         public void assertRvSize() {
    485             if (shouldWrapContentHorizontally()) {
    486                 int expectedW = mRawChildrenBox.width() + mRvPadding.left + mRvPadding.right;
    487                 assertTrue(mRvSize.width() + " <= " + expectedW, mRvSize.width() <= expectedW);
    488             }
    489             if (shouldWrapContentVertically()) {
    490                 int expectedH = mRawChildrenBox.height() + mRvPadding.top + mRvPadding.bottom;
    491                 assertTrue(mRvSize.height() + "<=" + expectedH, mRvSize.height() <= expectedH);
    492             }
    493         }
    494     }
    495 
    496     protected boolean shouldWrapContentHorizontally() {
    497         return true;
    498     }
    499 
    500     protected boolean shouldWrapContentVertically() {
    501         return true;
    502     }
    503 
    504     static class WrappedRecyclerView extends RecyclerView {
    505 
    506         public WrappedRecyclerView(Context context) {
    507             super(context);
    508         }
    509 
    510         public void waitUntilLayout() {
    511             while (isLayoutRequested()) {
    512                 try {
    513                     Thread.sleep(100);
    514                 } catch (InterruptedException e) {
    515                     e.printStackTrace();
    516                 }
    517             }
    518         }
    519 
    520         public void waitUntilAnimations() throws InterruptedException {
    521             final CountDownLatch latch = new CountDownLatch(1);
    522             if (mItemAnimator == null || !mItemAnimator.isRunning(
    523                     new ItemAnimator.ItemAnimatorFinishedListener() {
    524                         @Override
    525                         public void onAnimationsFinished() {
    526                             latch.countDown();
    527                         }
    528                     })) {
    529                 latch.countDown();
    530             }
    531             MatcherAssert.assertThat("waiting too long for animations",
    532                     latch.await(60, TimeUnit.SECONDS), CoreMatchers.is(true));
    533         }
    534 
    535         @Override
    536         protected void onLayout(boolean changed, int l, int t, int r, int b) {
    537             super.onLayout(changed, l, t, r, b);
    538         }
    539     }
    540 
    541     static class WrapContentConfig {
    542 
    543         public boolean unlimitedWidth;
    544         public boolean unlimitedHeight;
    545         public Rect padding = new Rect(0, 0, 0, 0);
    546 
    547         public WrapContentConfig(boolean unlimitedWidth, boolean unlimitedHeight) {
    548             this.unlimitedWidth = unlimitedWidth;
    549             this.unlimitedHeight = unlimitedHeight;
    550         }
    551 
    552         public WrapContentConfig(boolean unlimitedWidth, boolean unlimitedHeight, Rect padding) {
    553             this.unlimitedWidth = unlimitedWidth;
    554             this.unlimitedHeight = unlimitedHeight;
    555             this.padding.set(padding);
    556         }
    557 
    558         public boolean isUnlimitedWidth() {
    559             return unlimitedWidth;
    560         }
    561 
    562         public WrapContentConfig setUnlimitedWidth(boolean unlimitedWidth) {
    563             this.unlimitedWidth = unlimitedWidth;
    564             return this;
    565         }
    566 
    567         public boolean isUnlimitedHeight() {
    568             return unlimitedHeight;
    569         }
    570 
    571         public WrapContentConfig setUnlimitedHeight(boolean unlimitedHeight) {
    572             this.unlimitedHeight = unlimitedHeight;
    573             return this;
    574         }
    575 
    576         @Override
    577         public String toString() {
    578             return "WrapContentConfig{"
    579                     + "unlimitedWidth=" + unlimitedWidth
    580                     + ", unlimitedHeight=" + unlimitedHeight
    581                     + ", padding=" + padding
    582                     + '}';
    583         }
    584 
    585         public TestedFrameLayout.FullControlLayoutParams toLayoutParams(int wDim, int hDim) {
    586             TestedFrameLayout.FullControlLayoutParams
    587                     lp = new TestedFrameLayout.FullControlLayoutParams(
    588                     wDim, hDim);
    589             if (unlimitedWidth) {
    590                 lp.wSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    591             }
    592             if (unlimitedHeight) {
    593                 lp.hSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    594             }
    595             return lp;
    596         }
    597     }
    598 }
    599