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,
    212                 padding.right, padding.bottom);
    213         setRecyclerView(recyclerView);
    214         recyclerView.waitUntilLayout();
    215         Snapshot snapshot = takeSnapshot();
    216         int index = 0;
    217         Rect tmp = new Rect();
    218         for (BaseWrapContentWithAspectRatioTest.MeasureBehavior behavior : adapter.behaviors) {
    219             tmp.set(expected[index]);
    220             tmp.offset(padding.left, padding.top);
    221             assertThat("behavior " + index, snapshot.mChildCoordinates.get(behavior.getId()),
    222                     is(tmp));
    223             index ++;
    224         }
    225         Rect boundingBox = new Rect(0, 0, 0, 0);
    226         for (Rect rect : expected) {
    227             boundingBox.union(rect);
    228         }
    229         assertThat(recyclerView.getWidth(), is(width + padding.left + padding.right));
    230         assertThat(recyclerView.getHeight(), is(height + padding.top + padding.bottom));
    231     }
    232 
    233 
    234     abstract protected int getVerticalGravity(RecyclerView.LayoutManager layoutManager);
    235 
    236     abstract protected int getHorizontalGravity(RecyclerView.LayoutManager layoutManager);
    237 
    238     protected Snapshot takeSnapshot() throws Throwable {
    239         Snapshot snapshot = new Snapshot(mRecyclerView, mLoggingItemAnimator,
    240                 getHorizontalGravity(mLayoutManager), getVerticalGravity(mLayoutManager));
    241         return snapshot;
    242     }
    243 
    244     abstract class Scenario {
    245 
    246         ArrayList<Step> mStepList = new ArrayList<>();
    247 
    248         public Scenario(Step... steps) {
    249             Collections.addAll(mStepList, steps);
    250         }
    251 
    252         public int getSeedAdapterSize() {
    253             return 10;
    254         }
    255 
    256         public RecyclerView.LayoutManager createLayoutManager() {
    257             return BaseWrapContentTest.this.createLayoutManager();
    258         }
    259     }
    260 
    261     abstract static class Step {
    262 
    263         abstract void onRun() throws Throwable;
    264     }
    265 
    266     class Snapshot {
    267 
    268         Rect mRawChildrenBox = new Rect();
    269 
    270         Rect mRvSize = new Rect();
    271 
    272         Rect mRvPadding = new Rect();
    273 
    274         Rect mRvParentSize = new Rect();
    275 
    276         LongSparseArray<Rect> mChildCoordinates = new LongSparseArray<>();
    277 
    278         LongSparseArray<String> mAppear = new LongSparseArray<>();
    279 
    280         LongSparseArray<String> mDisappear = new LongSparseArray<>();
    281 
    282         LongSparseArray<String> mPersistent = new LongSparseArray<>();
    283 
    284         LongSparseArray<String> mChanged = new LongSparseArray<>();
    285 
    286         int mVerticalGravity;
    287 
    288         int mHorizontalGravity;
    289 
    290         int mOffsetX, mOffsetY;// how much we should offset children
    291 
    292         public Snapshot(RecyclerView recyclerView, LoggingItemAnimator loggingItemAnimator,
    293                 int horizontalGravity, int verticalGravity)
    294                 throws Throwable {
    295             mRvSize = getViewBounds(recyclerView);
    296             mRvParentSize = getViewBounds((View) recyclerView.getParent());
    297             mRvPadding = new Rect(recyclerView.getPaddingLeft(), recyclerView.getPaddingTop(),
    298                     recyclerView.getPaddingRight(), recyclerView.getPaddingBottom());
    299             mVerticalGravity = verticalGravity;
    300             mHorizontalGravity = horizontalGravity;
    301             if (mVerticalGravity == Gravity.TOP) {
    302                 mOffsetY = 0;
    303             } else {
    304                 mOffsetY = mRvParentSize.bottom - mRvSize.bottom;
    305             }
    306 
    307             if (mHorizontalGravity == Gravity.LEFT) {
    308                 mOffsetX = 0;
    309             } else {
    310                 mOffsetX = mRvParentSize.right - mRvSize.right;
    311             }
    312             collectChildCoordinates(recyclerView);
    313             if (loggingItemAnimator != null) {
    314                 collectInto(mAppear, loggingItemAnimator.mAnimateAppearanceList);
    315                 collectInto(mDisappear, loggingItemAnimator.mAnimateDisappearanceList);
    316                 collectInto(mPersistent, loggingItemAnimator.mAnimatePersistenceList);
    317                 collectInto(mChanged, loggingItemAnimator.mAnimateChangeList);
    318             }
    319         }
    320 
    321         public boolean doesChildrenFitVertically() {
    322             return mRawChildrenBox.top >= mRvPadding.top
    323                     && mRawChildrenBox.bottom <= mRvSize.bottom - mRvPadding.bottom;
    324         }
    325 
    326         public boolean doesChildrenFitHorizontally() {
    327             return mRawChildrenBox.left >= mRvPadding.left
    328                     && mRawChildrenBox.right <= mRvSize.right - mRvPadding.right;
    329         }
    330 
    331         public void assertSame(Snapshot other, int step) {
    332             if (mWrapContentConfig.isUnlimitedHeight() &&
    333                     (!doesChildrenFitVertically() || !other.doesChildrenFitVertically())) {
    334                 if (DEBUG) {
    335                     Log.d(TAG, "cannot assert coordinates because it does not fit vertically");
    336                 }
    337                 return;
    338             }
    339             if (mWrapContentConfig.isUnlimitedWidth() &&
    340                     (!doesChildrenFitHorizontally() || !other.doesChildrenFitHorizontally())) {
    341                 if (DEBUG) {
    342                     Log.d(TAG, "cannot assert coordinates because it does not fit horizontally");
    343                 }
    344                 return;
    345             }
    346             assertMap("child coordinates. step:" + step, mChildCoordinates,
    347                     other.mChildCoordinates);
    348             if (mWrapContentConfig.isUnlimitedHeight() || mWrapContentConfig.isUnlimitedWidth()) {
    349                 return;//cannot assert animatinos in unlimited size
    350             }
    351             assertMap("appearing step:" + step, mAppear, other.mAppear);
    352             assertMap("disappearing step:" + step, mDisappear, other.mDisappear);
    353             assertMap("persistent step:" + step, mPersistent, other.mPersistent);
    354             assertMap("changed step:" + step, mChanged, other.mChanged);
    355         }
    356 
    357         private void assertMap(String prefix, LongSparseArray<?> map1, LongSparseArray<?> map2) {
    358             StringBuilder logBuilder = new StringBuilder();
    359             logBuilder.append(prefix).append("\n");
    360             logBuilder.append("map1").append("\n");
    361             logInto(map1, logBuilder);
    362             logBuilder.append("map2").append("\n");
    363             logInto(map2, logBuilder);
    364             final String log = logBuilder.toString();
    365             assertEquals(log + " same size", map1.size(), map2.size());
    366             for (int i = 0; i < map1.size(); i++) {
    367                 assertAtIndex(log, map1, map2, i);
    368             }
    369         }
    370 
    371         private void assertAtIndex(String prefix, LongSparseArray<?> map1, LongSparseArray<?> map2,
    372                 int index) {
    373             long key1 = map1.keyAt(index);
    374             long key2 = map2.keyAt(index);
    375             assertEquals(prefix + "key mismatch at index " + index, key1, key2);
    376             Object value1 = map1.valueAt(index);
    377             Object value2 = map2.valueAt(index);
    378             assertEquals(prefix + " value mismatch at index " + index, value1, value2);
    379         }
    380 
    381         private void logInto(LongSparseArray<?> map, StringBuilder sb) {
    382             for (int i = 0; i < map.size(); i++) {
    383                 long key = map.keyAt(i);
    384                 Object value = map.valueAt(i);
    385                 sb.append(key).append(" : ").append(value).append("\n");
    386             }
    387         }
    388 
    389         @Override
    390         public String toString() {
    391             StringBuilder sb = new StringBuilder("Snapshot{\n");
    392             sb.append("child coordinates:\n");
    393             logInto(mChildCoordinates, sb);
    394             sb.append("appear animations:\n");
    395             logInto(mAppear, sb);
    396             sb.append("disappear animations:\n");
    397             logInto(mDisappear, sb);
    398             sb.append("change animations:\n");
    399             logInto(mChanged, sb);
    400             sb.append("persistent animations:\n");
    401             logInto(mPersistent, sb);
    402             sb.append("}");
    403             return sb.toString();
    404         }
    405 
    406         @Override
    407         public int hashCode() {
    408             int result = mChildCoordinates.hashCode();
    409             result = 31 * result + mAppear.hashCode();
    410             result = 31 * result + mDisappear.hashCode();
    411             result = 31 * result + mPersistent.hashCode();
    412             result = 31 * result + mChanged.hashCode();
    413             return result;
    414         }
    415 
    416         private void collectInto(
    417                 LongSparseArray<String> target,
    418                 List<? extends BaseRecyclerViewAnimationsTest.AnimateLogBase> list) {
    419             for (BaseRecyclerViewAnimationsTest.AnimateLogBase base : list) {
    420                 long id = getItemId(base.viewHolder);
    421                 assertNull(target.get(id));
    422                 target.put(id, log(base));
    423             }
    424         }
    425 
    426         private String log(BaseRecyclerViewAnimationsTest.AnimateLogBase base) {
    427             return base.getClass().getSimpleName() +
    428                     ((TextView) base.viewHolder.itemView).getText() + ": " +
    429                     "[pre:" + log(base.postInfo) +
    430                     ", post:" + log(base.postInfo) + "]";
    431         }
    432 
    433         private String log(BaseRecyclerViewAnimationsTest.LoggingInfo postInfo) {
    434             if (postInfo == null) {
    435                 return "?";
    436             }
    437             return "PI[flags: " + postInfo.changeFlags
    438                     + ",l:" + (postInfo.left + mOffsetX)
    439                     + ",t:" + (postInfo.top + mOffsetY)
    440                     + ",r:" + (postInfo.right + mOffsetX)
    441                     + ",b:" + (postInfo.bottom + mOffsetY) + "]";
    442         }
    443 
    444         void collectChildCoordinates(RecyclerView recyclerView) throws Throwable {
    445             mRawChildrenBox = new Rect(0, 0, 0, 0);
    446             final int childCount = recyclerView.getChildCount();
    447             for (int i = 0; i < childCount; i++) {
    448                 View child = recyclerView.getChildAt(i);
    449                 Rect childBounds = getChildBounds(recyclerView, child, true);
    450                 mRawChildrenBox.union(getChildBounds(recyclerView, child, false));
    451                 RecyclerView.ViewHolder childViewHolder = recyclerView.getChildViewHolder(child);
    452                 mChildCoordinates.put(getItemId(childViewHolder), childBounds);
    453             }
    454         }
    455 
    456         private Rect getViewBounds(View view) {
    457             return new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
    458         }
    459 
    460         private Rect getChildBounds(RecyclerView recyclerView, View child, boolean offset) {
    461             RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
    462             RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
    463             Rect rect = new Rect(layoutManager.getDecoratedLeft(child) - lp.leftMargin,
    464                     layoutManager.getDecoratedTop(child) - lp.topMargin,
    465                     layoutManager.getDecoratedRight(child) + lp.rightMargin,
    466                     layoutManager.getDecoratedBottom(child) + lp.bottomMargin);
    467             if (offset) {
    468                 rect.offset(mOffsetX, mOffsetY);
    469             }
    470             return rect;
    471         }
    472 
    473         private long getItemId(RecyclerView.ViewHolder vh) {
    474             if (vh instanceof TestViewHolder) {
    475                 return ((TestViewHolder) vh).mBoundItem.mId;
    476             } else if (vh instanceof BaseWrapContentWithAspectRatioTest.WrapContentViewHolder) {
    477                 BaseWrapContentWithAspectRatioTest.WrapContentViewHolder casted =
    478                         (BaseWrapContentWithAspectRatioTest.WrapContentViewHolder) vh;
    479                 return casted.mView.mBehavior.getId();
    480             } else {
    481                 throw new IllegalArgumentException("i don't support any VH");
    482             }
    483         }
    484 
    485         public void assertRvSize() {
    486             if (shouldWrapContentHorizontally()) {
    487                 int expectedW = mRawChildrenBox.width() + mRvPadding.left + mRvPadding.right;
    488                 assertTrue(mRvSize.width() + " <= " + expectedW, mRvSize.width() <= expectedW);
    489             }
    490             if (shouldWrapContentVertically()) {
    491                 int expectedH = mRawChildrenBox.height() + mRvPadding.top + mRvPadding.bottom;
    492                 assertTrue(mRvSize.height() + "<=" + expectedH, mRvSize.height() <= expectedH);
    493             }
    494         }
    495     }
    496 
    497     protected boolean shouldWrapContentHorizontally() {
    498         return true;
    499     }
    500 
    501     protected boolean shouldWrapContentVertically() {
    502         return true;
    503     }
    504 
    505     static class WrappedRecyclerView extends RecyclerView {
    506 
    507         public WrappedRecyclerView(Context context) {
    508             super(context);
    509         }
    510 
    511         public void waitUntilLayout() {
    512             while (isLayoutRequested()) {
    513                 try {
    514                     Thread.sleep(100);
    515                 } catch (InterruptedException e) {
    516                     e.printStackTrace();
    517                 }
    518             }
    519         }
    520 
    521         public void waitUntilAnimations() throws InterruptedException {
    522             final CountDownLatch latch = new CountDownLatch(1);
    523             if (mItemAnimator == null || !mItemAnimator.isRunning(
    524                     new ItemAnimator.ItemAnimatorFinishedListener() {
    525                         @Override
    526                         public void onAnimationsFinished() {
    527                             latch.countDown();
    528                         }
    529                     })) {
    530                 latch.countDown();
    531             }
    532             MatcherAssert.assertThat("waiting too long for animations",
    533                     latch.await(60, TimeUnit.SECONDS), CoreMatchers.is(true));
    534         }
    535 
    536         @Override
    537         protected void onLayout(boolean changed, int l, int t, int r, int b) {
    538             super.onLayout(changed, l, t, r, b);
    539         }
    540     }
    541 
    542     static class WrapContentConfig {
    543 
    544         public boolean unlimitedWidth;
    545         public boolean unlimitedHeight;
    546         public Rect padding = new Rect(0, 0, 0, 0);
    547 
    548         public WrapContentConfig(boolean unlimitedWidth, boolean unlimitedHeight) {
    549             this.unlimitedWidth = unlimitedWidth;
    550             this.unlimitedHeight = unlimitedHeight;
    551         }
    552 
    553         public WrapContentConfig(boolean unlimitedWidth, boolean unlimitedHeight, Rect padding) {
    554             this.unlimitedWidth = unlimitedWidth;
    555             this.unlimitedHeight = unlimitedHeight;
    556             this.padding.set(padding);
    557         }
    558 
    559         public boolean isUnlimitedWidth() {
    560             return unlimitedWidth;
    561         }
    562 
    563         public WrapContentConfig setUnlimitedWidth(boolean unlimitedWidth) {
    564             this.unlimitedWidth = unlimitedWidth;
    565             return this;
    566         }
    567 
    568         public boolean isUnlimitedHeight() {
    569             return unlimitedHeight;
    570         }
    571 
    572         public WrapContentConfig setUnlimitedHeight(boolean unlimitedHeight) {
    573             this.unlimitedHeight = unlimitedHeight;
    574             return this;
    575         }
    576 
    577         @Override
    578         public String toString() {
    579             return "WrapContentConfig{"
    580                     + "unlimitedWidth=" + unlimitedWidth
    581                     + ", unlimitedHeight=" + unlimitedHeight
    582                     + ", padding=" + padding
    583                     + '}';
    584         }
    585 
    586         public TestedFrameLayout.FullControlLayoutParams toLayoutParams(int wDim, int hDim) {
    587             TestedFrameLayout.FullControlLayoutParams
    588                     lp = new TestedFrameLayout.FullControlLayoutParams(
    589                     wDim, hDim);
    590             if (unlimitedWidth) {
    591                 lp.wSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    592             }
    593             if (unlimitedHeight) {
    594                 lp.hSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    595             }
    596             return lp;
    597         }
    598     }
    599 }
    600