blob: ee6dc39eb6da832d9ee53acad47056ab94f42025 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.widget.cts;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.os.SystemClock;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.LargeTest;
import android.support.test.filters.Suppress;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import com.android.compatibility.common.util.CtsTouchUtils;
import com.android.compatibility.common.util.CtsTouchUtils.EventInjectionListener;
import com.android.compatibility.common.util.PollingCheck;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class AbsListView_ScrollTest {
private static final String[] COUNTRY_LIST = new String[] {
"Argentina", "Armenia", "Aruba", "Australia", "Belarus", "Belgium", "Belize", "Benin",
"Botswana", "Brazil", "Cameroon", "China", "Colombia", "Costa Rica", "Cyprus",
"Denmark", "Djibouti", "Ethiopia", "Fiji", "Finland", "France", "Gabon", "Germany",
"Ghana", "Haiti", "Honduras", "Iceland", "India", "Indonesia", "Ireland", "Italy",
"Japan", "Kiribati", "Laos", "Lesotho", "Liberia", "Malaysia", "Mongolia", "Myanmar",
"Nauru", "Norway", "Oman", "Pakistan", "Philippines", "Portugal", "Romania", "Russia",
"Rwanda", "Singapore", "Slovakia", "Slovenia", "Somalia", "Swaziland", "Togo", "Tuvalu",
"Uganda", "Ukraine", "United States", "Vanuatu", "Venezuela", "Zimbabwe"
};
@Rule
public ActivityTestRule<ListViewFixedCtsActivity> mActivityRule =
new ActivityTestRule<>(ListViewFixedCtsActivity.class);
private Instrumentation mInstrumentation;
private AbsListView mListView;
private Context mContext;
private ArrayAdapter<String> mCountriesAdapter;
private int mRowHeightPx;
private static class ListScrollPosition {
public int mFirstVisiblePosition;
public int mFirstViewVerticalOffset;
}
@Before
public void setup() throws Throwable {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mContext = mInstrumentation.getTargetContext();
final Activity activity = mActivityRule.getActivity();
PollingCheck.waitFor(() -> activity.hasWindowFocus());
mCountriesAdapter = new ArrayAdapter<>(mContext,
R.layout.listitemfixed_layout, COUNTRY_LIST);
mListView = (ListView) activity.findViewById(R.id.listview_default);
mActivityRule.runOnUiThread(() -> mListView.setAdapter(mCountriesAdapter));
mInstrumentation.waitForIdleSync();
mRowHeightPx = activity.getResources().getDimensionPixelSize(R.dimen.listrow_height);
}
/**
* Listener that allows waiting for the end of a scroll. When the tracked
* {@link AbsListView} transitions to idle state, the passed {@link CountDownLatch}
* is notified.
*/
private class ScrollIdleListListener implements OnScrollListener {
private CountDownLatch mLatchToNotify;
public ScrollIdleListListener(CountDownLatch latchToNotify) {
mLatchToNotify = latchToNotify;
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
mListView.setOnScrollListener(null);
mLatchToNotify.countDown();
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
}
}
/**
* Listener that allows waiting until a specific position in the list becomes visible.
* When the tracked position in the {@link AbsListView} becomes visible, the passed
* {@link CountDownLatch} is notified.
*/
private class ScrollPositionListListener implements AbsListView.OnScrollListener {
private CountDownLatch mLatchToNotify;
private int mTargetPosition;
public ScrollPositionListListener(CountDownLatch latchToNotify, int targetPosition) {
mLatchToNotify = latchToNotify;
mTargetPosition = targetPosition;
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
// Is our target in current visible range?
int lastVisibleItem = firstVisibleItem + visibleItemCount - 1;
boolean isInRange = (mTargetPosition >= firstVisibleItem) &&
(mTargetPosition <= lastVisibleItem);
if (!isInRange) {
return;
}
// Is our target also fully visible?
int visibleIndexOfTarget = mTargetPosition - firstVisibleItem;
View targetChild = mListView.getChildAt(visibleIndexOfTarget);
boolean isTargetFullyVisible = (targetChild.getTop() >= 0) &&
(targetChild.getBottom() <= mListView.getHeight());
if (isTargetFullyVisible) {
mListView.setOnScrollListener(null);
mLatchToNotify.countDown();
}
}
}
private boolean isItemVisible(int position) {
return (position >= mListView.getFirstVisiblePosition()) &&
(position <= mListView.getLastVisiblePosition());
}
private void verifyScrollToPosition(int positionToScrollTo) throws Throwable {
final int firstVisiblePosition = mListView.getFirstVisiblePosition();
final int lastVisiblePosition = mListView.getLastVisiblePosition();
if ((positionToScrollTo >= firstVisiblePosition) &&
(positionToScrollTo <= lastVisiblePosition)) {
// If it's already on the screen, we won't have any real scrolling taking
// place, so our tracking based on scroll state change will time out. This
// is why we're returning here early.
return;
}
// Register a scroll listener on our ListView. The listener will notify our latch
// when the scroll state changes to IDLE. If that never happens, the latch will
// time out and fail the test.
final CountDownLatch latch = new CountDownLatch(1);
mListView.setOnScrollListener(new ScrollIdleListListener(latch));
mActivityRule.runOnUiThread(() -> mListView.smoothScrollToPosition(
positionToScrollTo));
assertTrue("Timed out while waiting for the scroll to complete",
latch.await(2, TimeUnit.SECONDS));
// Verify that the position we've been asked to scroll to is visible
assertTrue("Asked to scroll to " + positionToScrollTo + ", first visible is "
+ mListView.getFirstVisiblePosition() + ", last visible is "
+ mListView.getLastVisiblePosition(), isItemVisible(positionToScrollTo));
}
@Test
public void testSmoothScrollToPositionDownUpDown() throws Throwable {
final int itemCount = COUNTRY_LIST.length;
// Scroll closer to the end of the list
verifyScrollToPosition(itemCount - 10);
// Scroll back towards the beginning of the list
verifyScrollToPosition(5);
// And then towards the end of the list again
verifyScrollToPosition(itemCount - 1);
// And back up to the middle of the list
verifyScrollToPosition(itemCount / 2);
}
@Test
public void testSmoothScrollToPositionEveryRow() throws Throwable {
final int itemCount = COUNTRY_LIST.length;
for (int i = 0; i < itemCount; i++) {
// Scroll one row down
verifyScrollToPosition(i);
}
for (int i = itemCount - 1; i >= 0; i--) {
// Scroll one row up
verifyScrollToPosition(i);
}
}
private void verifyScrollToPositionWithBound(int positionToScrollTo, int boundPosition,
boolean expectTargetPositionToBeVisibleAtEnd) throws Throwable {
final int firstVisiblePosition = mListView.getFirstVisiblePosition();
final int lastVisiblePosition = mListView.getLastVisiblePosition();
if ((positionToScrollTo >= firstVisiblePosition) &&
(positionToScrollTo <= lastVisiblePosition)) {
// If it's already on the screen, we won't have any real scrolling taking
// place, so our tracking based on scroll state change will time out. This
// is why we're returning here early.
return;
}
// Register a scroll listener on our ListView. The listener will notify our latch
// when the scroll state changes to IDLE. If that never happens, the latch will
// time out and fail the test.
final CountDownLatch latch = new CountDownLatch(1);
mListView.setOnScrollListener(new ScrollIdleListListener(latch));
mActivityRule.runOnUiThread(() -> mListView.smoothScrollToPosition(
positionToScrollTo, boundPosition));
assertTrue("Timed out while waiting for the scroll to complete",
latch.await(2, TimeUnit.SECONDS));
// Verify that the bound position is visible
assertTrue("Asked to scroll to " + positionToScrollTo + " with bound " + boundPosition
+ ", first visible is " + mListView.getFirstVisiblePosition()
+ ", last visible is " + mListView.getLastVisiblePosition(),
isItemVisible(boundPosition));
assertEquals("Asked to scroll to " + positionToScrollTo + " with bound " + boundPosition
+ ", first visible is " + mListView.getFirstVisiblePosition()
+ ", last visible is " + mListView.getLastVisiblePosition(),
expectTargetPositionToBeVisibleAtEnd, isItemVisible(positionToScrollTo));
}
@Test
public void testSmoothScrollToPositionWithBound() throws Throwable {
// Our list is 300dp high and each row is 40dp high. Without being too precise,
// the logic in this method relies on at least 8 and at most 10 items on the screen
// at any time.
// Scroll to 20 with bound at 6. This should result in the scroll stopping before it
// gets to 20 so that 6 is still visible
verifyScrollToPositionWithBound(20, 6, false);
// Scroll to 40 with bound at 35. This should result in the scroll getting to 40 becoming
// visible with 35 visible as well
verifyScrollToPositionWithBound(40, 35, true);
// Scroll to 10 with bound at 25. This should result in the scroll stopping before it
// gets to 10 so that 25 is still visible
verifyScrollToPositionWithBound(10, 25, false);
// Scroll to 5 with bound at 8. This should result in the scroll getting to 5 becoming
// visible with 8 visible as well
verifyScrollToPositionWithBound(5, 8, true);
}
private void verifyScrollToPositionFromTop(int positionToScrollTo, int offset,
int durationMs) throws Throwable {
final int startTopPositionInListCoordinates =
mListView.getFirstVisiblePosition() * mRowHeightPx -
mListView.getChildAt(0).getTop();
int targetTopPositionInListCoordinates = positionToScrollTo * mRowHeightPx - offset;
// Need to clamp it to account for requests that would scroll the content outside
// of the available bounds
targetTopPositionInListCoordinates = Math.max(0, targetTopPositionInListCoordinates);
targetTopPositionInListCoordinates = Math.min(
COUNTRY_LIST.length * mRowHeightPx - mListView.getHeight(),
targetTopPositionInListCoordinates);
if (targetTopPositionInListCoordinates == startTopPositionInListCoordinates) {
// If it's already at the target state, we won't have any real scrolling taking
// place, so our tracking based on scroll state change will time out. This
// is why we're returning here early.
return;
}
// Register a scroll listener on our ListView. The listener will notify our latch
// when the scroll state changes to IDLE. If that never happens, the latch will
// time out and fail the test.
final CountDownLatch latch = new CountDownLatch(1);
mListView.setOnScrollListener(new ScrollIdleListListener(latch));
if (durationMs > 0) {
mActivityRule.runOnUiThread(() -> mListView.smoothScrollToPositionFromTop(
positionToScrollTo, offset, durationMs));
} else {
mActivityRule.runOnUiThread(() -> mListView.smoothScrollToPositionFromTop(
positionToScrollTo, offset));
}
// Since position-based scroll is emulated as a series of mini-flings, scrolling
// might take considerable time.
int timeoutMs = durationMs > 0 ? 5000 + durationMs : 5000;
assertTrue("Timed out while waiting for the scroll to complete",
latch.await(timeoutMs, TimeUnit.MILLISECONDS));
final int endTopPositionInListCoordinates =
mListView.getFirstVisiblePosition() * mRowHeightPx -
mListView.getChildAt(0).getTop();
assertEquals(targetTopPositionInListCoordinates, endTopPositionInListCoordinates);
}
@Test
public void testSmoothScrollToPositionFromTop() throws Throwable {
// Ask to scroll so that the top of position 5 is 10 pixels below the top edge of the list
verifyScrollToPositionFromTop(5, 10, -1);
// Ask to scroll so that the top of position 10 is right at the top edge of the list
verifyScrollToPositionFromTop(10, 0, -1);
// Ask to scroll so that the top of position 5 is 80 dps below the top edge of the list
// (which means that since row height is 40 dps high, the top item should be 3
verifyScrollToPositionFromTop(5, 2 * mRowHeightPx, -1);
// Ask to scroll so that the top of position 20 is 20 pixels above the top edge of the list
verifyScrollToPositionFromTop(20, 20, -1);
// Ask to scroll so that the top of position 20 is right at the top edge of the list
verifyScrollToPositionFromTop(20, 0, -1);
// Ask to scroll so that the top of position 20 is 20 pixels below the top edge of the list
verifyScrollToPositionFromTop(20, 20, -1);
// Ask to scroll beyond the top of the content
verifyScrollToPositionFromTop(0, -20, -1);
verifyScrollToPositionFromTop(0, -60, -1);
// Ask to scroll beyond the bottom of the content
final int itemCount = COUNTRY_LIST.length;
verifyScrollToPositionFromTop(itemCount - 1, 0, -1);
verifyScrollToPositionFromTop(itemCount - 1, mListView.getHeight(), -1);
}
@Test
public void testSmoothScrollToPositionFromTopWithTime() throws Throwable {
// Ask to scroll so that the top of position 5 is 20 pixels below the top edge of the list
verifyScrollToPositionFromTop(5, 10, 200);
// Ask to scroll so that the top of position 10 is right at the top edge of the list
verifyScrollToPositionFromTop(10, 0, 1000);
// Ask to scroll so that the top of position 5 is 80 dps below the top edge of the list
// (which means that since row height is 40 dps high, the top item should be 3
verifyScrollToPositionFromTop(5, 2 * mRowHeightPx, 500);
// Ask to scroll so that the top of position 20 is 20 pixels above the top edge of the list
verifyScrollToPositionFromTop(20, 20, 100);
// Ask to scroll so that the top of position 20 is right at the top edge of the list
verifyScrollToPositionFromTop(20, 0, 700);
// Ask to scroll so that the top of position 20 is 20 pixels below the top edge of the list
verifyScrollToPositionFromTop(20, 20, 600);
// Ask to scroll beyond the top of the content
verifyScrollToPositionFromTop(0, -20, 2000);
verifyScrollToPositionFromTop(0, -60, 300);
// Ask to scroll beyond the bottom of the content
final int itemCount = COUNTRY_LIST.length;
verifyScrollToPositionFromTop(itemCount - 1, 0, 600);
verifyScrollToPositionFromTop(itemCount - 1, mListView.getHeight(), 200);
}
@Test
public void testCanScrollList() throws Throwable {
final int itemCount = COUNTRY_LIST.length;
assertEquals(0, mListView.getFirstVisiblePosition());
// Verify that when we're at the top of the list, we can't scroll up but we can scroll
// down.
assertFalse(mListView.canScrollList(-1));
assertTrue(mListView.canScrollList(1));
// Scroll down to the very end of the list
verifyScrollToPosition(itemCount - 1);
assertEquals(itemCount - 1, mListView.getLastVisiblePosition());
// Verify that when we're at the bottom of the list, we can't scroll down but we can scroll
// up.
assertFalse(mListView.canScrollList(1));
assertTrue(mListView.canScrollList(-1));
// Scroll up to the middle of the list
final int itemInTheMiddle = itemCount / 2;
verifyScrollToPosition(itemInTheMiddle);
// Verify that when we're in the middle of the list, we can scroll both up and down.
assertTrue(mListView.canScrollList(-1));
assertTrue(mListView.canScrollList(1));
}
private void verifyScrollBy(int y) throws Throwable {
// Here we rely on knowing the fixed pixel height of each row
final int startTopPositionInListCoordinates =
mListView.getFirstVisiblePosition() * mRowHeightPx -
mListView.getChildAt(0).getTop();
// Since scrollListBy is a synchronous operation, we do not need to wait
// until we can proceed to test the result
mActivityRule.runOnUiThread(() -> mListView.scrollListBy(y));
final int endTopPositionInListCoordinates =
mListView.getFirstVisiblePosition() * mRowHeightPx -
mListView.getChildAt(0).getTop();
// As specified in the Javadocs of AbsListView.scrollListBy, the actual scroll amount
// will be capped by the list height minus one pixel
final int listHeight = mListView.getHeight();
final int expectedScrollAmount = (y > 0) ? Math.min(y, listHeight - 1)
: Math.max(y, -(listHeight - 1));
int expectedTopPositionInListCoordinates =
startTopPositionInListCoordinates + expectedScrollAmount;
// Need to clamp it to account for requests that would scroll the content outside
// of the available bounds
expectedTopPositionInListCoordinates = Math.max(0, expectedTopPositionInListCoordinates);
expectedTopPositionInListCoordinates = Math.min(
COUNTRY_LIST.length * mRowHeightPx - mListView.getHeight(),
expectedTopPositionInListCoordinates);
assertEquals(expectedTopPositionInListCoordinates, endTopPositionInListCoordinates);
}
@Test
public void testScrollListBy() throws Throwable {
final int listHeight = mListView.getHeight();
final int itemCount = COUNTRY_LIST.length;
// Scroll down by half row height
verifyScrollBy(mRowHeightPx / 2);
// Scroll up by full row height - verifying that we're going to stop at the top of the first
// row
verifyScrollBy(-mRowHeightPx);
// Scroll down by slightly more than a screenful of rows - we expect it to be capped
// by the list height minus one pixel.
verifyScrollBy(listHeight + mRowHeightPx);
// Scroll down by another half row
verifyScrollBy(mRowHeightPx / 2);
// Scroll up by full row height
verifyScrollBy(-mRowHeightPx);
// Now scroll all the way down (using position-based scrolling)
verifyScrollToPosition(itemCount - 1);
assertEquals(itemCount - 1, mListView.getLastVisiblePosition());
// Scroll up by half row height
verifyScrollBy(-mRowHeightPx / 2);
// Scroll down by full row height - verifying that we're going to stop at the bottom of the
// last row
verifyScrollBy(mRowHeightPx);
// Scroll up halfway into the list - we expect it to be capped by the list height minus
// one pixel.
verifyScrollBy(-itemCount * mRowHeightPx / 2);
}
@Test
public void testListScrollAndTap() throws Throwable {
// Start a programmatic scroll to position 30. We register a scroll listener on the list
// to notify us when position 15 becomes visible.
final CountDownLatch scrollLatch = new CountDownLatch(1);
mListView.setOnScrollListener(new ScrollPositionListListener(scrollLatch, 15));
mActivityRule.runOnUiThread(() -> mListView.smoothScrollToPosition(30));
// Since position-based scroll is emulated as a series of mini-flings, scrolling
// might take considerable time.
assertTrue("Timed out while waiting for the scroll to complete",
scrollLatch.await(5, TimeUnit.SECONDS));
// Verify that we're here in the middle of the programmatic scroll
assertTrue(mListView.getLastVisiblePosition() < 30);
// Emulate tap in the middle of the list - this should stop our programmatic scroll.
// Note that due to asynchronous nature of the moving pieces, we might still get one
// more scroll frame as the injected motion events that constitute an emulated tap
// are being processed by our list view.
CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mListView);
// Sleep for a second
SystemClock.sleep(1000);
// and verify that we're still haven't scrolled down to position 30
assertTrue(mListView.getLastVisiblePosition() < 30);
}
private void verifyListScrollAndEmulateFlingGesture(boolean isDownwardsFlingGesture)
throws Throwable {
// Start a programmatic scroll to position 30. We register a scroll listener on the list
// to notify us when position 15 becomes visible.
final CountDownLatch scrollLatch = new CountDownLatch(1);
mListView.setOnScrollListener(new ScrollPositionListListener(scrollLatch, 15));
mActivityRule.runOnUiThread(() -> mListView.smoothScrollToPosition(30));
// Since position-based scroll is emulated as a series of mini-flings, scrolling
// might take considerable time.
assertTrue("Timed out while waiting for the scroll to complete",
scrollLatch.await(5, TimeUnit.SECONDS));
// Verify that we're here in the middle of the programmatic scroll
assertTrue(mListView.getLastVisiblePosition() < 30);
final int firstVisiblePositionBeforeFling = mListView.getFirstVisiblePosition();
// At this point the programmatic scroll is still going. Now emulate a fling
// gesture and verify that we're going to get to the IDLE state
final CountDownLatch flingLatch = new CountDownLatch(1);
mListView.setOnScrollListener(new ScrollIdleListListener(flingLatch));
CtsTouchUtils.emulateFlingGesture(mInstrumentation, mListView, isDownwardsFlingGesture);
assertTrue("Timed out while waiting for the fling to complete",
flingLatch.await(5, TimeUnit.SECONDS));
// Note that the actual position in the list at the end of the fling depends on
// the processing of the injected sequence of motion events that might differ at milli/micro
// second level from run to run
if (isDownwardsFlingGesture) {
// Verify that the fling gesture has been processed, getting us closer to the
// beginning of the list.
assertTrue(mListView.getFirstVisiblePosition() < firstVisiblePositionBeforeFling);
} else {
// Verify that the fling gesture has been processed, getting us closer to the
// end of the list.
assertTrue(mListView.getFirstVisiblePosition() > firstVisiblePositionBeforeFling);
}
}
@Test
public void testListScrollAndEmulateDownwardsFlingGesture() throws Throwable {
verifyListScrollAndEmulateFlingGesture(true);
}
@Test
public void testListScrollAndEmulateUpwardsFlingGesture() throws Throwable {
verifyListScrollAndEmulateFlingGesture(false);
}
private ListScrollPosition getCurrentScrollPosition() {
ListScrollPosition result = new ListScrollPosition();
result.mFirstVisiblePosition = mListView.getFirstVisiblePosition();
result.mFirstViewVerticalOffset = mListView.getChildAt(0).getTop();
return result;
}
@Test
public void testListFlingWithZeroVelocity() throws Throwable {
mListView.setVelocityScale(0.0f);
final CountDownLatch flingLatch = new CountDownLatch(2);
mListView.setOnScrollListener(new ScrollIdleListListener(flingLatch));
final ListScrollPosition[] scrollPositionAfterUpEvent =
new ListScrollPosition[1];
final EventInjectionListener eventInjectionListener =
new EventInjectionListener() {
@Override
public void onDownInjected(int xOnScreen, int yOnScreen) {
}
@Override
public void onMoveInjected(int[] xOnScreen, int[] yOnScreen) {
}
@Override
public void onUpInjected(int xOnScreen, int yOnScreen) {
scrollPositionAfterUpEvent[0] = getCurrentScrollPosition();
flingLatch.countDown();
}
};
CtsTouchUtils.emulateFlingGesture(mInstrumentation, mListView, false,
eventInjectionListener);
assertTrue("Timed out while waiting for the fling to complete",
flingLatch.await(5, TimeUnit.SECONDS));
// Since our velocity scale is 0, we expect that the emulated fling gesture didn't
// result in any fling, but just a simple scroll that stopped at the ACTION_UP
// event.
final ListScrollPosition scrollPositionAtRest = getCurrentScrollPosition();
assertEquals("First visible position", scrollPositionAtRest.mFirstVisiblePosition,
scrollPositionAfterUpEvent[0].mFirstVisiblePosition);
assertEquals("First view offset", scrollPositionAtRest.mFirstViewVerticalOffset,
scrollPositionAfterUpEvent[0].mFirstViewVerticalOffset);
}
private static class LargeContentAdapter extends BaseAdapter {
private final Context mContext;
private final int mCount;
private final LayoutInflater mLayoutInflater;
public LargeContentAdapter(Context context, int count) {
mContext = context;
mCount = count;
mLayoutInflater = LayoutInflater.from(mContext);
}
@Override
public int getCount() {
return mCount;
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final TextView textView = (convertView != null) ? (TextView) convertView
: (TextView) mLayoutInflater.inflate(R.layout.listitemfixed_layout,
parent, false);
textView.setText("Item " + position);
return textView;
}
}
@Suppress
@Test
// Disabled due to flakiness. CtsTouchUtils cannot guarantee the amount of scroll since it is
// using sleeps and it is unreliable on cloud devices.
public void testFriction() throws Throwable {
// Set an adapter with 100K items so that no matter how fast our fling is, we won't
// get to the bottom of the list in one fling
mActivityRule.runOnUiThread(
() -> mListView.setAdapter(new LargeContentAdapter(mContext, 100000)));
mInstrumentation.waitForIdleSync();
final CountDownLatch initialFlingLatch = new CountDownLatch(1);
mListView.setOnScrollListener(new ScrollIdleListListener(initialFlingLatch));
CtsTouchUtils.emulateFlingGesture(mInstrumentation, mListView, false);
assertTrue("Timed out while waiting for the fling to complete",
initialFlingLatch.await(5, TimeUnit.SECONDS));
final int lastVisiblePositionAfterDefaultFling = mListView.getLastVisiblePosition();
// Scroll back to the top of the list
verifyScrollToPosition(0);
// configure the fling to have less friction
mListView.setFriction(ViewConfiguration.getScrollFriction() / 4.0f);
// and do the fling again
final CountDownLatch fastFlingLatch = new CountDownLatch(1);
mListView.setOnScrollListener(new ScrollIdleListListener(fastFlingLatch));
CtsTouchUtils.emulateFlingGesture(mInstrumentation, mListView, false);
assertTrue("Timed out while waiting for the fling to complete",
fastFlingLatch.await(5, TimeUnit.SECONDS));
final int lastVisiblePositionAfterFastFling = mListView.getLastVisiblePosition();
// We expect a fast fling (with lower scroll friction) to end up scrolling more
// of our content
assertTrue("Default fling ended at " + lastVisiblePositionAfterDefaultFling
+ ", while fast fling ended at " + lastVisiblePositionAfterFastFling,
lastVisiblePositionAfterFastFling > lastVisiblePositionAfterDefaultFling);
// Scroll back to the top of the list
verifyScrollToPosition(0);
// configure the fling to have more friction
mListView.setFriction(ViewConfiguration.getScrollFriction() * 4.0f);
// and do the fling again
final CountDownLatch slowFlingLatch = new CountDownLatch(1);
mListView.setOnScrollListener(new ScrollIdleListListener(slowFlingLatch));
CtsTouchUtils.emulateFlingGesture(mInstrumentation, mListView, false);
assertTrue("Timed out while waiting for the fling to complete",
slowFlingLatch.await(5, TimeUnit.SECONDS));
final int lastVisiblePositionAfterSlowFling = mListView.getLastVisiblePosition();
// We expect a slow fling (with higher scroll friction) to end up scrolling less
// of our content
assertTrue("Default fling ended at " + lastVisiblePositionAfterDefaultFling
+ ", while slow fling ended at " + lastVisiblePositionAfterSlowFling,
lastVisiblePositionAfterSlowFling < lastVisiblePositionAfterDefaultFling);
}
}