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 static com.android.compatibility.common.util.CtsMockitoUtils.within; 20 21 import static org.junit.Assert.assertEquals; 22 import static org.junit.Assert.assertFalse; 23 import static org.junit.Assert.assertNotNull; 24 import static org.junit.Assert.assertNull; 25 import static org.junit.Assert.assertSame; 26 import static org.junit.Assert.assertTrue; 27 import static org.mockito.Mockito.any; 28 import static org.mockito.Mockito.anyInt; 29 import static org.mockito.Mockito.atLeastOnce; 30 import static org.mockito.Mockito.doAnswer; 31 import static org.mockito.Mockito.eq; 32 import static org.mockito.Mockito.inOrder; 33 import static org.mockito.Mockito.mock; 34 import static org.mockito.Mockito.never; 35 import static org.mockito.Mockito.reset; 36 import static org.mockito.Mockito.times; 37 import static org.mockito.Mockito.verify; 38 import static org.mockito.Mockito.verifyNoMoreInteractions; 39 import static org.mockito.Mockito.verifyZeroInteractions; 40 41 import android.app.Activity; 42 import android.app.Instrumentation; 43 import android.content.Context; 44 import android.content.pm.PackageManager; 45 import android.content.res.Configuration; 46 import android.graphics.Canvas; 47 import android.graphics.Color; 48 import android.graphics.Insets; 49 import android.graphics.Rect; 50 import android.graphics.drawable.Drawable; 51 import android.text.Editable; 52 import android.text.SpannableStringBuilder; 53 import android.text.TextUtils; 54 import android.util.AttributeSet; 55 import android.util.Xml; 56 import android.view.ActionMode; 57 import android.view.ContextMenu.ContextMenuInfo; 58 import android.view.KeyEvent; 59 import android.view.Menu; 60 import android.view.View; 61 import android.view.ViewGroup; 62 import android.view.WindowInsets; 63 import android.widget.AbsListView; 64 import android.widget.AbsListView.OnScrollListener; 65 import android.widget.AdapterView; 66 import android.widget.ArrayAdapter; 67 import android.widget.EdgeEffect; 68 import android.widget.ListAdapter; 69 import android.widget.ListView; 70 import android.widget.TextView; 71 import android.widget.cts.util.TestUtils; 72 import android.widget.cts.util.TestUtilsMatchers; 73 74 import androidx.test.InstrumentationRegistry; 75 import androidx.test.annotation.UiThreadTest; 76 import androidx.test.filters.LargeTest; 77 import androidx.test.filters.MediumTest; 78 import androidx.test.filters.SmallTest; 79 import androidx.test.rule.ActivityTestRule; 80 import androidx.test.runner.AndroidJUnit4; 81 82 import com.android.compatibility.common.util.CtsKeyEventUtil; 83 import com.android.compatibility.common.util.CtsTouchUtils; 84 import com.android.compatibility.common.util.PollingCheck; 85 import com.android.compatibility.common.util.WidgetTestUtils; 86 import com.android.compatibility.common.util.WindowUtil; 87 88 import org.hamcrest.MatcherAssert; 89 import org.junit.Before; 90 import org.junit.Rule; 91 import org.junit.Test; 92 import org.junit.runner.RunWith; 93 import org.mockito.ArgumentCaptor; 94 import org.mockito.InOrder; 95 import org.mockito.invocation.InvocationOnMock; 96 import org.xmlpull.v1.XmlPullParser; 97 import org.xmlpull.v1.XmlPullParserException; 98 99 import java.io.IOException; 100 import java.util.ArrayList; 101 import java.util.Arrays; 102 import java.util.List; 103 104 @SmallTest 105 @RunWith(AndroidJUnit4.class) 106 public class AbsListViewTest { 107 private static final String[] SHORT_LIST = new String[] { "This", "is", "short", "!" }; 108 109 private static final String[] COUNTRY_LIST = new String[] { 110 "Argentina", "Armenia", "Aruba", "Australia", "Belarus", "Belgium", "Belize", "Benin", 111 "Botswana", "Brazil", "Cameroon", "China", "Colombia", "Costa Rica", "Cyprus", 112 "Denmark", "Djibouti", "Ethiopia", "Fiji", "Finland", "France", "Gabon", "Germany", 113 "Ghana", "Haiti", "Honduras", "Iceland", "India", "Indonesia", "Ireland", "Italy", 114 "Japan", "Kiribati", "Laos", "Lesotho", "Liberia", "Malaysia", "Mongolia", "Myanmar", 115 "Nauru", "Norway", "Oman", "Pakistan", "Philippines", "Portugal", "Romania", "Russia", 116 "Rwanda", "Singapore", "Slovakia", "Slovenia", "Somalia", "Swaziland", "Togo", "Tuvalu", 117 "Uganda", "Ukraine", "United States", "Vanuatu", "Venezuela", "Zimbabwe" 118 }; 119 120 @Rule 121 public ActivityTestRule<ListViewCtsActivity> mActivityRule = 122 new ActivityTestRule<>(ListViewCtsActivity.class); 123 124 private Instrumentation mInstrumentation; 125 private CtsTouchUtils mCtsTouchUtils; 126 private CtsKeyEventUtil mCtsKeyEventUtil; 127 private AbsListView mListView; 128 private Context mContext; 129 private AttributeSet mAttributeSet; 130 private ArrayAdapter<String> mShortAdapter; 131 private ArrayAdapter<String> mCountriesAdapter; 132 private AbsListView.MultiChoiceModeListener mMultiChoiceModeListener; 133 134 private static final float DELTA = 0.001f; 135 136 @Before setup()137 public void setup() throws Throwable { 138 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 139 mCtsTouchUtils = new CtsTouchUtils(mInstrumentation.getTargetContext()); 140 mCtsKeyEventUtil = new CtsKeyEventUtil(mInstrumentation.getTargetContext()); 141 final Activity activity = mActivityRule.getActivity(); 142 // Always use the activity context 143 mContext = activity; 144 145 WindowUtil.waitForFocus(activity); 146 147 XmlPullParser parser = mContext.getResources().getXml(R.layout.listview_layout); 148 WidgetTestUtils.beginDocument(parser, "FrameLayout"); 149 mAttributeSet = Xml.asAttributeSet(parser); 150 151 mShortAdapter = new ArrayAdapter<>(mContext, 152 android.R.layout.simple_list_item_1, SHORT_LIST); 153 mCountriesAdapter = new ArrayAdapter<>(mContext, 154 android.R.layout.simple_list_item_1, COUNTRY_LIST); 155 156 mListView = (ListView) activity.findViewById(R.id.listview_default); 157 158 // Full-height drag gestures clash with system navigation gestures (such as 159 // swipe up from the bottom of the screen to go home). Get the system 160 // gesture insets and apply bottom padding on the entire content so 161 // that our own drag gestures are processed within the activity. 162 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, () -> { 163 ViewGroup content = activity.findViewById(R.id.content); 164 WindowInsets rootWindowInsets = content.getRootWindowInsets(); 165 Insets systemGestureInsets = rootWindowInsets.getSystemGestureInsets(); 166 content.setPadding(0, 0, 0, systemGestureInsets.bottom); 167 }); 168 } 169 isWatch()170 private boolean isWatch() { 171 return (mContext.getResources().getConfiguration().uiMode 172 & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_WATCH; 173 } 174 175 @Test 176 @UiThreadTest testAccessFastScrollEnabled_UiThread()177 public void testAccessFastScrollEnabled_UiThread() { 178 mListView.setFastScrollAlwaysVisible(false); 179 mListView.setFastScrollEnabled(false); 180 assertFalse(mListView.isFastScrollEnabled()); 181 182 mListView.setFastScrollAlwaysVisible(true); 183 mListView.setFastScrollEnabled(true); 184 assertTrue(mListView.isFastScrollEnabled()); 185 } 186 187 @Test testAccessFastScrollEnabled()188 public void testAccessFastScrollEnabled() { 189 mListView.setFastScrollAlwaysVisible(false); 190 mListView.setFastScrollEnabled(false); 191 PollingCheck.waitFor(() -> !mListView.isFastScrollEnabled()); 192 193 mListView.setFastScrollAlwaysVisible(true); 194 mListView.setFastScrollEnabled(true); 195 PollingCheck.waitFor(mListView::isFastScrollEnabled); 196 } 197 198 @Test testAccessSmoothScrollbarEnabled()199 public void testAccessSmoothScrollbarEnabled() { 200 mListView.setSmoothScrollbarEnabled(false); 201 assertFalse(mListView.isSmoothScrollbarEnabled()); 202 203 mListView.setSmoothScrollbarEnabled(true); 204 assertTrue(mListView.isSmoothScrollbarEnabled()); 205 } 206 207 @Test testAccessScrollingCacheEnabled()208 public void testAccessScrollingCacheEnabled() { 209 mListView.setScrollingCacheEnabled(false); 210 assertFalse(mListView.isScrollingCacheEnabled()); 211 212 mListView.setScrollingCacheEnabled(true); 213 assertTrue(mListView.isScrollingCacheEnabled()); 214 } 215 setAdapter()216 private void setAdapter() throws Throwable { 217 setAdapter(mCountriesAdapter); 218 } 219 setAdapter(final ListAdapter adapter)220 private void setAdapter(final ListAdapter adapter) throws Throwable { 221 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, 222 () -> mListView.setAdapter(adapter)); 223 } 224 setListSelection(int index)225 private void setListSelection(int index) throws Throwable { 226 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, 227 () -> mListView.setSelection(index)); 228 } 229 230 @LargeTest 231 @Test testSetOnScrollListener()232 public void testSetOnScrollListener() throws Throwable { 233 mListView.setOverScrollMode(View.OVER_SCROLL_NEVER); 234 AbsListView.OnScrollListener mockScrollListener = 235 mock(AbsListView.OnScrollListener.class); 236 237 verifyZeroInteractions(mockScrollListener); 238 239 mListView.setOnScrollListener(mockScrollListener); 240 verify(mockScrollListener, times(1)).onScroll(mListView, 0, 0, 0); 241 verifyNoMoreInteractions(mockScrollListener); 242 243 reset(mockScrollListener); 244 245 setAdapter(); 246 verify(mockScrollListener, times(1)).onScroll(mListView, 0, mListView.getChildCount(), 247 COUNTRY_LIST.length); 248 verifyNoMoreInteractions(mockScrollListener); 249 250 reset(mockScrollListener); 251 252 mCtsTouchUtils.emulateScrollToBottom(mInstrumentation, mActivityRule, mListView); 253 254 ArgumentCaptor<Integer> firstVisibleItemCaptor = ArgumentCaptor.forClass(Integer.class); 255 ArgumentCaptor<Integer> visibleItemCountCaptor = ArgumentCaptor.forClass(Integer.class); 256 verify(mockScrollListener, atLeastOnce()).onScroll(eq(mListView), 257 firstVisibleItemCaptor.capture(), visibleItemCountCaptor.capture(), 258 eq(COUNTRY_LIST.length)); 259 260 // We expect the first visible item values to be increasing 261 MatcherAssert.assertThat(firstVisibleItemCaptor.getAllValues(), 262 TestUtilsMatchers.inAscendingOrder()); 263 // The number of visible items during scrolling may change depending on the specific 264 // scroll position. As such we only test this number at the very end 265 final List<Integer> capturedVisibleItemCounts = visibleItemCountCaptor.getAllValues(); 266 assertEquals(mListView.getChildCount(), 267 (int) capturedVisibleItemCounts.get(capturedVisibleItemCounts.size() - 1)); 268 269 ArgumentCaptor<Integer> scrollStateCaptor = ArgumentCaptor.forClass(Integer.class); 270 verify(mockScrollListener, atLeastOnce()).onScrollStateChanged(eq(mListView), 271 scrollStateCaptor.capture()); 272 273 // Verify that the last scroll state is IDLE 274 final List<Integer> capturedScrollStates = scrollStateCaptor.getAllValues(); 275 assertEquals(AbsListView.OnScrollListener.SCROLL_STATE_IDLE, 276 (int) capturedScrollStates.get(capturedScrollStates.size() - 1)); 277 } 278 279 @LargeTest 280 @Test testFling()281 public void testFling() throws Throwable { 282 AbsListView.OnScrollListener mockScrollListener = mock(AbsListView.OnScrollListener.class); 283 mListView.setOnScrollListener(mockScrollListener); 284 285 setAdapter(); 286 287 // Fling down from top, expect a scroll. 288 fling(10000, mockScrollListener); 289 ArgumentCaptor<Integer> firstVisibleItemCaptor = ArgumentCaptor.forClass(Integer.class); 290 verify(mockScrollListener, atLeastOnce()).onScroll(eq(mListView), 291 firstVisibleItemCaptor.capture(), anyInt(), eq(COUNTRY_LIST.length)); 292 List<Integer> capturedFirstVisibleItems = firstVisibleItemCaptor.getAllValues(); 293 assertTrue(capturedFirstVisibleItems.get(capturedFirstVisibleItems.size() - 1) > 0); 294 295 // Fling up the same amount, expect a scroll to the original position. 296 fling(-10000, mockScrollListener); 297 firstVisibleItemCaptor = ArgumentCaptor.forClass(Integer.class); 298 verify(mockScrollListener, atLeastOnce()).onScroll(eq(mListView), 299 firstVisibleItemCaptor.capture(), anyInt(), eq(COUNTRY_LIST.length)); 300 capturedFirstVisibleItems = firstVisibleItemCaptor.getAllValues(); 301 assertTrue(capturedFirstVisibleItems.get(capturedFirstVisibleItems.size() - 1) == 0); 302 303 // Fling up again, expect no scroll, as the viewport is already at top. 304 fling(-10000, mockScrollListener); 305 verify(mockScrollListener, never()).onScroll(any(AbsListView.class), anyInt(), anyInt(), 306 anyInt()); 307 308 // Fling up again with a huge velocity, expect no scroll. 309 fling(-50000, mockScrollListener); 310 verify(mockScrollListener, never()).onScroll(any(AbsListView.class), anyInt(), anyInt(), 311 anyInt()); 312 } 313 fling(int velocityY, OnScrollListener mockScrollListener)314 private void fling(int velocityY, OnScrollListener mockScrollListener) throws Throwable { 315 reset(mockScrollListener); 316 317 // Fling the list view 318 mActivityRule.runOnUiThread(() -> mListView.fling(velocityY)); 319 320 // and wait until our mock listener is invoked with IDLE state 321 verify(mockScrollListener, within(20000)).onScrollStateChanged( 322 mListView, OnScrollListener.SCROLL_STATE_IDLE); 323 } 324 325 @Test testGetFocusedRect()326 public void testGetFocusedRect() throws Throwable { 327 setAdapter(mShortAdapter); 328 setListSelection(0); 329 330 Rect r1 = new Rect(); 331 mListView.getFocusedRect(r1); 332 333 assertEquals(0, r1.top); 334 assertTrue(r1.bottom > 0); 335 assertEquals(0, r1.left); 336 assertTrue(r1.right > 0); 337 338 setListSelection(3); 339 Rect r2 = new Rect(); 340 mListView.getFocusedRect(r2); 341 assertTrue(r2.top > 0); 342 assertTrue(r2.bottom > 0); 343 assertEquals(0, r2.left); 344 assertTrue(r2.right > 0); 345 346 assertTrue(r2.top > r1.top); 347 assertEquals(r1.bottom - r1.top, r2.bottom - r2.top); 348 assertEquals(r1.right, r2.right); 349 } 350 351 @Test testAccessStackFromBottom()352 public void testAccessStackFromBottom() throws Throwable { 353 setAdapter(); 354 355 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, 356 () -> mListView.setStackFromBottom(false)); 357 assertFalse(mListView.isStackFromBottom()); 358 assertEquals(0, mListView.getSelectedItemPosition()); 359 360 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, 361 () -> mListView.setStackFromBottom(true)); 362 assertTrue(mListView.isStackFromBottom()); 363 // ensure last item in list is selected 364 assertEquals(COUNTRY_LIST.length-1, mListView.getSelectedItemPosition()); 365 } 366 367 @Test testAccessSelectedItem()368 public void testAccessSelectedItem() throws Throwable { 369 assertNull(mListView.getSelectedView()); 370 371 setAdapter(); 372 373 final int lastVisiblePosition = mListView.getLastVisiblePosition(); 374 375 TextView tv = (TextView) mListView.getSelectedView(); 376 assertEquals(COUNTRY_LIST[0], tv.getText().toString()); 377 378 if (lastVisiblePosition >= 5) { 379 setListSelection(5); 380 tv = (TextView) mListView.getSelectedView(); 381 assertEquals(COUNTRY_LIST[5], tv.getText().toString()); 382 } 383 384 if (lastVisiblePosition >= 2) { 385 setListSelection(2); 386 tv = (TextView) mListView.getSelectedView(); 387 assertEquals(COUNTRY_LIST[2], tv.getText().toString()); 388 } 389 } 390 391 @Test testAccessListPadding()392 public void testAccessListPadding() throws Throwable { 393 setAdapter(); 394 395 assertEquals(0, mListView.getListPaddingLeft()); 396 assertEquals(0, mListView.getListPaddingTop()); 397 assertEquals(0, mListView.getListPaddingRight()); 398 assertEquals(0, mListView.getListPaddingBottom()); 399 400 final Rect r = new Rect(0, 0, 40, 60); 401 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, 402 () -> mListView.setPadding(r.left, r.top, r.right, r.bottom)); 403 404 assertEquals(r.left, mListView.getListPaddingLeft()); 405 assertEquals(r.top, mListView.getListPaddingTop()); 406 assertEquals(r.right, mListView.getListPaddingRight()); 407 assertEquals(r.bottom, mListView.getListPaddingBottom()); 408 } 409 410 @Test testAccessSelector()411 public void testAccessSelector() throws Throwable { 412 setAdapter(); 413 414 final Drawable d = mContext.getDrawable(R.drawable.pass); 415 mListView.setSelector(d); 416 417 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, mListView::requestLayout); 418 assertSame(d, mListView.getSelector()); 419 assertTrue(mListView.verifyDrawable(d)); 420 421 mListView.setSelector(R.drawable.failed); 422 mListView.setDrawSelectorOnTop(true); 423 424 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, mListView::requestLayout); 425 426 Drawable drawable = mListView.getSelector(); 427 assertNotNull(drawable); 428 final Rect r = drawable.getBounds(); 429 430 final TextView v = (TextView) mListView.getSelectedView(); 431 PollingCheck.waitFor(() -> v.getRight() == r.right); 432 assertEquals(v.getLeft(), r.left); 433 assertEquals(v.getTop(), r.top); 434 assertEquals(v.getBottom(), r.bottom); 435 436 437 } 438 439 @Test testSelectorOnScreen()440 public void testSelectorOnScreen() throws Throwable { 441 // leave touch-mode 442 mInstrumentation.setInTouchMode(false); 443 setAdapter(); 444 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, null); 445 // Entering touch mode hides selector 446 if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)) { 447 // make sure we've left touchmode (including message sending. instrumentation just sets 448 // a variable without any broadcast). 449 mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_TAB); 450 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, () -> { 451 mListView.requestFocus(); 452 mListView.setSelectionFromTop(1, 0); 453 }); 454 assertEquals(1, mListView.getSelectedItemPosition()); 455 final int[] pt = new int[2]; 456 mListView.getLocationOnScreen(pt); 457 mCtsTouchUtils.emulateDragGesture(mInstrumentation, mActivityRule, 458 pt[0] + 2, pt[1] + 2, 0, 10); 459 assertEquals(AdapterView.INVALID_POSITION, mListView.getSelectedItemPosition()); 460 // leave touch-mode 461 mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_TAB); 462 } 463 464 // Scroll off-screen hides selector, but shows up again when on-screen 465 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, () -> { 466 mListView.requestFocus(); 467 mListView.setSelectionFromTop(1, 0); 468 }); 469 assertEquals(1, mListView.getSelectedItemPosition()); 470 int selViewHeight = mListView.getSelectedView().getHeight(); 471 final int[] pt = new int[2]; 472 mListView.getLocationOnScreen(pt); 473 pt[0] += mListView.getWidth() / 2; 474 pt[1] += selViewHeight / 2; 475 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, 476 () -> mListView.scrollListBy(selViewHeight * 2)); 477 assertEquals(1, mListView.getSelectedItemPosition()); 478 assertFalse(mListView.shouldDrawSelector()); 479 mActivityRule.runOnUiThread(() -> mListView.scrollListBy(-(selViewHeight * 4) / 3)); 480 assertEquals(1, mListView.getSelectedItemPosition()); 481 assertTrue(mListView.shouldDrawSelector()); 482 } 483 484 @Test testSelectedChildViewEnabled()485 public void testSelectedChildViewEnabled() throws Throwable { 486 // leave touch-mode 487 mInstrumentation.setInTouchMode(false); 488 setAdapter(); 489 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, () -> { 490 mListView.requestFocus(); 491 mListView.setSelectionFromTop(1, 0); 492 }); 493 mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_TAB); 494 495 final int enabledState = (new MyListView(mContext)).getEnabledStateConstant(); 496 final Drawable d = mListView.getSelector(); 497 assertTrue(d.isStateful()); 498 int[] state; 499 boolean enabledFound; 500 501 assertTrue(mListView.isSelectedChildViewEnabled()); 502 503 // If selectedChildViewEnabled is false, then the selector shouldn't contain ENABLED state. 504 mListView.setSelectedChildViewEnabled(false); 505 assertFalse(mListView.isSelectedChildViewEnabled()); 506 mActivityRule.runOnUiThread(() -> mListView.refreshDrawableState()); 507 state = d.getState(); 508 enabledFound = false; 509 for (int i = state.length - 1; i >= 0; i--) { 510 if (state[i] == enabledState) { 511 enabledFound = true; 512 break; 513 } 514 } 515 assertFalse(enabledFound); 516 517 // If selectedChildViewEnabled is true, then the selector should contain ENABLED state. 518 mListView.setSelectedChildViewEnabled(true); 519 assertTrue(mListView.isSelectedChildViewEnabled()); 520 mActivityRule.runOnUiThread(() -> mListView.refreshDrawableState()); 521 state = d.getState(); 522 enabledFound = false; 523 for (int i = state.length - 1; i >= 0; i--) { 524 if (state[i] == enabledState) { 525 enabledFound = true; 526 break; 527 } 528 } 529 assertTrue(enabledFound); 530 } 531 532 @Test testSetScrollIndicators()533 public void testSetScrollIndicators() throws Throwable { 534 final Activity activity = mActivityRule.getActivity(); 535 TextView tv1 = (TextView) activity.findViewById(R.id.headerview1); 536 TextView tv2 = (TextView) activity.findViewById(R.id.footerview1); 537 538 setAdapter(); 539 540 mListView.setScrollIndicators(tv1, tv2); 541 542 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, mListView::requestLayout); 543 } 544 545 @Test testShowContextMenuForChild()546 public void testShowContextMenuForChild() throws Throwable { 547 setAdapter(); 548 setListSelection(1); 549 550 TextView tv = (TextView) mListView.getSelectedView(); 551 assertFalse(mListView.showContextMenuForChild(tv)); 552 553 // TODO: how to show the contextMenu success 554 } 555 556 @Test testPointToPosition()557 public void testPointToPosition() throws Throwable { 558 assertEquals(AbsListView.INVALID_POSITION, mListView.pointToPosition(-1, -1)); 559 assertEquals(AbsListView.INVALID_ROW_ID, mListView.pointToRowId(-1, -1)); 560 561 setAdapter(); 562 563 View row = mListView.getChildAt(0); 564 int rowHeight = row.getHeight(); 565 int middleOfSecondRow = rowHeight + rowHeight/2; 566 567 int position1 = mListView.pointToPosition(0, 0); 568 int position2 = mListView.pointToPosition(50, middleOfSecondRow); 569 570 assertEquals(mCountriesAdapter.getItemId(position1), mListView.pointToRowId(0, 0)); 571 assertEquals(mCountriesAdapter.getItemId(position2), 572 mListView.pointToRowId(50, middleOfSecondRow)); 573 574 assertTrue(position2 > position1); 575 } 576 577 @Test testSetRecyclerListener()578 public void testSetRecyclerListener() throws Throwable { 579 setAdapter(); 580 581 AbsListView.RecyclerListener mockRecyclerListener = 582 mock(AbsListView.RecyclerListener.class); 583 verifyZeroInteractions(mockRecyclerListener); 584 585 mListView.setRecyclerListener(mockRecyclerListener); 586 List<View> views = new ArrayList<>(); 587 mActivityRule.runOnUiThread(() -> mListView.reclaimViews(views)); 588 589 assertTrue(views.size() > 0); 590 591 // Verify that onMovedToScrapHeap was called on each view in the order that they were 592 // put in the list that we passed to reclaimViews 593 final InOrder reclaimedOrder = inOrder(mockRecyclerListener); 594 for (View reclaimed : views) { 595 reclaimedOrder.verify(mockRecyclerListener, times(1)).onMovedToScrapHeap(reclaimed); 596 } 597 verifyNoMoreInteractions(mockRecyclerListener); 598 } 599 600 @Test testAccessCacheColorHint()601 public void testAccessCacheColorHint() { 602 mListView.setCacheColorHint(Color.RED); 603 assertEquals(Color.RED, mListView.getCacheColorHint()); 604 assertEquals(Color.RED, mListView.getSolidColor()); 605 606 mListView.setCacheColorHint(Color.LTGRAY); 607 assertEquals(Color.LTGRAY, mListView.getCacheColorHint()); 608 assertEquals(Color.LTGRAY, mListView.getSolidColor()); 609 610 mListView.setCacheColorHint(Color.GRAY); 611 assertEquals(Color.GRAY, mListView.getCacheColorHint()); 612 assertEquals(Color.GRAY, mListView.getSolidColor()); 613 } 614 615 @Test testAccessTranscriptMode()616 public void testAccessTranscriptMode() { 617 mListView.setTranscriptMode(AbsListView.TRANSCRIPT_MODE_ALWAYS_SCROLL); 618 assertEquals(AbsListView.TRANSCRIPT_MODE_ALWAYS_SCROLL, mListView.getTranscriptMode()); 619 620 mListView.setTranscriptMode(AbsListView.TRANSCRIPT_MODE_DISABLED); 621 assertEquals(AbsListView.TRANSCRIPT_MODE_DISABLED, mListView.getTranscriptMode()); 622 623 mListView.setTranscriptMode(AbsListView.TRANSCRIPT_MODE_NORMAL); 624 assertEquals(AbsListView.TRANSCRIPT_MODE_NORMAL, mListView.getTranscriptMode()); 625 } 626 627 @Test testCheckLayoutParams()628 public void testCheckLayoutParams() { 629 MyListView listView = new MyListView(mContext); 630 631 AbsListView.LayoutParams param1 = new AbsListView.LayoutParams(10, 10); 632 assertTrue(listView.checkLayoutParams(param1)); 633 634 ViewGroup.LayoutParams param2 = new ViewGroup.LayoutParams(10, 10); 635 assertFalse(listView.checkLayoutParams(param2)); 636 } 637 638 @Test testComputeVerticalScrollValues()639 public void testComputeVerticalScrollValues() { 640 MyListView listView = new MyListView(mContext); 641 assertEquals(0, listView.computeVerticalScrollRange()); 642 assertEquals(0, listView.computeVerticalScrollOffset()); 643 assertEquals(0, listView.computeVerticalScrollExtent()); 644 645 listView.setAdapter(mCountriesAdapter); 646 listView.setSmoothScrollbarEnabled(false); 647 assertEquals(mCountriesAdapter.getCount(), listView.computeVerticalScrollRange()); 648 assertEquals(0, listView.computeVerticalScrollOffset()); 649 assertEquals(0, listView.computeVerticalScrollExtent()); 650 651 listView.setSmoothScrollbarEnabled(true); 652 assertEquals(0, listView.computeVerticalScrollOffset()); 653 assertEquals(0, listView.computeVerticalScrollExtent()); 654 } 655 656 @Test testGenerateLayoutParams()657 public void testGenerateLayoutParams() throws XmlPullParserException, IOException { 658 ViewGroup.LayoutParams res = mListView.generateLayoutParams(mAttributeSet); 659 assertNotNull(res); 660 assertTrue(res instanceof AbsListView.LayoutParams); 661 662 MyListView listView = new MyListView(mContext); 663 ViewGroup.LayoutParams p = new ViewGroup.LayoutParams( 664 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 665 666 res = listView.generateLayoutParams(p); 667 assertNotNull(res); 668 assertTrue(res instanceof AbsListView.LayoutParams); 669 assertEquals(ViewGroup.LayoutParams.MATCH_PARENT, res.width); 670 assertEquals(ViewGroup.LayoutParams.WRAP_CONTENT, res.height); 671 } 672 673 @UiThreadTest 674 @Test testBeforeAndAfterTextChanged()675 public void testBeforeAndAfterTextChanged() { 676 // The java doc says these two methods do nothing 677 CharSequence str = "test"; 678 SpannableStringBuilder sb = new SpannableStringBuilder(); 679 680 mListView.beforeTextChanged(str, 0, str.length(), str.length()); 681 mListView.afterTextChanged(sb); 682 683 // test callback 684 MyListView listView = new MyListView(mContext); 685 TextView tv = new TextView(mContext); 686 687 assertFalse(listView.isBeforeTextChangedCalled()); 688 assertFalse(listView.isOnTextChangedCalled()); 689 assertFalse(listView.isAfterTextChangedCalled()); 690 691 tv.addTextChangedListener(listView); 692 assertFalse(listView.isBeforeTextChangedCalled()); 693 assertFalse(listView.isOnTextChangedCalled()); 694 assertFalse(listView.isAfterTextChangedCalled()); 695 696 tv.setText("abc"); 697 assertTrue(listView.isBeforeTextChangedCalled()); 698 assertTrue(listView.isOnTextChangedCalled()); 699 assertTrue(listView.isAfterTextChangedCalled()); 700 } 701 702 @Test testAddTouchables()703 public void testAddTouchables() throws Throwable { 704 ArrayList<View> views = new ArrayList<>(); 705 assertEquals(0, views.size()); 706 707 setAdapter(); 708 709 mListView.addTouchables(views); 710 assertEquals(mListView.getChildCount(), views.size()); 711 } 712 713 @Test testInvalidateViews()714 public void testInvalidateViews() throws Throwable { 715 final Activity activity = mActivityRule.getActivity(); 716 TextView tv1 = (TextView) activity.findViewById(R.id.headerview1); 717 TextView tv2 = (TextView) activity.findViewById(R.id.footerview1); 718 719 setAdapter(); 720 721 mListView.setScrollIndicators(tv1, tv2); 722 723 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, mListView::invalidateViews); 724 } 725 726 @Test testGetContextMenuInfo()727 public void testGetContextMenuInfo() throws Throwable { 728 final MyListView listView = new MyListView(mContext, mAttributeSet); 729 730 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, listView, () -> { 731 mActivityRule.getActivity().setContentView(listView); 732 listView.setAdapter(mCountriesAdapter); 733 listView.setSelection(2); 734 }); 735 736 final TextView v = (TextView) listView.getSelectedView(); 737 assertNull(listView.getContextMenuInfo()); 738 739 final AbsListView.OnItemLongClickListener mockOnItemLongClickListener = 740 mock(AbsListView.OnItemLongClickListener.class); 741 mActivityRule.runOnUiThread( 742 () -> listView.setOnItemLongClickListener(mockOnItemLongClickListener) 743 ); 744 745 verifyZeroInteractions(mockOnItemLongClickListener); 746 747 // Now long click our view 748 mCtsTouchUtils.emulateLongPressOnViewCenter(mInstrumentation, mActivityRule, v, 500); 749 // and wait until our mock listener is invoked with the expected view 750 verify(mockOnItemLongClickListener, within(5000)).onItemLongClick(listView, v, 2, 751 listView.getItemIdAtPosition(2)); 752 753 ContextMenuInfo cmi = listView.getContextMenuInfo(); 754 assertNotNull(cmi); 755 } 756 757 @Test testGetTopBottomFadingEdgeStrength()758 public void testGetTopBottomFadingEdgeStrength() { 759 MyListView listView = new MyListView(mContext); 760 761 assertEquals(0.0f, listView.getTopFadingEdgeStrength(), DELTA); 762 assertEquals(0.0f, listView.getBottomFadingEdgeStrength(), DELTA); 763 } 764 765 @Test testHandleDataChanged()766 public void testHandleDataChanged() { 767 MyListView listView = new MyListView(mContext, mAttributeSet, 0); 768 listView.handleDataChanged(); 769 // TODO: how to check? 770 } 771 772 @UiThreadTest 773 @Test testSetFilterText()774 public void testSetFilterText() { 775 MyListView listView = new MyListView(mContext, mAttributeSet, 0); 776 String filterText = "xyz"; 777 778 assertFalse(listView.isTextFilterEnabled()); 779 assertFalse(listView.hasTextFilter()); 780 assertFalse(listView.isInFilterMode()); 781 assertTrue(mListView.checkInputConnectionProxy(null)); 782 783 listView.setTextFilterEnabled(false); 784 listView.setFilterText(filterText); 785 assertFalse(listView.isTextFilterEnabled()); 786 assertFalse(listView.hasTextFilter()); 787 assertFalse(listView.isInFilterMode()); 788 789 listView.setTextFilterEnabled(true); 790 listView.setFilterText(null); 791 assertTrue(listView.isTextFilterEnabled()); 792 assertFalse(listView.hasTextFilter()); 793 assertFalse(listView.isInFilterMode()); 794 795 listView.setTextFilterEnabled(true); 796 listView.setFilterText(filterText); 797 assertTrue(listView.isTextFilterEnabled()); 798 assertTrue(listView.hasTextFilter()); 799 assertTrue(listView.isInFilterMode()); 800 801 listView.clearTextFilter(); 802 assertTrue(listView.isTextFilterEnabled()); 803 assertFalse(listView.hasTextFilter()); 804 assertFalse(listView.isInFilterMode()); 805 } 806 807 @MediumTest 808 @Test testSetItemChecked_multipleModeSameValue()809 public void testSetItemChecked_multipleModeSameValue() throws Throwable { 810 // Calling setItemChecked with the same value in multiple choice mode should not cause 811 // requestLayout 812 mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); 813 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, 814 () -> mListView.setItemChecked(0, false)); 815 assertFalse(mListView.isLayoutRequested()); 816 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, 817 () -> mListView.setItemChecked(0, false)); 818 assertFalse(mListView.isLayoutRequested()); 819 } 820 821 @MediumTest 822 @Test testSetItemChecked_singleModeSameValue()823 public void testSetItemChecked_singleModeSameValue() throws Throwable { 824 // Calling setItemChecked with the same value in single choice mode should not cause 825 // requestLayout 826 mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 827 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, 828 () -> mListView.setItemChecked(0, false)); 829 assertFalse(mListView.isLayoutRequested()); 830 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, 831 () -> mListView.setItemChecked(0, false)); 832 assertFalse(mListView.isLayoutRequested()); 833 } 834 private boolean checkResult = false; 835 836 @MediumTest 837 @Test testSetItemChecked_multipleModeDifferentValue()838 public void testSetItemChecked_multipleModeDifferentValue() throws Throwable { 839 // Calling setItemChecked with a different value in multiple choice mode should cause 840 // requestLayout 841 mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); 842 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, 843 () -> mListView.setItemChecked(0, false)); 844 assertFalse(mListView.isLayoutRequested()); 845 checkResult = false; 846 mActivityRule.runOnUiThread(() -> { 847 mListView.setItemChecked(0, true); 848 checkResult = mListView.isLayoutRequested(); 849 }); 850 assertTrue(checkResult); 851 } 852 853 @MediumTest 854 @Test testSetItemChecked_singleModeDifferentValue()855 public void testSetItemChecked_singleModeDifferentValue() throws Throwable { 856 // Calling setItemChecked with a different value in single choice mode should cause 857 // requestLayout 858 mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 859 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, 860 () -> mListView.setItemChecked(0, false)); 861 assertFalse(mListView.isLayoutRequested()); 862 checkResult = false; 863 mActivityRule.runOnUiThread(() -> { 864 mListView.setItemChecked(0, true); 865 checkResult = mListView.isLayoutRequested(); 866 }); 867 assertTrue(checkResult); 868 } 869 870 @LargeTest 871 @Test testTextFilter()872 public void testTextFilter() throws Throwable { 873 setAdapter(); 874 875 // Default state - no text filter 876 assertFalse(mListView.isTextFilterEnabled()); 877 assertFalse(mListView.hasTextFilter()); 878 assertTrue(TextUtils.isEmpty(mListView.getTextFilter())); 879 880 // Enable text filter and verify that while it's enabled, the filtering is 881 // still no on 882 mActivityRule.runOnUiThread(() -> mListView.setTextFilterEnabled(true)); 883 assertTrue(mListView.isTextFilterEnabled()); 884 assertFalse(mListView.hasTextFilter()); 885 assertTrue(TextUtils.isEmpty(mListView.getTextFilter())); 886 887 // Verify the initial content of the list 888 assertEquals(COUNTRY_LIST.length, mListView.getCount()); 889 890 // Set text filter to A - we expect four entries to be left displayed in the list 891 mActivityRule.runOnUiThread(() -> mListView.setFilterText("A")); 892 PollingCheck.waitFor(() -> mListView.getCount() == 4); 893 assertTrue(mListView.isTextFilterEnabled()); 894 assertTrue(mListView.hasTextFilter()); 895 assertTrue(TextUtils.equals("A", mListView.getTextFilter())); 896 897 // Set text filter to Ar - we expect three entries to be left displayed in the list 898 mActivityRule.runOnUiThread(() -> mListView.setFilterText("Ar")); 899 PollingCheck.waitFor(() -> mListView.getCount() == 3); 900 assertTrue(mListView.isTextFilterEnabled()); 901 assertTrue(mListView.hasTextFilter()); 902 assertTrue(TextUtils.equals("Ar", mListView.getTextFilter())); 903 904 // Clear text filter - we expect to go back to the initial content 905 mActivityRule.runOnUiThread(() -> mListView.clearTextFilter()); 906 PollingCheck.waitFor(() -> mListView.getCount() == COUNTRY_LIST.length); 907 assertTrue(mListView.isTextFilterEnabled()); 908 assertFalse(mListView.hasTextFilter()); 909 assertTrue(TextUtils.isEmpty(mListView.getTextFilter())); 910 911 // Set text filter to Be - we expect four entries to be left displayed in the list 912 mActivityRule.runOnUiThread(() -> mListView.setFilterText("Be")); 913 PollingCheck.waitFor(() -> mListView.getCount() == 4); 914 assertTrue(mListView.isTextFilterEnabled()); 915 assertTrue(mListView.hasTextFilter()); 916 assertTrue(TextUtils.equals("Be", mListView.getTextFilter())); 917 918 // Set text filter to Q - we no entries displayed in the list 919 mActivityRule.runOnUiThread(() -> mListView.setFilterText("Q")); 920 PollingCheck.waitFor(() -> mListView.getCount() == 0); 921 assertTrue(mListView.isTextFilterEnabled()); 922 assertTrue(mListView.hasTextFilter()); 923 assertTrue(TextUtils.equals("Q", mListView.getTextFilter())); 924 } 925 926 @Test testOnFilterComplete()927 public void testOnFilterComplete() throws Throwable { 928 // Note that we're not using spy() due to Mockito not being able to spy on ListView, 929 // at least yet. 930 final MyListView listView = new MyListView(mContext, mAttributeSet); 931 932 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, listView, () -> { 933 mActivityRule.getActivity().setContentView(listView); 934 listView.setAdapter(mCountriesAdapter); 935 listView.setTextFilterEnabled(true); 936 }); 937 938 // Set text filter to A - we expect four entries to be left displayed in the list 939 mActivityRule.runOnUiThread(() -> listView.setFilterText("A")); 940 PollingCheck.waitFor(() -> listView.getCount() == 4); 941 assertTrue(listView.isTextFilterEnabled()); 942 assertTrue(listView.hasTextFilter()); 943 assertTrue(TextUtils.equals("A", listView.getTextFilter())); 944 945 assertEquals(4, listView.getOnFilterCompleteCount()); 946 } 947 948 private static class PositionArrayAdapter<T> extends ArrayAdapter<T> { PositionArrayAdapter(Context context, int resource, List<T> objects)949 public PositionArrayAdapter(Context context, int resource, List<T> objects) { 950 super(context, resource, objects); 951 } 952 953 @Override getItemId(int position)954 public long getItemId(int position) { 955 return position; 956 } 957 958 @Override hasStableIds()959 public boolean hasStableIds() { 960 return true; 961 } 962 } 963 verifyCheckedState(final long[] expectedCheckedItems)964 private void verifyCheckedState(final long[] expectedCheckedItems) { 965 TestUtils.assertIdentical(expectedCheckedItems, mListView.getCheckedItemIds()); 966 967 assertEquals(expectedCheckedItems.length, mListView.getCheckedItemCount()); 968 969 final long expectedCheckedItemPosition = 970 (mListView.getChoiceMode() == AbsListView.CHOICE_MODE_SINGLE) && 971 (expectedCheckedItems.length == 1) 972 ? expectedCheckedItems[0] 973 : AbsListView.INVALID_POSITION; 974 assertEquals(expectedCheckedItemPosition, mListView.getCheckedItemPosition()); 975 976 // Note that getCheckedItemPositions doesn't have a guarantee that it only holds 977 // true values, which is why we're not doing the size() == 0 check even in the initial 978 // state 979 TestUtils.assertTrueValuesAtPositions( 980 expectedCheckedItems, mListView.getCheckedItemPositions()); 981 } 982 983 @Test 984 @UiThreadTest testCheckItemCount()985 public void testCheckItemCount() throws Throwable { 986 final ArrayList<String> items = new ArrayList<>(Arrays.asList(COUNTRY_LIST)); 987 final ArrayAdapter<String> adapter = new PositionArrayAdapter<>(mContext, 988 android.R.layout.simple_list_item_1, items); 989 mListView.setAdapter(adapter); 990 mListView.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE); 991 mListView.setItemChecked(0, true); 992 mListView.setItemChecked(1, true); 993 assertEquals(2, mListView.getCheckedItemCount()); 994 995 mListView.setAdapter(adapter); 996 assertEquals(0, mListView.getCheckedItemCount()); 997 } 998 999 @MediumTest 1000 @Test testCheckedItemsUnderNoneChoiceMode()1001 public void testCheckedItemsUnderNoneChoiceMode() throws Throwable { 1002 final ArrayList<String> items = new ArrayList<>(Arrays.asList(COUNTRY_LIST)); 1003 final ArrayAdapter<String> adapter = new PositionArrayAdapter<>(mContext, 1004 android.R.layout.simple_list_item_1, items); 1005 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, 1006 () -> mListView.setAdapter(adapter)); 1007 1008 mActivityRule.runOnUiThread( 1009 () -> mListView.setChoiceMode(AbsListView.CHOICE_MODE_NONE)); 1010 verifyCheckedState(new long[] {}); 1011 1012 mActivityRule.runOnUiThread(() -> mListView.setItemChecked(2, true)); 1013 verifyCheckedState(new long[] {}); 1014 1015 mActivityRule.runOnUiThread(() -> mListView.setItemChecked(4, true)); 1016 verifyCheckedState(new long[] {}); 1017 1018 mActivityRule.runOnUiThread(() -> mListView.setItemChecked(2, false)); 1019 verifyCheckedState(new long[] {}); 1020 1021 mActivityRule.runOnUiThread(() -> mListView.setItemChecked(4, false)); 1022 verifyCheckedState(new long[] {}); 1023 } 1024 1025 @MediumTest 1026 @Test testCheckedItemsUnderSingleChoiceMode()1027 public void testCheckedItemsUnderSingleChoiceMode() throws Throwable { 1028 final ArrayList<String> items = new ArrayList<>(Arrays.asList(COUNTRY_LIST)); 1029 final ArrayAdapter<String> adapter = new PositionArrayAdapter<>(mContext, 1030 android.R.layout.simple_list_item_1, items); 1031 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, 1032 () -> mListView.setAdapter(adapter)); 1033 1034 mActivityRule.runOnUiThread( 1035 () -> mListView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE)); 1036 verifyCheckedState(new long[] {}); 1037 1038 mActivityRule.runOnUiThread(() -> mListView.setItemChecked(2, true)); 1039 verifyCheckedState(new long[] { 2 }); 1040 1041 mActivityRule.runOnUiThread(() -> mListView.setItemChecked(4, true)); 1042 verifyCheckedState(new long[] { 4 }); 1043 1044 mActivityRule.runOnUiThread(() -> mListView.setItemChecked(2, false)); 1045 verifyCheckedState(new long[] { 4 }); 1046 1047 mActivityRule.runOnUiThread(() -> mListView.setItemChecked(4, false)); 1048 verifyCheckedState(new long[] {}); 1049 } 1050 1051 @MediumTest 1052 @Test testCheckedItemsUnderMultipleChoiceMode()1053 public void testCheckedItemsUnderMultipleChoiceMode() throws Throwable { 1054 final ArrayList<String> items = new ArrayList<>(Arrays.asList(COUNTRY_LIST)); 1055 final ArrayAdapter<String> adapter = new PositionArrayAdapter<>(mContext, 1056 android.R.layout.simple_list_item_1, items); 1057 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, 1058 () -> mListView.setAdapter(adapter)); 1059 1060 mActivityRule.runOnUiThread( 1061 () -> mListView.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE)); 1062 verifyCheckedState(new long[] {}); 1063 1064 mActivityRule.runOnUiThread(() -> mListView.setItemChecked(2, true)); 1065 verifyCheckedState(new long[] { 2 }); 1066 1067 mActivityRule.runOnUiThread(() -> mListView.setItemChecked(4, true)); 1068 verifyCheckedState(new long[] { 2, 4 }); 1069 1070 mActivityRule.runOnUiThread(() -> mListView.setItemChecked(2, false)); 1071 verifyCheckedState(new long[] { 4 }); 1072 1073 mActivityRule.runOnUiThread(() -> mListView.setItemChecked(4, false)); 1074 verifyCheckedState(new long[] {}); 1075 } 1076 configureMultiChoiceModalState()1077 private void configureMultiChoiceModalState() throws Throwable { 1078 final ArrayList<String> items = new ArrayList<>(Arrays.asList(COUNTRY_LIST)); 1079 final ArrayAdapter<String> adapter = new PositionArrayAdapter<>(mContext, 1080 android.R.layout.simple_list_item_1, items); 1081 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, 1082 () -> mListView.setAdapter(adapter)); 1083 1084 // Configure a multi-choice mode listener to configure our test contextual action bar 1085 // content. We will subsequently query that listener for calls to its 1086 // onItemCheckedStateChanged method 1087 mMultiChoiceModeListener = 1088 mock(AbsListView.MultiChoiceModeListener.class); 1089 doAnswer((InvocationOnMock invocation) -> { 1090 final ActionMode actionMode = (ActionMode) invocation.getArguments() [0]; 1091 final Menu menu = (Menu) invocation.getArguments() [1]; 1092 actionMode.getMenuInflater().inflate(R.menu.cab_menu, menu); 1093 return true; 1094 }).when(mMultiChoiceModeListener).onCreateActionMode( 1095 any(ActionMode.class), any(Menu.class)); 1096 mListView.setMultiChoiceModeListener(mMultiChoiceModeListener); 1097 1098 mActivityRule.runOnUiThread( 1099 () -> mListView.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE_MODAL)); 1100 verifyCheckedState(new long[] {}); 1101 } 1102 1103 @MediumTest 1104 @Test testCheckedItemsUnderMultipleModalChoiceMode()1105 public void testCheckedItemsUnderMultipleModalChoiceMode() throws Throwable { 1106 configureMultiChoiceModalState(); 1107 1108 mActivityRule.runOnUiThread(() -> mListView.setItemChecked(2, true)); 1109 verifyCheckedState(new long[] { 2 }); 1110 if (!isWatch()) { 1111 verify(mMultiChoiceModeListener, times(1)).onItemCheckedStateChanged( 1112 any(ActionMode.class), eq(2), eq(2L), eq(true)); 1113 } 1114 1115 reset(mMultiChoiceModeListener); 1116 mActivityRule.runOnUiThread(() -> mListView.setItemChecked(4, true)); 1117 verifyCheckedState(new long[] { 2, 4 }); 1118 if (!isWatch()) { 1119 verify(mMultiChoiceModeListener, times(1)).onItemCheckedStateChanged( 1120 any(ActionMode.class), eq(4), eq(4L), eq(true)); 1121 } 1122 1123 reset(mMultiChoiceModeListener); 1124 mActivityRule.runOnUiThread(() -> mListView.setItemChecked(2, false)); 1125 verifyCheckedState(new long[] { 4 }); 1126 if (!isWatch()) { 1127 verify(mMultiChoiceModeListener, times(1)).onItemCheckedStateChanged( 1128 any(ActionMode.class), eq(2), eq(2L), eq(false)); 1129 } 1130 1131 reset(mMultiChoiceModeListener); 1132 mActivityRule.runOnUiThread(() -> mListView.setItemChecked(4, false)); 1133 verifyCheckedState(new long[] {}); 1134 mListView.setMultiChoiceModeListener(mMultiChoiceModeListener); 1135 if (!isWatch()) { 1136 verify(mMultiChoiceModeListener, times(1)).onItemCheckedStateChanged( 1137 any(ActionMode.class), eq(4), eq(4L), eq(false)); 1138 } 1139 } 1140 1141 @LargeTest 1142 @Test testMultiSelectionWithLongPressAndTaps()1143 public void testMultiSelectionWithLongPressAndTaps() throws Throwable { 1144 if (isWatch()) { 1145 return; // watch type devices do not support multichoice action mode 1146 } 1147 configureMultiChoiceModalState(); 1148 mListView.setOverScrollMode(View.OVER_SCROLL_NEVER); 1149 1150 final int firstVisiblePosition = mListView.getFirstVisiblePosition(); 1151 final int lastVisiblePosition = mListView.getLastVisiblePosition(); 1152 1153 // Emulate long-click on the middle item of the currently visible content 1154 final int positionForInitialSelection = (firstVisiblePosition + lastVisiblePosition) / 2; 1155 mCtsTouchUtils.emulateLongPressOnViewCenter(mInstrumentation, mActivityRule, 1156 mListView.getChildAt(positionForInitialSelection)); 1157 // wait until our listener has been notified that the item has been checked 1158 verify(mMultiChoiceModeListener, within(1000)).onItemCheckedStateChanged( 1159 any(ActionMode.class), eq(positionForInitialSelection), 1160 eq((long) positionForInitialSelection), eq(true)); 1161 // and verify the overall checked state of our list 1162 verifyCheckedState(new long[] { positionForInitialSelection }); 1163 1164 if (firstVisiblePosition != positionForInitialSelection) { 1165 // Tap the first element in our list 1166 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, 1167 mListView.getChildAt(firstVisiblePosition)); 1168 // wait until our listener has been notified that the item has been checked 1169 verify(mMultiChoiceModeListener, within(1000)).onItemCheckedStateChanged( 1170 any(ActionMode.class), eq(firstVisiblePosition), 1171 eq((long) firstVisiblePosition), eq(true)); 1172 // and verify the overall checked state of our list 1173 verifyCheckedState(new long[] { firstVisiblePosition, positionForInitialSelection }); 1174 } 1175 1176 // Scroll down 1177 mCtsTouchUtils.emulateScrollToBottom(mInstrumentation, mActivityRule, mListView); 1178 final int lastListPosition = COUNTRY_LIST.length - 1; 1179 if (lastListPosition != positionForInitialSelection) { 1180 // Tap the last element in our list 1181 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, 1182 mListView.getChildAt(mListView.getChildCount() - 1)); 1183 // wait until our listener has been notified that the item has been checked 1184 verify(mMultiChoiceModeListener, within(1000)).onItemCheckedStateChanged( 1185 any(ActionMode.class), eq(lastListPosition), 1186 eq((long) lastListPosition), eq(true)); 1187 // and verify the overall checked state of our list 1188 verifyCheckedState(new long[] { firstVisiblePosition, positionForInitialSelection, 1189 lastListPosition }); 1190 } 1191 } 1192 1193 @SmallTest 1194 @UiThreadTest 1195 @Test testEdgeEffectColors()1196 public void testEdgeEffectColors() { 1197 int defaultColor = new EdgeEffect(mListView.getContext()).getColor(); 1198 assertEquals(mListView.getTopEdgeEffectColor(), defaultColor); 1199 assertEquals(mListView.getBottomEdgeEffectColor(), defaultColor); 1200 1201 mListView.setEdgeEffectColor(Color.BLUE); 1202 assertEquals(mListView.getTopEdgeEffectColor(), Color.BLUE); 1203 assertEquals(mListView.getBottomEdgeEffectColor(), Color.BLUE); 1204 1205 mListView.setTopEdgeEffectColor(Color.RED); 1206 assertEquals(mListView.getTopEdgeEffectColor(), Color.RED); 1207 assertEquals(mListView.getBottomEdgeEffectColor(), Color.BLUE); 1208 1209 mListView.setBottomEdgeEffectColor(Color.GREEN); 1210 assertEquals(mListView.getTopEdgeEffectColor(), Color.RED); 1211 assertEquals(mListView.getBottomEdgeEffectColor(), Color.GREEN); 1212 } 1213 1214 // Helper method that emulates fast scroll by dragging along the right edge of our ListView. verifyFastScroll()1215 private void verifyFastScroll() throws Throwable { 1216 setAdapter(); 1217 1218 final int lastVisiblePosition = mListView.getLastVisiblePosition(); 1219 if (lastVisiblePosition == (COUNTRY_LIST.length - 1)) { 1220 // This can happen on very large screens - the entire content fits and there's 1221 // nothing to scroll 1222 return; 1223 } 1224 1225 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, 1226 () -> mListView.setFastScrollAlwaysVisible(true)); 1227 assertTrue(mListView.isFastScrollEnabled()); 1228 assertTrue(mListView.isFastScrollAlwaysVisible()); 1229 1230 final int[] listViewOnScreenXY = new int[2]; 1231 mListView.getLocationOnScreen(listViewOnScreenXY); 1232 1233 final int topEdgeY = listViewOnScreenXY[1]; 1234 final int bottomEdgeY = listViewOnScreenXY[1] + mListView.getHeight(); 1235 final int rightEdgeX = listViewOnScreenXY[0] + mListView.getWidth(); 1236 1237 // Emulate a downwards gesture that should bring us all the way to the last element 1238 // of the list (when fast scroll is enabled) 1239 mCtsTouchUtils.emulateDragGesture(mInstrumentation, mActivityRule, 1240 rightEdgeX - 1, // X start of the drag 1241 topEdgeY + 1, // Y start of the drag 1242 0, // X amount of the drag (vertical) 1243 mListView.getHeight() - 2); // Y amount of the drag (downwards) 1244 1245 assertEquals(COUNTRY_LIST.length - 1, mListView.getLastVisiblePosition()); 1246 1247 // Emulate an upwards gesture that should bring us all the way to the first element 1248 // of the list (when fast scroll is enabled) 1249 mCtsTouchUtils.emulateDragGesture(mInstrumentation, mActivityRule, 1250 rightEdgeX - 1, // X start of the drag 1251 bottomEdgeY - 1, // Y start of the drag 1252 0, // X amount of the drag (vertical) 1253 -mListView.getHeight() + 2); // Y amount of the drag (upwards) 1254 1255 assertEquals(0, mListView.getFirstVisiblePosition()); 1256 } 1257 1258 @LargeTest 1259 @Test testFastScroll()1260 public void testFastScroll() throws Throwable { 1261 verifyFastScroll(); 1262 } 1263 1264 @LargeTest 1265 @Test testFastScrollStyle()1266 public void testFastScrollStyle() throws Throwable { 1267 mListView.setFastScrollStyle(R.style.FastScrollCustomStyle); 1268 1269 verifyFastScroll(); 1270 } 1271 1272 @Test testRequestChildRectangleOnScreen_onScrollChangedCalled()1273 public void testRequestChildRectangleOnScreen_onScrollChangedCalled() throws Throwable { 1274 final MyListView listView = new MyListView(mContext, mAttributeSet); 1275 1276 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, listView, () -> { 1277 mActivityRule.getActivity().setContentView(listView); 1278 listView.setAdapter(mCountriesAdapter); 1279 }); 1280 View row = listView.getChildAt(0); 1281 1282 // Initialize the test scrolled down by half the height of the first child. 1283 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, listView, () -> { 1284 listView.scrollListBy(row.getHeight() / 2); 1285 }); 1286 listView.resetIsOnScrollChangedCalled(); 1287 assertFalse(listView.isOnScrollChangedCalled()); 1288 1289 // Scroll the first child back completely into view (back to the top of the AbsListView). 1290 Rect r = new Rect(); 1291 r.set(0, 0, row.getWidth(), row.getHeight()); 1292 mActivityRule.runOnUiThread(() -> listView.requestChildRectangleOnScreen(row, r, true)); 1293 1294 assertTrue(listView.isOnScrollChangedCalled()); 1295 } 1296 1297 @Test testEnterKey()1298 public void testEnterKey() throws Throwable { 1299 final MyListView listView = new MyListView(mContext, mAttributeSet); 1300 1301 WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, listView, () -> { 1302 mActivityRule.getActivity().setContentView(listView); 1303 listView.setAdapter(mCountriesAdapter); 1304 listView.setTextFilterEnabled(true); 1305 listView.requestFocus(); 1306 }); 1307 1308 // KEYCODE_ENTER is handled by isConfirmKey, so it comsumed before sendToTextFilter called. 1309 // because of this, make keyevent with repeat count. 1310 KeyEvent event = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 1); 1311 mCtsKeyEventUtil.sendKey(mInstrumentation, listView, event); 1312 assertTrue(listView.isTextFilterEnabled()); 1313 1314 // we expect keyevent will be passed with nothing to do 1315 assertFalse(listView.hasTextFilter()); 1316 assertEquals(-1, listView.getOnFilterCompleteCount()); 1317 1318 // KEYCODE_NUMPAD_ENTER is handled by isConfirmKey, too. 1319 // so make keyevent with repeat count. 1320 event = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_NUMPAD_ENTER, 1); 1321 mCtsKeyEventUtil.sendKey(mInstrumentation, listView, event); 1322 assertTrue(listView.isTextFilterEnabled()); 1323 1324 // we expect keyevent will be passed with nothing to do, like KEYCODE_ENTER 1325 assertFalse(listView.hasTextFilter()); 1326 assertEquals(-1, listView.getOnFilterCompleteCount()); 1327 } 1328 1329 /** 1330 * MyListView for test. 1331 */ 1332 private static class MyListView extends ListView { MyListView(Context context)1333 public MyListView(Context context) { 1334 super(context); 1335 } 1336 MyListView(Context context, AttributeSet attrs)1337 public MyListView(Context context, AttributeSet attrs) { 1338 super(context, attrs); 1339 } 1340 MyListView(Context context, AttributeSet attrs, int defStyle)1341 public MyListView(Context context, AttributeSet attrs, int defStyle) { 1342 super(context, attrs, defStyle); 1343 } 1344 1345 @Override checkLayoutParams(ViewGroup.LayoutParams p)1346 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 1347 return super.checkLayoutParams(p); 1348 } 1349 1350 @Override computeVerticalScrollExtent()1351 protected int computeVerticalScrollExtent() { 1352 return super.computeVerticalScrollExtent(); 1353 } 1354 1355 @Override computeVerticalScrollOffset()1356 protected int computeVerticalScrollOffset() { 1357 return super.computeVerticalScrollOffset(); 1358 } 1359 1360 @Override computeVerticalScrollRange()1361 protected int computeVerticalScrollRange() { 1362 return super.computeVerticalScrollRange(); 1363 } 1364 1365 @Override dispatchDraw(Canvas canvas)1366 protected void dispatchDraw(Canvas canvas) { 1367 super.dispatchDraw(canvas); 1368 } 1369 1370 @Override dispatchSetPressed(boolean pressed)1371 protected void dispatchSetPressed(boolean pressed) { 1372 super.dispatchSetPressed(pressed); 1373 } 1374 1375 @Override generateLayoutParams(ViewGroup.LayoutParams p)1376 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 1377 return super.generateLayoutParams(p); 1378 } 1379 1380 @Override getBottomFadingEdgeStrength()1381 protected float getBottomFadingEdgeStrength() { 1382 return super.getBottomFadingEdgeStrength(); 1383 } 1384 1385 @Override getContextMenuInfo()1386 protected ContextMenuInfo getContextMenuInfo() { 1387 return super.getContextMenuInfo(); 1388 } 1389 1390 @Override getTopFadingEdgeStrength()1391 protected float getTopFadingEdgeStrength() { 1392 return super.getTopFadingEdgeStrength(); 1393 } 1394 1395 @Override handleDataChanged()1396 protected void handleDataChanged() { 1397 super.handleDataChanged(); 1398 } 1399 1400 @Override isInFilterMode()1401 protected boolean isInFilterMode() { 1402 return super.isInFilterMode(); 1403 } 1404 1405 private boolean mIsBeforeTextChangedCalled; 1406 private boolean mIsOnTextChangedCalled; 1407 private boolean mIsAfterTextChangedCalled; 1408 1409 @Override beforeTextChanged(CharSequence s, int start, int count, int after)1410 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 1411 mIsBeforeTextChangedCalled = true; 1412 super.beforeTextChanged(s, start, count, after); 1413 } 1414 1415 @Override onTextChanged(CharSequence s, int start, int before, int count)1416 public void onTextChanged(CharSequence s, int start, int before, int count) { 1417 mIsOnTextChangedCalled = true; 1418 super.onTextChanged(s, start, before, count); 1419 } 1420 1421 @Override afterTextChanged(Editable s)1422 public void afterTextChanged(Editable s) { 1423 mIsAfterTextChangedCalled = true; 1424 super.afterTextChanged(s); 1425 } 1426 isBeforeTextChangedCalled()1427 public boolean isBeforeTextChangedCalled() { 1428 return mIsBeforeTextChangedCalled; 1429 } 1430 isOnTextChangedCalled()1431 public boolean isOnTextChangedCalled() { 1432 return mIsOnTextChangedCalled; 1433 } 1434 isAfterTextChangedCalled()1435 public boolean isAfterTextChangedCalled() { 1436 return mIsAfterTextChangedCalled; 1437 } 1438 1439 private int mOnFilterCompleteCount = -1; 1440 1441 @Override onFilterComplete(int count)1442 public void onFilterComplete(int count) { 1443 super.onFilterComplete(count); 1444 mOnFilterCompleteCount = count; 1445 } 1446 getOnFilterCompleteCount()1447 public int getOnFilterCompleteCount() { 1448 return mOnFilterCompleteCount; 1449 } 1450 1451 private boolean mIsOnScrollChangedCalled; 1452 1453 @Override onScrollChanged(int l, int t, int oldl, int oldt)1454 protected void onScrollChanged(int l, int t, int oldl, int oldt) { 1455 mIsOnScrollChangedCalled = true; 1456 super.onScrollChanged(l, t, oldl, oldt); 1457 } 1458 isOnScrollChangedCalled()1459 public boolean isOnScrollChangedCalled() { 1460 return mIsOnScrollChangedCalled; 1461 } 1462 resetIsOnScrollChangedCalled()1463 public void resetIsOnScrollChangedCalled() { 1464 mIsOnScrollChangedCalled = false; 1465 } 1466 getEnabledStateConstant()1467 public int getEnabledStateConstant() { 1468 return ENABLED_STATE_SET[0]; 1469 } 1470 } 1471 } 1472