• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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