1/*
2 * Copyright (C) 2014 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
17package android.support.v7.widget;
18
19import static org.junit.Assert.assertEquals;
20import static org.junit.Assert.assertFalse;
21import static org.junit.Assert.assertNotEquals;
22import static org.junit.Assert.assertNotNull;
23import static org.junit.Assert.assertNotSame;
24import static org.junit.Assert.assertNull;
25import static org.junit.Assert.assertSame;
26import static org.junit.Assert.assertTrue;
27import static org.junit.Assert.fail;
28
29import android.content.Context;
30import android.os.Build;
31import android.os.Parcel;
32import android.os.Parcelable;
33import android.os.SystemClock;
34import android.support.test.InstrumentationRegistry;
35import android.support.test.filters.SmallTest;
36import android.support.test.runner.AndroidJUnit4;
37import android.util.AttributeSet;
38import android.util.SparseArray;
39import android.view.MotionEvent;
40import android.view.View;
41import android.view.ViewGroup;
42import android.view.animation.Interpolator;
43import android.view.animation.LinearInterpolator;
44import android.widget.FrameLayout;
45import android.widget.TextView;
46
47import org.junit.Before;
48import org.junit.Test;
49import org.junit.runner.RunWith;
50
51import java.lang.ref.WeakReference;
52import java.util.ArrayList;
53import java.util.List;
54import java.util.UUID;
55
56@SmallTest
57@RunWith(AndroidJUnit4.class)
58public class RecyclerViewBasicTest {
59
60    RecyclerView mRecyclerView;
61
62    @Before
63    public void setUp() throws Exception {
64        mRecyclerView = new RecyclerView(getContext());
65    }
66
67    private Context getContext() {
68        return InstrumentationRegistry.getContext();
69    }
70
71    @Test
72    public void measureWithoutLayoutManager() {
73        measure();
74    }
75
76    private void measure() {
77        mRecyclerView.measure(View.MeasureSpec.AT_MOST | 320, View.MeasureSpec.AT_MOST | 240);
78    }
79
80    private void layout() {
81        mRecyclerView.layout(0, 0, 320, 320);
82    }
83
84    private void focusSearch() {
85        mRecyclerView.focusSearch(1);
86    }
87
88    @Test
89    public void layoutWithoutAdapter() throws InterruptedException {
90        MockLayoutManager layoutManager = new MockLayoutManager();
91        mRecyclerView.setLayoutManager(layoutManager);
92        layout();
93        assertEquals("layout manager should not be called if there is no adapter attached",
94                0, layoutManager.mLayoutCount);
95    }
96
97    public void setScrollContainer() {
98        assertEquals("RecyclerView should announce itself as scroll container for the IME to "
99                + "handle it properly", true, mRecyclerView.isScrollContainer());
100    }
101
102    @Test
103    public void layoutWithoutLayoutManager() throws InterruptedException {
104        mRecyclerView.setAdapter(new MockAdapter(20));
105        measure();
106        layout();
107    }
108
109    @Test
110    public void focusWithoutLayoutManager() throws InterruptedException {
111        mRecyclerView.setAdapter(new MockAdapter(20));
112        measure();
113        layout();
114        focusSearch();
115    }
116
117    @Test
118    public void scrollWithoutLayoutManager() throws InterruptedException {
119        mRecyclerView.setAdapter(new MockAdapter(20));
120        measure();
121        layout();
122        mRecyclerView.scrollBy(10, 10);
123    }
124
125    @Test
126    public void smoothScrollWithoutLayoutManager() throws InterruptedException {
127        mRecyclerView.setAdapter(new MockAdapter(20));
128        measure();
129        layout();
130        mRecyclerView.smoothScrollBy(10, 10);
131    }
132
133    @Test
134    public void scrollToPositionWithoutLayoutManager() throws InterruptedException {
135        mRecyclerView.setAdapter(new MockAdapter(20));
136        measure();
137        layout();
138        mRecyclerView.scrollToPosition(5);
139    }
140
141    @Test
142    public void smoothScrollToPositionWithoutLayoutManager() throws InterruptedException {
143        mRecyclerView.setAdapter(new MockAdapter(20));
144        measure();
145        layout();
146        mRecyclerView.smoothScrollToPosition(5);
147    }
148
149    @Test
150    public void interceptTouchWithoutLayoutManager() {
151        mRecyclerView.setAdapter(new MockAdapter(20));
152        measure();
153        layout();
154        assertFalse(mRecyclerView.onInterceptTouchEvent(
155                MotionEvent.obtain(SystemClock.uptimeMillis(),
156                        SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 10, 10, 0)));
157    }
158
159    @Test
160    public void onTouchWithoutLayoutManager() {
161        mRecyclerView.setAdapter(new MockAdapter(20));
162        measure();
163        layout();
164        assertFalse(mRecyclerView.onTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(),
165                SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 10, 10, 0)));
166    }
167
168    @Test
169    public void layoutSimple() throws InterruptedException {
170        MockLayoutManager layoutManager = new MockLayoutManager();
171        mRecyclerView.setLayoutManager(layoutManager);
172        mRecyclerView.setAdapter(new MockAdapter(3));
173        layout();
174        assertEquals("when both layout manager and activity is set, recycler view should call"
175                + " layout manager's layout method", 1, layoutManager.mLayoutCount);
176    }
177
178    @Test
179    public void observingAdapters() {
180        MockAdapter adapterOld = new MockAdapter(1);
181        mRecyclerView.setAdapter(adapterOld);
182        assertTrue("attached adapter should have observables", adapterOld.hasObservers());
183
184        MockAdapter adapterNew = new MockAdapter(2);
185        mRecyclerView.setAdapter(adapterNew);
186        assertFalse("detached adapter should lose observable", adapterOld.hasObservers());
187        assertTrue("new adapter should have observers", adapterNew.hasObservers());
188
189        mRecyclerView.setAdapter(null);
190        assertNull("adapter should be removed successfully", mRecyclerView.getAdapter());
191        assertFalse("when adapter is removed, observables should be removed too",
192                adapterNew.hasObservers());
193    }
194
195    @Test
196    public void adapterChangeCallbacks() {
197        MockLayoutManager layoutManager = new MockLayoutManager();
198        mRecyclerView.setLayoutManager(layoutManager);
199        MockAdapter adapterOld = new MockAdapter(1);
200        mRecyclerView.setAdapter(adapterOld);
201        layoutManager.assertPrevNextAdapters(null, adapterOld);
202
203        MockAdapter adapterNew = new MockAdapter(2);
204        mRecyclerView.setAdapter(adapterNew);
205        layoutManager.assertPrevNextAdapters("switching adapters should trigger correct callbacks"
206                , adapterOld, adapterNew);
207
208        mRecyclerView.setAdapter(null);
209        layoutManager.assertPrevNextAdapters(
210                "Setting adapter null should trigger correct callbacks",
211                adapterNew, null);
212    }
213
214    @Test
215    public void recyclerOffsetsOnMove() {
216        MockLayoutManager  layoutManager = new MockLayoutManager();
217        final List<RecyclerView.ViewHolder> recycledVhs = new ArrayList<>();
218        mRecyclerView.setLayoutManager(layoutManager);
219        MockAdapter adapter = new MockAdapter(100) {
220            @Override
221            public void onViewRecycled(RecyclerView.ViewHolder holder) {
222                super.onViewRecycled(holder);
223                recycledVhs.add(holder);
224            }
225        };
226        MockViewHolder mvh = new MockViewHolder(new TextView(getContext()));
227        mRecyclerView.setAdapter(adapter);
228        adapter.bindViewHolder(mvh, 20);
229        mRecyclerView.mRecycler.mCachedViews.add(mvh);
230        mRecyclerView.offsetPositionRecordsForRemove(10, 9, false);
231
232        mRecyclerView.offsetPositionRecordsForRemove(11, 1, false);
233        assertEquals(1, recycledVhs.size());
234        assertSame(mvh, recycledVhs.get(0));
235    }
236
237    @Test
238    public void recyclerOffsetsOnAdd() {
239        MockLayoutManager  layoutManager = new MockLayoutManager();
240        final List<RecyclerView.ViewHolder> recycledVhs = new ArrayList<>();
241        mRecyclerView.setLayoutManager(layoutManager);
242        MockAdapter adapter = new MockAdapter(100) {
243            @Override
244            public void onViewRecycled(RecyclerView.ViewHolder holder) {
245                super.onViewRecycled(holder);
246                recycledVhs.add(holder);
247            }
248        };
249        MockViewHolder mvh = new MockViewHolder(new TextView(getContext()));
250        mRecyclerView.setAdapter(adapter);
251        adapter.bindViewHolder(mvh, 20);
252        mRecyclerView.mRecycler.mCachedViews.add(mvh);
253        mRecyclerView.offsetPositionRecordsForRemove(10, 9, false);
254
255        mRecyclerView.offsetPositionRecordsForInsert(15, 10);
256        assertEquals(11, mvh.mPosition);
257    }
258
259    @Test
260    public void savedStateWithStatelessLayoutManager() throws InterruptedException {
261        mRecyclerView.setLayoutManager(new MockLayoutManager() {
262            @Override
263            public Parcelable onSaveInstanceState() {
264                return null;
265            }
266        });
267        mRecyclerView.setAdapter(new MockAdapter(3));
268        Parcel parcel = Parcel.obtain();
269        String parcelSuffix = UUID.randomUUID().toString();
270        Parcelable savedState = mRecyclerView.onSaveInstanceState();
271        savedState.writeToParcel(parcel, 0);
272        parcel.writeString(parcelSuffix);
273
274        // reset position for reading
275        parcel.setDataPosition(0);
276        RecyclerView restored = new RecyclerView(getContext());
277        restored.setLayoutManager(new MockLayoutManager());
278        mRecyclerView.setAdapter(new MockAdapter(3));
279        // restore
280        savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
281        restored.onRestoreInstanceState(savedState);
282
283        assertEquals("Parcel reading should not go out of bounds", parcelSuffix,
284                parcel.readString());
285        assertEquals("When unmarshalling, all of the parcel should be read", 0, parcel.dataAvail());
286
287    }
288
289    @Test
290    public void savedState() throws InterruptedException {
291        MockLayoutManager mlm = new MockLayoutManager();
292        mRecyclerView.setLayoutManager(mlm);
293        mRecyclerView.setAdapter(new MockAdapter(3));
294        layout();
295        Parcelable savedState = mRecyclerView.onSaveInstanceState();
296        // we append a suffix to the parcelable to test out of bounds
297        String parcelSuffix = UUID.randomUUID().toString();
298        Parcel parcel = Parcel.obtain();
299        savedState.writeToParcel(parcel, 0);
300        parcel.writeString(parcelSuffix);
301
302        // reset for reading
303        parcel.setDataPosition(0);
304        // re-create
305        savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
306
307        RecyclerView restored = new RecyclerView(getContext());
308        MockLayoutManager mlmRestored = new MockLayoutManager();
309        restored.setLayoutManager(mlmRestored);
310        restored.setAdapter(new MockAdapter(3));
311        restored.onRestoreInstanceState(savedState);
312
313        assertEquals("Parcel reading should not go out of bounds", parcelSuffix,
314                parcel.readString());
315        assertEquals("When unmarshalling, all of the parcel should be read", 0, parcel.dataAvail());
316        assertEquals("uuid in layout manager should be preserved properly", mlm.mUuid,
317                mlmRestored.mUuid);
318        assertNotSame("stateless parameter should not be preserved", mlm.mLayoutCount,
319                mlmRestored.mLayoutCount);
320        layout();
321    }
322
323    @Test
324    public void dontSaveChildrenState() throws InterruptedException {
325        MockLayoutManager mlm = new MockLayoutManager() {
326            @Override
327            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
328                super.onLayoutChildren(recycler, state);
329                View view = recycler.getViewForPosition(0);
330                addView(view);
331                measureChildWithMargins(view, 0, 0);
332                view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
333            }
334        };
335        mRecyclerView.setLayoutManager(mlm);
336        mRecyclerView.setAdapter(new MockAdapter(3) {
337            @Override
338            public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
339                final LoggingView itemView = new LoggingView(parent.getContext());
340                //noinspection ResourceType
341                itemView.setId(3);
342                return new MockViewHolder(itemView);
343            }
344        });
345        measure();
346        layout();
347        View view = mRecyclerView.getChildAt(0);
348        assertNotNull("test sanity", view);
349        LoggingView loggingView = (LoggingView) view;
350        SparseArray<Parcelable> container = new SparseArray<Parcelable>();
351        mRecyclerView.saveHierarchyState(container);
352        assertEquals("children's save state method should not be called", 0,
353                loggingView.getOnSavedInstanceCnt());
354    }
355
356    @Test
357    public void smoothScrollWithCustomInterpolator() {
358        mRecyclerView.setLayoutManager(new MockLayoutManager());
359        mRecyclerView.setAdapter(new MockAdapter(20));
360        Interpolator interpolator = new LinearInterpolator();
361        mRecyclerView.smoothScrollBy(0, 100, interpolator);
362        assertSame(interpolator, mRecyclerView.mViewFlinger.mInterpolator);
363
364        mRecyclerView.smoothScrollBy(0, -100);
365        assertSame(RecyclerView.sQuinticInterpolator, mRecyclerView.mViewFlinger.mInterpolator);
366    }
367
368    @Test
369    public void prefetchChangesCacheSize() {
370        mRecyclerView.setAdapter(new MockAdapter(20));
371        MockLayoutManager mlm = new MockLayoutManager() {
372            @Override
373            public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state,
374                    RecyclerView.LayoutManager.LayoutPrefetchRegistry prefetchManager) {
375                prefetchManager.addPosition(0, 0);
376                prefetchManager.addPosition(1, 0);
377                prefetchManager.addPosition(2, 0);
378            }
379        };
380
381        RecyclerView.Recycler recycler = mRecyclerView.mRecycler;
382        assertEquals(RecyclerView.Recycler.DEFAULT_CACHE_SIZE, recycler.mViewCacheMax);
383        mRecyclerView.setLayoutManager(mlm);
384        assertEquals(RecyclerView.Recycler.DEFAULT_CACHE_SIZE, recycler.mViewCacheMax);
385
386        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
387            // layout, so prefetches can occur
388            mRecyclerView.measure(View.MeasureSpec.EXACTLY | 100, View.MeasureSpec.EXACTLY | 100);
389            mRecyclerView.layout(0, 0, 100, 100);
390
391            // prefetch gets 3 items, so expands cache by 3
392            mRecyclerView.mPrefetchRegistry.collectPrefetchPositionsFromView(mRecyclerView, false);
393            assertEquals(3, mRecyclerView.mPrefetchRegistry.mCount);
394            assertEquals(RecyclerView.Recycler.DEFAULT_CACHE_SIZE + 3, recycler.mViewCacheMax);
395
396            // Reset to default by removing layout
397            mRecyclerView.setLayoutManager(null);
398            assertEquals(RecyclerView.Recycler.DEFAULT_CACHE_SIZE, recycler.mViewCacheMax);
399
400            // And restore by restoring layout
401            mRecyclerView.setLayoutManager(mlm);
402            assertEquals(RecyclerView.Recycler.DEFAULT_CACHE_SIZE + 3, recycler.mViewCacheMax);
403        }
404    }
405
406    @Test
407    public void getNanoTime() {
408        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
409            // check that it looks vaguely time-ish
410            long time = mRecyclerView.getNanoTime();
411            assertNotEquals(0, time);
412            assertNotEquals(time, mRecyclerView.getNanoTime());
413        } else {
414            // expect to avoid cost of system.nanoTime on older platforms that don't do prefetch
415            assertEquals(0, mRecyclerView.getNanoTime());
416        }
417    }
418
419    @Test
420    public void findNestedRecyclerView() {
421        RecyclerView recyclerView = new RecyclerView(getContext());
422        assertEquals(recyclerView, RecyclerView.findNestedRecyclerView(recyclerView));
423
424        ViewGroup parent = new FrameLayout(getContext());
425        assertEquals(null, RecyclerView.findNestedRecyclerView(parent));
426        parent.addView(recyclerView);
427        assertEquals(recyclerView, RecyclerView.findNestedRecyclerView(parent));
428
429        ViewGroup grandParent = new FrameLayout(getContext());
430        assertEquals(null, RecyclerView.findNestedRecyclerView(grandParent));
431        grandParent.addView(parent);
432        assertEquals(recyclerView, RecyclerView.findNestedRecyclerView(grandParent));
433    }
434
435    @Test
436    public void clearNestedRecyclerViewIfNotNested() {
437        RecyclerView recyclerView = new RecyclerView(getContext());
438        ViewGroup parent = new FrameLayout(getContext());
439        parent.addView(recyclerView);
440        ViewGroup grandParent = new FrameLayout(getContext());
441        grandParent.addView(parent);
442
443        // verify trivial noop case
444        RecyclerView.ViewHolder holder = new RecyclerView.ViewHolder(recyclerView) {};
445        holder.mNestedRecyclerView = new WeakReference<>(recyclerView);
446        RecyclerView.clearNestedRecyclerViewIfNotNested(holder);
447        assertEquals(recyclerView, holder.mNestedRecyclerView.get());
448
449        // verify clear case
450        holder = new RecyclerView.ViewHolder(new View(getContext())) {};
451        holder.mNestedRecyclerView = new WeakReference<>(recyclerView);
452        RecyclerView.clearNestedRecyclerViewIfNotNested(holder);
453        assertNull(holder.mNestedRecyclerView);
454
455        // verify more deeply nested case
456        holder = new RecyclerView.ViewHolder(grandParent) {};
457        holder.mNestedRecyclerView = new WeakReference<>(recyclerView);
458        RecyclerView.clearNestedRecyclerViewIfNotNested(holder);
459        assertEquals(recyclerView, holder.mNestedRecyclerView.get());
460    }
461
462    @Test
463    public void toStringContainsClasses() {
464        RecyclerView recyclerView = new RecyclerView(getContext());
465        recyclerView.setAdapter(new MockAdapter(10));
466        recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
467
468        String string = recyclerView.toString();
469        assertTrue("must contain RV class", string.contains(RecyclerView.class.getName()));
470        assertTrue("must contain Adapter class", string.contains(MockAdapter.class.getName()));
471        assertTrue("must contain LM class", string.contains(LinearLayoutManager.class.getName()));
472        assertTrue("must contain ctx class", string.contains(getContext().getClass().getName()));
473    }
474
475    @Test
476    public void exceptionContainsClasses() {
477        RecyclerView recyclerView = new RecyclerView(getContext());
478        recyclerView.setAdapter(new MockAdapter(10));
479
480        try {
481            recyclerView.generateDefaultLayoutParams();
482            fail("exception expected");
483        } catch (IllegalStateException e) {
484            String message = e.getMessage();
485            assertTrue("must contain RV class",
486                    message.contains(RecyclerView.class.getName()));
487            assertTrue("must contain Adapter class",
488                    message.contains(MockAdapter.class.getName()));
489        }
490    }
491
492    static class MockLayoutManager extends RecyclerView.LayoutManager {
493
494        int mLayoutCount = 0;
495
496        int mAdapterChangedCount = 0;
497
498        RecyclerView.Adapter mPrevAdapter;
499
500        RecyclerView.Adapter mNextAdapter;
501
502        String mUuid = UUID.randomUUID().toString();
503
504        @Override
505        public void onAdapterChanged(RecyclerView.Adapter oldAdapter,
506                RecyclerView.Adapter newAdapter) {
507            super.onAdapterChanged(oldAdapter, newAdapter);
508            mPrevAdapter = oldAdapter;
509            mNextAdapter = newAdapter;
510            mAdapterChangedCount++;
511        }
512
513        @Override
514        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
515            mLayoutCount += 1;
516        }
517
518        @Override
519        public Parcelable onSaveInstanceState() {
520            LayoutManagerSavedState lss = new LayoutManagerSavedState();
521            lss.mUuid = mUuid;
522            return lss;
523        }
524
525        @Override
526        public void onRestoreInstanceState(Parcelable state) {
527            super.onRestoreInstanceState(state);
528            if (state instanceof LayoutManagerSavedState) {
529                mUuid = ((LayoutManagerSavedState) state).mUuid;
530            }
531        }
532
533        @Override
534        public RecyclerView.LayoutParams generateDefaultLayoutParams() {
535            return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
536                    ViewGroup.LayoutParams.WRAP_CONTENT);
537        }
538
539        public void assertPrevNextAdapters(String message, RecyclerView.Adapter prevAdapter,
540                RecyclerView.Adapter nextAdapter) {
541            assertSame(message, prevAdapter, mPrevAdapter);
542            assertSame(message, nextAdapter, mNextAdapter);
543        }
544
545        public void assertPrevNextAdapters(RecyclerView.Adapter prevAdapter,
546                RecyclerView.Adapter nextAdapter) {
547            assertPrevNextAdapters("Adapters from onAdapterChanged callback should match",
548                    prevAdapter, nextAdapter);
549        }
550
551        @Override
552        public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
553                RecyclerView.State state) {
554            return dx;
555        }
556
557        @Override
558        public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
559                RecyclerView.State state) {
560            return dy;
561        }
562
563        @Override
564        public boolean canScrollHorizontally() {
565            return true;
566        }
567
568        @Override
569        public boolean canScrollVertically() {
570            return true;
571        }
572    }
573
574    static class LayoutManagerSavedState implements Parcelable {
575
576        String mUuid;
577
578        public LayoutManagerSavedState(Parcel in) {
579            mUuid = in.readString();
580        }
581
582        public LayoutManagerSavedState() {
583
584        }
585
586        @Override
587        public int describeContents() {
588            return 0;
589        }
590
591        @Override
592        public void writeToParcel(Parcel dest, int flags) {
593            dest.writeString(mUuid);
594        }
595
596        public static final Parcelable.Creator<LayoutManagerSavedState> CREATOR
597                = new Parcelable.Creator<LayoutManagerSavedState>() {
598            @Override
599            public LayoutManagerSavedState createFromParcel(Parcel in) {
600                return new LayoutManagerSavedState(in);
601            }
602
603            @Override
604            public LayoutManagerSavedState[] newArray(int size) {
605                return new LayoutManagerSavedState[size];
606            }
607        };
608    }
609
610    static class MockAdapter extends RecyclerView.Adapter {
611
612        private int mCount = 0;
613
614
615        MockAdapter(int count) {
616            this.mCount = count;
617        }
618
619        @Override
620        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
621            return new MockViewHolder(new TextView(parent.getContext()));
622        }
623
624        @Override
625        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
626
627        }
628
629        @Override
630        public int getItemCount() {
631            return mCount;
632        }
633
634        void removeItems(int start, int count) {
635            mCount -= count;
636            notifyItemRangeRemoved(start, count);
637        }
638
639        void addItems(int start, int count) {
640            mCount += count;
641            notifyItemRangeInserted(start, count);
642        }
643    }
644
645    static class MockViewHolder extends RecyclerView.ViewHolder {
646        public Object mItem;
647        public MockViewHolder(View itemView) {
648            super(itemView);
649        }
650    }
651
652    static class LoggingView extends TextView {
653        private int mOnSavedInstanceCnt = 0;
654
655        public LoggingView(Context context) {
656            super(context);
657        }
658
659        public LoggingView(Context context, AttributeSet attrs) {
660            super(context, attrs);
661        }
662
663        public LoggingView(Context context, AttributeSet attrs, int defStyleAttr) {
664            super(context, attrs, defStyleAttr);
665        }
666
667        @Override
668        public Parcelable onSaveInstanceState() {
669            mOnSavedInstanceCnt ++;
670            return super.onSaveInstanceState();
671        }
672
673        public int getOnSavedInstanceCnt() {
674            return mOnSavedInstanceCnt;
675        }
676    }
677}