/* * Copyright (C) 2017 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 androidx.contentpager.content; import static androidx.contentpager.content.ContentPager.createArgs; import static androidx.contentpager.content.TestContentProvider.PAGED_URI; import static androidx.contentpager.content.TestContentProvider.PAGED_WINDOWED_URI; import static androidx.contentpager.content.TestContentProvider.UNPAGED_URI; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import android.app.Activity; import android.content.ContentResolver; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.support.test.filters.MediumTest; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; import androidx.annotation.Nullable; import androidx.contentpager.content.ContentPager.ContentCallback; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @MediumTest @RunWith(AndroidJUnit4.class) public class ContentPagerTest { private ContentResolver mResolver; private TestQueryRunner mRunner; private TestContentCallback mCallback; private ContentPager mPager; @Rule public ActivityTestRule mActivityRule = new ActivityTestRule(TestActivity.class); @Before public void setUp() { mRunner = new TestQueryRunner(); mResolver = mActivityRule.getActivity().getContentResolver(); mCallback = new TestContentCallback(); mPager = new ContentPager(mResolver, mRunner); } @Test public void testRelaysProviderPagedResults() throws Throwable { int offset = 0; int limit = 10; // NOTE: Paging on Android O is accompolished by way of ContentResolver#query that // accepts a Bundle. That means on older platforms we either have to cook up // a special way of paging that doesn't use the bundle (that's what we do here) // or we simply skip testing how we deal with provider paged results. Uri uriWithTestPagingData = TestContentProvider.forcePagingSpec(PAGED_URI, offset, limit); Query query = mPager.query( uriWithTestPagingData, null, createArgs(offset, limit), null, mCallback); mCallback.assertNumPagesLoaded(1); mCallback.assertPageLoaded(query); Cursor cursor = mCallback.getCursor(query); Bundle extras = cursor.getExtras(); assertExpectedRecords(cursor, query.getOffset()); assertEquals( ContentPager.CURSOR_DISPOSITION_PAGED, extras.getInt(ContentPager.CURSOR_DISPOSITION, -1)); assertEquals( TestContentProvider.DEFAULT_RECORD_COUNT, extras.getInt(ContentResolver.EXTRA_TOTAL_COUNT)); assertHasHonoredArgs( extras, ContentResolver.QUERY_ARG_LIMIT, ContentResolver.QUERY_ARG_OFFSET); assertEquals( 1, extras.getInt(ContentPager.Stats.EXTRA_PROVIDER_PAGED)); assertEquals( 1, extras.getInt(ContentPager.Stats.EXTRA_RESOLVED_QUERIES)); assertEquals( 1, extras.getInt(ContentPager.Stats.EXTRA_TOTAL_QUERIES)); } @Test public void testLimitsPagedResultsToWindowSize() throws Throwable { int offset = 0; int limit = 10; // NOTE: Paging on Android O is accompolished by way of ContentResolver#query that // accepts a Bundle. That means on older platforms we either have to cook up // a special way of paging that doesn't use the bundle (that's what we do here) // or we simply skip testing how we deal with provider paged results. Uri uriWithTestPagingData = TestContentProvider.forcePagingSpec( PAGED_WINDOWED_URI, offset, limit); Query query = mPager.query( uriWithTestPagingData, null, createArgs(offset, limit), null, mCallback); mCallback.assertNumPagesLoaded(1); mCallback.assertPageLoaded(query); Cursor cursor = mCallback.getCursor(query); Bundle extras = cursor.getExtras(); assertExpectedRecords(cursor, query.getOffset()); assertEquals( ContentPager.CURSOR_DISPOSITION_REPAGED, extras.getInt(ContentPager.CURSOR_DISPOSITION, -1)); assertEquals( TestContentProvider.DEFAULT_RECORD_COUNT, extras.getInt(ContentResolver.EXTRA_TOTAL_COUNT)); assertEquals(limit, extras.getInt(ContentPager.EXTRA_REQUESTED_LIMIT)); assertEquals(7, extras.getInt(ContentPager.EXTRA_SUGGESTED_LIMIT)); assertHasHonoredArgs( extras, ContentResolver.QUERY_ARG_LIMIT, ContentResolver.QUERY_ARG_OFFSET); assertEquals( 1, extras.getInt(ContentPager.Stats.EXTRA_PROVIDER_PAGED)); assertEquals( 1, extras.getInt(ContentPager.Stats.EXTRA_RESOLVED_QUERIES)); assertEquals( 1, extras.getInt(ContentPager.Stats.EXTRA_TOTAL_QUERIES)); } @Test public void testAdaptsUnpagedToPaged() throws Throwable { Query query = mPager.query( UNPAGED_URI, null, createArgs(0, 10), null, mCallback); mCallback.assertNumPagesLoaded(1); mCallback.assertPageLoaded(query); Cursor cursor = mCallback.getCursor(query); Bundle extras = cursor.getExtras(); assertExpectedRecords(cursor, query.getOffset()); assertEquals( ContentPager.CURSOR_DISPOSITION_COPIED, extras.getInt(ContentPager.CURSOR_DISPOSITION)); assertEquals( TestContentProvider.DEFAULT_RECORD_COUNT, extras.getInt(ContentResolver.EXTRA_TOTAL_COUNT)); assertHasHonoredArgs( extras, ContentResolver.QUERY_ARG_LIMIT, ContentResolver.QUERY_ARG_OFFSET); assertEquals( 1, extras.getInt(ContentPager.Stats.EXTRA_COMPAT_PAGED)); assertEquals( 1, extras.getInt(ContentPager.Stats.EXTRA_RESOLVED_QUERIES)); assertEquals( 1, extras.getInt(ContentPager.Stats.EXTRA_TOTAL_QUERIES)); } @Test public void testCachesUnpagedCursor() throws Throwable { mPager.query( UNPAGED_URI, null, createArgs(0, 10), null, mCallback); mPager.query( UNPAGED_URI, null, createArgs(10, 10), null, mCallback); // Rerun the same query as the first...extra exercise to ensure we can return // to previously loaded results. Query query = mPager.query( UNPAGED_URI, null, createArgs(0, 10), null, mCallback); mCallback.assertNumPagesLoaded(3); Cursor cursor = mCallback.getCursor(query); Bundle extras = cursor.getExtras(); assertEquals( 3, extras.getInt(ContentPager.Stats.EXTRA_COMPAT_PAGED)); assertEquals( 1, extras.getInt(ContentPager.Stats.EXTRA_RESOLVED_QUERIES)); assertEquals( 3, extras.getInt(ContentPager.Stats.EXTRA_TOTAL_QUERIES)); } @Test public void testWrapsCursorsThatJustHappenToFitInPageRange() throws Throwable { // NOTE: Paging on Android O is accompolished by way of ContentResolver#query that // accepts a Bundle. That means on older platforms we either have to cook up // a special way of paging that doesn't use the bundle (that's what we do here) // or we simply skip testing how we deal with provider paged results. Uri uri = TestContentProvider.forceRecordCount(UNPAGED_URI, 22); Query query = mPager.query( uri, null, createArgs(0, 44), null, mCallback); mCallback.assertNumPagesLoaded(1); // mCallback.assertPageLoaded(pageId); mCallback.assertPageLoaded(query); Cursor cursor = mCallback.getCursor(query); Bundle extras = cursor.getExtras(); assertExpectedRecords(cursor, query.getOffset()); assertEquals( ContentPager.CURSOR_DISPOSITION_WRAPPED, extras.getInt(ContentPager.CURSOR_DISPOSITION)); assertEquals( 22, extras.getInt(ContentResolver.EXTRA_TOTAL_COUNT)); assertHasHonoredArgs( extras, ContentResolver.QUERY_ARG_LIMIT, ContentResolver.QUERY_ARG_OFFSET); assertEquals( 1, extras.getInt(ContentPager.Stats.EXTRA_COMPAT_PAGED)); assertEquals( 1, extras.getInt(ContentPager.Stats.EXTRA_RESOLVED_QUERIES)); assertEquals( 1, extras.getInt(ContentPager.Stats.EXTRA_TOTAL_QUERIES)); } @Test public void testCorrectlyCopiesRecords_EndOfResults() throws Throwable { // finally, check the last page. int limit = 100; // This will be the size of the last page. Should be 67. int leftOvers = TestContentProvider.DEFAULT_RECORD_COUNT % limit; int offset = TestContentProvider.DEFAULT_RECORD_COUNT - leftOvers; Query query = mPager.query( UNPAGED_URI, null, createArgs(offset, limit), null, mCallback); mCallback.assertNumPagesLoaded(1); mCallback.assertPageLoaded(query); Cursor cursor = mCallback.getCursor(query); assertEquals(leftOvers, cursor.getCount()); Bundle extras = cursor.getExtras(); assertExpectedRecords(cursor, query.getOffset()); assertEquals( ContentPager.CURSOR_DISPOSITION_COPIED, extras.getInt(ContentPager.CURSOR_DISPOSITION)); assertHasHonoredArgs( extras, ContentResolver.QUERY_ARG_LIMIT, ContentResolver.QUERY_ARG_OFFSET); assertEquals( 1, extras.getInt(ContentPager.Stats.EXTRA_COMPAT_PAGED)); assertEquals( 1, extras.getInt(ContentPager.Stats.EXTRA_RESOLVED_QUERIES)); assertEquals( 1, extras.getInt(ContentPager.Stats.EXTRA_TOTAL_QUERIES)); } @Test public void testCancelsRunningQueriesOnReset() throws Throwable { mRunner.runQuery = false; Query query = mPager.query( UNPAGED_URI, null, createArgs(0, 10), null, mCallback); assertTrue(mRunner.isRunning(query)); mPager.reset(); assertFalse(mRunner.isRunning(query)); } @Test public void testRelaysContentChangeNotificationsOnPagedCursors() throws Throwable { TestContentObserver observer = new TestContentObserver( new Handler(Looper.getMainLooper())); observer.expectNotifications(1); Query query = mPager.query( UNPAGED_URI, null, createArgs(10, 99), null, mCallback); Cursor cursor = mCallback.getCursor(query); cursor.registerContentObserver(observer); mResolver.notifyChange(UNPAGED_URI, null); assertTrue(observer.mNotifiedLatch.await(1000, TimeUnit.MILLISECONDS)); } private void assertExpectedRecords(Cursor cursor, int offset) { for (int row = 0; row < cursor.getCount(); row++) { assertTrue(cursor.moveToPosition(row)); int unpagedRow = offset + row; for (int column = 0; column < cursor.getColumnCount(); column++) { TestContentProvider.assertExpectedCellValue(cursor, unpagedRow, column); } } } private static void assertHasHonoredArgs(Bundle extras, String... expectedArgs) { List honored = Arrays.asList( extras.getStringArray(ContentResolver.EXTRA_HONORED_ARGS)); for (String arg : expectedArgs) { assertTrue(honored.contains(arg)); } } private static final class TestContentCallback implements ContentCallback { private int mPagesLoaded; private Map mCursors = new HashMap<>(); @Override public void onCursorReady(Query query, Cursor cursor) { mPagesLoaded++; mCursors.put(query, cursor); } private void assertPageLoaded(Query query) { assertTrue(mCursors.containsKey(query)); assertNotNull(mCursors.get(query)); } private void assertNumPagesLoaded(int expected) { assertEquals(expected, mPagesLoaded); } private @Nullable Cursor getCursor(Query query) { return mCursors.get(query); } } }