1 /*
2  * Copyright (C) 2008 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 
17 package android.widget.cts;
18 
19 import android.test.suitebuilder.annotation.MediumTest;
20 import android.widget.cts.R;
21 
22 
23 import org.xmlpull.v1.XmlPullParser;
24 
25 import android.app.Activity;
26 import android.app.Instrumentation;
27 import android.content.Context;
28 import android.cts.util.PollingCheck;
29 import android.database.DataSetObservable;
30 import android.database.DataSetObserver;
31 import android.graphics.Rect;
32 import android.test.ActivityInstrumentationTestCase;
33 import android.test.TouchUtils;
34 import android.test.UiThreadTest;
35 import android.test.ViewAsserts;
36 import android.util.AttributeSet;
37 import android.util.Xml;
38 import android.view.Gravity;
39 import android.view.KeyEvent;
40 import android.view.View;
41 import android.view.ViewGroup;
42 import android.view.animation.GridLayoutAnimationController.AnimationParameters;
43 import android.widget.AbsListView;
44 import android.widget.AdapterView;
45 import android.widget.Filter;
46 import android.widget.Filterable;
47 import android.widget.GridView;
48 import android.widget.ImageView;
49 import android.widget.ListAdapter;
50 import android.widget.AdapterView.OnItemClickListener;
51 import android.widget.cts.util.ViewTestUtils;
52 
53 /**
54  * Test {@link GridView}.
55  */
56 public class GridViewTest extends ActivityInstrumentationTestCase<GridViewCtsActivity> {
57     private GridView mGridView;
58     private Activity mActivity;
59     private Instrumentation mInstrumentation;
60 
GridViewTest()61     public GridViewTest() {
62         super("android.widget.cts", GridViewCtsActivity.class);
63     }
64 
findGridViewById(int id)65     private GridView findGridViewById(int id) {
66         return (GridView) mActivity.findViewById(id);
67     }
68 
69     @Override
setUp()70     protected void setUp() throws Exception {
71         super.setUp();
72         mGridView = null;
73         mActivity = getActivity();
74         new PollingCheck() {
75             @Override
76             protected boolean check() {
77                 return mActivity.hasWindowFocus();
78             }
79         }.run();
80         mInstrumentation = getInstrumentation();
81     }
82 
testConstructor()83     public void testConstructor() {
84         new GridView(mActivity);
85 
86         new GridView(mActivity, null);
87 
88         new GridView(mActivity, null, android.R.attr.gridViewStyle);
89 
90         XmlPullParser parser = mActivity.getResources().getXml(R.layout.gridview_layout);
91         AttributeSet attrs = Xml.asAttributeSet(parser);
92         new GridView(mActivity, attrs);
93         new GridView(mActivity, attrs, 0);
94 
95         try {
96             new GridView(null);
97             fail("should throw NullPointerException.");
98         } catch (NullPointerException e) {
99         }
100 
101         try {
102             new GridView(null, null);
103             fail("should throw NullPointerException.");
104         } catch (NullPointerException e) {
105         }
106 
107         try {
108             new GridView(null, null, 0);
109             fail("should throw NullPointerException.");
110         } catch (NullPointerException e) {
111         }
112     }
113 
testAccessAdapter()114     public void testAccessAdapter() {
115         mGridView = new GridView(mActivity);
116         // set Adapter
117         ImageAdapter adapter = new ImageAdapter(mActivity);
118         mGridView.setAdapter(adapter);
119         assertSame(adapter, mGridView.getAdapter());
120 
121         mGridView.setAdapter(null);
122         assertNull(mGridView.getAdapter());
123     }
124 
testSetSelection()125     public void testSetSelection() {
126         mGridView = new GridView(mActivity);
127         mGridView.setSelection(0);
128         assertEquals(0, mGridView.getSelectedItemPosition());
129 
130         mGridView.setSelection(-1);
131         assertEquals(-1, mGridView.getSelectedItemPosition());
132 
133         mGridView.setSelection(mGridView.getCount());
134         assertEquals(mGridView.getCount(), mGridView.getSelectedItemPosition());
135     }
136 
testPressKey()137     public void testPressKey() {
138         final int NUM_COLUMNS = 3;
139         mGridView = findGridViewById(R.id.gridview);
140 
141         MockOnItemClickListener listener = new MockOnItemClickListener();
142         mGridView.setOnItemClickListener(listener);
143 
144         // this test case can not be ran in UI thread.
145         mActivity.runOnUiThread(new Runnable() {
146             public void run() {
147                 mGridView.setAdapter(new ImageAdapter(mActivity));
148                 mGridView.setNumColumns(NUM_COLUMNS);
149                 mGridView.invalidate();
150                 mGridView.requestLayout();
151                 mGridView.requestFocus();
152             }
153         });
154         mInstrumentation.waitForIdleSync();
155 
156         assertEquals(0, mGridView.getSelectedItemPosition());
157         KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT);
158         mInstrumentation.sendKeySync(event);
159         assertEquals(1, mGridView.getSelectedItemPosition());
160 
161         event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_LEFT);
162         mInstrumentation.sendKeySync(event);
163         assertEquals(0, mGridView.getSelectedItemPosition());
164 
165         assertEquals(0, mGridView.getSelectedItemPosition());
166         mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_RIGHT);
167         assertEquals(1, mGridView.getSelectedItemPosition());
168 
169         mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_LEFT);
170         assertEquals(0, mGridView.getSelectedItemPosition());
171 
172         mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
173         assertEquals(NUM_COLUMNS, mGridView.getSelectedItemPosition());
174 
175         assertFalse(listener.hasOnItemClickCalled());
176         mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
177         assertTrue(listener.hasOnItemClickCalled());
178 
179         listener.reset();
180         assertFalse(listener.hasOnItemClickCalled());
181         mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_ENTER);
182         assertTrue(listener.hasOnItemClickCalled());
183     }
184 
testSetGravity()185     public void testSetGravity() {
186         mGridView = findGridViewById(R.id.gridview);
187 
188         View child;
189         final int NUM_COLUMNS = 1;
190         // this test case can not be ran in UI thread.
191         mActivity.runOnUiThread(new Runnable() {
192             public void run() {
193                 mGridView.setAdapter(new ImageAdapter(mActivity));
194                 mGridView.setNumColumns(NUM_COLUMNS);
195                 mGridView.setHorizontalSpacing(0);
196                 mGridView.setVerticalSpacing(0);
197             }
198         });
199         mInstrumentation.waitForIdleSync();
200 
201         mActivity.runOnUiThread(new Runnable() {
202             public void run() {
203                 mGridView.setGravity(Gravity.CENTER_HORIZONTAL);
204                 mGridView.invalidate();
205                 mGridView.requestLayout();
206             }
207         });
208         mInstrumentation.waitForIdleSync();
209 
210         child = mGridView.getChildAt(0); // get the first view.
211         ViewAsserts.assertHorizontalCenterAligned(mGridView, child);
212 
213         mActivity.runOnUiThread(new Runnable() {
214             public void run() {
215                 mGridView.setGravity(Gravity.LEFT);
216                 mGridView.invalidate();
217                 mGridView.requestLayout();
218             }
219         });
220         mInstrumentation.waitForIdleSync();
221 
222         child = mGridView.getChildAt(0); // get the first view.
223         ViewAsserts.assertLeftAligned(mGridView, child, mGridView.getListPaddingLeft());
224 
225         mActivity.runOnUiThread(new Runnable() {
226             public void run() {
227                 mGridView.setGravity(Gravity.RIGHT);
228                 mGridView.invalidate();
229                 mGridView.requestLayout();
230             }
231         });
232         mInstrumentation.waitForIdleSync();
233 
234         child = mGridView.getChildAt(0); // get the first view.
235         ViewAsserts.assertRightAligned(mGridView, child, mGridView.getListPaddingRight());
236     }
237 
testSetHorizontalSpacing()238     public void testSetHorizontalSpacing() {
239         testSetHorizontalSpacing(View.LAYOUT_DIRECTION_LTR);
240     }
241 
testSetHorizontalSpacingRTL()242     public void testSetHorizontalSpacingRTL() {
243         testSetHorizontalSpacing(View.LAYOUT_DIRECTION_RTL);
244     }
245 
testSetHorizontalSpacing(final int layoutDir)246     public void testSetHorizontalSpacing(final int layoutDir) {
247         mGridView = findGridViewById(R.id.gridview);
248         mActivity.runOnUiThread(new Runnable() {
249             @Override
250             public void run() {
251                 mGridView.setLayoutDirection(layoutDir);
252             }
253         });
254         mGridView.setStretchMode(GridView.NO_STRETCH);
255         // Number of columns should be big enough, otherwise the
256         // horizontal spacing cannot be correctly verified.
257         mGridView.setNumColumns(28);
258 
259 
260         // this test case can not be ran in UI thread.
261         mActivity.runOnUiThread(new Runnable() {
262             public void run() {
263                 mGridView.setAdapter(new MockGridViewAdapter(3));
264                 mGridView.setHorizontalSpacing(0);
265             }
266         });
267         mInstrumentation.waitForIdleSync();
268 
269         View child0 = mGridView.getChildAt(0);
270         View child1 = mGridView.getChildAt(1);
271         if (layoutDir == View.LAYOUT_DIRECTION_LTR) {
272             assertEquals(0, child1.getLeft() - child0.getRight());
273         } else {
274             assertEquals(0, child0.getLeft() - child1.getRight());
275         }
276 
277         mActivity.runOnUiThread(new Runnable() {
278             public void run() {
279                 mGridView.setHorizontalSpacing(5);
280             }
281         });
282         mInstrumentation.waitForIdleSync();
283 
284         child0 = mGridView.getChildAt(0);
285         child1 = mGridView.getChildAt(1);
286         if (layoutDir == View.LAYOUT_DIRECTION_LTR) {
287             assertEquals(5, child1.getLeft() - child0.getRight());
288         } else {
289             assertEquals(5, child0.getLeft() - child1.getRight());
290         }
291     }
292 
testSetVerticalSpacing()293     public void testSetVerticalSpacing() {
294         mGridView = findGridViewById(R.id.gridview);
295 
296         // this test case can not be ran in UI thread.
297         mActivity.runOnUiThread(new Runnable() {
298             public void run() {
299                 mGridView.setAdapter(new MockGridViewAdapter(3));
300                 mGridView.setVerticalSpacing(0);
301             }
302         });
303         mInstrumentation.waitForIdleSync();
304 
305         View child0 = mGridView.getChildAt(0);
306         View child1 = mGridView.getChildAt(1);
307         assertEquals(0, child1.getTop() - child0.getBottom());
308 
309         mActivity.runOnUiThread(new Runnable() {
310             public void run() {
311                 mGridView.setVerticalSpacing(5);
312             }
313         });
314         mInstrumentation.waitForIdleSync();
315 
316         child0 = mGridView.getChildAt(0);
317         child1 = mGridView.getChildAt(1);
318         assertEquals(5, child1.getTop() - child0.getBottom());
319     }
320 
testAccessStretchMode()321     public void testAccessStretchMode() {
322         mGridView = findGridViewById(R.id.gridview);
323         View child;
324 
325         final int NUM_COLUMNS = 8;
326         // this test case can not be ran in UI thread.
327         mActivity.runOnUiThread(new Runnable() {
328             public void run() {
329                 mGridView.setAdapter(new ImageAdapter(mActivity));
330                 mGridView.setColumnWidth(10);
331                 mGridView.setNumColumns(NUM_COLUMNS);
332                 mGridView.setHorizontalSpacing(0);
333                 mGridView.setVerticalSpacing(0);
334                 mGridView.invalidate();
335                 mGridView.requestLayout();
336             }
337         });
338         mInstrumentation.waitForIdleSync();
339 
340         int[][] childRight = new int[3][3];
341         int STRETCH_SPACING = 0;
342         int STRETCH_COLUMN_WIDTH = 1;
343         int STRETCH_SPACING_UNIFORM = 2;
344         int INDEX_RIGHTMOST = 0;
345         int INDEX_0 = 1;
346         int INDEX_1 = 2;
347 
348         mActivity.runOnUiThread(new Runnable() {
349             public void run() {
350                 mGridView.setColumnWidth(15);
351                 mGridView.setStretchMode(GridView.STRETCH_SPACING);
352                 mGridView.invalidate();
353                 mGridView.requestLayout();
354             }
355         });
356         mInstrumentation.waitForIdleSync();
357         assertEquals(GridView.STRETCH_SPACING, mGridView.getStretchMode());
358         child = mGridView.getChildAt(NUM_COLUMNS - 1); // get the rightmost view at the first line.
359         childRight[STRETCH_SPACING][INDEX_RIGHTMOST] = child.getRight();
360 
361         child = mGridView.getChildAt(0);
362         childRight[STRETCH_SPACING][INDEX_0] = child.getRight();
363 
364         child = mGridView.getChildAt(1);
365         childRight[STRETCH_SPACING][INDEX_1] = child.getRight();
366 
367         mActivity.runOnUiThread(new Runnable() {
368             public void run() {
369                 mGridView.setColumnWidth(15);
370                 mGridView.setStretchMode(GridView.STRETCH_COLUMN_WIDTH);
371                 mGridView.invalidate();
372                 mGridView.requestLayout();
373             }
374         });
375         mInstrumentation.waitForIdleSync();
376         assertEquals(GridView.STRETCH_COLUMN_WIDTH, mGridView.getStretchMode());
377         child = mGridView.getChildAt(NUM_COLUMNS - 1); // get the rightmost view at the first line.
378         childRight[STRETCH_COLUMN_WIDTH][INDEX_RIGHTMOST] = child.getRight();
379 
380         child = mGridView.getChildAt(0);
381         childRight[STRETCH_COLUMN_WIDTH][INDEX_0] = child.getRight();
382 
383         child = mGridView.getChildAt(1);
384         childRight[STRETCH_COLUMN_WIDTH][INDEX_1] = child.getRight();
385 
386         mActivity.runOnUiThread(new Runnable() {
387             public void run() {
388                 mGridView.setColumnWidth(15);
389                 mGridView.setStretchMode(GridView.STRETCH_SPACING_UNIFORM);
390                 mGridView.invalidate();
391                 mGridView.requestLayout();
392             }
393         });
394         mInstrumentation.waitForIdleSync();
395         assertEquals(GridView.STRETCH_SPACING_UNIFORM, mGridView.getStretchMode());
396         child = mGridView.getChildAt(NUM_COLUMNS - 1); // get the rightmost view at the first line.
397         childRight[STRETCH_SPACING_UNIFORM][INDEX_RIGHTMOST] = child.getRight();
398 
399         child = mGridView.getChildAt(0);
400         childRight[STRETCH_SPACING_UNIFORM][INDEX_0] = child.getRight();
401 
402         child = mGridView.getChildAt(1);
403         childRight[STRETCH_SPACING_UNIFORM][INDEX_1] = child.getRight();
404 
405         assertTrue(childRight[STRETCH_SPACING][INDEX_RIGHTMOST]
406                 > childRight[STRETCH_COLUMN_WIDTH][INDEX_RIGHTMOST]);
407         assertTrue(childRight[STRETCH_SPACING][INDEX_RIGHTMOST]
408                 > childRight[STRETCH_SPACING_UNIFORM][INDEX_RIGHTMOST]);
409         assertTrue(childRight[STRETCH_SPACING][INDEX_0]
410                 == childRight[STRETCH_COLUMN_WIDTH][INDEX_0]);
411         assertTrue(childRight[STRETCH_SPACING][INDEX_0]
412                 < childRight[STRETCH_SPACING_UNIFORM][INDEX_0]);
413         assertTrue(childRight[STRETCH_SPACING][INDEX_1]
414                 > childRight[STRETCH_COLUMN_WIDTH][INDEX_1]);
415         assertTrue(childRight[STRETCH_SPACING][INDEX_1]
416                 < childRight[STRETCH_SPACING_UNIFORM][INDEX_1]);
417     }
418 
419     public void testSetNumColumns() {
420         mGridView = findGridViewById(R.id.gridview);
421 
422         // this test case can not be ran in UI thread.
423         mActivity.runOnUiThread(new Runnable() {
424             public void run() {
425                 mGridView.setAdapter(new MockGridViewAdapter(10));
426                 mGridView.setHorizontalSpacing(0);
427                 mGridView.setVerticalSpacing(0);
428                 mGridView.setNumColumns(10);
429             }
430         });
431         mInstrumentation.waitForIdleSync();
432 
433         View child0 = mGridView.getChildAt(0);
434         View child9 = mGridView.getChildAt(9);
435         assertEquals(child0.getBottom(), child9.getBottom());
436 
437         mActivity.runOnUiThread(new Runnable() {
438             public void run() {
439                 mGridView.setNumColumns(9);
440             }
441         });
442         mInstrumentation.waitForIdleSync();
443 
444         child0 = mGridView.getChildAt(0);
445         child9 = mGridView.getChildAt(9);
446         assertEquals(child0.getBottom(), child9.getTop());
447         assertEquals(child0.getLeft(), child9.getLeft());
448 
449         mActivity.runOnUiThread(new Runnable() {
450             public void run() {
451                 mGridView.setNumColumns(1);
452             }
453         });
454         mInstrumentation.waitForIdleSync();
455 
456         for (int i = 0; i < mGridView.getChildCount(); i++) {
457             View child = mGridView.getChildAt(i);
458             assertEquals(0, child.getLeft() - mGridView.getListPaddingLeft());
459         }
460     }
461 
462     public void testGetNumColumns() {
463         mGridView = new GridView(mActivity);
464 
465         assertEquals(mGridView.getNumColumns(), GridView.AUTO_FIT);
466 
467         mGridView = findGridViewById(R.id.gridview);
468 
469         mActivity.runOnUiThread(new Runnable() {
470             public void run() {
471                 mGridView.setAdapter(new MockGridViewAdapter(10));
472                 mGridView.setNumColumns(10);
473             }
474         });
475         mInstrumentation.waitForIdleSync();
476 
477         assertEquals(mGridView.getNumColumns(), 10);
478 
479         mActivity.runOnUiThread(new Runnable() {
480             public void run() {
481                 mGridView.setNumColumns(1);
482             }
483         });
484         mInstrumentation.waitForIdleSync();
485 
486         assertEquals(mGridView.getNumColumns(), 1);
487 
488         mActivity.runOnUiThread(new Runnable() {
489             public void run() {
490                 mGridView.setNumColumns(0);
491             }
492         });
493         mInstrumentation.waitForIdleSync();
494 
495         //although setNumColumns(0) was called, the number of columns should be 1
496         assertEquals(mGridView.getNumColumns(), 1);
497     }
498 
499     public void testAttachLayoutAnimationParameters() {
500         MockGridView mockGridView = new MockGridView(mActivity);
501         ViewGroup.LayoutParams p = new ViewGroup.LayoutParams(320, 480);
502         mockGridView.attachLayoutAnimationParameters(null, p, 1, 2);
503         AnimationParameters animationParams = (AnimationParameters) p.layoutAnimationParameters;
504         assertEquals(1, animationParams.index);
505         assertEquals(2, animationParams.count);
506     }
507 
508     public void testLayoutChildren() {
509         MockGridView mockGridView = new MockGridView(mActivity);
510         mockGridView.layoutChildren();
511     }
512 
513     @UiThreadTest
514     public void testOnFocusChanged() {
515         final MockGridView mockGridView = new MockGridView(mActivity);
516 
517         assertFalse(mockGridView.hasCalledOnFocusChanged());
518         mockGridView.setAdapter(new MockGridViewAdapter(10));
519         mockGridView.setFocusable(true);
520         mockGridView.requestFocus();
521 
522         assertTrue(mockGridView.hasCalledOnFocusChanged());
523         mockGridView.reset();
524         assertFalse(mockGridView.hasCalledOnFocusChanged());
525 
526         mockGridView.clearFocus();
527 
528         assertTrue(mockGridView.hasCalledOnFocusChanged());
529     }
530 
531     public void testOnMeasure() {
532         // Do not test it. It's implementation detail.
533     }
534 
535     public void testSetColumnWidth() {
536         mGridView = findGridViewById(R.id.gridview);
537 
538         // this test case can not be ran in UI thread.
539         mActivity.runOnUiThread(new Runnable() {
540             public void run() {
541                 mGridView.setAdapter(new MockGridViewAdapter(10));
542                 mGridView.setNumColumns(GridView.AUTO_FIT);
543                 mGridView.setHorizontalSpacing(0);
544                 mGridView.setVerticalSpacing(0);
545                 mGridView.setColumnWidth(0);
546             }
547         });
548         mInstrumentation.waitForIdleSync();
549 
550         // Verify whether column number equals 2.
551         View child0 = mGridView.getChildAt(0);
552         View child1 = mGridView.getChildAt(1);
553         View child2 = mGridView.getChildAt(2);
554         assertEquals(child0.getBottom(), child1.getBottom());
555         assertEquals(child0.getLeft(), child2.getLeft());
556 
557         mActivity.runOnUiThread(new Runnable() {
558             public void run() {
559                 mGridView.setNumColumns(GridView.AUTO_FIT);
560                 mGridView.setColumnWidth(Integer.MAX_VALUE);
561             }
562         });
563         mInstrumentation.waitForIdleSync();
564 
565         child0 = mGridView.getChildAt(0);
566         child1 = mGridView.getChildAt(1);
567         assertEquals(child0.getBottom(), child1.getTop());
568         assertEquals(child0.getLeft(), child1.getLeft());
569     }
570 
571     @MediumTest
572     public void testFullyDetachUnusedViewOnScroll() {
573         mGridView = findGridViewById(R.id.gridview);
574         final AttachDetachAwareView theView = new AttachDetachAwareView(mActivity);
575         ViewTestUtils.runOnMainAndDrawSync(getInstrumentation(), mGridView, () -> {
576             mGridView.setAdapter(new DummyAdapter(1000, theView));
577         });
578         assertEquals("test sanity", 1, theView.mOnAttachCount);
579         assertEquals("test sanity", 0, theView.mOnDetachCount);
580         ViewTestUtils.runOnMainAndDrawSync(getInstrumentation(), mGridView, () -> {
581             mGridView.scrollListBy(mGridView.getHeight() * 2);
582         });
583         assertNull("test sanity, unused view should be removed", theView.getParent());
584         assertEquals("unused view should be detached", 1, theView.mOnDetachCount);
585         assertFalse(theView.isTemporarilyDetached());
586         ViewTestUtils.runOnMainAndDrawSync(getInstrumentation(), mGridView, () -> {
587             mGridView.scrollListBy(-mGridView.getHeight() * 2);
588             // listview limits scroll to 1 page which is why we call it twice here.
589             mGridView.scrollListBy(-mGridView.getHeight() * 2);
590         });
591         assertNotNull("test sanity, view should be re-added", theView.getParent());
592         assertEquals("view should receive another attach call", 2, theView.mOnAttachCount);
593         assertEquals("view should not receive a detach call", 1, theView.mOnDetachCount);
594         assertFalse(theView.isTemporarilyDetached());
595     }
596 
597     @MediumTest
testFullyDetachUnusedViewOnReLayout()598     public void testFullyDetachUnusedViewOnReLayout() {
599         mGridView = findGridViewById(R.id.gridview);
600         final AttachDetachAwareView theView = new AttachDetachAwareView(mActivity);
601         ViewTestUtils.runOnMainAndDrawSync(getInstrumentation(), mGridView, () -> {
602             mGridView.setAdapter(new DummyAdapter(1000, theView));
603         });
604         assertEquals("test sanity", 1, theView.mOnAttachCount);
605         assertEquals("test sanity", 0, theView.mOnDetachCount);
606         ViewTestUtils.runOnMainAndDrawSync(getInstrumentation(), mGridView, () -> {
607             mGridView.setSelection(800);
608         });
609         assertNull("test sanity, unused view should be removed", theView.getParent());
610         assertEquals("unused view should be detached", 1, theView.mOnDetachCount);
611         assertFalse(theView.isTemporarilyDetached());
612         ViewTestUtils.runOnMainAndDrawSync(getInstrumentation(), mGridView, () -> {
613             mGridView.setSelection(0);
614         });
615         assertNotNull("test sanity, view should be re-added", theView.getParent());
616         assertEquals("view should receive another attach call", 2, theView.mOnAttachCount);
617         assertEquals("view should not receive a detach call", 1, theView.mOnDetachCount);
618         assertFalse(theView.isTemporarilyDetached());
619     }
620 
621     @MediumTest
testFullyDetachUnusedViewOnScrollForFocus()622     public void testFullyDetachUnusedViewOnScrollForFocus() {
623         mGridView = findGridViewById(R.id.gridview);
624         final AttachDetachAwareView theView = new AttachDetachAwareView(mActivity);
625         ViewTestUtils.runOnMainAndDrawSync(getInstrumentation(), mGridView, () -> {
626             mGridView.setAdapter(new DummyAdapter(1000, theView));
627         });
628         assertEquals("test sanity", 1, theView.mOnAttachCount);
629         assertEquals("test sanity", 0, theView.mOnDetachCount);
630         while(theView.getParent() != null) {
631             assertEquals("the view should NOT be detached", 0, theView.mOnDetachCount);
632             sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
633             ViewTestUtils.runOnMainAndDrawSync(getInstrumentation(), mGridView, null);
634         }
635         assertEquals("the view should be detached", 1, theView.mOnDetachCount);
636         assertFalse(theView.isTemporarilyDetached());
637         while(theView.getParent() == null) {
638             sendKeys(KeyEvent.KEYCODE_DPAD_UP);
639             ViewTestUtils.runOnMainAndDrawSync(getInstrumentation(), mGridView, null);
640         }
641         assertEquals("the view should be re-attached", 2, theView.mOnAttachCount);
642         assertEquals("the view should not recieve another detach", 1, theView.mOnDetachCount);
643         assertFalse(theView.isTemporarilyDetached());
644     }
645 
646     private static class MockGridView extends GridView {
647         private boolean mCalledOnMeasure = false;
648         private boolean mCalledOnFocusChanged = false;
649 
hasCalledOnMeasure()650         public boolean hasCalledOnMeasure() {
651             return mCalledOnMeasure;
652         }
653 
hasCalledOnFocusChanged()654         public boolean hasCalledOnFocusChanged() {
655             return mCalledOnFocusChanged;
656         }
657 
reset()658         public void reset() {
659             mCalledOnMeasure = false;
660             mCalledOnFocusChanged = false;
661         }
662 
MockGridView(Context context)663         public MockGridView(Context context) {
664             super(context);
665         }
666 
MockGridView(Context context, AttributeSet attrs)667         public MockGridView(Context context, AttributeSet attrs) {
668             super(context, attrs);
669         }
670 
MockGridView(Context context, AttributeSet attrs, int defStyle)671         public MockGridView(Context context, AttributeSet attrs, int defStyle) {
672             super(context, attrs, defStyle);
673         }
674 
675         @Override
attachLayoutAnimationParameters(View child, ViewGroup.LayoutParams params, int index, int count)676         public void attachLayoutAnimationParameters(View child,
677                 ViewGroup.LayoutParams params, int index, int count) {
678             super.attachLayoutAnimationParameters(child, params, index, count);
679         }
680 
681         @Override
layoutChildren()682         protected void layoutChildren() {
683             super.layoutChildren();
684         }
685 
686         @Override
computeVerticalScrollExtent()687         protected int computeVerticalScrollExtent() {
688             return super.computeVerticalScrollExtent();
689         }
690 
691         @Override
computeVerticalScrollOffset()692         protected int computeVerticalScrollOffset() {
693             return super.computeVerticalScrollOffset();
694         }
695 
696         @Override
computeVerticalScrollRange()697         protected int computeVerticalScrollRange() {
698             return super.computeVerticalScrollRange();
699         }
700 
701         @Override
onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect)702         protected void onFocusChanged(boolean gainFocus, int direction,
703                 Rect previouslyFocusedRect) {
704             mCalledOnFocusChanged = true;
705             super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
706         }
707 
708         @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)709         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
710             mCalledOnMeasure = true;
711             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
712         }
713     }
714 
715     class MockOnItemClickListener implements OnItemClickListener {
716         private boolean mOnItemClickCalled = false;
717 
hasOnItemClickCalled()718         public boolean hasOnItemClickCalled() {
719             return mOnItemClickCalled;
720         }
721 
reset()722         public void reset() {
723             mOnItemClickCalled = false;
724         }
725 
onItemClick(AdapterView<?> parent, View view, int position, long id)726         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
727             mOnItemClickCalled = true;
728         }
729     }
730 
731     private class MockGridViewAdapter implements ListAdapter, Filterable {
732         private final int mCount;
733 
MockGridViewAdapter(int count)734         MockGridViewAdapter(int count) {
735             mCount = count;
736         }
737 
MockGridViewAdapter()738         MockGridViewAdapter() {
739             this(1);
740         }
741 
areAllItemsEnabled()742         public boolean areAllItemsEnabled() {
743             return true;
744         }
745 
isEnabled(int position)746         public boolean isEnabled(int position) {
747             return true;
748         }
749 
registerDataSetObserver(DataSetObserver observer)750         public void registerDataSetObserver(DataSetObserver observer) {
751         }
752 
unregisterDataSetObserver(DataSetObserver observer)753         public void unregisterDataSetObserver(DataSetObserver observer) {
754         }
755 
getCount()756         public int getCount() {
757             return mCount;
758         }
759 
getItem(int position)760         public Object getItem(int position) {
761             return position;
762         }
763 
getItemId(int position)764         public long getItemId(int position) {
765             return position;
766         }
767 
hasStableIds()768         public boolean hasStableIds() {
769             return false;
770         }
771 
getView(int position, View convertView, ViewGroup parent)772         public View getView(int position, View convertView, ViewGroup parent) {
773             if ((convertView != null) && (convertView instanceof ImageView)) {
774                 ((ImageView) convertView).setImageResource(R.drawable.size_48x48);
775                 return convertView;
776             }
777 
778             ImageView newView = new ImageView(mActivity);
779             AbsListView.LayoutParams params = new AbsListView.LayoutParams(
780                                                   AbsListView.LayoutParams.WRAP_CONTENT,
781                                                   AbsListView.LayoutParams.WRAP_CONTENT);
782             newView.setLayoutParams(params);
783             newView.setImageResource(R.drawable.size_48x48);
784             return newView;
785         }
786 
getItemViewType(int position)787         public int getItemViewType(int position) {
788             return 0;
789         }
790 
getViewTypeCount()791         public int getViewTypeCount() {
792             return 1;
793         }
794 
isEmpty()795         public boolean isEmpty() {
796             return false;
797         }
798 
getFilter()799         public Filter getFilter() {
800             return new FilterTest();
801         }
802     }
803 
804     private static class FilterTest extends Filter {
805         @Override
performFiltering(CharSequence constraint)806         protected Filter.FilterResults performFiltering(CharSequence constraint) {
807             return null;
808         }
809 
810         @Override
publishResults(CharSequence constraint, Filter.FilterResults results)811         protected void publishResults(CharSequence constraint, Filter.FilterResults results) {
812         }
813     }
814 
815     public class ImageAdapter implements ListAdapter {
ImageAdapter(Context c)816         public ImageAdapter(Context c) {
817             mContext = c;
818         }
819 
getCount()820         public int getCount() {
821             return mThumbIds.length;
822         }
823 
getItem(int position)824         public Object getItem(int position) {
825             return position;
826         }
827 
getItemId(int position)828         public long getItemId(int position) {
829             return position;
830         }
831 
getView(int position, View convertView, ViewGroup parent)832         public View getView(int position, View convertView, ViewGroup parent) {
833             ImageView imageView;
834             if (convertView == null) {
835                 imageView = new ImageView(mContext);
836                 int layoutSize = (int)(50 * mContext.getResources().getDisplayMetrics().density);
837                 imageView.setLayoutParams(new GridView.LayoutParams(layoutSize, layoutSize));
838                 imageView.setAdjustViewBounds(false);
839                 imageView.setScaleType(ImageView.ScaleType.CENTER);
840                 imageView.setPadding(0, 0, 0, 0);
841             } else {
842                 imageView = (ImageView) convertView;
843             }
844 
845             imageView.setImageResource(mThumbIds[position]);
846 
847             return imageView;
848         }
849 
850         private Context mContext;
851 
852         private Integer[] mThumbIds = {
853                 R.drawable.failed, R.drawable.pass,
854                 R.drawable.animated, R.drawable.black,
855                 R.drawable.blue, R.drawable.red,
856                 R.drawable.animated, R.drawable.black,
857                 R.drawable.blue, R.drawable.failed,
858                 R.drawable.pass, R.drawable.red,
859         };
860 
861         private final DataSetObservable mDataSetObservable = new DataSetObservable();
862 
hasStableIds()863         public boolean hasStableIds() {
864             return false;
865         }
866 
registerDataSetObserver(DataSetObserver observer)867         public void registerDataSetObserver(DataSetObserver observer) {
868             mDataSetObservable.registerObserver(observer);
869         }
870 
unregisterDataSetObserver(DataSetObserver observer)871         public void unregisterDataSetObserver(DataSetObserver observer) {
872             mDataSetObservable.unregisterObserver(observer);
873         }
874 
notifyDataSetChanged()875         public void notifyDataSetChanged() {
876             mDataSetObservable.notifyChanged();
877         }
878 
notifyDataSetInvalidated()879         public void notifyDataSetInvalidated() {
880             mDataSetObservable.notifyInvalidated();
881         }
882 
areAllItemsEnabled()883         public boolean areAllItemsEnabled() {
884             return true;
885         }
886 
isEnabled(int position)887         public boolean isEnabled(int position) {
888             return true;
889         }
890 
getDropDownView(int position, View convertView, ViewGroup parent)891         public View getDropDownView(int position, View convertView, ViewGroup parent) {
892             return getView(position, convertView, parent);
893         }
894 
getItemViewType(int position)895         public int getItemViewType(int position) {
896             return 0;
897         }
898 
getViewTypeCount()899         public int getViewTypeCount() {
900             return 1;
901         }
902 
isEmpty()903         public boolean isEmpty() {
904             return getCount() == 0;
905         }
906     }
907 }
908