diff options
-rw-r--r-- | api/current.xml | 362 | ||||
-rw-r--r-- | core/java/android/content/ContentProvider.java | 38 | ||||
-rw-r--r-- | core/java/android/content/ContentProviderClient.java | 16 | ||||
-rw-r--r-- | core/java/android/content/ContentProviderNative.java | 98 | ||||
-rw-r--r-- | core/java/android/content/ContentProviderOperation.java | 146 | ||||
-rw-r--r-- | core/java/android/content/ContentProviderResult.java | 40 | ||||
-rw-r--r-- | core/java/android/content/ContentResolver.java | 100 | ||||
-rw-r--r-- | core/java/android/content/IContentProvider.java | 11 | ||||
-rw-r--r-- | core/java/android/content/IEntityIterator.java | 11 | ||||
-rw-r--r-- | core/java/android/database/DatabaseUtils.java | 15 | ||||
-rw-r--r-- | test-runner/android/test/mock/MockContentProvider.java | 13 | ||||
-rw-r--r-- | tests/AndroidTests/src/com/android/unit_tests/content/ContentProviderOperationTest.java | 211 | ||||
-rw-r--r-- | tests/FrameworkTest/tests/src/android/content/ContentProviderOperationTest.java | 508 |
13 files changed, 1045 insertions, 524 deletions
diff --git a/api/current.xml b/api/current.xml index 6179e4b..54d98a7 100644 --- a/api/current.xml +++ b/api/current.xml @@ -28053,36 +28053,6 @@ <parameter name="values" type="android.content.ContentValues[]"> </parameter> </method> -<method name="bulkInsertEntities" - return="android.net.Uri[]" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="uri" type="android.net.Uri"> -</parameter> -<parameter name="entities" type="android.content.Entity[]"> -</parameter> -</method> -<method name="bulkUpdateEntities" - return="int[]" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="uri" type="android.net.Uri"> -</parameter> -<parameter name="entities" type="android.content.Entity[]"> -</parameter> -</method> <method name="delete" return="int" abstract="true" @@ -28382,8 +28352,8 @@ deprecated="not deprecated" visibility="public" > -<method name="bulkInsert" - return="int" +<method name="applyBatch" + return="android.content.ContentProviderResult[]" abstract="false" native="false" synchronized="false" @@ -28392,32 +28362,15 @@ deprecated="not deprecated" visibility="public" > -<parameter name="url" type="android.net.Uri"> -</parameter> -<parameter name="initialValues" type="android.content.ContentValues[]"> +<parameter name="operations" type="android.content.ContentProviderOperation[]"> </parameter> -<exception name="RemoteException" type="android.os.RemoteException"> +<exception name="OperationApplicationException" type="android.content.OperationApplicationException"> </exception> -</method> -<method name="bulkInsertEntities" - return="android.net.Uri[]" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="uri" type="android.net.Uri"> -</parameter> -<parameter name="entities" type="android.content.Entity[]"> -</parameter> <exception name="RemoteException" type="android.os.RemoteException"> </exception> </method> -<method name="bulkUpdateEntities" - return="int[]" +<method name="bulkInsert" + return="int" abstract="false" native="false" synchronized="false" @@ -28426,9 +28379,9 @@ deprecated="not deprecated" visibility="public" > -<parameter name="uri" type="android.net.Uri"> +<parameter name="url" type="android.net.Uri"> </parameter> -<parameter name="entities" type="android.content.Entity[]"> +<parameter name="initialValues" type="android.content.ContentValues[]"> </parameter> <exception name="RemoteException" type="android.os.RemoteException"> </exception> @@ -28652,6 +28605,8 @@ deprecated="not deprecated" visibility="public" > +<implements name="android.os.Parcelable"> +</implements> <method name="apply" return="android.content.ContentProviderResult" abstract="false" @@ -28671,6 +28626,50 @@ <exception name="OperationApplicationException" type="android.content.OperationApplicationException"> </exception> </method> +<method name="describeContents" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getUri" + return="android.net.Uri" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="isReadOperation" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="isWriteOperation" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="newCountQuery" return="android.content.ContentProviderOperation.Builder" abstract="false" @@ -28753,6 +28752,31 @@ <parameter name="numBackRefs" type="int"> </parameter> </method> +<method name="writeToParcel" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="dest" type="android.os.Parcel"> +</parameter> +<parameter name="flags" type="int"> +</parameter> +</method> +<field name="CREATOR" + type="android.os.Parcelable.Creator" + transient="false" + volatile="false" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> </class> <class name="ContentProviderOperation.Builder" extends="java.lang.Object" @@ -28773,6 +28797,19 @@ visibility="public" > </method> +<method name="withEntity" + return="android.content.ContentProviderOperation.Builder" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="entity" type="android.content.Entity"> +</parameter> +</method> <method name="withExpectedCount" return="android.content.ContentProviderOperation.Builder" abstract="false" @@ -28849,6 +28886,8 @@ deprecated="not deprecated" visibility="public" > +<implements name="android.os.Parcelable"> +</implements> <constructor name="ContentProviderResult" type="android.content.ContentProviderResult" static="false" @@ -28869,6 +28908,52 @@ <parameter name="count" type="int"> </parameter> </constructor> +<constructor name="ContentProviderResult" + type="android.content.ContentProviderResult" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="source" type="android.os.Parcel"> +</parameter> +</constructor> +<method name="describeContents" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="writeToParcel" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="dest" type="android.os.Parcel"> +</parameter> +<parameter name="flags" type="int"> +</parameter> +</method> +<field name="CREATOR" + type="android.os.Parcelable.Creator" + transient="false" + volatile="false" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="count" type="java.lang.Integer" transient="false" @@ -29018,40 +29103,27 @@ <parameter name="name" type="java.lang.String"> </parameter> </method> -<method name="bulkInsert" - return="int" - abstract="false" - native="false" - synchronized="false" - static="false" - final="true" - deprecated="not deprecated" - visibility="public" -> -<parameter name="url" type="android.net.Uri"> -</parameter> -<parameter name="values" type="android.content.ContentValues[]"> -</parameter> -</method> -<method name="bulkInsertEntities" - return="android.net.Uri[]" +<method name="applyBatch" + return="android.content.ContentProviderResult[]" abstract="false" native="false" synchronized="false" static="false" - final="true" + final="false" deprecated="not deprecated" visibility="public" > -<parameter name="uri" type="android.net.Uri"> +<parameter name="authority" type="java.lang.String"> </parameter> -<parameter name="entities" type="android.content.Entity[]"> +<parameter name="operations" type="android.content.ContentProviderOperation[]"> </parameter> +<exception name="OperationApplicationException" type="android.content.OperationApplicationException"> +</exception> <exception name="RemoteException" type="android.os.RemoteException"> </exception> </method> -<method name="bulkUpdateEntities" - return="int[]" +<method name="bulkInsert" + return="int" abstract="false" native="false" synchronized="false" @@ -29060,12 +29132,10 @@ deprecated="not deprecated" visibility="public" > -<parameter name="uri" type="android.net.Uri"> +<parameter name="url" type="android.net.Uri"> </parameter> -<parameter name="entities" type="android.content.Entity[]"> +<parameter name="values" type="android.content.ContentValues[]"> </parameter> -<exception name="RemoteException" type="android.os.RemoteException"> -</exception> </method> <method name="cancelSync" return="void" @@ -29276,7 +29346,7 @@ <parameter name="sortOrder" type="java.lang.String"> </parameter> </method> -<method name="queryEntity" +<method name="queryEntities" return="android.content.EntityIterator" abstract="false" native="false" @@ -32604,119 +32674,6 @@ </exception> </method> </interface> -<interface name="IEntityIterator" - abstract="true" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<implements name="android.os.IInterface"> -</implements> -<method name="close" - return="void" - abstract="true" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<exception name="RemoteException" type="android.os.RemoteException"> -</exception> -</method> -<method name="hasNext" - return="boolean" - abstract="true" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<exception name="RemoteException" type="android.os.RemoteException"> -</exception> -</method> -<method name="next" - return="android.content.Entity" - abstract="true" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<exception name="RemoteException" type="android.os.RemoteException"> -</exception> -</method> -</interface> -<class name="IEntityIterator.Stub" - extends="android.os.Binder" - abstract="true" - static="true" - final="false" - deprecated="not deprecated" - visibility="public" -> -<implements name="android.content.IEntityIterator"> -</implements> -<constructor name="IEntityIterator.Stub" - type="android.content.IEntityIterator.Stub" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -</constructor> -<method name="asBinder" - return="android.os.IBinder" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -</method> -<method name="asInterface" - return="android.content.IEntityIterator" - abstract="false" - native="false" - synchronized="false" - static="true" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="obj" type="android.os.IBinder"> -</parameter> -</method> -<method name="onTransact" - return="boolean" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="code" type="int"> -</parameter> -<parameter name="data" type="android.os.Parcel"> -</parameter> -<parameter name="reply" type="android.os.Parcel"> -</parameter> -<parameter name="flags" type="int"> -</parameter> -<exception name="RemoteException" type="android.os.RemoteException"> -</exception> -</method> -</class> <class name="Intent" extends="java.lang.Object" abstract="false" @@ -46712,6 +46669,21 @@ <exception name="FileNotFoundException" type="java.io.FileNotFoundException"> </exception> </method> +<method name="readExceptionWithOperationApplicationExceptionFromParcel" + return="void" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="reply" type="android.os.Parcel"> +</parameter> +<exception name="OperationApplicationException" type="android.content.OperationApplicationException"> +</exception> +</method> <method name="sqlEscapeString" return="java.lang.String" abstract="false" diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index c204dda..bb25b68 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -151,9 +151,23 @@ public abstract class ContentProvider implements ComponentCallbacks { return ContentProvider.this.bulkInsert(uri, initialValues); } - public Uri[] bulkInsertEntities(Uri uri, Entity[] entities) { + public Uri insertEntity(Uri uri, Entity entities) { checkWritePermission(uri); - return ContentProvider.this.bulkInsertEntities(uri, entities); + return ContentProvider.this.insertEntity(uri, entities); + } + + public ContentProviderResult[] applyBatch(ContentProviderOperation[] operations) + throws OperationApplicationException { + for (ContentProviderOperation operation : operations) { + if (operation.isReadOperation()) { + checkReadPermission(operation.getUri()); + } + + if (operation.isWriteOperation()) { + checkWritePermission(operation.getUri()); + } + } + return ContentProvider.this.applyBatch(operations); } public int delete(Uri uri, String selection, String[] selectionArgs) { @@ -167,9 +181,9 @@ public abstract class ContentProvider implements ComponentCallbacks { return ContentProvider.this.update(uri, values, selection, selectionArgs); } - public int[] bulkUpdateEntities(Uri uri, Entity[] entities) { + public int updateEntity(Uri uri, Entity entity) { checkWritePermission(uri); - return ContentProvider.this.bulkUpdateEntities(uri, entities); + return ContentProvider.this.updateEntity(uri, entity); } public ParcelFileDescriptor openFile(Uri uri, String mode) @@ -403,14 +417,6 @@ public abstract class ContentProvider implements ComponentCallbacks { throw new UnsupportedOperationException(); } - public Uri[] bulkInsertEntities(Uri uri, Entity[] entities) { - Uri[] result = new Uri[entities.length]; - for (int i = 0; i < entities.length; i++) { - result[i] = insertEntity(uri, entities[i]); - } - return result; - } - /** * A request to delete one or more rows. The selection clause is applied when performing * the deletion, allowing the operation to affect multiple rows in a @@ -459,14 +465,6 @@ public abstract class ContentProvider implements ComponentCallbacks { throw new UnsupportedOperationException(); } - public int[] bulkUpdateEntities(Uri uri, Entity[] entities) { - int[] result = new int[entities.length]; - for (int i = 0; i < entities.length; i++) { - result[i] = updateEntity(uri, entities[i]); - } - return result; - } - /** * Open a file blob associated with a content URI. * This method can be called from multiple diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java index ed3ed42..08d4fca 100644 --- a/core/java/android/content/ContentProviderClient.java +++ b/core/java/android/content/ContentProviderClient.java @@ -96,22 +96,18 @@ public class ContentProviderClient { /** see {@link ContentProvider#insertEntity} */ public Uri insertEntity(Uri uri, Entity entity) throws RemoteException { - return mContentProvider.bulkInsertEntities(uri, new Entity[]{entity})[0]; - } - - /** see {@link ContentProvider#bulkInsertEntities} */ - public Uri[] bulkInsertEntities(Uri uri, Entity[] entities) throws RemoteException { - return mContentProvider.bulkInsertEntities(uri, entities); + return mContentProvider.insertEntity(uri, entity); } /** see {@link ContentProvider#updateEntity} */ public int updateEntity(Uri uri, Entity entity) throws RemoteException { - return mContentProvider.bulkUpdateEntities(uri, new Entity[]{entity})[0]; + return mContentProvider.updateEntity(uri, entity); } - /** see {@link ContentProvider#bulkUpdateEntities} */ - public int[] bulkUpdateEntities(Uri uri, Entity[] entities) throws RemoteException { - return mContentProvider.bulkUpdateEntities(uri, entities); + /** see {@link ContentProvider#applyBatch} */ + public ContentProviderResult[] applyBatch(ContentProviderOperation[] operations) + throws RemoteException, OperationApplicationException { + return mContentProvider.applyBatch(operations); } /** diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java index ac67076..4747726 100644 --- a/core/java/android/content/ContentProviderNative.java +++ b/core/java/android/content/ContentProviderNative.java @@ -154,37 +154,36 @@ abstract public class ContentProviderNative extends Binder implements IContentPr return true; } - case BULK_INSERT_ENTITIES_TRANSACTION: + case INSERT_ENTITIES_TRANSACTION: { data.enforceInterface(IContentProvider.descriptor); Uri uri = Uri.CREATOR.createFromParcel(data); - String className = data.readString(); - Class entityClass = Class.forName(className); - int numEntities = data.readInt(); - Entity[] entities = new Entity[numEntities]; - for (int i = 0; i < numEntities; i++) { - entities[i] = (Entity) data.readParcelable(entityClass.getClassLoader()); - } - Uri[] uris = bulkInsertEntities(uri, entities); + Entity entity = (Entity) data.readParcelable(null); + Uri newUri = insertEntity(uri, entity); reply.writeNoException(); - reply.writeTypedArray(uris, 0); + Uri.writeToParcel(reply, newUri); return true; } - case BULK_UPDATE_ENTITIES_TRANSACTION: + case UPDATE_ENTITIES_TRANSACTION: { data.enforceInterface(IContentProvider.descriptor); Uri uri = Uri.CREATOR.createFromParcel(data); - String className = data.readString(); - Class entityClass = Class.forName(className); - int numEntities = data.readInt(); - Entity[] entities = new Entity[numEntities]; - for (int i = 0; i < numEntities; i++) { - entities[i] = (Entity) data.readParcelable(entityClass.getClassLoader()); - } - int[] counts = bulkUpdateEntities(uri, entities); + Entity entity = (Entity) data.readParcelable(null); + int count = updateEntity(uri, entity); + reply.writeNoException(); + reply.writeInt(count); + return true; + } + + case APPLY_BATCH_TRANSACTION: + { + data.enforceInterface(IContentProvider.descriptor); + final ContentProviderOperation[] operations = + data.createTypedArray(ContentProviderOperation.CREATOR); + final ContentProviderResult[] results = applyBatch(operations); reply.writeNoException(); - reply.writeIntArray(counts); + reply.writeTypedArray(results, 0); return true; } @@ -472,23 +471,18 @@ final class ContentProviderProxy implements IContentProvider return count; } - public Uri[] bulkInsertEntities(Uri uri, Entity[] entities) throws RemoteException { + public ContentProviderResult[] applyBatch(ContentProviderOperation[] operations) + throws RemoteException, OperationApplicationException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IContentProvider.descriptor); - uri.writeToParcel(data, 0); - data.writeString(entities[0].getClass().getName()); - data.writeInt(entities.length); - for (Entity entity : entities) { - data.writeParcelable(entity, 0); - } + data.writeTypedArray(operations, 0); + mRemote.transact(IContentProvider.APPLY_BATCH_TRANSACTION, data, reply, 0); - mRemote.transact(IContentProvider.BULK_INSERT_ENTITIES_TRANSACTION, data, reply, 0); - - DatabaseUtils.readExceptionFromParcel(reply); - Uri[] results = new Uri[entities.length]; - reply.readTypedArray(results, Uri.CREATOR); + DatabaseUtils.readExceptionWithOperationApplicationExceptionFromParcel(reply); + final ContentProviderResult[] results = + reply.createTypedArray(ContentProviderResult.CREATOR); data.recycle(); reply.recycle(); @@ -496,28 +490,42 @@ final class ContentProviderProxy implements IContentProvider return results; } - public int[] bulkUpdateEntities(Uri uri, Entity[] entities) throws RemoteException { + public Uri insertEntity(Uri uri, Entity entity) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); - data.writeInterfaceToken(IContentProvider.descriptor); - uri.writeToParcel(data, 0); - data.writeString(entities[0].getClass().getName()); - data.writeInt(entities.length); - for (Entity entity : entities) { + try { + data.writeInterfaceToken(IContentProvider.descriptor); + uri.writeToParcel(data, 0); data.writeParcelable(entity, 0); + + mRemote.transact(IContentProvider.INSERT_ENTITIES_TRANSACTION, data, reply, 0); + + DatabaseUtils.readExceptionFromParcel(reply); + return Uri.CREATOR.createFromParcel(reply); + } finally { + data.recycle(); + reply.recycle(); } + } - mRemote.transact(IContentProvider.BULK_UPDATE_ENTITIES_TRANSACTION, data, reply, 0); + public int updateEntity(Uri uri, Entity entity) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); - DatabaseUtils.readExceptionFromParcel(reply); - int[] results = new int[entities.length]; - reply.readIntArray(results); + try { + data.writeInterfaceToken(IContentProvider.descriptor); + uri.writeToParcel(data, 0); + data.writeParcelable(entity, 0); - data.recycle(); - reply.recycle(); + mRemote.transact(IContentProvider.UPDATE_ENTITIES_TRANSACTION, data, reply, 0); - return results; + DatabaseUtils.readExceptionFromParcel(reply); + return reply.readInt(); + } finally { + data.recycle(); + reply.recycle(); + } } public int delete(Uri url, String selection, String[] selectionArgs) diff --git a/core/java/android/content/ContentProviderOperation.java b/core/java/android/content/ContentProviderOperation.java index 148cc35..ce92198 100644 --- a/core/java/android/content/ContentProviderOperation.java +++ b/core/java/android/content/ContentProviderOperation.java @@ -18,10 +18,15 @@ package android.content; import android.net.Uri; import android.database.Cursor; +import android.os.Parcelable; +import android.os.Parcel; import java.util.Map; +import java.util.HashMap; -public class ContentProviderOperation { +import dalvik.system.VMStack; + +public class ContentProviderOperation implements Parcelable { private final static int TYPE_INSERT = 1; private final static int TYPE_UPDATE = 2; private final static int TYPE_DELETE = 3; @@ -32,6 +37,7 @@ public class ContentProviderOperation { private final String mSelection; private final String[] mSelectionArgs; private final ContentValues mValues; + private final Entity mEntity; private final Integer mExpectedCount; private final ContentValues mValuesBackReferences; private final Map<Integer, Integer> mSelectionArgsBackReferences; @@ -46,6 +52,7 @@ public class ContentProviderOperation { mType = builder.mType; mUri = builder.mUri; mValues = builder.mValues; + mEntity = builder.mEntity; mSelection = builder.mSelection; mSelectionArgs = builder.mSelectionArgs; mExpectedCount = builder.mExpectedCount; @@ -53,6 +60,75 @@ public class ContentProviderOperation { mValuesBackReferences = builder.mValuesBackReferences; } + private ContentProviderOperation(Parcel source, ClassLoader classLoader) { + mType = source.readInt(); + mUri = Uri.CREATOR.createFromParcel(source); + mValues = source.readInt() != 0 ? ContentValues.CREATOR.createFromParcel(source) : null; + mEntity = (Entity) source.readParcelable(classLoader); + mSelection = source.readInt() != 0 ? source.readString() : null; + mSelectionArgs = source.readInt() != 0 ? source.readStringArray() : null; + mExpectedCount = source.readInt() != 0 ? source.readInt() : null; + mValuesBackReferences = source.readInt() != 0 + + ? ContentValues.CREATOR.createFromParcel(source) + : null; + mSelectionArgsBackReferences = source.readInt() != 0 + ? new HashMap<Integer, Integer>() + : null; + if (mSelectionArgsBackReferences != null) { + final int count = source.readInt(); + for (int i = 0; i < count; i++) { + mSelectionArgsBackReferences.put(source.readInt(), source.readInt()); + } + } + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mType); + Uri.writeToParcel(dest, mUri); + if (mValues != null) { + dest.writeInt(1); + mValues.writeToParcel(dest, 0); + } else { + dest.writeInt(0); + } + dest.writeParcelable(mEntity, 0); + if (mSelection != null) { + dest.writeInt(1); + dest.writeString(mSelection); + } else { + dest.writeInt(0); + } + if (mSelectionArgs != null) { + dest.writeInt(1); + dest.writeStringArray(mSelectionArgs); + } else { + dest.writeInt(0); + } + if (mExpectedCount != null) { + dest.writeInt(1); + dest.writeInt(mExpectedCount); + } else { + dest.writeInt(0); + } + if (mValuesBackReferences != null) { + dest.writeInt(1); + mValuesBackReferences.writeToParcel(dest, 0); + } else { + dest.writeInt(0); + } + if (mSelectionArgsBackReferences != null) { + dest.writeInt(1); + dest.writeInt(mSelectionArgsBackReferences.size()); + for (Map.Entry<Integer, Integer> entry : mSelectionArgsBackReferences.entrySet()) { + dest.writeInt(entry.getKey()); + dest.writeInt(entry.getValue()); + } + } else { + dest.writeInt(0); + } + } + /** * Create a {@link Builder} suitable for building an insert {@link ContentProviderOperation}. * @param uri The {@link Uri} that is the target of the insert. @@ -92,6 +168,18 @@ public class ContentProviderOperation { return new Builder(TYPE_COUNT, uri); } + public Uri getUri() { + return mUri; + } + + public boolean isWriteOperation() { + return mType == TYPE_DELETE || mType == TYPE_INSERT || mType == TYPE_UPDATE; + } + + public boolean isReadOperation() { + return mType == TYPE_COUNT; + } + /** * Applies this operation using the given provider. The backRefs array is used to resolve any * back references that were requested using @@ -113,7 +201,12 @@ public class ContentProviderOperation { resolveSelectionArgsBackReferences(backRefs, numBackRefs); if (mType == TYPE_INSERT) { - Uri newUri = provider.insert(mUri, values); + Uri newUri; + if (mEntity != null) { + newUri = provider.insertEntity(mUri, mEntity); + } else { + newUri = provider.insert(mUri, values); + } if (newUri == null) { throw new OperationApplicationException("insert failed"); } @@ -124,7 +217,11 @@ public class ContentProviderOperation { if (mType == TYPE_DELETE) { numRows = provider.delete(mUri, mSelection, selectionArgs); } else if (mType == TYPE_UPDATE) { - numRows = provider.update(mUri, values, mSelection, selectionArgs); + if (mEntity != null) { + numRows = provider.updateEntity(mUri, mEntity); + } else { + numRows = provider.update(mUri, values, mSelection, selectionArgs); + } } else if (mType == TYPE_COUNT) { Cursor cursor = provider.query(mUri, COUNT_COLUMNS, mSelection, selectionArgs, null); try { @@ -239,6 +336,22 @@ public class ContentProviderOperation { return backRefValue; } + public int describeContents() { + return 0; + } + + public static final Creator<ContentProviderOperation> CREATOR = + new Creator<ContentProviderOperation>() { + public ContentProviderOperation createFromParcel(Parcel source) { + return new ContentProviderOperation(source, VMStack.getCallingClassLoader2()); + } + + public ContentProviderOperation[] newArray(int size) { + return new ContentProviderOperation[size]; + } + }; + + /** * Used to add parameters to a {@link ContentProviderOperation}. The {@link Builder} is * first created by calling {@link ContentProviderOperation#newInsert(android.net.Uri)}, @@ -255,6 +368,7 @@ public class ContentProviderOperation { private String mSelection; private String[] mSelectionArgs; private ContentValues mValues; + private Entity mEntity; private Integer mExpectedCount; private ContentValues mValuesBackReferences; private Map<Integer, Integer> mSelectionArgsBackReferences; @@ -268,8 +382,16 @@ public class ContentProviderOperation { mUri = uri; } - /** Create a ContentroviderOperation from this {@link Builder}. */ + /** Create a ContentProviderOperation from this {@link Builder}. */ public ContentProviderOperation build() { + if (mValues != null && mEntity != null) { + throw new IllegalArgumentException("you are not allowed to specify both an entity " + + "and a values"); + } + if (mEntity != null && mValuesBackReferences != null) { + throw new IllegalArgumentException("you are not allowed to specify both an entity " + + "and a values backreference"); + } return new ContentProviderOperation(this); } @@ -295,7 +417,7 @@ public class ContentProviderOperation { * 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. + * that was specified by {@link #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. */ @@ -323,6 +445,20 @@ public class ContentProviderOperation { } /** + * 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 withEntity(Entity entity) { + if (mType != TYPE_INSERT && mType != TYPE_UPDATE) { + throw new IllegalArgumentException("only inserts and updates can have an entity"); + } + mEntity = entity; + 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 diff --git a/core/java/android/content/ContentProviderResult.java b/core/java/android/content/ContentProviderResult.java index 2e34e40..34aaa6d 100644 --- a/core/java/android/content/ContentProviderResult.java +++ b/core/java/android/content/ContentProviderResult.java @@ -17,12 +17,14 @@ package android.content; import android.net.Uri; +import android.os.Parcelable; +import android.os.Parcel; /** * 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 class ContentProviderResult implements Parcelable { public final Uri uri; public final Integer count; @@ -36,4 +38,40 @@ public class ContentProviderResult { this.count = count; this.uri = null; } + + public ContentProviderResult(Parcel source) { + int type = source.readInt(); + if (type == 1) { + count = source.readInt(); + uri = null; + } else { + count = null; + uri = Uri.CREATOR.createFromParcel(source); + } + } + + public void writeToParcel(Parcel dest, int flags) { + if (uri == null) { + dest.writeInt(1); + dest.writeInt(count); + } else { + dest.writeInt(2); + uri.writeToParcel(dest, 0); + } + } + + public int describeContents() { + return 0; + } + + public static final Creator<ContentProviderResult> CREATOR = + new Creator<ContentProviderResult>() { + public ContentProviderResult createFromParcel(Parcel source) { + return new ContentProviderResult(source); + } + + public ContentProviderResult[] newArray(int size) { + return new ContentProviderResult[size]; + } + }; }
\ No newline at end of file diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index f9bed59..f7b52fa 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -166,35 +166,69 @@ public abstract class ContentResolver { } } - class EntityIteratorWrapper implements EntityIterator { + /** + * EntityIterator wrapper that releases the associated ContentProviderClient when the + * iterator is closed. + */ + private class EntityIteratorWrapper implements EntityIterator { private final EntityIterator mInner; private final ContentProviderClient mClient; + private volatile boolean mClientReleased; EntityIteratorWrapper(EntityIterator inner, ContentProviderClient client) { mInner = inner; mClient = client; + mClientReleased = false; } public boolean hasNext() throws RemoteException { + if (mClientReleased) { + throw new IllegalStateException("this iterator is already closed"); + } return mInner.hasNext(); } public Entity next() throws RemoteException { + if (mClientReleased) { + throw new IllegalStateException("this iterator is already closed"); + } return mInner.next(); } public void close() { mClient.release(); mInner.close(); + mClientReleased = true; } protected void finalize() throws Throwable { - close(); + if (!mClientReleased) { + mClient.release(); + } super.finalize(); } } - public final EntityIterator queryEntity(Uri uri, + /** + * Query the given URI, returning an {@link EntityIterator} over the result set. + * + * @param uri The URI, using the content:// scheme, for the content to + * retrieve. + * @param selection A filter declaring which rows to return, formatted as an + * SQL WHERE clause (excluding the WHERE itself). Passing null will + * return all rows for the given URI. + * @param selectionArgs You may include ?s in selection, which will be + * replaced by the values from selectionArgs, in the order that they + * appear in the selection. The values will be bound as Strings. + * @param sortOrder How to order the rows, formatted as an SQL ORDER BY + * clause (excluding the ORDER BY itself). Passing null will use the + * default sort order, which may be unordered. + * @return An EntityIterator object + * @throws RemoteException thrown if a RemoteException is encountered while attempting + * to communicate with a remote provider. + * @throws IllegalArgumentException thrown if there is no provider that matches the uri + */ + public final EntityIterator queryEntities(Uri uri, String selection, String[] selectionArgs, String sortOrder) throws RemoteException { ContentProviderClient provider = acquireContentProviderClient(uri); if (provider == null) { @@ -532,6 +566,16 @@ public abstract class ContentResolver { } } + /** + * Inserts an Entity at the given URL. + * + * @param uri The URL of the table to insert into. + * @param entity an Entity to insert at uri. This must be the same Entity subtype that the + * matching content provider expects at that uri. + * @return the URL of the newly created row. + * @throws RemoteException thrown if a RemoteException is encountered while attempting + * to communicate with a remote provider. + */ public final Uri insertEntity(Uri uri, Entity entity) throws RemoteException { ContentProviderClient provider = acquireContentProviderClient(uri); if (provider == null) { @@ -544,6 +588,16 @@ public abstract class ContentResolver { } } + /** + * Replaces the Entity at the given URL with the provided entity. + * + * @param uri The URL of the entity to update. + * @param entity the new version of the entity + * @return the number of rows that are updated. Will be 0 if an entity no longer exists + * at uri otherwise it will be 1. + * @throws RemoteException thrown if a RemoteException is encountered while attempting + * to communicate with a remote provider. + */ public final int updateEntity(Uri uri, Entity entity) throws RemoteException { ContentProviderClient provider = acquireContentProviderClient(uri); if (provider == null) { @@ -556,27 +610,31 @@ public abstract class ContentResolver { } } - public final Uri[] bulkInsertEntities(Uri uri, Entity[] entities) - throws RemoteException { - ContentProviderClient provider = acquireContentProviderClient(uri); - if (provider == null) { - throw new IllegalArgumentException("Unknown URL " + uri); - } - try { - return provider.bulkInsertEntities(uri, entities); - } finally { - provider.release(); - } - } - - public final int[] bulkUpdateEntities(Uri uri, Entity[] entities) - throws RemoteException { - ContentProviderClient provider = acquireContentProviderClient(uri); + /** + * 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 authority the authority of the ContentProvider to which this batch should be applied + * @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. + * @throws RemoteException thrown if a RemoteException is encountered while attempting + * to communicate with a remote provider. + */ + public ContentProviderResult[] applyBatch(String authority, + ContentProviderOperation[] operations) + throws RemoteException, OperationApplicationException { + ContentProviderClient provider = acquireContentProviderClient(authority); if (provider == null) { - throw new IllegalArgumentException("Unknown URL " + uri); + throw new IllegalArgumentException("Unknown authority " + authority); } try { - return provider.bulkUpdateEntities(uri, entities); + return provider.applyBatch(operations); } finally { provider.release(); } diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java index b4d1865..f82c982 100644 --- a/core/java/android/content/IContentProvider.java +++ b/core/java/android/content/IContentProvider.java @@ -50,16 +50,18 @@ public interface IContentProvider extends IInterface { public Uri insert(Uri url, ContentValues initialValues) throws RemoteException; public int bulkInsert(Uri url, ContentValues[] initialValues) throws RemoteException; - public Uri[] bulkInsertEntities(Uri uri, Entity[] entities) throws RemoteException; + public Uri insertEntity(Uri uri, Entity entities) throws RemoteException; public int delete(Uri url, String selection, String[] selectionArgs) throws RemoteException; public int update(Uri url, ContentValues values, String selection, String[] selectionArgs) throws RemoteException; - public int[] bulkUpdateEntities(Uri uri, Entity[] entities) throws RemoteException; + public int updateEntity(Uri uri, Entity entity) throws RemoteException; public ParcelFileDescriptor openFile(Uri url, String mode) throws RemoteException, FileNotFoundException; public AssetFileDescriptor openAssetFile(Uri url, String mode) throws RemoteException, FileNotFoundException; + public ContentProviderResult[] applyBatch(ContentProviderOperation[] operations) + throws RemoteException, OperationApplicationException; /* IPC constants */ static final String descriptor = "android.content.IContentProvider"; @@ -72,7 +74,8 @@ public interface IContentProvider extends IInterface { static final int BULK_INSERT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 12; static final int OPEN_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 13; static final int OPEN_ASSET_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 14; - static final int BULK_INSERT_ENTITIES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 16; - static final int BULK_UPDATE_ENTITIES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 17; + static final int INSERT_ENTITIES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 16; + static final int UPDATE_ENTITIES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 17; static final int QUERY_ENTITIES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 18; + static final int APPLY_BATCH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 19; } diff --git a/core/java/android/content/IEntityIterator.java b/core/java/android/content/IEntityIterator.java index eb800f7..1887df2 100644 --- a/core/java/android/content/IEntityIterator.java +++ b/core/java/android/content/IEntityIterator.java @@ -25,6 +25,7 @@ import android.util.Log; /** * ICPC interface methods for an iterator over Entity objects. + * @hide */ public interface IEntityIterator extends IInterface { /** Local-side IPC implementation stub class. */ @@ -92,7 +93,6 @@ public interface IEntityIterator extends IInterface { return true; } reply.writeNoException(); - reply.writeString(entity.getClass().getName()); reply.writeParcelable(entity, 0); return true; } @@ -145,22 +145,15 @@ public interface IEntityIterator extends IInterface { public Entity next() throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); - Entity _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_next, _data, _reply, 0); _reply.readException(); - final String className = _reply.readString(); - final Class entityClass = Class.forName(className); - ClassLoader classLoader = entityClass.getClassLoader(); - _result = (Entity) _reply.readParcelable(classLoader); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); + return (Entity) _reply.readParcelable(null /* classLoader */); } finally { _reply.recycle(); _data.recycle(); } - return _result; } public void close() throws RemoteException { diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java index 78ef374..4ca6601 100644 --- a/core/java/android/database/DatabaseUtils.java +++ b/core/java/android/database/DatabaseUtils.java @@ -20,6 +20,7 @@ import org.apache.commons.codec.binary.Hex; import android.content.ContentValues; import android.content.Context; +import android.content.OperationApplicationException; import android.database.sqlite.SQLiteAbortException; import android.database.sqlite.SQLiteConstraintException; import android.database.sqlite.SQLiteDatabase; @@ -82,6 +83,8 @@ public class DatabaseUtils { code = 8; } else if (e instanceof SQLiteException) { code = 9; + } else if (e instanceof OperationApplicationException) { + code = 10; } else { reply.writeException(e); Log.e(TAG, "Writing exception to parcel", e); @@ -123,6 +126,18 @@ public class DatabaseUtils { } } + public static void readExceptionWithOperationApplicationExceptionFromParcel( + Parcel reply) throws OperationApplicationException { + int code = reply.readInt(); + if (code == 0) return; + String msg = reply.readString(); + if (code == 10) { + throw new OperationApplicationException(msg); + } else { + DatabaseUtils.readExceptionFromParcel(reply, msg, code); + } + } + private static final void readExceptionFromParcel(Parcel reply, String msg, int code) { switch (code) { case 2: diff --git a/test-runner/android/test/mock/MockContentProvider.java b/test-runner/android/test/mock/MockContentProvider.java index fdba2e2..757c72d 100644 --- a/test-runner/android/test/mock/MockContentProvider.java +++ b/test-runner/android/test/mock/MockContentProvider.java @@ -20,6 +20,9 @@ import android.content.ContentValues; import android.content.IContentProvider; import android.content.Entity; import android.content.EntityIterator; +import android.content.ContentProviderResult; +import android.content.ContentProviderOperation; +import android.content.OperationApplicationException; import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.database.CursorWindow; @@ -49,7 +52,7 @@ public class MockContentProvider implements IContentProvider { return 0; } - public Uri[] bulkInsertEntities(Uri uri, Entity[] entities) throws RemoteException { + public Uri insertEntity(Uri uri, Entity entities) throws RemoteException { throw new UnsupportedOperationException("unimplemented mock method"); } @@ -87,7 +90,11 @@ public class MockContentProvider implements IContentProvider { throws FileNotFoundException { throw new UnsupportedOperationException("unimplemented mock method"); } - + + public ContentProviderResult[] applyBatch(ContentProviderOperation[] operations) throws RemoteException, OperationApplicationException { + throw new UnsupportedOperationException("unimplemented mock method"); + } + @SuppressWarnings("unused") public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs, String sortOrder) throws RemoteException { @@ -105,7 +112,7 @@ public class MockContentProvider implements IContentProvider { throw new UnsupportedOperationException("unimplemented mock method"); } - public int[] bulkUpdateEntities(Uri uri, Entity[] entities) throws RemoteException { + public int updateEntity(Uri uri, Entity entity) throws RemoteException { throw new UnsupportedOperationException("unimplemented mock method"); } diff --git a/tests/AndroidTests/src/com/android/unit_tests/content/ContentProviderOperationTest.java b/tests/AndroidTests/src/com/android/unit_tests/content/ContentProviderOperationTest.java deleted file mode 100644 index 46a12fd..0000000 --- a/tests/AndroidTests/src/com/android/unit_tests/content/ContentProviderOperationTest.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * 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(); - } - } -}
\ No newline at end of file diff --git a/tests/FrameworkTest/tests/src/android/content/ContentProviderOperationTest.java b/tests/FrameworkTest/tests/src/android/content/ContentProviderOperationTest.java new file mode 100644 index 0000000..27241b8 --- /dev/null +++ b/tests/FrameworkTest/tests/src/android/content/ContentProviderOperationTest.java @@ -0,0 +1,508 @@ +/* + * 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.database.Cursor; +import android.net.Uri; +import android.os.Parcel; +import android.test.suitebuilder.annotation.SmallTest; +import android.text.TextUtils; +import junit.framework.TestCase; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; + +@SmallTest +public class ContentProviderOperationTest extends TestCase { + private final static Uri sTestUri1 = Uri.parse("content://authority/blah"); + private final static ContentValues sTestValues1; + + private final static Class<ContentProviderOperation.Builder> CLASS_BUILDER = + ContentProviderOperation.Builder.class; + private final static Class<ContentProviderOperation> CLASS_OPERATION = + ContentProviderOperation.class; + + 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)); + } + + public void testParcelingOperation() throws NoSuchFieldException, IllegalAccessException, + NoSuchMethodException, InvocationTargetException, InstantiationException { + Parcel parcel = Parcel.obtain(); + ContentProviderOperation op1; + ContentProviderOperation op2; + + HashMap<Integer, Integer> selArgsBackRef = new HashMap<Integer, Integer>(); + selArgsBackRef.put(1, 2); + selArgsBackRef.put(3, 4); + + ContentValues values = new ContentValues(); + values.put("v1", "val1"); + values.put("v2", "43"); + + ContentValues valuesBackRef = new ContentValues(); + values.put("v3", "val3"); + values.put("v4", "44"); + + try { + ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert( + Uri.parse("content://goo/bar")); + + builderSetEntity(builder, new TestEntity("blah")); + builderSetExpectedCount(builder, 42); + builderSetSelection(builder, "selection"); + builderSetSelectionArgs(builder, new String[]{"a", "b"}); + builderSetSelectionArgsBackReferences(builder, selArgsBackRef); + builderSetValues(builder, values); + builderSetValuesBackReferences(builder, valuesBackRef); + + op1 = newOperationFromBuilder(builder); + op1.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + op2 = ContentProviderOperation.CREATOR.createFromParcel(parcel); + + assertEquals(1 /* ContentProviderOperation.TYPE_INSERT */, operationGetType(op2)); + assertEquals("content://goo/bar", operationGetUri(op2).toString()); + assertEquals("blah", ((TestEntity) operationGetEntity(op2)).mValue); + assertEquals(Integer.valueOf(42), operationGetExpectedCount(op2)); + assertEquals("selection", operationGetSelection(op2)); + assertEquals(2, operationGetSelectionArgs(op2).length); + assertEquals("a", operationGetSelectionArgs(op2)[0]); + assertEquals("b", operationGetSelectionArgs(op2)[1]); + assertEquals(values, operationGetValues(op2)); + assertEquals(valuesBackRef, operationGetValuesBackReferences(op2)); + assertEquals(2, operationGetSelectionArgsBackReferences(op2).size()); + assertEquals(Integer.valueOf(2), operationGetSelectionArgsBackReferences(op2).get(1)); + assertEquals(Integer.valueOf(4), operationGetSelectionArgsBackReferences(op2).get(3)); + } finally { + parcel.recycle(); + } + + try { + ContentProviderOperation.Builder builder = ContentProviderOperation.newUpdate( + Uri.parse("content://goo/bar")); + + builderSetSelectionArgsBackReferences(builder, selArgsBackRef); + + op1 = newOperationFromBuilder(builder); + op1.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + op2 = ContentProviderOperation.CREATOR.createFromParcel(parcel); + assertEquals(2 /* ContentProviderOperation.TYPE_UPDATE */, operationGetType(op2)); + assertEquals("content://goo/bar", operationGetUri(op2).toString()); + assertNull(operationGetEntity(op2)); + assertNull(operationGetExpectedCount(op2)); + assertNull(operationGetSelection(op2)); + assertNull(operationGetSelectionArgs(op2)); + assertNull(operationGetValues(op2)); + assertNull(operationGetValuesBackReferences(op2)); + assertEquals(2, operationGetSelectionArgsBackReferences(op2).size()); + assertEquals(Integer.valueOf(2), operationGetSelectionArgsBackReferences(op2).get(1)); + assertEquals(Integer.valueOf(4), operationGetSelectionArgsBackReferences(op2).get(3)); + } finally { + parcel.recycle(); + } + + try { + ContentProviderOperation.Builder builder = ContentProviderOperation.newDelete( + Uri.parse("content://goo/bar")); + + op1 = newOperationFromBuilder(builder); + op1.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + op2 = ContentProviderOperation.CREATOR.createFromParcel(parcel); + assertEquals(3 /* ContentProviderOperation.TYPE_DELETE */, operationGetType(op2)); + assertEquals("content://goo/bar", operationGetUri(op2).toString()); + assertNull(operationGetEntity(op2)); + assertNull(operationGetExpectedCount(op2)); + assertNull(operationGetSelection(op2)); + assertNull(operationGetSelectionArgs(op2)); + assertNull(operationGetValues(op2)); + assertNull(operationGetValuesBackReferences(op2)); + assertNull(operationGetSelectionArgsBackReferences(op2)); + } finally { + parcel.recycle(); + } + } + + private static ContentProviderOperation newOperationFromBuilder( + ContentProviderOperation.Builder builder) + throws NoSuchMethodException, InstantiationException, IllegalAccessException, + InvocationTargetException { + final Constructor constructor = CLASS_OPERATION.getDeclaredConstructor(CLASS_BUILDER); + constructor.setAccessible(true); + return (ContentProviderOperation) constructor.newInstance(builder); + } + + private void builderSetSelectionArgsBackReferences( + ContentProviderOperation.Builder builder, HashMap<Integer, Integer> selArgsBackRef) + throws NoSuchFieldException, IllegalAccessException { + Field field; + field = CLASS_BUILDER.getDeclaredField("mSelectionArgsBackReferences"); + field.setAccessible(true); + field.set(builder, selArgsBackRef); + } + + private void builderSetValuesBackReferences( + ContentProviderOperation.Builder builder, ContentValues valuesBackReferences) + throws NoSuchFieldException, IllegalAccessException { + Field field; + field = CLASS_BUILDER.getDeclaredField("mValuesBackReferences"); + field.setAccessible(true); + field.set(builder, valuesBackReferences); + } + + private void builderSetSelection( + ContentProviderOperation.Builder builder, String selection) + throws NoSuchFieldException, IllegalAccessException { + Field field; + field = CLASS_BUILDER.getDeclaredField("mSelection"); + field.setAccessible(true); + field.set(builder, selection); + } + + private void builderSetSelectionArgs( + ContentProviderOperation.Builder builder, String[] selArgs) + throws NoSuchFieldException, IllegalAccessException { + Field field; + field = CLASS_BUILDER.getDeclaredField("mSelectionArgs"); + field.setAccessible(true); + field.set(builder, selArgs); + } + + private void builderSetValues( + ContentProviderOperation.Builder builder, ContentValues values) + throws NoSuchFieldException, IllegalAccessException { + Field field; + field = CLASS_BUILDER.getDeclaredField("mValues"); + field.setAccessible(true); + field.set(builder, values); + } + + private void builderSetEntity( + ContentProviderOperation.Builder builder, Entity entity) + throws NoSuchFieldException, IllegalAccessException { + Field field; + field = CLASS_BUILDER.getDeclaredField("mEntity"); + field.setAccessible(true); + field.set(builder, entity); + } + + private void builderSetExpectedCount( + ContentProviderOperation.Builder builder, Integer expectedCount) + throws NoSuchFieldException, IllegalAccessException { + Field field; + field = CLASS_BUILDER.getDeclaredField("mExpectedCount"); + field.setAccessible(true); + field.set(builder, expectedCount); + } + + private int operationGetType(ContentProviderOperation operation) + throws NoSuchFieldException, IllegalAccessException { + final Field field = CLASS_OPERATION.getDeclaredField("mType"); + field.setAccessible(true); + return field.getInt(operation); + } + + private Uri operationGetUri(ContentProviderOperation operation) + throws NoSuchFieldException, IllegalAccessException { + final Field field = CLASS_OPERATION.getDeclaredField("mUri"); + field.setAccessible(true); + return (Uri) field.get(operation); + } + + private String operationGetSelection(ContentProviderOperation operation) + throws NoSuchFieldException, IllegalAccessException { + final Field field = CLASS_OPERATION.getDeclaredField("mSelection"); + field.setAccessible(true); + return (String) field.get(operation); + } + + private String[] operationGetSelectionArgs(ContentProviderOperation operation) + throws NoSuchFieldException, IllegalAccessException { + final Field field = CLASS_OPERATION.getDeclaredField("mSelectionArgs"); + field.setAccessible(true); + return (String[]) field.get(operation); + } + + private ContentValues operationGetValues(ContentProviderOperation operation) + throws NoSuchFieldException, IllegalAccessException { + final Field field = CLASS_OPERATION.getDeclaredField("mValues"); + field.setAccessible(true); + return (ContentValues) field.get(operation); + } + + private Entity operationGetEntity(ContentProviderOperation operation) + throws NoSuchFieldException, IllegalAccessException { + final Field field = CLASS_OPERATION.getDeclaredField("mEntity"); + field.setAccessible(true); + return (Entity) field.get(operation); + } + + private Integer operationGetExpectedCount(ContentProviderOperation operation) + throws NoSuchFieldException, IllegalAccessException { + final Field field = CLASS_OPERATION.getDeclaredField("mExpectedCount"); + field.setAccessible(true); + return (Integer) field.get(operation); + } + + private ContentValues operationGetValuesBackReferences(ContentProviderOperation operation) + throws NoSuchFieldException, IllegalAccessException { + final Field field = CLASS_OPERATION.getDeclaredField("mValuesBackReferences"); + field.setAccessible(true); + return (ContentValues) field.get(operation); + } + + private Map<Integer, Integer> operationGetSelectionArgsBackReferences( + ContentProviderOperation operation) + throws NoSuchFieldException, IllegalAccessException { + final Field field = CLASS_OPERATION.getDeclaredField("mSelectionArgsBackReferences"); + field.setAccessible(true); + return (Map<Integer, Integer>) field.get(operation); + } + + public static class TestEntity extends Entity { + public final String mValue; + public TestEntity(String value) { + mValue = value; + } + + public TestEntity(Parcel source) { + mValue = source.readString(); + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mValue); + } + + public static final Creator<TestEntity> CREATOR = new Creator<TestEntity>() { + public TestEntity createFromParcel(Parcel source) { + return new TestEntity(source); + } + + public TestEntity[] newArray(int size) { + return new TestEntity[size]; + } + }; + } + + public void testParcelingResult() { + Parcel parcel = Parcel.obtain(); + ContentProviderResult result1; + ContentProviderResult result2; + try { + result1 = new ContentProviderResult(Uri.parse("content://goo/bar")); + result1.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + result2 = ContentProviderResult.CREATOR.createFromParcel(parcel); + assertEquals("content://goo/bar", result2.uri.toString()); + assertNull(result2.count); + } finally { + parcel.recycle(); + } + + parcel = Parcel.obtain(); + try { + result1 = new ContentProviderResult(42); + result1.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + result2 = ContentProviderResult.CREATOR.createFromParcel(parcel); + assertEquals(Integer.valueOf(42), result2.count); + assertNull(result2.uri); + } finally { + parcel.recycle(); + } + } + + 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(); + } + } +}
\ No newline at end of file |