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