1/*
2 * Copyright (C) 2007 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.widget.focus;
18
19import android.graphics.Rect;
20import android.test.InstrumentationTestCase;
21import android.test.suitebuilder.annotation.LargeTest;
22import android.test.suitebuilder.annotation.MediumTest;
23import android.util.InternalSelectionView;
24import android.view.KeyEvent;
25import android.widget.ListView;
26
27
28/**
29 * TODO: extract base test case that launches {@link ListOfInternalSelectionViews} with
30 * bundle params.
31 */
32public class ScrollingThroughListOfFocusablesTest extends InstrumentationTestCase {
33
34    Rect mTempRect = new Rect();
35
36    private ListOfInternalSelectionViews mActivity;
37    private ListView mListView;
38
39    private int mNumItems = 4;
40    private int mNumRowsPerItem = 5;
41    private double mScreenHeightFactor = 5 /4;
42
43    @Override
44    protected void setUp() throws Exception {
45        mActivity = launchActivity(
46                "com.android.frameworks.coretests",
47                ListOfInternalSelectionViews.class,
48                ListOfInternalSelectionViews.getBundleFor(
49                    mNumItems,      // 4 items
50                    mNumRowsPerItem,      // 5 internally selectable rows per item
51                    mScreenHeightFactor)); // each item is 5 / 4 screen height tall
52        mListView = mActivity.getListView();
53        // Make sure we have some fading edge regardless of ListView style.
54        mListView.setVerticalFadingEdgeEnabled(true);
55        mListView.setFadingEdgeLength(10);
56        ensureNotInTouchMode();
57
58        // focus the listview
59        mActivity.runOnUiThread(() -> mListView.requestFocus());
60        getInstrumentation().waitForIdleSync();
61    }
62
63    @Override
64    protected void tearDown() throws Exception {
65        mActivity.finish();
66        super.tearDown();
67    }
68
69    @MediumTest
70    public void testPreconditions() throws Exception {
71        assertNotNull(mActivity);
72        assertNotNull(mListView);
73        assertEquals(mNumItems, mActivity.getNumItems());
74        assertEquals(mNumRowsPerItem, mActivity.getNumRowsPerItem());
75    }
76
77    @MediumTest
78    public void testScrollingDownInFirstItem() throws Exception {
79
80        for (int i = 0; i < mNumRowsPerItem; i++) {
81            assertEquals(0, mListView.getSelectedItemPosition());
82
83            InternalSelectionView view = mActivity.getSelectedView();
84
85            assertInternallySelectedRowOnScreen(view, i);
86
87            // move to next row
88            if (i < mNumRowsPerItem - 1) {
89                sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
90                getInstrumentation().waitForIdleSync();
91            }
92        }
93
94        {
95            assertEquals(0, mListView.getSelectedItemPosition());
96            InternalSelectionView view = (InternalSelectionView)
97                    mListView.getSelectedView();
98
99            // 1 pixel tolerance in case height / 4 is not an even number
100            final int bottomFadingEdgeTop =
101                mListView.getBottom() - mListView.getVerticalFadingEdgeLength();
102            assertTrue("bottom of view should be just above fading edge",
103                    view.getBottom() == bottomFadingEdgeTop);
104        }
105
106        // make sure fading edge is the expected view
107        {
108            assertEquals("should be a second view visible due to the fading edge",
109                            2, mListView.getChildCount());
110            InternalSelectionView peekingChild = (InternalSelectionView)
111                    mListView.getChildAt(1);
112            assertNotNull(peekingChild);
113            assertEquals("wrong value for peeking list item",
114                    mActivity.getLabelForPosition(1), peekingChild.getLabel());
115        }
116    }
117
118    @MediumTest
119    public void testScrollingToSecondItem() throws Exception {
120
121        for (int i = 0; i < mNumRowsPerItem; i++) {
122            sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
123            getInstrumentation().waitForIdleSync();
124        }
125
126        assertEquals("should have moved to second item",
127                1, mListView.getSelectedItemPosition());
128    }
129
130    @LargeTest
131    public void testNoFadingEdgeAtBottomOfLastItem() {
132
133        // move down to last item
134        for (int i = 0; i < mNumItems; i++) {
135            for (int j = 0; j < mNumRowsPerItem; j++) {
136                if (i < mNumItems - 1 || j < mNumRowsPerItem - 1) {
137                    sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
138                    getInstrumentation().waitForIdleSync();
139                }
140            }
141        }
142
143        assertEquals(mNumItems - 1, mListView.getSelectedItemPosition());
144        InternalSelectionView view = mActivity.getSelectedView();
145        assertEquals(mNumRowsPerItem - 1, view.getSelectedRow());
146
147        view.getRectForRow(mTempRect, mNumRowsPerItem - 1);
148        mListView.offsetDescendantRectToMyCoords(view, mTempRect);
149
150        assertTrue("bottom of last row of last item should be at " +
151                "the bottom of the list view (no fading edge)",
152                mListView.getBottom() - mListView.getVerticalFadingEdgeLength() < mTempRect.bottom);
153    }
154
155    @LargeTest
156    public void testNavigatingUpThroughInternalSelection() throws Exception {
157
158        // get to bottom of second item
159        for (int i = 0; i < 2; i++) {
160            for (int j = 0; j < mNumRowsPerItem; j++) {
161                if (i < 1 || j < mNumRowsPerItem - 1) {
162                    sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
163                    getInstrumentation().waitForIdleSync();
164                }
165            }
166        }
167
168
169        // (make sure we are at last row of second item)
170        {
171            assertEquals(1, mListView.getSelectedItemPosition());
172            InternalSelectionView view = mActivity.getSelectedView();
173            assertEquals(mNumRowsPerItem - 1, view.getSelectedRow());
174        }
175
176        // go back up to the top of the second item
177        for (int i = mNumRowsPerItem - 1; i >= 0; i--) {
178            assertEquals(1, mListView.getSelectedItemPosition());
179            InternalSelectionView view = mActivity.getSelectedView();
180
181            assertInternallySelectedRowOnScreen(view, i);
182
183            // move up to next row
184            if (i > 0) {
185                sendKeys(KeyEvent.KEYCODE_DPAD_UP);
186                getInstrumentation().waitForIdleSync();
187            }
188        }
189
190        // now we are at top row, should have caused scrolling, and fading edge...
191        {
192            assertEquals(1, mListView.getSelectedItemPosition());
193            InternalSelectionView view = mActivity.getSelectedView();
194            assertEquals(0, view.getSelectedRow());
195
196            view.getDrawingRect(mTempRect);
197            mListView.offsetDescendantRectToMyCoords(view, mTempRect);
198            assertEquals("top of selected row should be just below top vertical fading edge",
199                    mListView.getVerticalFadingEdgeLength(),
200                    view.getTop());
201        }
202
203        // make sure fading edge is the view we expect
204        {
205            final InternalSelectionView view =
206                    (InternalSelectionView) mListView.getChildAt(0);
207            assertEquals(mActivity.getLabelForPosition(0), view.getLabel());
208        }
209
210
211    }
212
213    /**
214     * @param internalFocused The view to check
215     * @param row
216     */
217    private void assertInternallySelectedRowOnScreen(
218            InternalSelectionView internalFocused,
219            int row) {
220        assertEquals("expecting selected row",
221                row, internalFocused.getSelectedRow());
222
223        internalFocused.getRectForRow(mTempRect, row);
224        mListView.offsetDescendantRectToMyCoords(internalFocused, mTempRect);
225
226        assertTrue("top of row " + row + " should be on sreen",
227                mTempRect.top >= 0);
228        assertTrue("bottom of row " + row + " should be on sreen",
229                mTempRect.bottom < mActivity.getScreenHeight());
230    }
231
232    private void ensureNotInTouchMode() {
233        // If in touch mode inject a DPAD down event to exit that mode.
234        if (mListView.isInTouchMode()) {
235            sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
236            getInstrumentation().waitForIdleSync();
237        }
238    }
239}
240