1/*
2 * Copyright (C) 2016 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 */
16package android.support.v7.widget;
17
18import org.hamcrest.BaseMatcher;
19import org.hamcrest.Description;
20
21import android.content.Context;
22import android.graphics.Color;
23import android.support.v4.util.Pair;
24import android.util.AttributeSet;
25import android.view.View;
26import android.view.ViewGroup;
27
28import java.util.ArrayList;
29import java.util.Collections;
30import java.util.List;
31import java.util.concurrent.atomic.AtomicLong;
32
33import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
34import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
35
36abstract public class BaseWrapContentWithAspectRatioTest extends BaseRecyclerViewInstrumentationTest {
37    final BaseWrapContentTest.WrapContentConfig mWrapContentConfig;
38
39    protected BaseWrapContentWithAspectRatioTest(
40            BaseWrapContentTest.WrapContentConfig wrapContentConfig) {
41        mWrapContentConfig = wrapContentConfig;
42    }
43
44    int getSize(View view, int orientation) {
45        if (orientation == VERTICAL) {
46            return view.getHeight();
47        }
48        return view.getWidth();
49    }
50
51    static class LoggingView extends View {
52
53        MeasureBehavior mBehavior;
54
55        public void setBehavior(MeasureBehavior behavior) {
56            mBehavior = behavior;
57        }
58
59        public LoggingView(Context context) {
60            super(context);
61        }
62
63        public LoggingView(Context context, AttributeSet attrs) {
64            super(context, attrs);
65        }
66
67        public LoggingView(Context context, AttributeSet attrs, int defStyleAttr) {
68            super(context, attrs, defStyleAttr);
69        }
70
71        public LoggingView(Context context, AttributeSet attrs, int defStyleAttr,
72                int defStyleRes) {
73            super(context, attrs, defStyleAttr, defStyleRes);
74        }
75
76        @Override
77        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
78            mBehavior.onMeasure(this, widthMeasureSpec, heightMeasureSpec);
79        }
80
81        @Override
82        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
83            super.onLayout(changed, left, top, right, bottom);
84            mBehavior.onLayout(changed, left, top, right, bottom);
85        }
86
87        public void setMeasured(int w, int h) {
88            setMeasuredDimension(w, h);
89        }
90
91        public void prepareLayoutParams() {
92            mBehavior.setLayoutParams(this);
93        }
94    }
95
96    static class AspectRatioMeasureBehavior extends MeasureBehavior {
97
98        Float ratio;
99        int control;
100
101        public AspectRatioMeasureBehavior(int desiredW, int desiredH, int wMode, int hMode) {
102            super(desiredW, desiredH, wMode, hMode);
103        }
104
105        public AspectRatioMeasureBehavior aspectRatio(int control, float ratio) {
106            this.control = control;
107            this.ratio = ratio;
108            return this;
109        }
110
111        @Override
112        public void onMeasure(LoggingView view, int wSpec,
113                int hSpec) {
114            super.onMeasure(view, wSpec, hSpec);
115            if (control == VERTICAL) {
116                view.setMeasured(getSecondary(view.getMeasuredHeight()),
117                        view.getMeasuredHeight());
118            } else if (control == HORIZONTAL) {
119                view.setMeasured(view.getMeasuredWidth(),
120                        getSecondary(view.getMeasuredWidth()));
121            }
122        }
123
124        public int getSecondary(int controlSize) {
125            return (int) (controlSize * ratio);
126        }
127    }
128
129    static class MeasureBehavior {
130        private static final AtomicLong idCounter = new AtomicLong(0);
131        public List<Pair<Integer, Integer>> measureSpecs = new ArrayList<>();
132        public List<Pair<Integer, Integer>> layouts = new ArrayList<>();
133        int desiredW, desiredH;
134        final long mId = idCounter.incrementAndGet();
135
136        ViewGroup.MarginLayoutParams layoutParams;
137
138        public MeasureBehavior(int desiredW, int desiredH, int wMode, int hMode) {
139            this.desiredW = desiredW;
140            this.desiredH = desiredH;
141            layoutParams = new ViewGroup.MarginLayoutParams(
142                    wMode, hMode
143            );
144        }
145
146        public MeasureBehavior withMargins(int left, int top, int right, int bottom) {
147            layoutParams.leftMargin = left;
148            layoutParams.topMargin = top;
149            layoutParams.rightMargin = right;
150            layoutParams.bottomMargin = bottom;
151            return this;
152        }
153
154        public long getId() {
155            return mId;
156        }
157
158        public void onMeasure(LoggingView view, int wSpec, int hSpec) {
159            measureSpecs.add(new Pair<>(wSpec, hSpec));
160            view.setMeasured(
161                    RecyclerView.LayoutManager.chooseSize(wSpec, desiredW, 0),
162                    RecyclerView.LayoutManager.chooseSize(hSpec, desiredH, 0));
163        }
164
165        public int getSpec(int position, int orientation) {
166            if (orientation == VERTICAL) {
167                return measureSpecs.get(position).second;
168            } else {
169                return measureSpecs.get(position).first;
170            }
171        }
172
173        public void setLayoutParams(LoggingView view) {
174            view.setLayoutParams(layoutParams);
175        }
176
177        public void onLayout(boolean changed, int left, int top, int right, int bottom) {
178            if (changed) {
179                layouts.add(new Pair<>(right - left, bottom - top));
180            }
181        }
182    }
183
184
185    static class WrapContentViewHolder extends RecyclerView.ViewHolder {
186
187        LoggingView mView;
188
189        public WrapContentViewHolder(ViewGroup parent) {
190            super(new LoggingView(parent.getContext()));
191            mView = (LoggingView) itemView;
192            mView.setBackgroundColor(Color.GREEN);
193        }
194    }
195
196    static class WrapContentAdapter extends RecyclerView.Adapter<WrapContentViewHolder> {
197
198        List<MeasureBehavior> behaviors = new ArrayList<>();
199
200        public WrapContentAdapter(MeasureBehavior... behaviors) {
201            Collections.addAll(this.behaviors, behaviors);
202        }
203
204        @Override
205        public WrapContentViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
206            return new WrapContentViewHolder(parent);
207        }
208
209        @Override
210        public void onBindViewHolder(WrapContentViewHolder holder, int position) {
211            holder.mView.setBehavior(behaviors.get(position));
212            holder.mView.prepareLayoutParams();
213        }
214
215        @Override
216        public int getItemCount() {
217            return behaviors.size();
218        }
219    }
220
221    static class MeasureSpecMatcher extends BaseMatcher<Integer> {
222
223        private boolean checkSize = false;
224        private boolean checkMode = false;
225        private int mSize;
226        private int mMode;
227
228        public static MeasureSpecMatcher is(int size, int mode) {
229            MeasureSpecMatcher matcher = new MeasureSpecMatcher(size, mode);
230            matcher.checkSize = true;
231            matcher.checkMode = true;
232            return matcher;
233        }
234
235        public static MeasureSpecMatcher size(int size) {
236            MeasureSpecMatcher matcher = new MeasureSpecMatcher(size, 0);
237            matcher.checkSize = true;
238            matcher.checkMode = false;
239            return matcher;
240        }
241
242        public static MeasureSpecMatcher mode(int mode) {
243            MeasureSpecMatcher matcher = new MeasureSpecMatcher(0, mode);
244            matcher.checkSize = false;
245            matcher.checkMode = true;
246            return matcher;
247        }
248
249        private MeasureSpecMatcher(int size, int mode) {
250            mSize = size;
251            mMode = mode;
252
253        }
254
255        @Override
256        public boolean matches(Object item) {
257            if (item == null) {
258                return false;
259            }
260            Integer intValue = (Integer) item;
261            final int size = View.MeasureSpec.getSize(intValue);
262            final int mode = View.MeasureSpec.getMode(intValue);
263            if (checkSize && size != mSize) {
264                return false;
265            }
266            if (checkMode && mode != mMode) {
267                return false;
268            }
269            return true;
270        }
271
272        @Override
273        public void describeMismatch(Object item, Description description) {
274            Integer intValue = (Integer) item;
275            final int size = View.MeasureSpec.getSize(intValue);
276            final int mode = View.MeasureSpec.getMode(intValue);
277            if (checkSize && size != mSize) {
278                description.appendText(" Expected size was ").appendValue(mSize)
279                        .appendText(" but received size is ").appendValue(size);
280            }
281            if (checkMode && mode != mMode) {
282                description.appendText(" Expected mode was ").appendValue(modeName(mMode))
283                        .appendText(" but received mode is ").appendValue(modeName(mode));
284            }
285        }
286
287        @Override
288        public void describeTo(Description description) {
289            if (checkSize) {
290                description.appendText(" Measure spec size:").appendValue(mSize);
291            }
292            if (checkMode) {
293                description.appendText(" Measure spec mode:").appendValue(modeName(mMode));
294            }
295        }
296
297        private static String modeName(int mode) {
298            switch (mode) {
299                case View.MeasureSpec.AT_MOST:
300                    return "at most";
301                case View.MeasureSpec.EXACTLY:
302                    return "exactly";
303                default:
304                    return "unspecified";
305            }
306        }
307    }
308}
309