1/*
2 * Copyright (C) 2017 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
17package android.support.v4.widget;
18
19import static org.junit.Assert.assertEquals;
20import static org.junit.Assert.assertFalse;
21import static org.junit.Assert.assertTrue;
22
23import android.annotation.SuppressLint;
24import android.support.annotation.NonNull;
25import android.support.annotation.Nullable;
26import android.support.compat.test.R;
27import android.support.test.filters.SmallTest;
28import android.support.test.rule.ActivityTestRule;
29import android.support.v4.BaseInstrumentationTestCase;
30import android.view.LayoutInflater;
31import android.view.View;
32import android.view.ViewGroup;
33import android.view.ViewTreeObserver.OnGlobalLayoutListener;
34import android.widget.BaseAdapter;
35import android.widget.ListView;
36import android.widget.TextView;
37
38import junit.framework.Assert;
39
40import org.junit.Before;
41import org.junit.Test;
42
43import java.util.concurrent.CountDownLatch;
44import java.util.concurrent.TimeUnit;
45
46@SmallTest
47public class ListViewCompatTest extends BaseInstrumentationTestCase<ListViewTestActivity> {
48    private ListView mListView;
49
50    public ListViewCompatTest() {
51        super(ListViewTestActivity.class);
52    }
53
54    @Before
55    public void setUp() throws Throwable {
56        mListView = mActivityTestRule.getActivity().findViewById(R.id.content);
57        runOnMainAndLayoutSync(mActivityTestRule, mListView, new Runnable() {
58            @Override
59            public void run() {
60                mListView.setAdapter(new BaseAdapter() {
61                    @Override
62                    public int getCount() {
63                        return 500;
64                    }
65
66                    @Override
67                    public Object getItem(int position) {
68                        return null;
69                    }
70
71                    @Override
72                    public long getItemId(int position) {
73                        return position;
74                    }
75
76                    @SuppressLint("SetTextI18n")
77                    @Override
78                    public View getView(int position, View convertView, ViewGroup parent) {
79                        if (convertView == null) {
80                            convertView = LayoutInflater.from(mListView.getContext()).inflate(
81                                    R.layout.list_view_row, parent, false);
82                        }
83                        TextView result = (TextView) convertView;
84                        result.setText("row #" + (position + 1));
85                        return result;
86                    }
87                });
88            }
89        }, false);
90    }
91
92    private void runOnMainAndLayoutSync(@NonNull final ActivityTestRule activityTestRule,
93            @NonNull final View view, @Nullable final Runnable runner, final boolean forceLayout)
94            throws Throwable {
95        final View rootView = view.getRootView();
96
97        final CountDownLatch latch = new CountDownLatch(1);
98
99        activityTestRule.runOnUiThread(new Runnable() {
100            @Override
101            public void run() {
102                final OnGlobalLayoutListener listener = new OnGlobalLayoutListener() {
103                    @Override
104                    public void onGlobalLayout() {
105                        rootView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
106                        // countdown immediately since the layout we were waiting on has happened
107                        latch.countDown();
108                    }
109                };
110
111                rootView.getViewTreeObserver().addOnGlobalLayoutListener(listener);
112
113                if (runner != null) {
114                    runner.run();
115                }
116
117                if (forceLayout) {
118                    rootView.requestLayout();
119                }
120            }
121        });
122
123        try {
124            Assert.assertTrue("Expected layout pass within 5 seconds",
125                    latch.await(5, TimeUnit.SECONDS));
126        } catch (InterruptedException e) {
127            throw new RuntimeException(e);
128        }
129    }
130
131    @Test
132    public void testCanScroll() throws Throwable {
133        final int itemCount = mListView.getAdapter().getCount();
134
135        assertEquals(0, mListView.getFirstVisiblePosition());
136
137        // Verify that when we're at the top of the list, we can't scroll up but we can scroll
138        // down.
139        assertFalse(ListViewCompat.canScrollList(mListView, -1));
140        assertTrue(ListViewCompat.canScrollList(mListView, 1));
141
142        // Scroll down to the very end of the list
143        runOnMainAndLayoutSync(mActivityTestRule, mListView,
144                new Runnable() {
145                    @Override
146                    public void run() {
147                        mListView.setStackFromBottom(true);
148                    }
149                }, false);
150        assertEquals(itemCount - 1, mListView.getLastVisiblePosition());
151
152        // Verify that when we're at the bottom of the list, we can't scroll down but we can scroll
153        // up.
154        assertFalse(ListViewCompat.canScrollList(mListView, 1));
155        assertTrue(ListViewCompat.canScrollList(mListView, -1));
156    }
157}
158