summaryrefslogtreecommitdiff
path: root/core/java/android
diff options
context:
space:
mode:
authorSteve McKay <smckay@google.com>2017-03-02 11:24:20 -0800
committerSteve McKay <smckay@google.com>2017-03-15 15:29:52 -0700
commite5f868fd31baedc49bb74f1bc1e28b995cc2cd71 (patch)
tree6f2dcc1c80d3b2ae62b85376a47b0bbf1fdcb3ef /core/java/android
parentb20f320954f32bd71b2543a2d033a5a4512d1b67 (diff)
Adapt unpaged cursors to paged requests.
Allow all client targeting Android O to assume paging support for any provider. Adds a new PageViewCursor that adapts an unpaged cursor to a paged request. Updates ContentProviderNative to perform wrapping on unpaged results. Bug: 30927484 Change-Id: I4e225dc16761793c85ef8a195bf049113c79cd20 Test: Added for new class. Run info @ frameworks/base/core/tests/coretests/README
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/content/ContentProviderNative.java2
-rw-r--r--core/java/android/database/PageViewCursor.java245
2 files changed, 247 insertions, 0 deletions
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index d428a3a857b7..2f87633a39d3 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -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;
@@ -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());
diff --git a/core/java/android/database/PageViewCursor.java b/core/java/android/database/PageViewCursor.java
new file mode 100644
index 000000000000..fbd039d91742
--- /dev/null
+++ b/core/java/android/database/PageViewCursor.java
@@ -0,0 +1,245 @@
+/*
+ * 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;
+ }
+}