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