enhance ContentProvider with the ability to perform batch operations
This commit is contained in:
327
api/current.xml
327
api/current.xml
@@ -25060,6 +25060,21 @@
|
||||
visibility="public"
|
||||
>
|
||||
</constructor>
|
||||
<method name="applyBatch"
|
||||
return="android.content.ContentProviderResult[]"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="operations" type="android.content.ContentProviderOperation[]">
|
||||
</parameter>
|
||||
<exception name="OperationApplicationException" type="android.content.OperationApplicationException">
|
||||
</exception>
|
||||
</method>
|
||||
<method name="attachInfo"
|
||||
return="void"
|
||||
abstract="false"
|
||||
@@ -25681,6 +25696,269 @@
|
||||
</exception>
|
||||
</method>
|
||||
</class>
|
||||
<class name="ContentProviderOperation"
|
||||
extends="java.lang.Object"
|
||||
abstract="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<method name="apply"
|
||||
return="android.content.ContentProviderResult"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="provider" type="android.content.ContentProvider">
|
||||
</parameter>
|
||||
<parameter name="backRefs" type="android.content.ContentProviderResult[]">
|
||||
</parameter>
|
||||
<parameter name="numBackRefs" type="int">
|
||||
</parameter>
|
||||
<exception name="OperationApplicationException" type="android.content.OperationApplicationException">
|
||||
</exception>
|
||||
</method>
|
||||
<method name="backRefToValue"
|
||||
return="java.lang.String"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="true"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="backRefs" type="android.content.ContentProviderResult[]">
|
||||
</parameter>
|
||||
<parameter name="numBackRefs" type="int">
|
||||
</parameter>
|
||||
<parameter name="backRefIndex" type="java.lang.Integer">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="newCountQuery"
|
||||
return="android.content.ContentProviderOperation.Builder"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="true"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="uri" type="android.net.Uri">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="newDelete"
|
||||
return="android.content.ContentProviderOperation.Builder"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="true"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="uri" type="android.net.Uri">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="newInsert"
|
||||
return="android.content.ContentProviderOperation.Builder"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="true"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="uri" type="android.net.Uri">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="newUpdate"
|
||||
return="android.content.ContentProviderOperation.Builder"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="true"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="uri" type="android.net.Uri">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="resolveSelectionArgsBackReferences"
|
||||
return="java.lang.String[]"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="backRef" type="android.content.ContentProviderResult[]">
|
||||
</parameter>
|
||||
<parameter name="numBackRefs" type="int">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="resolveValueBackReferences"
|
||||
return="android.content.ContentValues"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="backRefs" type="android.content.ContentProviderResult[]">
|
||||
</parameter>
|
||||
<parameter name="numBackRefs" type="int">
|
||||
</parameter>
|
||||
</method>
|
||||
</class>
|
||||
<class name="ContentProviderOperation.Builder"
|
||||
extends="java.lang.Object"
|
||||
abstract="false"
|
||||
static="true"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<method name="build"
|
||||
return="android.content.ContentProviderOperation"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</method>
|
||||
<method name="withExpectedCount"
|
||||
return="android.content.ContentProviderOperation.Builder"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="count" type="int">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="withSelection"
|
||||
return="android.content.ContentProviderOperation.Builder"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="selection" type="java.lang.String">
|
||||
</parameter>
|
||||
<parameter name="selectionArgs" type="java.lang.String[]">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="withSelectionBackReferences"
|
||||
return="android.content.ContentProviderOperation.Builder"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="backReferences" type="java.util.Map<java.lang.Integer, java.lang.Integer>">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="withValueBackReferences"
|
||||
return="android.content.ContentProviderOperation.Builder"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="backReferences" type="android.content.ContentValues">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="withValues"
|
||||
return="android.content.ContentProviderOperation.Builder"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="values" type="android.content.ContentValues">
|
||||
</parameter>
|
||||
</method>
|
||||
</class>
|
||||
<class name="ContentProviderResult"
|
||||
extends="java.lang.Object"
|
||||
abstract="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<constructor name="ContentProviderResult"
|
||||
type="android.content.ContentProviderResult"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="uri" type="android.net.Uri">
|
||||
</parameter>
|
||||
</constructor>
|
||||
<constructor name="ContentProviderResult"
|
||||
type="android.content.ContentProviderResult"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="count" type="int">
|
||||
</parameter>
|
||||
</constructor>
|
||||
<field name="count"
|
||||
type="java.lang.Integer"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
static="false"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="uri"
|
||||
type="android.net.Uri"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
static="false"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
</class>
|
||||
<class name="ContentQueryMap"
|
||||
extends="java.util.Observable"
|
||||
abstract="false"
|
||||
@@ -33581,6 +33859,55 @@
|
||||
</parameter>
|
||||
</method>
|
||||
</class>
|
||||
<class name="OperationApplicationException"
|
||||
extends="java.lang.Exception"
|
||||
abstract="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<constructor name="OperationApplicationException"
|
||||
type="android.content.OperationApplicationException"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</constructor>
|
||||
<constructor name="OperationApplicationException"
|
||||
type="android.content.OperationApplicationException"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="message" type="java.lang.String">
|
||||
</parameter>
|
||||
</constructor>
|
||||
<constructor name="OperationApplicationException"
|
||||
type="android.content.OperationApplicationException"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="message" type="java.lang.String">
|
||||
</parameter>
|
||||
<parameter name="cause" type="java.lang.Throwable">
|
||||
</parameter>
|
||||
</constructor>
|
||||
<constructor name="OperationApplicationException"
|
||||
type="android.content.OperationApplicationException"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="cause" type="java.lang.Throwable">
|
||||
</parameter>
|
||||
</constructor>
|
||||
</class>
|
||||
<class name="ReceiverCallNotAllowedException"
|
||||
extends="android.util.AndroidRuntimeException"
|
||||
abstract="false"
|
||||
|
||||
@@ -17,6 +17,8 @@ import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Vector;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
|
||||
import com.google.android.collect.Maps;
|
||||
|
||||
@@ -55,6 +57,9 @@ public abstract class AbstractSyncableContentProvider extends SyncableContentPro
|
||||
return mIsTemporary;
|
||||
}
|
||||
|
||||
private final ThreadLocal<Boolean> mApplyingBatch = new ThreadLocal<Boolean>();
|
||||
private final ThreadLocal<Set<Uri>> mPendingBatchNotifications = new ThreadLocal<Set<Uri>>();
|
||||
|
||||
/**
|
||||
* Indicates whether or not this ContentProvider contains a full
|
||||
* set of data or just diffs. This knowledge comes in handy when
|
||||
@@ -243,26 +248,37 @@ public abstract class AbstractSyncableContentProvider extends SyncableContentPro
|
||||
public final int update(final Uri url, final ContentValues values,
|
||||
final String selection, final String[] selectionArgs) {
|
||||
mDb = mOpenHelper.getWritableDatabase();
|
||||
mDb.beginTransaction();
|
||||
final boolean notApplyingBatch = !applyingBatch();
|
||||
if (notApplyingBatch) {
|
||||
mDb.beginTransaction();
|
||||
}
|
||||
try {
|
||||
if (isTemporary() && mSyncState.matches(url)) {
|
||||
int numRows = mSyncState.asContentProvider().update(
|
||||
url, values, selection, selectionArgs);
|
||||
mDb.setTransactionSuccessful();
|
||||
if (notApplyingBatch) {
|
||||
mDb.setTransactionSuccessful();
|
||||
}
|
||||
return numRows;
|
||||
}
|
||||
|
||||
int result = updateInternal(url, values, selection, selectionArgs);
|
||||
mDb.setTransactionSuccessful();
|
||||
|
||||
if (!isTemporary() && result > 0) {
|
||||
getContext().getContentResolver().notifyChange(url, null /* observer */,
|
||||
changeRequiresLocalSync(url));
|
||||
if (notApplyingBatch) {
|
||||
mDb.setTransactionSuccessful();
|
||||
}
|
||||
if (!isTemporary() && result > 0) {
|
||||
if (notApplyingBatch) {
|
||||
getContext().getContentResolver().notifyChange(url, null /* observer */,
|
||||
changeRequiresLocalSync(url));
|
||||
} else {
|
||||
mPendingBatchNotifications.get().add(url);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
} finally {
|
||||
mDb.endTransaction();
|
||||
if (notApplyingBatch) {
|
||||
mDb.endTransaction();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,44 +286,74 @@ public abstract class AbstractSyncableContentProvider extends SyncableContentPro
|
||||
public final int delete(final Uri url, final String selection,
|
||||
final String[] selectionArgs) {
|
||||
mDb = mOpenHelper.getWritableDatabase();
|
||||
mDb.beginTransaction();
|
||||
final boolean notApplyingBatch = !applyingBatch();
|
||||
if (notApplyingBatch) {
|
||||
mDb.beginTransaction();
|
||||
}
|
||||
try {
|
||||
if (isTemporary() && mSyncState.matches(url)) {
|
||||
int numRows = mSyncState.asContentProvider().delete(url, selection, selectionArgs);
|
||||
mDb.setTransactionSuccessful();
|
||||
if (notApplyingBatch) {
|
||||
mDb.setTransactionSuccessful();
|
||||
}
|
||||
return numRows;
|
||||
}
|
||||
int result = deleteInternal(url, selection, selectionArgs);
|
||||
mDb.setTransactionSuccessful();
|
||||
if (notApplyingBatch) {
|
||||
mDb.setTransactionSuccessful();
|
||||
}
|
||||
if (!isTemporary() && result > 0) {
|
||||
getContext().getContentResolver().notifyChange(url, null /* observer */,
|
||||
changeRequiresLocalSync(url));
|
||||
if (notApplyingBatch) {
|
||||
getContext().getContentResolver().notifyChange(url, null /* observer */,
|
||||
changeRequiresLocalSync(url));
|
||||
} else {
|
||||
mPendingBatchNotifications.get().add(url);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
mDb.endTransaction();
|
||||
if (notApplyingBatch) {
|
||||
mDb.endTransaction();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean applyingBatch() {
|
||||
return mApplyingBatch.get() != null && mApplyingBatch.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Uri insert(final Uri url, final ContentValues values) {
|
||||
mDb = mOpenHelper.getWritableDatabase();
|
||||
mDb.beginTransaction();
|
||||
final boolean notApplyingBatch = !applyingBatch();
|
||||
if (notApplyingBatch) {
|
||||
mDb.beginTransaction();
|
||||
}
|
||||
try {
|
||||
if (isTemporary() && mSyncState.matches(url)) {
|
||||
Uri result = mSyncState.asContentProvider().insert(url, values);
|
||||
mDb.setTransactionSuccessful();
|
||||
if (notApplyingBatch) {
|
||||
mDb.setTransactionSuccessful();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Uri result = insertInternal(url, values);
|
||||
mDb.setTransactionSuccessful();
|
||||
if (notApplyingBatch) {
|
||||
mDb.setTransactionSuccessful();
|
||||
}
|
||||
if (!isTemporary() && result != null) {
|
||||
getContext().getContentResolver().notifyChange(url, null /* observer */,
|
||||
changeRequiresLocalSync(url));
|
||||
if (notApplyingBatch) {
|
||||
getContext().getContentResolver().notifyChange(url, null /* observer */,
|
||||
changeRequiresLocalSync(url));
|
||||
} else {
|
||||
mPendingBatchNotifications.get().add(url);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
mDb.endTransaction();
|
||||
if (notApplyingBatch) {
|
||||
mDb.endTransaction();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,6 +388,34 @@ public abstract class AbstractSyncableContentProvider extends SyncableContentPro
|
||||
return completed;
|
||||
}
|
||||
|
||||
public ContentProviderResult[] applyBatch(ContentProviderOperation[] operations)
|
||||
throws OperationApplicationException {
|
||||
// initialize if this is the first time this thread has applied a batch
|
||||
if (mApplyingBatch.get() == null) {
|
||||
mApplyingBatch.set(false);
|
||||
mPendingBatchNotifications.set(new HashSet<Uri>());
|
||||
}
|
||||
|
||||
if (applyingBatch()) {
|
||||
throw new IllegalStateException(
|
||||
"applyBatch is not reentrant but mApplyingBatch is already set");
|
||||
}
|
||||
getDatabase().beginTransaction();
|
||||
try {
|
||||
mApplyingBatch.set(true);
|
||||
ContentProviderResult[] results = super.applyBatch(operations);
|
||||
getDatabase().setTransactionSuccessful();
|
||||
return results;
|
||||
} finally {
|
||||
mApplyingBatch.set(false);
|
||||
getDatabase().endTransaction();
|
||||
for (Uri url : mPendingBatchNotifications.get()) {
|
||||
getContext().getContentResolver().notifyChange(url, null /* observer */,
|
||||
changeRequiresLocalSync(url));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if changes to this URI can be syncable changes.
|
||||
* @param uri the URI of the resource that was changed
|
||||
|
||||
@@ -629,4 +629,26 @@ public abstract class ContentProvider implements ComponentCallbacks {
|
||||
ContentProvider.this.onCreate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies each of the {@link ContentProviderOperation} objects and returns an array
|
||||
* of their results. Passes through OperationApplicationException, which may be thrown
|
||||
* by the call to {@link ContentProviderOperation#apply}.
|
||||
* If all the applications succeed then a {@link ContentProviderResult} array with the
|
||||
* same number of elements as the operations will be returned. It is implementation-specific
|
||||
* how many, if any, operations will have been successfully applied if a call to
|
||||
* apply results in a {@link OperationApplicationException}.
|
||||
* @param operations the operations to apply
|
||||
* @return the results of the applications
|
||||
* @throws OperationApplicationException thrown if an application fails.
|
||||
* See {@link ContentProviderOperation#apply} for more information.
|
||||
*/
|
||||
public ContentProviderResult[] applyBatch(ContentProviderOperation[] operations)
|
||||
throws OperationApplicationException {
|
||||
ContentProviderResult[] results = new ContentProviderResult[operations.length];
|
||||
for (int i = 0; i < operations.length; i++) {
|
||||
results[i] = operations[i].apply(this, results, i);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
}
|
||||
358
core/java/android/content/ContentProviderOperation.java
Normal file
358
core/java/android/content/ContentProviderOperation.java
Normal file
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
* Copyright (C) 2009 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.content;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.database.Cursor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class ContentProviderOperation {
|
||||
private final static int TYPE_INSERT = 1;
|
||||
private final static int TYPE_UPDATE = 2;
|
||||
private final static int TYPE_DELETE = 3;
|
||||
private final static int TYPE_COUNT = 4;
|
||||
|
||||
private final int mType;
|
||||
private final Uri mUri;
|
||||
private final String mSelection;
|
||||
private final String[] mSelectionArgs;
|
||||
private final ContentValues mValues;
|
||||
private final Integer mExpectedCount;
|
||||
private final ContentValues mValuesBackReferences;
|
||||
private final Map<Integer, Integer> mSelectionArgsBackReferences;
|
||||
|
||||
private static final String[] COUNT_COLUMNS = new String[]{"count(*)"};
|
||||
|
||||
/**
|
||||
* Creates a {@link ContentProviderOperation} by copying the contents of a
|
||||
* {@link Builder}.
|
||||
*/
|
||||
private ContentProviderOperation(Builder builder) {
|
||||
mType = builder.mType;
|
||||
mUri = builder.mUri;
|
||||
mValues = builder.mValues;
|
||||
mSelection = builder.mSelection;
|
||||
mSelectionArgs = builder.mSelectionArgs;
|
||||
mExpectedCount = builder.mExpectedCount;
|
||||
mSelectionArgsBackReferences = builder.mSelectionArgsBackReferences;
|
||||
mValuesBackReferences = builder.mValuesBackReferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link Builder} suitable for building an insert {@link ContentProviderOperation}.
|
||||
* @param uri The {@link Uri} that is the target of the insert.
|
||||
* @return a {@link Builder}
|
||||
*/
|
||||
public static Builder newInsert(Uri uri) {
|
||||
return new Builder(TYPE_INSERT, uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link Builder} suitable for building an update {@link ContentProviderOperation}.
|
||||
* @param uri The {@link Uri} that is the target of the update.
|
||||
* @return a {@link Builder}
|
||||
*/
|
||||
public static Builder newUpdate(Uri uri) {
|
||||
return new Builder(TYPE_UPDATE, uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link Builder} suitable for building a delete {@link ContentProviderOperation}.
|
||||
* @param uri The {@link Uri} that is the target of the delete.
|
||||
* @return a {@link Builder}
|
||||
*/
|
||||
public static Builder newDelete(Uri uri) {
|
||||
return new Builder(TYPE_DELETE, uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link Builder} suitable for building a count query. When used in conjunction
|
||||
* with {@link Builder#withExpectedCount(int)} this is useful for checking that the
|
||||
* uri/selection has the expected number of rows.
|
||||
* {@link ContentProviderOperation}.
|
||||
* @param uri The {@link Uri} to query.
|
||||
* @return a {@link Builder}
|
||||
*/
|
||||
public static Builder newCountQuery(Uri uri) {
|
||||
return new Builder(TYPE_COUNT, uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies this operation using the given provider. The backRefs array is used to resolve any
|
||||
* back references that were requested using
|
||||
* {@link Builder#withValueBackReferences(ContentValues)} and
|
||||
* {@link Builder#withSelectionBackReferences(java.util.Map)}.
|
||||
* @param provider the {@link ContentProvider} on which this batch is applied
|
||||
* @param backRefs a {@link ContentProviderResult} array that will be consulted
|
||||
* to resolve any requested back references.
|
||||
* @param numBackRefs the number of valid results on the backRefs array.
|
||||
* @return a {@link ContentProviderResult} that contains either the {@link Uri} of the inserted
|
||||
* row if this was an insert otherwise the number of rows affected.
|
||||
* @throws OperationApplicationException thrown if either the insert fails or
|
||||
* if the number of rows affected didn't match the expected count
|
||||
*/
|
||||
public ContentProviderResult apply(ContentProvider provider, ContentProviderResult[] backRefs,
|
||||
int numBackRefs) throws OperationApplicationException {
|
||||
ContentValues values = resolveValueBackReferences(backRefs, numBackRefs);
|
||||
String[] selectionArgs =
|
||||
resolveSelectionArgsBackReferences(backRefs, numBackRefs);
|
||||
|
||||
if (mType == TYPE_INSERT) {
|
||||
Uri newUri = provider.insert(mUri, values);
|
||||
if (newUri == null) {
|
||||
throw new OperationApplicationException("insert failed");
|
||||
}
|
||||
return new ContentProviderResult(newUri);
|
||||
}
|
||||
|
||||
int numRows;
|
||||
if (mType == TYPE_DELETE) {
|
||||
numRows = provider.delete(mUri, mSelection, selectionArgs);
|
||||
} else if (mType == TYPE_UPDATE) {
|
||||
numRows = provider.update(mUri, values, mSelection, selectionArgs);
|
||||
} else if (mType == TYPE_COUNT) {
|
||||
Cursor cursor = provider.query(mUri, COUNT_COLUMNS, mSelection, selectionArgs, null);
|
||||
try {
|
||||
if (!cursor.moveToNext()) {
|
||||
throw new RuntimeException("since we are doing a count query we should always "
|
||||
+ "be able to move to the first row");
|
||||
}
|
||||
if (cursor.getCount() != 1) {
|
||||
throw new RuntimeException("since we are doing a count query there should "
|
||||
+ "always be exacly row, found " + cursor.getCount());
|
||||
}
|
||||
numRows = cursor.getInt(0);
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("bad type, " + mType);
|
||||
}
|
||||
|
||||
if (mExpectedCount != null && mExpectedCount != numRows) {
|
||||
throw new OperationApplicationException("wrong number of rows: " + numRows);
|
||||
}
|
||||
|
||||
return new ContentProviderResult(numRows);
|
||||
}
|
||||
|
||||
/**
|
||||
* The ContentValues back references are represented as a ContentValues object where the
|
||||
* key refers to a column and the value is an index of the back reference whose
|
||||
* valued should be associated with the column.
|
||||
* @param backRefs an array of previous results
|
||||
* @param numBackRefs the number of valid previous results in backRefs
|
||||
* @return the ContentValues that should be used in this operation application after
|
||||
* expansion of back references. This can be called if either mValues or mValuesBackReferences
|
||||
* is null
|
||||
* @VisibleForTesting this is intended to be a private method but it is exposed for
|
||||
* unit testing purposes
|
||||
*/
|
||||
public ContentValues resolveValueBackReferences(
|
||||
ContentProviderResult[] backRefs, int numBackRefs) {
|
||||
if (mValuesBackReferences == null) {
|
||||
return mValues;
|
||||
}
|
||||
final ContentValues values;
|
||||
if (mValues == null) {
|
||||
values = new ContentValues();
|
||||
} else {
|
||||
values = new ContentValues(mValues);
|
||||
}
|
||||
for (Map.Entry<String, Object> entry : mValuesBackReferences.valueSet()) {
|
||||
String key = entry.getKey();
|
||||
Integer backRefIndex = mValuesBackReferences.getAsInteger(key);
|
||||
if (backRefIndex == null) {
|
||||
throw new IllegalArgumentException("values backref " + key + " is not an integer");
|
||||
}
|
||||
values.put(key, backRefToValue(backRefs, numBackRefs, backRefIndex));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Selection Arguments back references are represented as a Map of Integer->Integer where
|
||||
* the key is an index into the selection argument array (see {@link Builder#withSelection})
|
||||
* and the value is the index of the previous result that should be used for that selection
|
||||
* argument array slot.
|
||||
* @param backRefs an array of previous results
|
||||
* @param numBackRefs the number of valid previous results in backRefs
|
||||
* @return the ContentValues that should be used in this operation application after
|
||||
* expansion of back references. This can be called if either mValues or mValuesBackReferences
|
||||
* is null
|
||||
* @VisibleForTesting this is intended to be a private method but it is exposed for
|
||||
* unit testing purposes
|
||||
*/
|
||||
public String[] resolveSelectionArgsBackReferences(
|
||||
ContentProviderResult[] backRefs, int numBackRefs) {
|
||||
if (mSelectionArgsBackReferences == null) {
|
||||
return mSelectionArgs;
|
||||
}
|
||||
String[] newArgs = new String[mSelectionArgs.length];
|
||||
System.arraycopy(mSelectionArgs, 0, newArgs, 0, mSelectionArgs.length);
|
||||
for (Map.Entry<Integer, Integer> selectionArgBackRef
|
||||
: mSelectionArgsBackReferences.entrySet()) {
|
||||
final Integer selectionArgIndex = selectionArgBackRef.getKey();
|
||||
final int backRefIndex = selectionArgBackRef.getValue();
|
||||
newArgs[selectionArgIndex] = backRefToValue(backRefs, numBackRefs, backRefIndex);
|
||||
}
|
||||
return newArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the string representation of the requested back reference.
|
||||
* @param backRefs an array of results
|
||||
* @param numBackRefs the number of items in the backRefs array that are valid
|
||||
* @param backRefIndex which backRef to be used
|
||||
* @throws ArrayIndexOutOfBoundsException thrown if the backRefIndex is larger than
|
||||
* the numBackRefs
|
||||
* @return the string representation of the requested back reference.
|
||||
*/
|
||||
private static String backRefToValue(ContentProviderResult[] backRefs, int numBackRefs,
|
||||
Integer backRefIndex) {
|
||||
if (backRefIndex > numBackRefs) {
|
||||
throw new ArrayIndexOutOfBoundsException("asked for back ref " + backRefIndex
|
||||
+ " but there are only " + numBackRefs + " back refs");
|
||||
}
|
||||
ContentProviderResult backRef = backRefs[backRefIndex];
|
||||
String backRefValue;
|
||||
if (backRef.uri != null) {
|
||||
backRefValue = backRef.uri.getLastPathSegment();
|
||||
} else {
|
||||
backRefValue = String.valueOf(backRef.count);
|
||||
}
|
||||
return backRefValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to add parameters to a {@link ContentProviderOperation}. The {@link Builder} is
|
||||
* first created by calling {@link ContentProviderOperation#newInsert(android.net.Uri)},
|
||||
* {@link ContentProviderOperation#newUpdate(android.net.Uri)},
|
||||
* {@link ContentProviderOperation#newDelete(android.net.Uri)} or
|
||||
* {@link ContentProviderOperation#newCountQuery(android.net.Uri)}. The withXXX methods
|
||||
* can then be used to add parameters to the builder. See the specific methods to find for
|
||||
* which {@link Builder} type each is allowed. Call {@link #build} to create the
|
||||
* {@link ContentProviderOperation} once all the parameters have been supplied.
|
||||
*/
|
||||
public static class Builder {
|
||||
private final int mType;
|
||||
private final Uri mUri;
|
||||
private String mSelection;
|
||||
private String[] mSelectionArgs;
|
||||
private ContentValues mValues;
|
||||
private Integer mExpectedCount;
|
||||
private ContentValues mValuesBackReferences;
|
||||
private Map<Integer, Integer> mSelectionArgsBackReferences;
|
||||
|
||||
/** Create a {@link Builder} of a given type. The uri must not be null. */
|
||||
private Builder(int type, Uri uri) {
|
||||
if (uri == null) {
|
||||
throw new IllegalArgumentException("uri must not be null");
|
||||
}
|
||||
mType = type;
|
||||
mUri = uri;
|
||||
}
|
||||
|
||||
/** Create a ContentroviderOperation from this {@link Builder}. */
|
||||
public ContentProviderOperation build() {
|
||||
return new ContentProviderOperation(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link ContentValues} of back references. The key is the name of the column
|
||||
* and the value is an integer that is the index of the previous result whose
|
||||
* value should be used for the column. The value is added as a {@link String}.
|
||||
* A column value from the back references takes precedence over a value specified in
|
||||
* {@link #withValues}.
|
||||
* This can only be used with builders of type insert or update.
|
||||
* @return this builder, to allow for chaining.
|
||||
*/
|
||||
public Builder withValueBackReferences(ContentValues backReferences) {
|
||||
if (mType != TYPE_INSERT && mType != TYPE_UPDATE) {
|
||||
throw new IllegalArgumentException(
|
||||
"only inserts and updates can have value back-references");
|
||||
}
|
||||
mValuesBackReferences = backReferences;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link Map} of back references. The integer key is the index of the selection arg
|
||||
* to set and the integer value is the index of the previous result whose
|
||||
* value should be used for the arg. If any value at that index of the selection arg
|
||||
* that was specified by {@likn withSelection} will be overwritten.
|
||||
* This can only be used with builders of type update, delete, or count query.
|
||||
* @return this builder, to allow for chaining.
|
||||
*/
|
||||
public Builder withSelectionBackReferences(Map<Integer, Integer> backReferences) {
|
||||
if (mType != TYPE_COUNT && mType != TYPE_UPDATE && mType != TYPE_DELETE) {
|
||||
throw new IllegalArgumentException(
|
||||
"only deletes, updates and counts can have selection back-references");
|
||||
}
|
||||
mSelectionArgsBackReferences = backReferences;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The ContentValues to use. This may be null. These values may be overwritten by
|
||||
* the corresponding value specified by {@link #withValueBackReferences(ContentValues)}.
|
||||
* This can only be used with builders of type insert or update.
|
||||
* @return this builder, to allow for chaining.
|
||||
*/
|
||||
public Builder withValues(ContentValues values) {
|
||||
if (mType != TYPE_INSERT && mType != TYPE_UPDATE) {
|
||||
throw new IllegalArgumentException("only inserts and updates can have values");
|
||||
}
|
||||
mValues = values;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The selection and arguments to use. An occurrence of '?' in the selection will be
|
||||
* replaced with the corresponding occurence of the selection argument. Any of the
|
||||
* selection arguments may be overwritten by a selection argument back reference as
|
||||
* specified by {@link #withSelectionBackReferences}.
|
||||
* This can only be used with builders of type update, delete, or count query.
|
||||
* @return this builder, to allow for chaining.
|
||||
*/
|
||||
public Builder withSelection(String selection, String[] selectionArgs) {
|
||||
if (mType != TYPE_DELETE && mType != TYPE_UPDATE && mType != TYPE_COUNT) {
|
||||
throw new IllegalArgumentException(
|
||||
"only deletes, updates and counts can have selections");
|
||||
}
|
||||
mSelection = selection;
|
||||
mSelectionArgs = selectionArgs;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* If set then if the number of rows affected by this operation do not match
|
||||
* this count {@link OperationApplicationException} will be throw.
|
||||
* This can only be used with builders of type update, delete, or count query.
|
||||
* @return this builder, to allow for chaining.
|
||||
*/
|
||||
public Builder withExpectedCount(int count) {
|
||||
if (mType != TYPE_DELETE && mType != TYPE_UPDATE && mType != TYPE_COUNT) {
|
||||
throw new IllegalArgumentException(
|
||||
"only deletes, updates and counts can have expected counts");
|
||||
}
|
||||
mExpectedCount = count;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
39
core/java/android/content/ContentProviderResult.java
Normal file
39
core/java/android/content/ContentProviderResult.java
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2009 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.content;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
/**
|
||||
* Contains the result of the application of a {@link ContentProviderOperation}. It is guaranteed
|
||||
* to have exactly one of {@link #uri} or {@link #count} set.
|
||||
*/
|
||||
public class ContentProviderResult {
|
||||
public final Uri uri;
|
||||
public final Integer count;
|
||||
|
||||
public ContentProviderResult(Uri uri) {
|
||||
if (uri == null) throw new IllegalArgumentException("uri must not be null");
|
||||
this.uri = uri;
|
||||
this.count = null;
|
||||
}
|
||||
|
||||
public ContentProviderResult(int count) {
|
||||
this.count = count;
|
||||
this.uri = null;
|
||||
}
|
||||
}
|
||||
36
core/java/android/content/OperationApplicationException.java
Normal file
36
core/java/android/content/OperationApplicationException.java
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2009 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.content;
|
||||
|
||||
/**
|
||||
* Thrown when an application of a {@link ContentProviderOperation} fails due the specified
|
||||
* constraints.
|
||||
*/
|
||||
public class OperationApplicationException extends Exception {
|
||||
public OperationApplicationException() {
|
||||
super();
|
||||
}
|
||||
public OperationApplicationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
public OperationApplicationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
public OperationApplicationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright (C) 2009 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 com.android.unit_tests.content;
|
||||
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.net.Uri;
|
||||
import android.content.ContentValues;
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.OperationApplicationException;
|
||||
import android.content.ContentProviderResult;
|
||||
import android.content.ContentProvider;
|
||||
import android.text.TextUtils;
|
||||
import android.database.Cursor;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Hashtable;
|
||||
|
||||
@SmallTest
|
||||
public class ContentProviderOperationTest extends TestCase {
|
||||
private final static Uri sTestUri1 = Uri.parse("content://authority/blah");
|
||||
private final static ContentValues sTestValues1;
|
||||
|
||||
static {
|
||||
sTestValues1 = new ContentValues();
|
||||
sTestValues1.put("a", 1);
|
||||
sTestValues1.put("b", "two");
|
||||
}
|
||||
|
||||
public void testInsert() throws OperationApplicationException {
|
||||
ContentProviderOperation op1 = ContentProviderOperation.newInsert(sTestUri1)
|
||||
.withValues(sTestValues1)
|
||||
.build();
|
||||
ContentProviderResult result = op1.apply(new TestContentProvider() {
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
assertEquals(sTestUri1.toString(), uri.toString());
|
||||
assertEquals(sTestValues1.toString(), values.toString());
|
||||
return uri.buildUpon().appendPath("19").build();
|
||||
}
|
||||
}, null, 0);
|
||||
assertEquals(sTestUri1.buildUpon().appendPath("19").toString(), result.uri.toString());
|
||||
}
|
||||
|
||||
public void testInsertNoValues() throws OperationApplicationException {
|
||||
ContentProviderOperation op1 = ContentProviderOperation.newInsert(sTestUri1)
|
||||
.build();
|
||||
ContentProviderResult result = op1.apply(new TestContentProvider() {
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
assertEquals(sTestUri1.toString(), uri.toString());
|
||||
assertNull(values);
|
||||
return uri.buildUpon().appendPath("19").build();
|
||||
}
|
||||
}, null, 0);
|
||||
assertEquals(sTestUri1.buildUpon().appendPath("19").toString(), result.uri.toString());
|
||||
}
|
||||
|
||||
public void testInsertFailed() {
|
||||
ContentProviderOperation op1 = ContentProviderOperation.newInsert(sTestUri1)
|
||||
.withValues(sTestValues1)
|
||||
.build();
|
||||
try {
|
||||
op1.apply(new TestContentProvider() {
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
assertEquals(sTestUri1.toString(), uri.toString());
|
||||
assertEquals(sTestValues1.toString(), values.toString());
|
||||
return null;
|
||||
}
|
||||
}, null, 0);
|
||||
fail("the apply should have thrown an OperationApplicationException");
|
||||
} catch (OperationApplicationException e) {
|
||||
// this is the expected case
|
||||
}
|
||||
}
|
||||
|
||||
public void testInsertWithBackRefs() throws OperationApplicationException {
|
||||
ContentValues valuesBackRefs = new ContentValues();
|
||||
valuesBackRefs.put("a1", 3);
|
||||
valuesBackRefs.put("a2", 1);
|
||||
|
||||
ContentProviderResult[] previousResults = new ContentProviderResult[4];
|
||||
previousResults[0] = new ContentProviderResult(100);
|
||||
previousResults[1] = new ContentProviderResult(101);
|
||||
previousResults[2] = new ContentProviderResult(102);
|
||||
previousResults[3] = new ContentProviderResult(103);
|
||||
ContentProviderOperation op1 = ContentProviderOperation.newInsert(sTestUri1)
|
||||
.withValues(sTestValues1)
|
||||
.withValueBackReferences(valuesBackRefs)
|
||||
.build();
|
||||
ContentProviderResult result = op1.apply(new TestContentProvider() {
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
assertEquals(sTestUri1.toString(), uri.toString());
|
||||
ContentValues expected = new ContentValues(sTestValues1);
|
||||
expected.put("a1", 103);
|
||||
expected.put("a2", 101);
|
||||
assertEquals(expected.toString(), values.toString());
|
||||
return uri.buildUpon().appendPath("19").build();
|
||||
}
|
||||
}, previousResults, previousResults.length);
|
||||
assertEquals(sTestUri1.buildUpon().appendPath("19").toString(), result.uri.toString());
|
||||
}
|
||||
|
||||
public void testUpdate() throws OperationApplicationException {
|
||||
ContentProviderOperation op1 = ContentProviderOperation.newInsert(sTestUri1)
|
||||
.withValues(sTestValues1)
|
||||
.build();
|
||||
ContentProviderResult[] backRefs = new ContentProviderResult[2];
|
||||
ContentProviderResult result = op1.apply(new TestContentProvider() {
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
assertEquals(sTestUri1.toString(), uri.toString());
|
||||
assertEquals(sTestValues1.toString(), values.toString());
|
||||
return uri.buildUpon().appendPath("19").build();
|
||||
}
|
||||
}, backRefs, 1);
|
||||
assertEquals(sTestUri1.buildUpon().appendPath("19").toString(), result.uri.toString());
|
||||
}
|
||||
|
||||
public void testValueBackRefs() {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("a", "in1");
|
||||
values.put("a2", "in2");
|
||||
values.put("b", "in3");
|
||||
values.put("c", "in4");
|
||||
|
||||
ContentProviderResult[] previousResults = new ContentProviderResult[4];
|
||||
previousResults[0] = new ContentProviderResult(100);
|
||||
previousResults[1] = new ContentProviderResult(101);
|
||||
previousResults[2] = new ContentProviderResult(102);
|
||||
previousResults[3] = new ContentProviderResult(103);
|
||||
|
||||
ContentValues valuesBackRefs = new ContentValues();
|
||||
valuesBackRefs.put("a1", 3); // a1 -> 103
|
||||
valuesBackRefs.put("a2", 1); // a2 -> 101
|
||||
valuesBackRefs.put("a3", 2); // a3 -> 102
|
||||
|
||||
ContentValues expectedValues = new ContentValues(values);
|
||||
expectedValues.put("a1", "103");
|
||||
expectedValues.put("a2", "101");
|
||||
expectedValues.put("a3", "102");
|
||||
|
||||
ContentProviderOperation op1 = ContentProviderOperation.newInsert(sTestUri1)
|
||||
.withValues(values)
|
||||
.withValueBackReferences(valuesBackRefs)
|
||||
.build();
|
||||
ContentValues v2 = op1.resolveValueBackReferences(previousResults, previousResults.length);
|
||||
assertEquals(expectedValues, v2);
|
||||
}
|
||||
|
||||
public void testSelectionBackRefs() {
|
||||
Map<Integer, Integer> selectionBackRefs = new Hashtable<Integer, Integer>();
|
||||
selectionBackRefs.put(1, 3);
|
||||
selectionBackRefs.put(2, 1);
|
||||
selectionBackRefs.put(4, 2);
|
||||
|
||||
ContentProviderResult[] previousResults = new ContentProviderResult[4];
|
||||
previousResults[0] = new ContentProviderResult(100);
|
||||
previousResults[1] = new ContentProviderResult(101);
|
||||
previousResults[2] = new ContentProviderResult(102);
|
||||
previousResults[3] = new ContentProviderResult(103);
|
||||
|
||||
String[] selectionArgs = new String[]{"a", null, null, "b", null};
|
||||
|
||||
ContentProviderOperation op1 = ContentProviderOperation.newUpdate(sTestUri1)
|
||||
.withSelectionBackReferences(selectionBackRefs)
|
||||
.withSelection("unused", selectionArgs)
|
||||
.build();
|
||||
String[] s2 = op1.resolveSelectionArgsBackReferences(
|
||||
previousResults, previousResults.length);
|
||||
assertEquals("a,103,101,b,102", TextUtils.join(",", s2));
|
||||
}
|
||||
|
||||
static class TestContentProvider extends ContentProvider {
|
||||
public boolean onCreate() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
|
||||
String sortOrder) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public String getType(Uri uri) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user