Loading core/java/android/content/ContentProviderNative.java +2 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import android.database.Cursor; import android.database.CursorToBulkCursorAdaptor; import android.database.DatabaseUtils; import android.database.IContentObserver; import android.database.PageViewCursor; import android.net.Uri; import android.os.Binder; import android.os.Bundle; Loading Loading @@ -103,6 +104,7 @@ abstract public class ContentProviderNative extends Binder implements IContentPr if (cursor != null) { CursorToBulkCursorAdaptor adaptor = null; cursor = PageViewCursor.wrap(cursor, queryArgs); try { adaptor = new CursorToBulkCursorAdaptor(cursor, observer, getProviderName()); Loading core/java/android/database/PageViewCursor.java 0 → 100644 +245 −0 Original line number Diff line number Diff line /* * 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 android.database; import static com.android.internal.util.Preconditions.checkArgument; import android.annotation.Nullable; import android.content.ContentResolver; import android.os.Bundle; import android.util.Log; import android.util.MathUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; /** * Cursor wrapper that provides visibility into a subset of a wrapped cursor. * * The window is specified by offset and limit. * * @hide */ public final class PageViewCursor extends CrossProcessCursorWrapper { /** * An extra added to results that are auto-paged using the wrapper. */ public static final String EXTRA_AUTO_PAGED = "android.content.extra.AUTO_PAGED"; private static final String TAG = "PageViewCursor"; private static final boolean DEBUG = false; private static final boolean VERBOSE = false; private final int mOffset; // aka first index private final int mCount; private final Bundle mExtras; private int mPos = -1; /** * @see PageViewCursor#wrap(Cursor, Bundle) */ @VisibleForTesting public PageViewCursor(Cursor cursor, int offset, int limit) { super(cursor); checkArgument(offset > -1); checkArgument(limit > -1); mOffset = offset; mExtras = new Bundle(); Bundle extras = cursor.getExtras(); if (extras != null) { mExtras.putAll(extras); } mExtras.putBoolean(EXTRA_AUTO_PAGED, true); // We need a mutable bundle so we can add QUERY_RESULT_SIZE. // Direct equality check is correct here. Bundle.EMPTY is a specific instance // of Bundle that is immutable by way of implementation. // mExtras = (extras == Bundle.EMPTY) ? new Bundle() : extras; // When we're wrapping another cursor, it should not already be "paged". checkArgument(!mExtras.containsKey(ContentResolver.EXTRA_TOTAL_SIZE)); int count = mCursor.getCount(); mExtras.putInt(ContentResolver.EXTRA_TOTAL_SIZE, count); mCount = MathUtils.constrain(count - offset, 0, limit); if (DEBUG) Log.d(TAG, "Wrapped cursor" + " offset: " + mOffset + ", limit: " + limit + ", delegate_size: " + count + ", paged_count: " + mCount); } @Override public Bundle getExtras() { return mExtras; } @Override public int getPosition() { return mPos; } @Override public boolean isBeforeFirst() { if (mCount == 0) { return true; } return mPos == -1; } @Override public boolean isAfterLast() { if (mCount == 0) { return true; } return mPos == mCount; } @Override public boolean isFirst() { return mPos == 0; } @Override public boolean isLast() { return mPos == mCount - 1; } @Override public boolean moveToFirst() { return moveToPosition(0); } @Override public boolean moveToLast() { return moveToPosition(mCount - 1); } @Override public boolean moveToNext() { return move(1); } @Override public boolean moveToPrevious() { return move(-1); } @Override public boolean move(int offset) { return moveToPosition(mPos + offset); } @Override public boolean moveToPosition(int position) { if (position >= mCount) { if (VERBOSE) Log.v(TAG, "Invalid Positon: " + position + " >= count: " + mCount + ". Moving to last record."); mPos = mCount; super.moveToPosition(mOffset + mPos); // move into "after last" state. return false; } // Make sure position isn't before the beginning of the cursor if (position < 0) { if (VERBOSE) Log.v(TAG, "Ignoring invalid move to position: " + position); mPos = -1; super.moveToPosition(mPos); return false; } if (position == mPos) { if (VERBOSE) Log.v(TAG, "Ignoring no-op move to position: " + position); return true; } int delegatePosition = position + mOffset; if (VERBOSE) Log.v(TAG, "Moving delegate cursor to position: " + delegatePosition); if (super.moveToPosition(delegatePosition)) { mPos = position; return true; } else { mPos = -1; super.moveToPosition(-1); return false; } } @Override public boolean onMove(int oldPosition, int newPosition) { throw new UnsupportedOperationException("Not supported."); } @Override public int getCount() { return mCount; } /** * Wraps the cursor such that it will honor paging args (if present), AND if the cursor * does not report paging size. * * <p>No-op if cursor already contains paging or is less than specified page size. */ public static Cursor wrap(Cursor cursor, @Nullable Bundle queryArgs) { boolean hasPagingArgs = queryArgs != null && (queryArgs.containsKey(ContentResolver.QUERY_ARG_OFFSET) || queryArgs.containsKey(ContentResolver.QUERY_ARG_LIMIT)); if (!hasPagingArgs) { if (VERBOSE) Log.d(TAG, "No-wrap: No paging args in request."); return cursor; } if (hasPagedResponseDetails(cursor.getExtras())) { if (VERBOSE) Log.d(TAG, "No-wrap. Cursor has paging details."); return cursor; } return new PageViewCursor( cursor, queryArgs.getInt(ContentResolver.QUERY_ARG_OFFSET, 0), queryArgs.getInt(ContentResolver.QUERY_ARG_LIMIT, Integer.MAX_VALUE)); } /** * @return true if the extras contains information indicating the associated * cursor is paged. */ private static boolean hasPagedResponseDetails(@Nullable Bundle extras) { if (extras != null && extras.containsKey(ContentResolver.EXTRA_TOTAL_SIZE)) { return true; } String[] honoredArgs = extras.getStringArray(ContentResolver.EXTRA_HONORED_ARGS); if (honoredArgs != null && ( ArrayUtils.contains(honoredArgs, ContentResolver.QUERY_ARG_OFFSET) || ArrayUtils.contains(honoredArgs, ContentResolver.QUERY_ARG_LIMIT))) { return true; } return false; } } core/tests/coretests/src/android/database/PageViewCursorTest.java 0 → 100644 +318 −0 Original line number Diff line number Diff line /* * 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 android.database; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.annotation.Nullable; import android.content.ContentResolver; import android.os.Bundle; import android.support.test.runner.AndroidJUnit4; import android.util.Log; import android.util.MathUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.util.Random; @RunWith(AndroidJUnit4.class) public class PageViewCursorTest { private static final int ITEM_COUNT = 20; private static final String NAME_COLUMN = "name"; private static final String NUM_COLUMN = "num"; private static final String[] COLUMNS = new String[]{ NAME_COLUMN, NUM_COLUMN }; private static final String[] NAMES = new String[] { "000", "111", "222", "333", "444", "555", "666", "777", "888", "999", "aaa", "bbb", "ccc", "ddd", "eee", "fff", "ggg", "hhh", "iii", "jjj" }; private MatrixCursor mDelegate; private PageViewCursor mCursor; @Before public void setUp() { Random rand = new Random(); mDelegate = new MatrixCursor(COLUMNS); for (int i = 0; i < ITEM_COUNT; i++) { MatrixCursor.RowBuilder row = mDelegate.newRow(); row.add(NAME_COLUMN, NAMES[i]); row.add(NUM_COLUMN, rand.nextInt()); } mCursor = new PageViewCursor(mDelegate, 10, 5); } @Test public void testPage_Size() { assertEquals(5, mCursor.getCount()); } @Test public void testPage_TotalSize() { assertEquals(ITEM_COUNT, mCursor.getExtras().getInt(ContentResolver.EXTRA_TOTAL_SIZE)); } @Test public void testPage_OffsetExceedsCursorCount_EffectivelyEmptyCursor() { mCursor = new PageViewCursor(mDelegate, ITEM_COUNT * 2, 5); assertEquals(0, mCursor.getCount()); } @Test public void testMoveToPosition() { assertTrue(mCursor.moveToPosition(0)); assertEquals(NAMES[10], mCursor.getString(0)); assertTrue(mCursor.moveToPosition(1)); assertEquals(NAMES[11], mCursor.getString(0)); assertTrue(mCursor.moveToPosition(4)); assertEquals(NAMES[14], mCursor.getString(0)); // and then back down again for good measure. assertTrue(mCursor.moveToPosition(1)); assertEquals(NAMES[11], mCursor.getString(0)); assertTrue(mCursor.moveToPosition(0)); assertEquals(NAMES[10], mCursor.getString(0)); } @Test public void testMoveToPosition_MoveToSamePosition_NoOp() { assertTrue(mCursor.moveToPosition(1)); assertEquals(NAMES[11], mCursor.getString(0)); assertTrue(mCursor.moveToPosition(1)); assertEquals(NAMES[11], mCursor.getString(0)); } @Test public void testMoveToPosition_PositionOutOfBounds_MovesToBeforeFirst() { assertTrue(mCursor.moveToPosition(0)); assertEquals(NAMES[10], mCursor.getString(0)); // move before assertFalse(mCursor.moveToPosition(-12)); assertTrue(mCursor.isBeforeFirst()); } @Test public void testMoveToPosition_PositionOutOfBounds_MovesToAfterLast() { assertTrue(mCursor.moveToPosition(0)); assertEquals(NAMES[10], mCursor.getString(0)); assertFalse(mCursor.moveToPosition(222)); assertTrue(mCursor.isAfterLast()); } @Test public void testPosition() { assertEquals(-1, mCursor.getPosition()); } @Test public void testIsBeforeFirst() { assertTrue(mCursor.isBeforeFirst()); mCursor.moveToFirst(); assertFalse(mCursor.isBeforeFirst()); } @Test public void testCount_ZeroForEmptyCursor() { mCursor = new PageViewCursor(mDelegate, 0, 0); assertEquals(0, mCursor.getCount()); } @Test public void testIsBeforeFirst_TrueForEmptyCursor() { mCursor = new PageViewCursor(mDelegate, 0, 0); assertTrue(mCursor.isBeforeFirst()); } @Test public void testIsAfterLast() { assertFalse(mCursor.isAfterLast()); mCursor.moveToLast(); mCursor.moveToNext(); assertTrue(mCursor.isAfterLast()); } @Test public void testIsAfterLast_TrueForEmptyCursor() { mCursor = new PageViewCursor(mDelegate, 0, 0); assertTrue(mCursor.isAfterLast()); } @Test public void testIsFirst() { assertFalse(mCursor.isFirst()); mCursor.moveToFirst(); assertTrue(mCursor.isFirst()); } @Test public void testIsLast() { assertFalse(mCursor.isLast()); mCursor.moveToLast(); assertTrue(mCursor.isLast()); } @Test public void testMove() { // note that initial position is -1, so moving // 2 will only put as at 1. mCursor.move(2); assertEquals(NAMES[11], mCursor.getString(0)); mCursor.move(-1); assertEquals(NAMES[10], mCursor.getString(0)); } @Test public void testMoveToFist() { mCursor.moveToPosition(3); mCursor.moveToFirst(); assertEquals(NAMES[10], mCursor.getString(0)); } @Test public void testMoveToLast() { mCursor.moveToLast(); assertEquals(NAMES[14], mCursor.getString(0)); } @Test public void testMoveToNext() { // default position is -1, so next is 0. mCursor.moveToNext(); assertEquals(NAMES[10], mCursor.getString(0)); } @Test public void testMoveToNext_AfterLastReturnsFalse() { mCursor.moveToLast(); assertFalse(mCursor.moveToNext()); } @Test public void testMoveToPrevious() { mCursor.moveToPosition(3); mCursor.moveToPrevious(); assertEquals(NAMES[12], mCursor.getString(0)); } @Test public void testMoveToPrevious_BeforeFirstReturnsFalse() { assertFalse(mCursor.moveToPrevious()); } @Test public void testWindow_ReadPastEnd() { assertFalse(mCursor.moveToPosition(10)); } @Test public void testOffset_LimitOutOfBounds() { mCursor = new PageViewCursor(mDelegate, 5, 100); assertEquals(15, mCursor.getCount()); } @Test public void testPagingMarker() { mCursor = new PageViewCursor(mDelegate, 5, 100); assertTrue(mCursor.getExtras().getBoolean(PageViewCursor.EXTRA_AUTO_PAGED)); } @Test public void testWrap() { Bundle queryArgs = new Bundle(); queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 5); queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 5); Cursor wrapped = PageViewCursor.wrap(mDelegate, queryArgs); assertTrue(wrapped instanceof PageViewCursor); assertEquals(5, wrapped.getCount()); } @Test public void testWrap_NoOpWithoutPagingArgs() { Cursor wrapped = PageViewCursor.wrap(mDelegate, Bundle.EMPTY); assertTrue(mDelegate == wrapped); } @Test public void testWrap_NoOpCursorsWithExistingPaging_ByTotalSize() { Bundle extras = new Bundle(); extras.putInt(ContentResolver.EXTRA_TOTAL_SIZE, 5); mDelegate.setExtras(extras); Bundle queryArgs = new Bundle(); queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 5); queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 5); Cursor wrapped = PageViewCursor.wrap(mDelegate, queryArgs); assertTrue(mDelegate == wrapped); } @Test public void testWrap_NoOpCursorsWithExistingPaging_ByHonoredArgs() { Bundle extras = new Bundle(); extras.putStringArray( ContentResolver.EXTRA_HONORED_ARGS, new String[] { ContentResolver.QUERY_ARG_OFFSET, ContentResolver.QUERY_ARG_LIMIT }); mDelegate.setExtras(extras); Bundle queryArgs = new Bundle(); queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 5); queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 5); Cursor wrapped = PageViewCursor.wrap(mDelegate, queryArgs); assertTrue(mDelegate == wrapped); } private void assertStringAt(int row, int column, String expected) { mCursor.moveToPosition(row); assertEquals(expected, mCursor.getString(column)); } } Loading
core/java/android/content/ContentProviderNative.java +2 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import android.database.Cursor; import android.database.CursorToBulkCursorAdaptor; import android.database.DatabaseUtils; import android.database.IContentObserver; import android.database.PageViewCursor; import android.net.Uri; import android.os.Binder; import android.os.Bundle; Loading Loading @@ -103,6 +104,7 @@ abstract public class ContentProviderNative extends Binder implements IContentPr if (cursor != null) { CursorToBulkCursorAdaptor adaptor = null; cursor = PageViewCursor.wrap(cursor, queryArgs); try { adaptor = new CursorToBulkCursorAdaptor(cursor, observer, getProviderName()); Loading
core/java/android/database/PageViewCursor.java 0 → 100644 +245 −0 Original line number Diff line number Diff line /* * 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 android.database; import static com.android.internal.util.Preconditions.checkArgument; import android.annotation.Nullable; import android.content.ContentResolver; import android.os.Bundle; import android.util.Log; import android.util.MathUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; /** * Cursor wrapper that provides visibility into a subset of a wrapped cursor. * * The window is specified by offset and limit. * * @hide */ public final class PageViewCursor extends CrossProcessCursorWrapper { /** * An extra added to results that are auto-paged using the wrapper. */ public static final String EXTRA_AUTO_PAGED = "android.content.extra.AUTO_PAGED"; private static final String TAG = "PageViewCursor"; private static final boolean DEBUG = false; private static final boolean VERBOSE = false; private final int mOffset; // aka first index private final int mCount; private final Bundle mExtras; private int mPos = -1; /** * @see PageViewCursor#wrap(Cursor, Bundle) */ @VisibleForTesting public PageViewCursor(Cursor cursor, int offset, int limit) { super(cursor); checkArgument(offset > -1); checkArgument(limit > -1); mOffset = offset; mExtras = new Bundle(); Bundle extras = cursor.getExtras(); if (extras != null) { mExtras.putAll(extras); } mExtras.putBoolean(EXTRA_AUTO_PAGED, true); // We need a mutable bundle so we can add QUERY_RESULT_SIZE. // Direct equality check is correct here. Bundle.EMPTY is a specific instance // of Bundle that is immutable by way of implementation. // mExtras = (extras == Bundle.EMPTY) ? new Bundle() : extras; // When we're wrapping another cursor, it should not already be "paged". checkArgument(!mExtras.containsKey(ContentResolver.EXTRA_TOTAL_SIZE)); int count = mCursor.getCount(); mExtras.putInt(ContentResolver.EXTRA_TOTAL_SIZE, count); mCount = MathUtils.constrain(count - offset, 0, limit); if (DEBUG) Log.d(TAG, "Wrapped cursor" + " offset: " + mOffset + ", limit: " + limit + ", delegate_size: " + count + ", paged_count: " + mCount); } @Override public Bundle getExtras() { return mExtras; } @Override public int getPosition() { return mPos; } @Override public boolean isBeforeFirst() { if (mCount == 0) { return true; } return mPos == -1; } @Override public boolean isAfterLast() { if (mCount == 0) { return true; } return mPos == mCount; } @Override public boolean isFirst() { return mPos == 0; } @Override public boolean isLast() { return mPos == mCount - 1; } @Override public boolean moveToFirst() { return moveToPosition(0); } @Override public boolean moveToLast() { return moveToPosition(mCount - 1); } @Override public boolean moveToNext() { return move(1); } @Override public boolean moveToPrevious() { return move(-1); } @Override public boolean move(int offset) { return moveToPosition(mPos + offset); } @Override public boolean moveToPosition(int position) { if (position >= mCount) { if (VERBOSE) Log.v(TAG, "Invalid Positon: " + position + " >= count: " + mCount + ". Moving to last record."); mPos = mCount; super.moveToPosition(mOffset + mPos); // move into "after last" state. return false; } // Make sure position isn't before the beginning of the cursor if (position < 0) { if (VERBOSE) Log.v(TAG, "Ignoring invalid move to position: " + position); mPos = -1; super.moveToPosition(mPos); return false; } if (position == mPos) { if (VERBOSE) Log.v(TAG, "Ignoring no-op move to position: " + position); return true; } int delegatePosition = position + mOffset; if (VERBOSE) Log.v(TAG, "Moving delegate cursor to position: " + delegatePosition); if (super.moveToPosition(delegatePosition)) { mPos = position; return true; } else { mPos = -1; super.moveToPosition(-1); return false; } } @Override public boolean onMove(int oldPosition, int newPosition) { throw new UnsupportedOperationException("Not supported."); } @Override public int getCount() { return mCount; } /** * Wraps the cursor such that it will honor paging args (if present), AND if the cursor * does not report paging size. * * <p>No-op if cursor already contains paging or is less than specified page size. */ public static Cursor wrap(Cursor cursor, @Nullable Bundle queryArgs) { boolean hasPagingArgs = queryArgs != null && (queryArgs.containsKey(ContentResolver.QUERY_ARG_OFFSET) || queryArgs.containsKey(ContentResolver.QUERY_ARG_LIMIT)); if (!hasPagingArgs) { if (VERBOSE) Log.d(TAG, "No-wrap: No paging args in request."); return cursor; } if (hasPagedResponseDetails(cursor.getExtras())) { if (VERBOSE) Log.d(TAG, "No-wrap. Cursor has paging details."); return cursor; } return new PageViewCursor( cursor, queryArgs.getInt(ContentResolver.QUERY_ARG_OFFSET, 0), queryArgs.getInt(ContentResolver.QUERY_ARG_LIMIT, Integer.MAX_VALUE)); } /** * @return true if the extras contains information indicating the associated * cursor is paged. */ private static boolean hasPagedResponseDetails(@Nullable Bundle extras) { if (extras != null && extras.containsKey(ContentResolver.EXTRA_TOTAL_SIZE)) { return true; } String[] honoredArgs = extras.getStringArray(ContentResolver.EXTRA_HONORED_ARGS); if (honoredArgs != null && ( ArrayUtils.contains(honoredArgs, ContentResolver.QUERY_ARG_OFFSET) || ArrayUtils.contains(honoredArgs, ContentResolver.QUERY_ARG_LIMIT))) { return true; } return false; } }
core/tests/coretests/src/android/database/PageViewCursorTest.java 0 → 100644 +318 −0 Original line number Diff line number Diff line /* * 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 android.database; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.annotation.Nullable; import android.content.ContentResolver; import android.os.Bundle; import android.support.test.runner.AndroidJUnit4; import android.util.Log; import android.util.MathUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.util.Random; @RunWith(AndroidJUnit4.class) public class PageViewCursorTest { private static final int ITEM_COUNT = 20; private static final String NAME_COLUMN = "name"; private static final String NUM_COLUMN = "num"; private static final String[] COLUMNS = new String[]{ NAME_COLUMN, NUM_COLUMN }; private static final String[] NAMES = new String[] { "000", "111", "222", "333", "444", "555", "666", "777", "888", "999", "aaa", "bbb", "ccc", "ddd", "eee", "fff", "ggg", "hhh", "iii", "jjj" }; private MatrixCursor mDelegate; private PageViewCursor mCursor; @Before public void setUp() { Random rand = new Random(); mDelegate = new MatrixCursor(COLUMNS); for (int i = 0; i < ITEM_COUNT; i++) { MatrixCursor.RowBuilder row = mDelegate.newRow(); row.add(NAME_COLUMN, NAMES[i]); row.add(NUM_COLUMN, rand.nextInt()); } mCursor = new PageViewCursor(mDelegate, 10, 5); } @Test public void testPage_Size() { assertEquals(5, mCursor.getCount()); } @Test public void testPage_TotalSize() { assertEquals(ITEM_COUNT, mCursor.getExtras().getInt(ContentResolver.EXTRA_TOTAL_SIZE)); } @Test public void testPage_OffsetExceedsCursorCount_EffectivelyEmptyCursor() { mCursor = new PageViewCursor(mDelegate, ITEM_COUNT * 2, 5); assertEquals(0, mCursor.getCount()); } @Test public void testMoveToPosition() { assertTrue(mCursor.moveToPosition(0)); assertEquals(NAMES[10], mCursor.getString(0)); assertTrue(mCursor.moveToPosition(1)); assertEquals(NAMES[11], mCursor.getString(0)); assertTrue(mCursor.moveToPosition(4)); assertEquals(NAMES[14], mCursor.getString(0)); // and then back down again for good measure. assertTrue(mCursor.moveToPosition(1)); assertEquals(NAMES[11], mCursor.getString(0)); assertTrue(mCursor.moveToPosition(0)); assertEquals(NAMES[10], mCursor.getString(0)); } @Test public void testMoveToPosition_MoveToSamePosition_NoOp() { assertTrue(mCursor.moveToPosition(1)); assertEquals(NAMES[11], mCursor.getString(0)); assertTrue(mCursor.moveToPosition(1)); assertEquals(NAMES[11], mCursor.getString(0)); } @Test public void testMoveToPosition_PositionOutOfBounds_MovesToBeforeFirst() { assertTrue(mCursor.moveToPosition(0)); assertEquals(NAMES[10], mCursor.getString(0)); // move before assertFalse(mCursor.moveToPosition(-12)); assertTrue(mCursor.isBeforeFirst()); } @Test public void testMoveToPosition_PositionOutOfBounds_MovesToAfterLast() { assertTrue(mCursor.moveToPosition(0)); assertEquals(NAMES[10], mCursor.getString(0)); assertFalse(mCursor.moveToPosition(222)); assertTrue(mCursor.isAfterLast()); } @Test public void testPosition() { assertEquals(-1, mCursor.getPosition()); } @Test public void testIsBeforeFirst() { assertTrue(mCursor.isBeforeFirst()); mCursor.moveToFirst(); assertFalse(mCursor.isBeforeFirst()); } @Test public void testCount_ZeroForEmptyCursor() { mCursor = new PageViewCursor(mDelegate, 0, 0); assertEquals(0, mCursor.getCount()); } @Test public void testIsBeforeFirst_TrueForEmptyCursor() { mCursor = new PageViewCursor(mDelegate, 0, 0); assertTrue(mCursor.isBeforeFirst()); } @Test public void testIsAfterLast() { assertFalse(mCursor.isAfterLast()); mCursor.moveToLast(); mCursor.moveToNext(); assertTrue(mCursor.isAfterLast()); } @Test public void testIsAfterLast_TrueForEmptyCursor() { mCursor = new PageViewCursor(mDelegate, 0, 0); assertTrue(mCursor.isAfterLast()); } @Test public void testIsFirst() { assertFalse(mCursor.isFirst()); mCursor.moveToFirst(); assertTrue(mCursor.isFirst()); } @Test public void testIsLast() { assertFalse(mCursor.isLast()); mCursor.moveToLast(); assertTrue(mCursor.isLast()); } @Test public void testMove() { // note that initial position is -1, so moving // 2 will only put as at 1. mCursor.move(2); assertEquals(NAMES[11], mCursor.getString(0)); mCursor.move(-1); assertEquals(NAMES[10], mCursor.getString(0)); } @Test public void testMoveToFist() { mCursor.moveToPosition(3); mCursor.moveToFirst(); assertEquals(NAMES[10], mCursor.getString(0)); } @Test public void testMoveToLast() { mCursor.moveToLast(); assertEquals(NAMES[14], mCursor.getString(0)); } @Test public void testMoveToNext() { // default position is -1, so next is 0. mCursor.moveToNext(); assertEquals(NAMES[10], mCursor.getString(0)); } @Test public void testMoveToNext_AfterLastReturnsFalse() { mCursor.moveToLast(); assertFalse(mCursor.moveToNext()); } @Test public void testMoveToPrevious() { mCursor.moveToPosition(3); mCursor.moveToPrevious(); assertEquals(NAMES[12], mCursor.getString(0)); } @Test public void testMoveToPrevious_BeforeFirstReturnsFalse() { assertFalse(mCursor.moveToPrevious()); } @Test public void testWindow_ReadPastEnd() { assertFalse(mCursor.moveToPosition(10)); } @Test public void testOffset_LimitOutOfBounds() { mCursor = new PageViewCursor(mDelegate, 5, 100); assertEquals(15, mCursor.getCount()); } @Test public void testPagingMarker() { mCursor = new PageViewCursor(mDelegate, 5, 100); assertTrue(mCursor.getExtras().getBoolean(PageViewCursor.EXTRA_AUTO_PAGED)); } @Test public void testWrap() { Bundle queryArgs = new Bundle(); queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 5); queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 5); Cursor wrapped = PageViewCursor.wrap(mDelegate, queryArgs); assertTrue(wrapped instanceof PageViewCursor); assertEquals(5, wrapped.getCount()); } @Test public void testWrap_NoOpWithoutPagingArgs() { Cursor wrapped = PageViewCursor.wrap(mDelegate, Bundle.EMPTY); assertTrue(mDelegate == wrapped); } @Test public void testWrap_NoOpCursorsWithExistingPaging_ByTotalSize() { Bundle extras = new Bundle(); extras.putInt(ContentResolver.EXTRA_TOTAL_SIZE, 5); mDelegate.setExtras(extras); Bundle queryArgs = new Bundle(); queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 5); queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 5); Cursor wrapped = PageViewCursor.wrap(mDelegate, queryArgs); assertTrue(mDelegate == wrapped); } @Test public void testWrap_NoOpCursorsWithExistingPaging_ByHonoredArgs() { Bundle extras = new Bundle(); extras.putStringArray( ContentResolver.EXTRA_HONORED_ARGS, new String[] { ContentResolver.QUERY_ARG_OFFSET, ContentResolver.QUERY_ARG_LIMIT }); mDelegate.setExtras(extras); Bundle queryArgs = new Bundle(); queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 5); queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 5); Cursor wrapped = PageViewCursor.wrap(mDelegate, queryArgs); assertTrue(mDelegate == wrapped); } private void assertStringAt(int row, int column, String expected) { mCursor.moveToPosition(row); assertEquals(expected, mCursor.getString(column)); } }