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 
17 package androidx.core.widget;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertTrue;
22 
23 import android.annotation.SuppressLint;
24 import android.support.v4.BaseInstrumentationTestCase;
25 import android.view.LayoutInflater;
26 import android.view.View;
27 import android.view.ViewGroup;
28 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
29 import android.widget.BaseAdapter;
30 import android.widget.ListView;
31 import android.widget.TextView;
32 
33 import androidx.core.test.R;
34 import androidx.test.filters.MediumTest;
35 import androidx.test.rule.ActivityTestRule;
36 
37 import org.jspecify.annotations.NonNull;
38 import org.jspecify.annotations.Nullable;
39 import org.junit.Before;
40 import org.junit.Test;
41 
42 import java.util.concurrent.CountDownLatch;
43 import java.util.concurrent.TimeUnit;
44 
45 @SuppressWarnings("deprecation")
46 @MediumTest
47 public class ListViewCompatTest extends BaseInstrumentationTestCase<ListViewTestActivity> {
48     private ListView mListView;
49 
ListViewCompatTest()50     public ListViewCompatTest() {
51         super(ListViewTestActivity.class);
52     }
53 
54     @Before
setUp()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 
runOnMainAndLayoutSync(final @NonNull ActivityTestRule activityTestRule, final @NonNull View view, final @Nullable Runnable runner, final boolean forceLayout)92     private void runOnMainAndLayoutSync(final @NonNull ActivityTestRule activityTestRule,
93             final @NonNull View view, final @Nullable 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             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
testCanScroll()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