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 
BaseWrapContentTest(WrapContentConfig config)71     public BaseWrapContentTest(WrapContentConfig config) {
72         mWrapContentConfig = config;
73     }
74 
createLayoutManager()75     abstract RecyclerView.LayoutManager createLayoutManager();
76 
unspecifiedWithHintTest(boolean horizontal)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 
testScenerio(Scenario scenario)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 
runScenario(Scenario scenario, ViewGroup.LayoutParams lp, @Nullable List<Snapshot> compareWith)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 
createRecyclerView(Activity activity)198     protected WrappedRecyclerView createRecyclerView(Activity activity) {
199         return new WrappedRecyclerView(getActivity());
200     }
201 
layoutAndCheck(TestedFrameLayout.FullControlLayoutParams lp, BaseWrapContentWithAspectRatioTest.WrapContentAdapter adapter, Rect[] expected, int width, int height)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 
getVerticalGravity(RecyclerView.LayoutManager layoutManager)234     abstract protected int getVerticalGravity(RecyclerView.LayoutManager layoutManager);
235 
getHorizontalGravity(RecyclerView.LayoutManager layoutManager)236     abstract protected int getHorizontalGravity(RecyclerView.LayoutManager layoutManager);
237 
takeSnapshot()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 
Scenario(Step... steps)248         public Scenario(Step... steps) {
249             Collections.addAll(mStepList, steps);
250         }
251 
getSeedAdapterSize()252         public int getSeedAdapterSize() {
253             return 10;
254         }
255 
createLayoutManager()256         public RecyclerView.LayoutManager createLayoutManager() {
257             return BaseWrapContentTest.this.createLayoutManager();
258         }
259     }
260 
261     abstract static class Step {
262 
onRun()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 
Snapshot(RecyclerView recyclerView, LoggingItemAnimator loggingItemAnimator, int horizontalGravity, int verticalGravity)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 
doesChildrenFitVertically()321         public boolean doesChildrenFitVertically() {
322             return mRawChildrenBox.top >= mRvPadding.top
323                     && mRawChildrenBox.bottom <= mRvSize.bottom - mRvPadding.bottom;
324         }
325 
doesChildrenFitHorizontally()326         public boolean doesChildrenFitHorizontally() {
327             return mRawChildrenBox.left >= mRvPadding.left
328                     && mRawChildrenBox.right <= mRvSize.right - mRvPadding.right;
329         }
330 
assertSame(Snapshot other, int step)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 
assertMap(String prefix, LongSparseArray<?> map1, LongSparseArray<?> map2)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 
assertAtIndex(String prefix, LongSparseArray<?> map1, LongSparseArray<?> map2, int index)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 
logInto(LongSparseArray<?> map, StringBuilder sb)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
toString()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
hashCode()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 
collectInto( LongSparseArray<String> target, List<? extends BaseRecyclerViewAnimationsTest.AnimateLogBase> list)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 
log(BaseRecyclerViewAnimationsTest.AnimateLogBase base)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 
log(BaseRecyclerViewAnimationsTest.LoggingInfo postInfo)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 
collectChildCoordinates(RecyclerView recyclerView)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 
getViewBounds(View view)456         private Rect getViewBounds(View view) {
457             return new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
458         }
459 
getChildBounds(RecyclerView recyclerView, View child, boolean offset)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 
getItemId(RecyclerView.ViewHolder vh)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 
assertRvSize()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 
shouldWrapContentHorizontally()497     protected boolean shouldWrapContentHorizontally() {
498         return true;
499     }
500 
shouldWrapContentVertically()501     protected boolean shouldWrapContentVertically() {
502         return true;
503     }
504 
505     static class WrappedRecyclerView extends RecyclerView {
506 
WrappedRecyclerView(Context context)507         public WrappedRecyclerView(Context context) {
508             super(context);
509         }
510 
waitUntilLayout()511         public void waitUntilLayout() {
512             while (isLayoutRequested()) {
513                 try {
514                     Thread.sleep(100);
515                 } catch (InterruptedException e) {
516                     e.printStackTrace();
517                 }
518             }
519         }
520 
waitUntilAnimations()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
onLayout(boolean changed, int l, int t, int r, int b)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 
WrapContentConfig(boolean unlimitedWidth, boolean unlimitedHeight)548         public WrapContentConfig(boolean unlimitedWidth, boolean unlimitedHeight) {
549             this.unlimitedWidth = unlimitedWidth;
550             this.unlimitedHeight = unlimitedHeight;
551         }
552 
WrapContentConfig(boolean unlimitedWidth, boolean unlimitedHeight, Rect padding)553         public WrapContentConfig(boolean unlimitedWidth, boolean unlimitedHeight, Rect padding) {
554             this.unlimitedWidth = unlimitedWidth;
555             this.unlimitedHeight = unlimitedHeight;
556             this.padding.set(padding);
557         }
558 
isUnlimitedWidth()559         public boolean isUnlimitedWidth() {
560             return unlimitedWidth;
561         }
562 
setUnlimitedWidth(boolean unlimitedWidth)563         public WrapContentConfig setUnlimitedWidth(boolean unlimitedWidth) {
564             this.unlimitedWidth = unlimitedWidth;
565             return this;
566         }
567 
isUnlimitedHeight()568         public boolean isUnlimitedHeight() {
569             return unlimitedHeight;
570         }
571 
setUnlimitedHeight(boolean unlimitedHeight)572         public WrapContentConfig setUnlimitedHeight(boolean unlimitedHeight) {
573             this.unlimitedHeight = unlimitedHeight;
574             return this;
575         }
576 
577         @Override
toString()578         public String toString() {
579             return "WrapContentConfig{"
580                     + "unlimitedWidth=" + unlimitedWidth
581                     + ", unlimitedHeight=" + unlimitedHeight
582                     + ", padding=" + padding
583                     + '}';
584         }
585 
toLayoutParams(int wDim, int hDim)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