Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d39da943 authored by Steve McKay's avatar Steve McKay
Browse files

Add honored args when auto-paging.

Increase test coverage.

Test: Improved!
Change-Id: Icba97c5e821e6cb60157e0c8a5b204d0d2813bc8
parent 98e4ae9f
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -11749,7 +11749,7 @@ package android.database {
  }
  public final class PageViewCursor extends android.database.CursorWrapper implements android.database.CrossProcessCursor {
    ctor public PageViewCursor(android.database.Cursor, int, int);
    ctor public PageViewCursor(android.database.Cursor, android.os.Bundle);
    method public void fillWindow(int, android.database.CursorWindow);
    method public android.database.CursorWindow getWindow();
    method public boolean onMove(int, int);
+65 −37
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
 */
package android.database;

import static com.android.internal.util.ArrayUtils.contains;
import static com.android.internal.util.Preconditions.checkArgument;

import android.annotation.Nullable;
@@ -25,7 +26,7 @@ import android.os.Bundle;
import android.util.Log;
import android.util.MathUtils;

import com.android.internal.util.ArrayUtils;
import java.util.Arrays;

/**
 * Cursor wrapper that provides visibility into a subset of a wrapped cursor.
@@ -40,6 +41,7 @@ public final class PageViewCursor extends CursorWrapper implements CrossProcessC
    /** 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[] EMPTY_ARGS = new String[0];
    private static final String TAG = "PageViewCursor";
    private static final boolean DEBUG = Build.IS_DEBUGGABLE;
    private static final boolean VERBOSE = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.VERBOSE);
@@ -55,12 +57,17 @@ public final class PageViewCursor extends CursorWrapper implements CrossProcessC
    /**
     * @see PageViewCursor#wrap(Cursor, Bundle)
     */
    public PageViewCursor(Cursor cursor, int offset, int limit) {
    public PageViewCursor(Cursor cursor, Bundle queryArgs) {
        super(cursor);

        int offset = queryArgs.getInt(ContentResolver.QUERY_ARG_OFFSET, 0);
        int limit = queryArgs.getInt(ContentResolver.QUERY_ARG_LIMIT, Integer.MAX_VALUE);

        checkArgument(offset > -1);
        checkArgument(limit > -1);

        int count = mCursor.getCount();

        mOffset = offset;

        mExtras = new Bundle();
@@ -68,19 +75,40 @@ public final class PageViewCursor extends CursorWrapper implements CrossProcessC
        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));
        checkArgument(!hasPagedResponseDetails(mExtras));

        int count = mCursor.getCount();
        mExtras.putBoolean(EXTRA_AUTO_PAGED, true);
        mExtras.putInt(ContentResolver.EXTRA_TOTAL_SIZE, count);

        // Ensure we retain any extra args supplied in cursor extras, and add
        // offset and/or limit.
        String[] existingArgs = mExtras.getStringArray(ContentResolver.EXTRA_HONORED_ARGS);
        existingArgs = existingArgs != null ? existingArgs : EMPTY_ARGS;

        int size = existingArgs.length;

        // copy the array with space for the extra query args we'll be adding.
        String[] newArgs = Arrays.copyOf(existingArgs, size + 2);

        if (queryArgs.containsKey(ContentResolver.QUERY_ARG_OFFSET)) {
            newArgs[size++] = ContentResolver.QUERY_ARG_OFFSET;
        }
        if (queryArgs.containsKey(ContentResolver.QUERY_ARG_LIMIT)) {
            newArgs[size++] = ContentResolver.QUERY_ARG_LIMIT;
        }

        assert(size > existingArgs.length);  // must add at least one arg.

        // At this point there may be a null element at the end of
        // the array because our pre-sizing didn't match the actualy
        // number of args we added. So we trim.
        if (size == newArgs.length - 1) {
            newArgs = Arrays.copyOf(newArgs, size);
        }
        mExtras.putStringArray(ContentResolver.EXTRA_HONORED_ARGS, newArgs);

        mCount = MathUtils.constrain(count - offset, 0, limit);

        if (DEBUG) Log.d(TAG, "Wrapped cursor"
@@ -224,15 +252,14 @@ public final class PageViewCursor extends CursorWrapper implements CrossProcessC
    }

    /**
     * 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.
     * 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
        boolean hasPagingArgs = queryArgs != null
                && (queryArgs.containsKey(ContentResolver.QUERY_ARG_OFFSET)
                        || queryArgs.containsKey(ContentResolver.QUERY_ARG_LIMIT));

@@ -253,25 +280,26 @@ public final class PageViewCursor extends CursorWrapper implements CrossProcessC
            return cursor;
        }

        return new PageViewCursor(
                cursor,
                queryArgs.getInt(ContentResolver.QUERY_ARG_OFFSET, 0),
                queryArgs.getInt(ContentResolver.QUERY_ARG_LIMIT, Integer.MAX_VALUE));
        return new PageViewCursor(cursor, queryArgs);
    }

    /**
     * @return true if the extras contains information indicating the associated
     * cursor is paged.
     * @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)) {
        if (extras == null) {
            return false;
        }

        if (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))) {
        if (honoredArgs != null
                && (contains(honoredArgs, ContentResolver.QUERY_ARG_OFFSET)
                        || contains(honoredArgs, ContentResolver.QUERY_ARG_LIMIT))) {
            return true;
        }

+119 −51
Original line number Diff line number Diff line
@@ -13,20 +13,28 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.database;

import static com.android.internal.util.ArrayUtils.contains;
import static com.android.internal.util.Preconditions.checkArgument;
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.Build;
import android.os.Bundle;
import android.support.test.runner.AndroidJUnit4;
import android.util.Log;
import android.util.MathUtils;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.Arrays;
import java.util.Random;

@RunWith(AndroidJUnit4.class)
@@ -79,7 +87,7 @@ public class PageViewCursorTest {
            row.add(NUM_COLUMN, rand.nextInt());
        }

        mCursor = new PageViewCursor(mDelegate, 10, 5);
        mCursor = new PageViewCursor(mDelegate, createArgs(10, 5));
    }

    @Test
@@ -94,7 +102,7 @@ public class PageViewCursorTest {

    @Test
    public void testPage_OffsetExceedsCursorCount_EffectivelyEmptyCursor() {
        mCursor = new PageViewCursor(mDelegate, ITEM_COUNT * 2, 5);
        mCursor = new PageViewCursor(mDelegate, createArgs(ITEM_COUNT * 2, 5));
        assertEquals(0, mCursor.getCount());
    }

@@ -155,13 +163,13 @@ public class PageViewCursorTest {

    @Test
    public void testCount_ZeroForEmptyCursor() {
        mCursor = new PageViewCursor(mDelegate, 0, 0);
        mCursor = new PageViewCursor(mDelegate, createArgs(0, 0));
        assertEquals(0, mCursor.getCount());
    }

    @Test
    public void testIsBeforeFirst_TrueForEmptyCursor() {
        mCursor = new PageViewCursor(mDelegate, 0, 0);
        mCursor = new PageViewCursor(mDelegate, createArgs(0, 0));
        assertTrue(mCursor.isBeforeFirst());
    }

@@ -175,7 +183,7 @@ public class PageViewCursorTest {

    @Test
    public void testIsAfterLast_TrueForEmptyCursor() {
        mCursor = new PageViewCursor(mDelegate, 0, 0);
        mCursor = new PageViewCursor(mDelegate, createArgs(0, 0));
        assertTrue(mCursor.isAfterLast());
    }

@@ -247,55 +255,110 @@ public class PageViewCursorTest {
    }

    @Test
    public void testOffset_LimitOutOfBounds() {
        mCursor = new PageViewCursor(mDelegate, 5, 100);
    public void testLimitOutOfBounds() {
        mCursor = new PageViewCursor(mDelegate, createArgs(5, 100));
        assertEquals(15, mCursor.getCount());
    }

    @Test
    public void testAutoPagedExtra() {
        mCursor = new PageViewCursor(mDelegate, 5, 100);
    public void testOffsetOutOfBounds_EmptyResult() {
        mCursor = new PageViewCursor(mDelegate, createArgs(100000, 100));
        assertEquals(0, mCursor.getCount());
    }

    @Test
    public void testAddsExtras() {
        mCursor = new PageViewCursor(mDelegate, createArgs(5, 100));
        assertTrue(mCursor.getExtras().getBoolean(PageViewCursor.EXTRA_AUTO_PAGED));
        String[] honoredArgs = mCursor.getExtras()
                .getStringArray(ContentResolver.EXTRA_HONORED_ARGS);
        assertTrue(contains(honoredArgs, ContentResolver.QUERY_ARG_OFFSET));
        assertTrue(contains(honoredArgs, ContentResolver.QUERY_ARG_LIMIT));
    }

    @Test
    public void testAddsExtras_OnlyOffset() {
        Bundle args = new Bundle();
        args.putInt(ContentResolver.QUERY_ARG_OFFSET, 5);
        mCursor = new PageViewCursor(mDelegate, args);
        String[] honoredArgs = mCursor.getExtras()
                .getStringArray(ContentResolver.EXTRA_HONORED_ARGS);
        assertTrue(contains(honoredArgs, ContentResolver.QUERY_ARG_OFFSET));
        assertFalse(contains(honoredArgs, ContentResolver.QUERY_ARG_LIMIT));
    }

    @Test
    public void testAddsExtras_OnlyLimit() {
        Bundle args = new Bundle();
        args.putInt(ContentResolver.QUERY_ARG_LIMIT, 5);
        mCursor = new PageViewCursor(mDelegate, args);
        String[] honoredArgs = mCursor.getExtras()
                .getStringArray(ContentResolver.EXTRA_HONORED_ARGS);
        assertFalse(contains(honoredArgs, ContentResolver.QUERY_ARG_OFFSET));
        assertTrue(contains(honoredArgs, ContentResolver.QUERY_ARG_LIMIT));
    }

    @Test
    public void testGetWindow() {
        mCursor = new PageViewCursor(mDelegate, 5, 5);
        mCursor = new PageViewCursor(mDelegate, createArgs(5, 5));
        CursorWindow window = mCursor.getWindow();
        assertEquals(5, window.getNumRows());
    }

    @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);
    public void testWraps() {
        Bundle args = createArgs(5, 5);
        Cursor wrapped = PageViewCursor.wrap(mDelegate, args);
        assertTrue(wrapped instanceof PageViewCursor);
        assertEquals(5, wrapped.getCount());
    }

    @Test
    public void testWrap_NoOpWithoutPagingArgs() {
    public void testWraps_NullExtras() {
        Bundle args = createArgs(5, 5);
        mDelegate.setExtras(null);
        Cursor wrapped = PageViewCursor.wrap(mDelegate, args);
        assertTrue(wrapped instanceof PageViewCursor);
        assertEquals(5, wrapped.getCount());
    }

    @Test
    public void testWraps_WithJustOffset() {
        Bundle args = new Bundle();
        args.putInt(ContentResolver.QUERY_ARG_OFFSET, 5);
        Cursor wrapped = PageViewCursor.wrap(mDelegate, args);
        assertTrue(wrapped instanceof PageViewCursor);
        assertEquals(15, wrapped.getCount());
    }

    @Test
    public void testWraps_WithJustLimit() {
        Bundle args = new Bundle();
        args.putInt(ContentResolver.QUERY_ARG_LIMIT, 5);
        Cursor wrapped = PageViewCursor.wrap(mDelegate, args);
        assertTrue(wrapped instanceof PageViewCursor);
        assertEquals(5, wrapped.getCount());
    }

    @Test
    public void testNoWrap_WithoutPagingArgs() {
        Cursor wrapped = PageViewCursor.wrap(mDelegate, Bundle.EMPTY);
        assertTrue(mDelegate == wrapped);
    }

    @Test
    public void testWrap_NoOpCursorsWithExistingPaging_ByTotalSize() {
    public void testNoWrap_CursorsHasExistingPaging_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);
        Bundle args = createArgs(5, 5);
        Cursor wrapped = PageViewCursor.wrap(mDelegate, args);
        assertTrue(mDelegate == wrapped);
    }

    @Test
    public void testWrap_NoOpCursorsWithExistingPaging_ByHonoredArgs() {
    public void testNoWrap_CursorsHasExistingPaging_ByHonoredArgs() {
        Bundle extras = new Bundle();
        extras.putStringArray(
                ContentResolver.EXTRA_HONORED_ARGS,
@@ -305,13 +368,18 @@ public class PageViewCursorTest {
                });
        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);
        Bundle args = createArgs(5, 5);
        Cursor wrapped = PageViewCursor.wrap(mDelegate, args);
        assertTrue(mDelegate == wrapped);
    }

    private static Bundle createArgs(int offset, int limit) {
        Bundle args = new Bundle();
        args.putInt(ContentResolver.QUERY_ARG_OFFSET, offset);
        args.putInt(ContentResolver.QUERY_ARG_LIMIT, limit);
        return args;
    }

    private void assertStringAt(int row, int column, String expected) {
        mCursor.moveToPosition(row);
        assertEquals(expected, mCursor.getString(column));