| /* | 
 |  * Copyright 2018 The Android Open Source Project | 
 |  * | 
 |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
 |  * you may not use this file except in compliance with the License. | 
 |  * You may obtain a copy of the License at | 
 |  * | 
 |  *      http://www.apache.org/licenses/LICENSE-2.0 | 
 |  * | 
 |  * Unless required by applicable law or agreed to in writing, software | 
 |  * distributed under the License is distributed on an "AS IS" BASIS, | 
 |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 |  * See the License for the specific language governing permissions and | 
 |  * limitations under the License. | 
 |  */ | 
 | package androidx.recyclerview.widget; | 
 |  | 
 | import static org.hamcrest.CoreMatchers.is; | 
 | import static org.hamcrest.MatcherAssert.assertThat; | 
 | import static org.junit.Assert.assertEquals; | 
 | import static org.junit.Assert.assertNull; | 
 | import static org.junit.Assert.assertTrue; | 
 |  | 
 | import android.app.Activity; | 
 | import android.graphics.Color; | 
 | import android.graphics.Rect; | 
 | import android.util.Log; | 
 | import android.view.Gravity; | 
 | import android.view.View; | 
 | import android.view.ViewGroup; | 
 | import android.widget.TextView; | 
 |  | 
 | import androidx.annotation.NonNull; | 
 | import androidx.annotation.Nullable; | 
 | import androidx.collection.LongSparseArray; | 
 |  | 
 | import org.hamcrest.CoreMatchers; | 
 |  | 
 | import java.util.ArrayList; | 
 | import java.util.Collections; | 
 | import java.util.List; | 
 |  | 
 | /** | 
 |  * Class to test any generic wrap content behavior. | 
 |  * It does so by running the same view scenario twice. Once with match parent setup to record all | 
 |  * dimensions and once with wrap_content setup. Then compares all child locations & ids + | 
 |  * RecyclerView size. | 
 |  */ | 
 | abstract public class BaseWrapContentTest extends BaseRecyclerViewInstrumentationTest { | 
 |  | 
 |     static final boolean DEBUG = false; | 
 |     static final String TAG = "WrapContentTest"; | 
 |     RecyclerView.LayoutManager mLayoutManager; | 
 |  | 
 |     TestAdapter mTestAdapter; | 
 |  | 
 |     LoggingItemAnimator mLoggingItemAnimator; | 
 |  | 
 |     boolean mIsWrapContent; | 
 |  | 
 |     protected final WrapContentConfig mWrapContentConfig; | 
 |  | 
 |     public BaseWrapContentTest(WrapContentConfig config) { | 
 |         mWrapContentConfig = config; | 
 |     } | 
 |  | 
 |     abstract RecyclerView.LayoutManager createLayoutManager(); | 
 |  | 
 |     void unspecifiedWithHintTest(boolean horizontal) throws Throwable { | 
 |         final int itemHeight = 20; | 
 |         final int itemWidth = 15; | 
 |         RecyclerView.LayoutManager layoutManager = createLayoutManager(); | 
 |         WrappedRecyclerView rv = createRecyclerView(getActivity()); | 
 |         TestAdapter testAdapter = new TestAdapter(20) { | 
 |             @Override | 
 |             public void onBindViewHolder(@NonNull TestViewHolder holder, | 
 |                     int position) { | 
 |                 super.onBindViewHolder(holder, position); | 
 |                 holder.itemView.setLayoutParams(new ViewGroup.LayoutParams(itemWidth, itemHeight)); | 
 |             } | 
 |         }; | 
 |         rv.setLayoutManager(layoutManager); | 
 |         rv.setAdapter(testAdapter); | 
 |         TestedFrameLayout.FullControlLayoutParams lp = | 
 |                 new TestedFrameLayout.FullControlLayoutParams(0, 0); | 
 |         if (horizontal) { | 
 |             lp.wSpec = View.MeasureSpec.makeMeasureSpec(25, View.MeasureSpec.UNSPECIFIED); | 
 |             lp.hSpec = View.MeasureSpec.makeMeasureSpec(50, View.MeasureSpec.AT_MOST); | 
 |         } else { | 
 |             lp.hSpec = View.MeasureSpec.makeMeasureSpec(25, View.MeasureSpec.UNSPECIFIED); | 
 |             lp.wSpec = View.MeasureSpec.makeMeasureSpec(50, View.MeasureSpec.AT_MOST); | 
 |         } | 
 |         rv.setLayoutParams(lp); | 
 |         setRecyclerView(rv); | 
 |         rv.waitUntilLayout(); | 
 |  | 
 |         // we don't assert against the given size hint because LM will still ask for more if it | 
 |         // lays out more children. This is the correct behavior because the spec is not AT_MOST, | 
 |         // it is UNSPECIFIED. | 
 |         if (horizontal) { | 
 |             int expectedWidth = rv.getPaddingLeft() + rv.getPaddingRight() + itemWidth; | 
 |             while (expectedWidth < 25) { | 
 |                 expectedWidth += itemWidth; | 
 |             } | 
 |             assertThat(rv.getWidth(), CoreMatchers.is(expectedWidth)); | 
 |         } else { | 
 |             int expectedHeight = rv.getPaddingTop() + rv.getPaddingBottom() + itemHeight; | 
 |             while (expectedHeight < 25) { | 
 |                 expectedHeight += itemHeight; | 
 |             } | 
 |             assertThat(rv.getHeight(), CoreMatchers.is(expectedHeight)); | 
 |         } | 
 |     } | 
 |  | 
 |     protected void testScenerio(Scenario scenario) throws Throwable { | 
 |         TestedFrameLayout.FullControlLayoutParams | 
 |                 matchParent = new TestedFrameLayout.FullControlLayoutParams( | 
 |                 ViewGroup.LayoutParams.MATCH_PARENT, | 
 |                 ViewGroup.LayoutParams.MATCH_PARENT); | 
 |         TestedFrameLayout.FullControlLayoutParams | 
 |                 wrapContent = new TestedFrameLayout.FullControlLayoutParams( | 
 |                 ViewGroup.LayoutParams.WRAP_CONTENT, | 
 |                 ViewGroup.LayoutParams.WRAP_CONTENT); | 
 |         if (mWrapContentConfig.isUnlimitedHeight()) { | 
 |             wrapContent.hSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); | 
 |         } | 
 |         if (mWrapContentConfig.isUnlimitedWidth()) { | 
 |             wrapContent.wSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); | 
 |         } | 
 |  | 
 |         mIsWrapContent = false; | 
 |         List<Snapshot> s1 = runScenario(scenario, matchParent, null); | 
 |         mIsWrapContent = true; | 
 |  | 
 |         List<Snapshot> s2 = runScenario(scenario, wrapContent, s1); | 
 |         assertEquals("test sanity", s1.size(), s2.size()); | 
 |  | 
 |         for (int i = 0; i < s1.size(); i++) { | 
 |             Snapshot step1 = s1.get(i); | 
 |             Snapshot step2 = s2.get(i); | 
 |             step1.assertSame(step2, i); | 
 |         } | 
 |     } | 
 |  | 
 |     public List<Snapshot> runScenario(Scenario scenario, ViewGroup.LayoutParams lp, | 
 |             @Nullable List<Snapshot> compareWith) | 
 |             throws Throwable { | 
 |         removeRecyclerView(); | 
 |         Item.idCounter.set(0); | 
 |         List<Snapshot> result = new ArrayList<>(); | 
 |         RecyclerView.LayoutManager layoutManager = scenario.createLayoutManager(); | 
 |         WrappedRecyclerView recyclerView = new WrappedRecyclerView(getActivity()); | 
 |         recyclerView.setBackgroundColor(Color.rgb(0, 0, 255)); | 
 |         recyclerView.setLayoutManager(layoutManager); | 
 |         recyclerView.setLayoutParams(lp); | 
 |         mLayoutManager = layoutManager; | 
 |         mTestAdapter = new TestAdapter(scenario.getSeedAdapterSize()); | 
 |         recyclerView.setAdapter(mTestAdapter); | 
 |         mLoggingItemAnimator = new LoggingItemAnimator(); | 
 |         recyclerView.setItemAnimator(mLoggingItemAnimator); | 
 |         setRecyclerView(recyclerView); | 
 |         recyclerView.waitUntilLayout(); | 
 |         int stepIndex = 0; | 
 |         for (Step step : scenario.mStepList) { | 
 |             mLoggingItemAnimator.reset(); | 
 |             step.onRun(); | 
 |             recyclerView.waitUntilLayout(); | 
 |             recyclerView.waitUntilAnimations(); | 
 |             Snapshot snapshot = takeSnapshot(); | 
 |             if (mIsWrapContent) { | 
 |                 snapshot.assertRvSize(); | 
 |             } | 
 |             result.add(snapshot); | 
 |             if (compareWith != null) { | 
 |                 compareWith.get(stepIndex).assertSame(snapshot, stepIndex); | 
 |             } | 
 |             stepIndex++; | 
 |         } | 
 |         recyclerView.waitUntilLayout(); | 
 |         recyclerView.waitUntilAnimations(); | 
 |         Snapshot snapshot = takeSnapshot(); | 
 |         if (mIsWrapContent) { | 
 |             snapshot.assertRvSize(); | 
 |         } | 
 |         result.add(snapshot); | 
 |         if (compareWith != null) { | 
 |             compareWith.get(stepIndex).assertSame(snapshot, stepIndex); | 
 |         } | 
 |         return result; | 
 |     } | 
 |  | 
 |     protected WrappedRecyclerView createRecyclerView(Activity activity) { | 
 |         return new WrappedRecyclerView(getActivity()); | 
 |     } | 
 |  | 
 |     void layoutAndCheck(TestedFrameLayout.FullControlLayoutParams lp, | 
 |             BaseWrapContentWithAspectRatioTest.WrapContentAdapter adapter, Rect[] expected, | 
 |             int width, int height) throws Throwable { | 
 |         WrappedRecyclerView recyclerView = createRecyclerView(getActivity()); | 
 |         recyclerView.setBackgroundColor(Color.rgb(0, 0, 255)); | 
 |         recyclerView.setLayoutManager(createLayoutManager()); | 
 |         recyclerView.setAdapter(adapter); | 
 |         recyclerView.setLayoutParams(lp); | 
 |         Rect padding = mWrapContentConfig.padding; | 
 |         recyclerView.setPadding(padding.left, padding.top, padding.right, padding.bottom); | 
 |         setRecyclerView(recyclerView); | 
 |         recyclerView.waitUntilLayout(); | 
 |         Snapshot snapshot = takeSnapshot(); | 
 |         int index = 0; | 
 |         Rect tmp = new Rect(); | 
 |         for (BaseWrapContentWithAspectRatioTest.MeasureBehavior behavior : adapter.behaviors) { | 
 |             tmp.set(expected[index]); | 
 |             tmp.offset(padding.left, padding.top); | 
 |             assertThat("behavior " + index, snapshot.mChildCoordinates.get(behavior.getId()), | 
 |                     is(tmp)); | 
 |             index ++; | 
 |         } | 
 |         Rect boundingBox = new Rect(0, 0, 0, 0); | 
 |         for (Rect rect : expected) { | 
 |             boundingBox.union(rect); | 
 |         } | 
 |         assertThat(recyclerView.getWidth(), is(width + padding.left + padding.right)); | 
 |         assertThat(recyclerView.getHeight(), is(height + padding.top + padding.bottom)); | 
 |     } | 
 |  | 
 |  | 
 |     abstract protected int getVerticalGravity(RecyclerView.LayoutManager layoutManager); | 
 |  | 
 |     abstract protected int getHorizontalGravity(RecyclerView.LayoutManager layoutManager); | 
 |  | 
 |     protected Snapshot takeSnapshot() throws Throwable { | 
 |         Snapshot snapshot = new Snapshot(mRecyclerView, mLoggingItemAnimator, | 
 |                 getHorizontalGravity(mLayoutManager), getVerticalGravity(mLayoutManager)); | 
 |         return snapshot; | 
 |     } | 
 |  | 
 |     abstract class Scenario { | 
 |  | 
 |         ArrayList<Step> mStepList = new ArrayList<>(); | 
 |  | 
 |         public Scenario(Step... steps) { | 
 |             Collections.addAll(mStepList, steps); | 
 |         } | 
 |  | 
 |         public int getSeedAdapterSize() { | 
 |             return 10; | 
 |         } | 
 |  | 
 |         public RecyclerView.LayoutManager createLayoutManager() { | 
 |             return BaseWrapContentTest.this.createLayoutManager(); | 
 |         } | 
 |     } | 
 |  | 
 |     abstract static class Step { | 
 |  | 
 |         abstract void onRun() throws Throwable; | 
 |     } | 
 |  | 
 |     class Snapshot { | 
 |  | 
 |         Rect mRawChildrenBox = new Rect(); | 
 |  | 
 |         Rect mRvSize = new Rect(); | 
 |  | 
 |         Rect mRvPadding = new Rect(); | 
 |  | 
 |         Rect mRvParentSize = new Rect(); | 
 |  | 
 |         LongSparseArray<Rect> mChildCoordinates = new LongSparseArray<>(); | 
 |  | 
 |         LongSparseArray<String> mAppear = new LongSparseArray<>(); | 
 |  | 
 |         LongSparseArray<String> mDisappear = new LongSparseArray<>(); | 
 |  | 
 |         LongSparseArray<String> mPersistent = new LongSparseArray<>(); | 
 |  | 
 |         LongSparseArray<String> mChanged = new LongSparseArray<>(); | 
 |  | 
 |         int mVerticalGravity; | 
 |  | 
 |         int mHorizontalGravity; | 
 |  | 
 |         int mOffsetX, mOffsetY;// how much we should offset children | 
 |  | 
 |         public Snapshot(RecyclerView recyclerView, LoggingItemAnimator loggingItemAnimator, | 
 |                 int horizontalGravity, int verticalGravity) | 
 |                 throws Throwable { | 
 |             mRvSize = getViewBounds(recyclerView); | 
 |             mRvParentSize = getViewBounds((View) recyclerView.getParent()); | 
 |             mRvPadding = new Rect(recyclerView.getPaddingLeft(), recyclerView.getPaddingTop(), | 
 |                     recyclerView.getPaddingRight(), recyclerView.getPaddingBottom()); | 
 |             mVerticalGravity = verticalGravity; | 
 |             mHorizontalGravity = horizontalGravity; | 
 |             if (mVerticalGravity == Gravity.TOP) { | 
 |                 mOffsetY = 0; | 
 |             } else { | 
 |                 mOffsetY = mRvParentSize.bottom - mRvSize.bottom; | 
 |             } | 
 |  | 
 |             if (mHorizontalGravity == Gravity.LEFT) { | 
 |                 mOffsetX = 0; | 
 |             } else { | 
 |                 mOffsetX = mRvParentSize.right - mRvSize.right; | 
 |             } | 
 |             collectChildCoordinates(recyclerView); | 
 |             if (loggingItemAnimator != null) { | 
 |                 collectInto(mAppear, loggingItemAnimator.mAnimateAppearanceList); | 
 |                 collectInto(mDisappear, loggingItemAnimator.mAnimateDisappearanceList); | 
 |                 collectInto(mPersistent, loggingItemAnimator.mAnimatePersistenceList); | 
 |                 collectInto(mChanged, loggingItemAnimator.mAnimateChangeList); | 
 |             } | 
 |         } | 
 |  | 
 |         public boolean doesChildrenFitVertically() { | 
 |             return mRawChildrenBox.top >= mRvPadding.top | 
 |                     && mRawChildrenBox.bottom <= mRvSize.bottom - mRvPadding.bottom; | 
 |         } | 
 |  | 
 |         public boolean doesChildrenFitHorizontally() { | 
 |             return mRawChildrenBox.left >= mRvPadding.left | 
 |                     && mRawChildrenBox.right <= mRvSize.right - mRvPadding.right; | 
 |         } | 
 |  | 
 |         public void assertSame(Snapshot other, int step) { | 
 |             if (mWrapContentConfig.isUnlimitedHeight() && | 
 |                     (!doesChildrenFitVertically() || !other.doesChildrenFitVertically())) { | 
 |                 if (DEBUG) { | 
 |                     Log.d(TAG, "cannot assert coordinates because it does not fit vertically"); | 
 |                 } | 
 |                 return; | 
 |             } | 
 |             if (mWrapContentConfig.isUnlimitedWidth() && | 
 |                     (!doesChildrenFitHorizontally() || !other.doesChildrenFitHorizontally())) { | 
 |                 if (DEBUG) { | 
 |                     Log.d(TAG, "cannot assert coordinates because it does not fit horizontally"); | 
 |                 } | 
 |                 return; | 
 |             } | 
 |             assertMap("child coordinates. step:" + step, mChildCoordinates, | 
 |                     other.mChildCoordinates); | 
 |             if (mWrapContentConfig.isUnlimitedHeight() || mWrapContentConfig.isUnlimitedWidth()) { | 
 |                 return;//cannot assert animatinos in unlimited size | 
 |             } | 
 |             assertMap("appearing step:" + step, mAppear, other.mAppear); | 
 |             assertMap("disappearing step:" + step, mDisappear, other.mDisappear); | 
 |             assertMap("persistent step:" + step, mPersistent, other.mPersistent); | 
 |             assertMap("changed step:" + step, mChanged, other.mChanged); | 
 |         } | 
 |  | 
 |         private void assertMap(String prefix, LongSparseArray<?> map1, LongSparseArray<?> map2) { | 
 |             StringBuilder logBuilder = new StringBuilder(); | 
 |             logBuilder.append(prefix).append("\n"); | 
 |             logBuilder.append("map1").append("\n"); | 
 |             logInto(map1, logBuilder); | 
 |             logBuilder.append("map2").append("\n"); | 
 |             logInto(map2, logBuilder); | 
 |             final String log = logBuilder.toString(); | 
 |             assertEquals(log + " same size", map1.size(), map2.size()); | 
 |             for (int i = 0; i < map1.size(); i++) { | 
 |                 assertAtIndex(log, map1, map2, i); | 
 |             } | 
 |         } | 
 |  | 
 |         private void assertAtIndex(String prefix, LongSparseArray<?> map1, LongSparseArray<?> map2, | 
 |                 int index) { | 
 |             long key1 = map1.keyAt(index); | 
 |             long key2 = map2.keyAt(index); | 
 |             assertEquals(prefix + "key mismatch at index " + index, key1, key2); | 
 |             Object value1 = map1.valueAt(index); | 
 |             Object value2 = map2.valueAt(index); | 
 |             assertEquals(prefix + " value mismatch at index " + index, value1, value2); | 
 |         } | 
 |  | 
 |         private void logInto(LongSparseArray<?> map, StringBuilder sb) { | 
 |             for (int i = 0; i < map.size(); i++) { | 
 |                 long key = map.keyAt(i); | 
 |                 Object value = map.valueAt(i); | 
 |                 sb.append(key).append(" : ").append(value).append("\n"); | 
 |             } | 
 |         } | 
 |  | 
 |         @Override | 
 |         public String toString() { | 
 |             StringBuilder sb = new StringBuilder("Snapshot{\n"); | 
 |             sb.append("child coordinates:\n"); | 
 |             logInto(mChildCoordinates, sb); | 
 |             sb.append("appear animations:\n"); | 
 |             logInto(mAppear, sb); | 
 |             sb.append("disappear animations:\n"); | 
 |             logInto(mDisappear, sb); | 
 |             sb.append("change animations:\n"); | 
 |             logInto(mChanged, sb); | 
 |             sb.append("persistent animations:\n"); | 
 |             logInto(mPersistent, sb); | 
 |             sb.append("}"); | 
 |             return sb.toString(); | 
 |         } | 
 |  | 
 |         @Override | 
 |         public int hashCode() { | 
 |             int result = mChildCoordinates.hashCode(); | 
 |             result = 31 * result + mAppear.hashCode(); | 
 |             result = 31 * result + mDisappear.hashCode(); | 
 |             result = 31 * result + mPersistent.hashCode(); | 
 |             result = 31 * result + mChanged.hashCode(); | 
 |             return result; | 
 |         } | 
 |  | 
 |         private void collectInto( | 
 |                 LongSparseArray<String> target, | 
 |                 List<? extends BaseRecyclerViewAnimationsTest.AnimateLogBase> list) { | 
 |             for (BaseRecyclerViewAnimationsTest.AnimateLogBase base : list) { | 
 |                 long id = getItemId(base.viewHolder); | 
 |                 assertNull(target.get(id)); | 
 |                 target.put(id, log(base)); | 
 |             } | 
 |         } | 
 |  | 
 |         private String log(BaseRecyclerViewAnimationsTest.AnimateLogBase base) { | 
 |             return base.getClass().getSimpleName() + | 
 |                     ((TextView) base.viewHolder.itemView).getText() + ": " + | 
 |                     "[pre:" + log(base.postInfo) + | 
 |                     ", post:" + log(base.postInfo) + "]"; | 
 |         } | 
 |  | 
 |         private String log(BaseRecyclerViewAnimationsTest.LoggingInfo postInfo) { | 
 |             if (postInfo == null) { | 
 |                 return "?"; | 
 |             } | 
 |             return "PI[flags: " + postInfo.changeFlags | 
 |                     + ",l:" + (postInfo.left + mOffsetX) | 
 |                     + ",t:" + (postInfo.top + mOffsetY) | 
 |                     + ",r:" + (postInfo.right + mOffsetX) | 
 |                     + ",b:" + (postInfo.bottom + mOffsetY) + "]"; | 
 |         } | 
 |  | 
 |         void collectChildCoordinates(RecyclerView recyclerView) throws Throwable { | 
 |             mRawChildrenBox = new Rect(0, 0, 0, 0); | 
 |             final int childCount = recyclerView.getChildCount(); | 
 |             for (int i = 0; i < childCount; i++) { | 
 |                 View child = recyclerView.getChildAt(i); | 
 |                 Rect childBounds = getChildBounds(recyclerView, child, true); | 
 |                 mRawChildrenBox.union(getChildBounds(recyclerView, child, false)); | 
 |                 RecyclerView.ViewHolder childViewHolder = recyclerView.getChildViewHolder(child); | 
 |                 mChildCoordinates.put(getItemId(childViewHolder), childBounds); | 
 |             } | 
 |         } | 
 |  | 
 |         private Rect getViewBounds(View view) { | 
 |             return new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()); | 
 |         } | 
 |  | 
 |         private Rect getChildBounds(RecyclerView recyclerView, View child, boolean offset) { | 
 |             RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); | 
 |             RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams(); | 
 |             Rect rect = new Rect(layoutManager.getDecoratedLeft(child) - lp.leftMargin, | 
 |                     layoutManager.getDecoratedTop(child) - lp.topMargin, | 
 |                     layoutManager.getDecoratedRight(child) + lp.rightMargin, | 
 |                     layoutManager.getDecoratedBottom(child) + lp.bottomMargin); | 
 |             if (offset) { | 
 |                 rect.offset(mOffsetX, mOffsetY); | 
 |             } | 
 |             return rect; | 
 |         } | 
 |  | 
 |         private long getItemId(RecyclerView.ViewHolder vh) { | 
 |             if (vh instanceof TestViewHolder) { | 
 |                 return ((TestViewHolder) vh).mBoundItem.mId; | 
 |             } else if (vh instanceof BaseWrapContentWithAspectRatioTest.WrapContentViewHolder) { | 
 |                 BaseWrapContentWithAspectRatioTest.WrapContentViewHolder casted = | 
 |                         (BaseWrapContentWithAspectRatioTest.WrapContentViewHolder) vh; | 
 |                 return casted.mView.mBehavior.getId(); | 
 |             } else { | 
 |                 throw new IllegalArgumentException("i don't support any VH"); | 
 |             } | 
 |         } | 
 |  | 
 |         public void assertRvSize() { | 
 |             if (shouldWrapContentHorizontally()) { | 
 |                 int expectedW = mRawChildrenBox.width() + mRvPadding.left + mRvPadding.right; | 
 |                 assertTrue(mRvSize.width() + " <= " + expectedW, mRvSize.width() <= expectedW); | 
 |             } | 
 |             if (shouldWrapContentVertically()) { | 
 |                 int expectedH = mRawChildrenBox.height() + mRvPadding.top + mRvPadding.bottom; | 
 |                 assertTrue(mRvSize.height() + "<=" + expectedH, mRvSize.height() <= expectedH); | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     protected boolean shouldWrapContentHorizontally() { | 
 |         return true; | 
 |     } | 
 |  | 
 |     protected boolean shouldWrapContentVertically() { | 
 |         return true; | 
 |     } | 
 |  | 
 |     static class WrapContentConfig { | 
 |  | 
 |         public boolean unlimitedWidth; | 
 |         public boolean unlimitedHeight; | 
 |         public Rect padding = new Rect(0, 0, 0, 0); | 
 |  | 
 |         public WrapContentConfig(boolean unlimitedWidth, boolean unlimitedHeight) { | 
 |             this.unlimitedWidth = unlimitedWidth; | 
 |             this.unlimitedHeight = unlimitedHeight; | 
 |         } | 
 |  | 
 |         public WrapContentConfig(boolean unlimitedWidth, boolean unlimitedHeight, Rect padding) { | 
 |             this.unlimitedWidth = unlimitedWidth; | 
 |             this.unlimitedHeight = unlimitedHeight; | 
 |             this.padding.set(padding); | 
 |         } | 
 |  | 
 |         public boolean isUnlimitedWidth() { | 
 |             return unlimitedWidth; | 
 |         } | 
 |  | 
 |         public WrapContentConfig setUnlimitedWidth(boolean unlimitedWidth) { | 
 |             this.unlimitedWidth = unlimitedWidth; | 
 |             return this; | 
 |         } | 
 |  | 
 |         public boolean isUnlimitedHeight() { | 
 |             return unlimitedHeight; | 
 |         } | 
 |  | 
 |         public WrapContentConfig setUnlimitedHeight(boolean unlimitedHeight) { | 
 |             this.unlimitedHeight = unlimitedHeight; | 
 |             return this; | 
 |         } | 
 |  | 
 |         @Override | 
 |         public String toString() { | 
 |             StringBuilder sb = new StringBuilder(32); | 
 |             sb.append("Rect("); sb.append(padding.left); sb.append(","); | 
 |             sb.append(padding.top); sb.append("-"); sb.append(padding.right); | 
 |             sb.append(","); sb.append(padding.bottom); sb.append(")"); | 
 |             return "WrapContentConfig{" | 
 |                     + "unlimitedWidth=" + unlimitedWidth | 
 |                     + ",unlimitedHeight=" + unlimitedHeight | 
 |                     + ",padding=" + sb.toString() | 
 |                     + '}'; | 
 |         } | 
 |  | 
 |         public TestedFrameLayout.FullControlLayoutParams toLayoutParams(int wDim, int hDim) { | 
 |             TestedFrameLayout.FullControlLayoutParams | 
 |                     lp = new TestedFrameLayout.FullControlLayoutParams( | 
 |                     wDim, hDim); | 
 |             if (unlimitedWidth) { | 
 |                 lp.wSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); | 
 |             } | 
 |             if (unlimitedHeight) { | 
 |                 lp.hSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); | 
 |             } | 
 |             return lp; | 
 |         } | 
 |     } | 
 | } |