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 androidx.contentpager.content;
18
19import static androidx.contentpager.content.ContentPager.createArgs;
20import static androidx.contentpager.content.TestContentProvider.PAGED_URI;
21import static androidx.contentpager.content.TestContentProvider.PAGED_WINDOWED_URI;
22import static androidx.contentpager.content.TestContentProvider.UNPAGED_URI;
23
24import static org.junit.Assert.assertEquals;
25import static org.junit.Assert.assertFalse;
26import static org.junit.Assert.assertNotNull;
27import static org.junit.Assert.assertTrue;
28
29import android.app.Activity;
30import android.content.ContentResolver;
31import android.database.Cursor;
32import android.net.Uri;
33import android.os.Bundle;
34import android.os.Handler;
35import android.os.Looper;
36import android.support.test.filters.MediumTest;
37import android.support.test.rule.ActivityTestRule;
38import android.support.test.runner.AndroidJUnit4;
39
40import androidx.annotation.Nullable;
41import androidx.contentpager.content.ContentPager.ContentCallback;
42
43import org.junit.Before;
44import org.junit.Rule;
45import org.junit.Test;
46import org.junit.runner.RunWith;
47
48import java.util.Arrays;
49import java.util.HashMap;
50import java.util.List;
51import java.util.Map;
52import java.util.concurrent.TimeUnit;
53
54@MediumTest
55@RunWith(AndroidJUnit4.class)
56public class ContentPagerTest {
57
58    private ContentResolver mResolver;
59    private TestQueryRunner mRunner;
60    private TestContentCallback mCallback;
61    private ContentPager mPager;
62
63    @Rule
64    public ActivityTestRule<Activity> mActivityRule = new ActivityTestRule(TestActivity.class);
65
66    @Before
67    public void setUp() {
68        mRunner = new TestQueryRunner();
69        mResolver = mActivityRule.getActivity().getContentResolver();
70        mCallback = new TestContentCallback();
71        mPager = new ContentPager(mResolver, mRunner);
72    }
73
74    @Test
75    public void testRelaysProviderPagedResults() throws Throwable {
76        int offset = 0;
77        int limit = 10;
78
79        // NOTE: Paging on Android O is accompolished by way of ContentResolver#query that
80        // accepts a Bundle. That means on older platforms we either have to cook up
81        // a special way of paging that doesn't use the bundle (that's what we do here)
82        // or we simply skip testing how we deal with provider paged results.
83        Uri uriWithTestPagingData = TestContentProvider.forcePagingSpec(PAGED_URI, offset, limit);
84
85        Query query = mPager.query(
86                uriWithTestPagingData,
87                null,
88                createArgs(offset, limit),
89                null,
90                mCallback);
91
92        mCallback.assertNumPagesLoaded(1);
93        mCallback.assertPageLoaded(query);
94        Cursor cursor = mCallback.getCursor(query);
95        Bundle extras = cursor.getExtras();
96
97        assertExpectedRecords(cursor, query.getOffset());
98
99        assertEquals(
100                ContentPager.CURSOR_DISPOSITION_PAGED,
101                extras.getInt(ContentPager.CURSOR_DISPOSITION, -1));
102
103        assertEquals(
104                TestContentProvider.DEFAULT_RECORD_COUNT,
105                extras.getInt(ContentResolver.EXTRA_TOTAL_COUNT));
106
107        assertHasHonoredArgs(
108                extras,
109                ContentResolver.QUERY_ARG_LIMIT,
110                ContentResolver.QUERY_ARG_OFFSET);
111
112        assertEquals(
113                1,
114                extras.getInt(ContentPager.Stats.EXTRA_PROVIDER_PAGED));
115        assertEquals(
116                1,
117                extras.getInt(ContentPager.Stats.EXTRA_RESOLVED_QUERIES));
118        assertEquals(
119                1,
120                extras.getInt(ContentPager.Stats.EXTRA_TOTAL_QUERIES));
121    }
122
123    @Test
124    public void testLimitsPagedResultsToWindowSize() throws Throwable {
125        int offset = 0;
126        int limit = 10;
127
128        // NOTE: Paging on Android O is accompolished by way of ContentResolver#query that
129        // accepts a Bundle. That means on older platforms we either have to cook up
130        // a special way of paging that doesn't use the bundle (that's what we do here)
131        // or we simply skip testing how we deal with provider paged results.
132        Uri uriWithTestPagingData = TestContentProvider.forcePagingSpec(
133                PAGED_WINDOWED_URI, offset, limit);
134
135        Query query = mPager.query(
136                uriWithTestPagingData,
137                null,
138                createArgs(offset, limit),
139                null,
140                mCallback);
141
142        mCallback.assertNumPagesLoaded(1);
143        mCallback.assertPageLoaded(query);
144        Cursor cursor = mCallback.getCursor(query);
145        Bundle extras = cursor.getExtras();
146
147        assertExpectedRecords(cursor, query.getOffset());
148
149        assertEquals(
150                ContentPager.CURSOR_DISPOSITION_REPAGED,
151                extras.getInt(ContentPager.CURSOR_DISPOSITION, -1));
152
153        assertEquals(
154                TestContentProvider.DEFAULT_RECORD_COUNT,
155                extras.getInt(ContentResolver.EXTRA_TOTAL_COUNT));
156
157
158        assertEquals(limit, extras.getInt(ContentPager.EXTRA_REQUESTED_LIMIT));
159
160        assertEquals(7, extras.getInt(ContentPager.EXTRA_SUGGESTED_LIMIT));
161
162        assertHasHonoredArgs(
163                extras,
164                ContentResolver.QUERY_ARG_LIMIT,
165                ContentResolver.QUERY_ARG_OFFSET);
166
167        assertEquals(
168                1,
169                extras.getInt(ContentPager.Stats.EXTRA_PROVIDER_PAGED));
170        assertEquals(
171                1,
172                extras.getInt(ContentPager.Stats.EXTRA_RESOLVED_QUERIES));
173        assertEquals(
174                1,
175                extras.getInt(ContentPager.Stats.EXTRA_TOTAL_QUERIES));
176    }
177
178    @Test
179    public void testAdaptsUnpagedToPaged() throws Throwable {
180        Query query = mPager.query(
181                UNPAGED_URI,
182                null,
183                createArgs(0, 10),
184                null,
185                mCallback);
186
187        mCallback.assertNumPagesLoaded(1);
188        mCallback.assertPageLoaded(query);
189        Cursor cursor = mCallback.getCursor(query);
190        Bundle extras = cursor.getExtras();
191
192        assertExpectedRecords(cursor, query.getOffset());
193
194        assertEquals(
195                ContentPager.CURSOR_DISPOSITION_COPIED,
196                extras.getInt(ContentPager.CURSOR_DISPOSITION));
197
198        assertEquals(
199                TestContentProvider.DEFAULT_RECORD_COUNT,
200                extras.getInt(ContentResolver.EXTRA_TOTAL_COUNT));
201
202        assertHasHonoredArgs(
203                extras,
204                ContentResolver.QUERY_ARG_LIMIT,
205                ContentResolver.QUERY_ARG_OFFSET);
206
207        assertEquals(
208                1,
209                extras.getInt(ContentPager.Stats.EXTRA_COMPAT_PAGED));
210        assertEquals(
211                1,
212                extras.getInt(ContentPager.Stats.EXTRA_RESOLVED_QUERIES));
213        assertEquals(
214                1,
215                extras.getInt(ContentPager.Stats.EXTRA_TOTAL_QUERIES));
216    }
217
218    @Test
219    public void testCachesUnpagedCursor() throws Throwable {
220        mPager.query(
221                UNPAGED_URI,
222                null,
223                createArgs(0, 10),
224                null,
225                mCallback);
226
227        mPager.query(
228                UNPAGED_URI,
229                null,
230                createArgs(10, 10),
231                null,
232                mCallback);
233
234        // Rerun the same query as the first...extra exercise to ensure we can return
235        // to previously loaded results.
236        Query query = mPager.query(
237                UNPAGED_URI,
238                null,
239                createArgs(0, 10),
240                null,
241                mCallback);
242
243        mCallback.assertNumPagesLoaded(3);
244        Cursor cursor = mCallback.getCursor(query);
245        Bundle extras = cursor.getExtras();
246
247        assertEquals(
248                3,
249                extras.getInt(ContentPager.Stats.EXTRA_COMPAT_PAGED));
250        assertEquals(
251                1,
252                extras.getInt(ContentPager.Stats.EXTRA_RESOLVED_QUERIES));
253        assertEquals(
254                3,
255                extras.getInt(ContentPager.Stats.EXTRA_TOTAL_QUERIES));
256    }
257
258    @Test
259    public void testWrapsCursorsThatJustHappenToFitInPageRange() throws Throwable {
260
261        // NOTE: Paging on Android O is accompolished by way of ContentResolver#query that
262        // accepts a Bundle. That means on older platforms we either have to cook up
263        // a special way of paging that doesn't use the bundle (that's what we do here)
264        // or we simply skip testing how we deal with provider paged results.
265        Uri uri = TestContentProvider.forceRecordCount(UNPAGED_URI, 22);
266
267        Query query = mPager.query(
268                uri,
269                null,
270                createArgs(0, 44),
271                null,
272                mCallback);
273
274        mCallback.assertNumPagesLoaded(1);
275        // mCallback.assertPageLoaded(pageId);
276        mCallback.assertPageLoaded(query);
277        Cursor cursor = mCallback.getCursor(query);
278        Bundle extras = cursor.getExtras();
279
280        assertExpectedRecords(cursor, query.getOffset());
281
282        assertEquals(
283                ContentPager.CURSOR_DISPOSITION_WRAPPED,
284                extras.getInt(ContentPager.CURSOR_DISPOSITION));
285
286        assertEquals(
287                22,
288                extras.getInt(ContentResolver.EXTRA_TOTAL_COUNT));
289
290        assertHasHonoredArgs(
291                extras,
292                ContentResolver.QUERY_ARG_LIMIT,
293                ContentResolver.QUERY_ARG_OFFSET);
294
295        assertEquals(
296                1,
297                extras.getInt(ContentPager.Stats.EXTRA_COMPAT_PAGED));
298        assertEquals(
299                1,
300                extras.getInt(ContentPager.Stats.EXTRA_RESOLVED_QUERIES));
301        assertEquals(
302                1,
303                extras.getInt(ContentPager.Stats.EXTRA_TOTAL_QUERIES));
304    }
305
306    @Test
307    public void testCorrectlyCopiesRecords_EndOfResults() throws Throwable {
308        // finally, check the last page.
309        int limit = 100;
310        // This will be the size of the last page. Should be 67.
311        int leftOvers = TestContentProvider.DEFAULT_RECORD_COUNT % limit;
312        int offset = TestContentProvider.DEFAULT_RECORD_COUNT - leftOvers;
313
314        Query query = mPager.query(
315                UNPAGED_URI,
316                null,
317                createArgs(offset, limit),
318                null,
319                mCallback);
320
321        mCallback.assertNumPagesLoaded(1);
322        mCallback.assertPageLoaded(query);
323        Cursor cursor = mCallback.getCursor(query);
324        assertEquals(leftOvers, cursor.getCount());
325        Bundle extras = cursor.getExtras();
326
327        assertExpectedRecords(cursor, query.getOffset());
328
329        assertEquals(
330                ContentPager.CURSOR_DISPOSITION_COPIED,
331                extras.getInt(ContentPager.CURSOR_DISPOSITION));
332
333        assertHasHonoredArgs(
334                extras,
335                ContentResolver.QUERY_ARG_LIMIT,
336                ContentResolver.QUERY_ARG_OFFSET);
337
338        assertEquals(
339                1,
340                extras.getInt(ContentPager.Stats.EXTRA_COMPAT_PAGED));
341        assertEquals(
342                1,
343                extras.getInt(ContentPager.Stats.EXTRA_RESOLVED_QUERIES));
344        assertEquals(
345                1,
346                extras.getInt(ContentPager.Stats.EXTRA_TOTAL_QUERIES));
347    }
348
349    @Test
350    public void testCancelsRunningQueriesOnReset() throws Throwable {
351        mRunner.runQuery = false;
352        Query query = mPager.query(
353                UNPAGED_URI,
354                null,
355                createArgs(0, 10),
356                null,
357                mCallback);
358
359        assertTrue(mRunner.isRunning(query));
360
361        mPager.reset();
362
363        assertFalse(mRunner.isRunning(query));
364    }
365
366    @Test
367    public void testRelaysContentChangeNotificationsOnPagedCursors() throws Throwable {
368
369        TestContentObserver observer = new TestContentObserver(
370                new Handler(Looper.getMainLooper()));
371        observer.expectNotifications(1);
372
373        Query query = mPager.query(
374                UNPAGED_URI,
375                null,
376                createArgs(10, 99),
377                null,
378                mCallback);
379
380        Cursor cursor = mCallback.getCursor(query);
381        cursor.registerContentObserver(observer);
382
383        mResolver.notifyChange(UNPAGED_URI, null);
384
385        assertTrue(observer.mNotifiedLatch.await(1000, TimeUnit.MILLISECONDS));
386    }
387
388    private void assertExpectedRecords(Cursor cursor, int offset) {
389        for (int row = 0; row < cursor.getCount(); row++) {
390            assertTrue(cursor.moveToPosition(row));
391            int unpagedRow = offset + row;
392            for (int column = 0; column < cursor.getColumnCount(); column++) {
393                TestContentProvider.assertExpectedCellValue(cursor, unpagedRow, column);
394            }
395        }
396    }
397
398    private static void assertHasHonoredArgs(Bundle extras, String... expectedArgs) {
399        List<String> honored = Arrays.asList(
400                extras.getStringArray(ContentResolver.EXTRA_HONORED_ARGS));
401
402        for (String arg : expectedArgs) {
403            assertTrue(honored.contains(arg));
404        }
405    }
406
407    private static final class TestContentCallback implements ContentCallback {
408
409        private int mPagesLoaded;
410        private Map<Query, Cursor> mCursors = new HashMap<>();
411
412        @Override
413        public void onCursorReady(Query query, Cursor cursor) {
414            mPagesLoaded++;
415            mCursors.put(query, cursor);
416        }
417
418        private void assertPageLoaded(Query query) {
419            assertTrue(mCursors.containsKey(query));
420            assertNotNull(mCursors.get(query));
421        }
422
423        private void assertNumPagesLoaded(int expected) {
424            assertEquals(expected, mPagesLoaded);
425        }
426
427        private @Nullable Cursor getCursor(Query query) {
428            return mCursors.get(query);
429        }
430    }
431}
432