Merge change 2723

* changes:
   - create a new generic ISyncAdapter implementation, SyncAdapterNew  - change the applyBatch to take an ArrayList rather than an []  - change Entity to be a final flass that contains ContentValues  - remove the ability to update/insert Entities by a ContentProviderOperation
This commit is contained in:
Android (Google) Code Review
2009-06-01 17:03:24 -07:00
19 changed files with 771 additions and 233 deletions

View File

@@ -1145,6 +1145,17 @@
visibility="public"
>
</field>
<field name="WRITE_EXTERNAL_STORAGE"
type="java.lang.String"
transient="false"
volatile="false"
value="&quot;android.permission.WRITE_EXTERNAL_STORAGE&quot;"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
<field name="WRITE_GSERVICES"
type="java.lang.String"
transient="false"
@@ -1167,17 +1178,6 @@
visibility="public"
>
</field>
<field name="WRITE_EXTERNAL_STORAGE"
type="java.lang.String"
transient="false"
volatile="false"
value="&quot;android.permission.WRITE_EXTERNAL_STORAGE&quot;"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
<field name="WRITE_SECURE_SETTINGS"
type="java.lang.String"
transient="false"
@@ -28029,7 +28029,7 @@
deprecated="not deprecated"
visibility="public"
>
<parameter name="operations" type="android.content.ContentProviderOperation[]">
<parameter name="operations" type="java.util.ArrayList&lt;android.content.ContentProviderOperation&gt;">
</parameter>
<exception name="OperationApplicationException" type="android.content.OperationApplicationException">
</exception>
@@ -28373,7 +28373,7 @@
deprecated="not deprecated"
visibility="public"
>
<parameter name="operations" type="android.content.ContentProviderOperation[]">
<parameter name="operations" type="java.util.ArrayList&lt;android.content.ContentProviderOperation&gt;">
</parameter>
<exception name="OperationApplicationException" type="android.content.OperationApplicationException">
</exception>
@@ -28808,19 +28808,6 @@
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,7 +28836,7 @@
<parameter name="selectionArgs" type="java.lang.String[]">
</parameter>
</method>
<method name="withSelectionBackReferences"
<method name="withSelectionBackReference"
return="android.content.ContentProviderOperation.Builder"
abstract="false"
native="false"
@@ -28859,7 +28846,24 @@
deprecated="not deprecated"
visibility="public"
>
<parameter name="backReferences" type="java.util.Map&lt;java.lang.Integer, java.lang.Integer&gt;">
<parameter name="selectionArgIndex" type="int">
</parameter>
<parameter name="previousResult" type="int">
</parameter>
</method>
<method name="withValueBackReference"
return="android.content.ContentProviderOperation.Builder"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="key" type="java.lang.String">
</parameter>
<parameter name="previousResult" type="int">
</parameter>
</method>
<method name="withValueBackReferences"
@@ -29126,7 +29130,7 @@
>
<parameter name="authority" type="java.lang.String">
</parameter>
<parameter name="operations" type="android.content.ContentProviderOperation[]">
<parameter name="operations" type="java.util.ArrayList&lt;android.content.ContentProviderOperation&gt;">
</parameter>
<exception name="OperationApplicationException" type="android.content.OperationApplicationException">
</exception>
@@ -29206,23 +29210,6 @@
<parameter name="values" type="android.content.ContentValues">
</parameter>
</method>
<method name="insertEntity"
return="android.net.Uri"
abstract="false"
native="false"
synchronized="false"
static="false"
final="true"
deprecated="not deprecated"
visibility="public"
>
<parameter name="uri" type="android.net.Uri">
</parameter>
<parameter name="entity" type="android.content.Entity">
</parameter>
<exception name="RemoteException" type="android.os.RemoteException">
</exception>
</method>
<method name="notifyChange"
return="void"
abstract="false"
@@ -29442,23 +29429,6 @@
<parameter name="selectionArgs" type="java.lang.String[]">
</parameter>
</method>
<method name="updateEntity"
return="int"
abstract="false"
native="false"
synchronized="false"
static="false"
final="true"
deprecated="not deprecated"
visibility="public"
>
<parameter name="uri" type="android.net.Uri">
</parameter>
<parameter name="entity" type="android.content.Entity">
</parameter>
<exception name="RemoteException" type="android.os.RemoteException">
</exception>
</method>
<method name="validateSyncExtrasBundle"
return="void"
abstract="false"
@@ -32623,9 +32593,9 @@
</interface>
<class name="Entity"
extends="java.lang.Object"
abstract="true"
abstract="false"
static="false"
final="false"
final="true"
deprecated="not deprecated"
visibility="public"
>
@@ -32638,7 +32608,123 @@
deprecated="not deprecated"
visibility="public"
>
<parameter name="values" type="android.content.ContentValues">
</parameter>
</constructor>
<method name="addSubValue"
return="void"
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="values" type="android.content.ContentValues">
</parameter>
</method>
<method name="describeContents"
return="int"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
</method>
<method name="getEntityValues"
return="android.content.ContentValues"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
</method>
<method name="getSubValues"
return="java.util.ArrayList&lt;android.content.Entity.NamedContentValues&gt;"
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>
</class>
<class name="Entity.NamedContentValues"
extends="java.lang.Object"
abstract="false"
static="true"
final="false"
deprecated="not deprecated"
visibility="public"
>
<constructor name="Entity.NamedContentValues"
type="android.content.Entity.NamedContentValues"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="uri" type="android.net.Uri">
</parameter>
<parameter name="values" type="android.content.ContentValues">
</parameter>
</constructor>
<field name="uri"
type="android.net.Uri"
transient="false"
volatile="false"
static="false"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
<field name="values"
type="android.content.ContentValues"
transient="false"
volatile="false"
static="false"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
</class>
<interface name="EntityIterator"
abstract="true"
@@ -44377,6 +44463,32 @@
<parameter name="columnIndex" type="int">
</parameter>
</method>
<method name="isFloat"
return="boolean"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="columnIndex" type="int">
</parameter>
</method>
<method name="isLong"
return="boolean"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="columnIndex" type="int">
</parameter>
</method>
<method name="isNull"
return="boolean"
abstract="false"
@@ -44390,6 +44502,19 @@
<parameter name="columnIndex" type="int">
</parameter>
</method>
<method name="isString"
return="boolean"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="columnIndex" type="int">
</parameter>
</method>
<method name="setWindow"
return="void"
abstract="false"
@@ -45470,6 +45595,36 @@
<parameter name="col" type="int">
</parameter>
</method>
<method name="isFloat"
return="boolean"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="row" type="int">
</parameter>
<parameter name="col" type="int">
</parameter>
</method>
<method name="isLong"
return="boolean"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="row" type="int">
</parameter>
<parameter name="col" type="int">
</parameter>
</method>
<method name="isNull"
return="boolean"
abstract="false"
@@ -45485,6 +45640,21 @@
<parameter name="col" type="int">
</parameter>
</method>
<method name="isString"
return="boolean"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="row" type="int">
</parameter>
<parameter name="col" type="int">
</parameter>
</method>
<method name="newFromParcel"
return="android.database.CursorWindow"
abstract="false"

View File

@@ -406,7 +406,7 @@ public abstract class AbstractSyncableContentProvider extends SyncableContentPro
* }
* </pre>
*
* @hide This method should be used only when {@link #applyBatch} is not enough and must be
* @hide This method should be used only when {@link ContentProvider#applyBatch} is not enough and must be
* used with {@link #endBatch}.
* e.g. If returned value has to be used during one transaction, this method might be useful.
*/
@@ -461,7 +461,7 @@ public abstract class AbstractSyncableContentProvider extends SyncableContentPro
}
}
public ContentProviderResult[] applyBatch(ContentProviderOperation[] operations)
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws OperationApplicationException {
boolean successful = false;
beginBatch();

View File

@@ -32,6 +32,7 @@ import android.os.ParcelFileDescriptor;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
/**
* Content providers are one of the primary building blocks of Android applications, providing
@@ -156,7 +157,7 @@ public abstract class ContentProvider implements ComponentCallbacks {
return ContentProvider.this.insertEntity(uri, entities);
}
public ContentProviderResult[] applyBatch(ContentProviderOperation[] operations)
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws OperationApplicationException {
for (ContentProviderOperation operation : operations) {
if (operation.isReadOperation()) {
@@ -641,11 +642,12 @@ public abstract class ContentProvider implements ComponentCallbacks {
* @throws OperationApplicationException thrown if an application fails.
* See {@link ContentProviderOperation#apply} for more information.
*/
public ContentProviderResult[] applyBatch(ContentProviderOperation[] operations)
public ContentProviderResult[] applyBatch(ArrayList<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);
final int numOperations = operations.size();
final ContentProviderResult[] results = new ContentProviderResult[numOperations];
for (int i = 0; i < numOperations; i++) {
results[i] = operations.get(i).apply(this, results, i);
}
return results;
}

View File

@@ -23,6 +23,7 @@ import android.os.ParcelFileDescriptor;
import android.content.res.AssetFileDescriptor;
import java.io.FileNotFoundException;
import java.util.ArrayList;
/**
* The public interface object used to interact with a {@link ContentProvider}. This is obtained by
@@ -105,7 +106,7 @@ public class ContentProviderClient {
}
/** see {@link ContentProvider#applyBatch} */
public ContentProviderResult[] applyBatch(ContentProviderOperation[] operations)
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws RemoteException, OperationApplicationException {
return mContentProvider.applyBatch(operations);
}

View File

@@ -33,6 +33,7 @@ import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import java.io.FileNotFoundException;
import java.util.ArrayList;
/**
* {@hide}
@@ -179,8 +180,12 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
case APPLY_BATCH_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
final ContentProviderOperation[] operations =
data.createTypedArray(ContentProviderOperation.CREATOR);
final int numOperations = data.readInt();
final ArrayList<ContentProviderOperation> operations =
new ArrayList<ContentProviderOperation>(numOperations);
for (int i = 0; i < numOperations; i++) {
operations.add(i, ContentProviderOperation.CREATOR.createFromParcel(data));
}
final ContentProviderResult[] results = applyBatch(operations);
reply.writeNoException();
reply.writeTypedArray(results, 0);
@@ -471,13 +476,16 @@ final class ContentProviderProxy implements IContentProvider
return count;
}
public ContentProviderResult[] applyBatch(ContentProviderOperation[] operations)
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws RemoteException, OperationApplicationException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IContentProvider.descriptor);
data.writeTypedArray(operations, 0);
data.writeInt(operations.size());
for (ContentProviderOperation operation : operations) {
operation.writeToParcel(data, 0);
}
mRemote.transact(IContentProvider.APPLY_BATCH_TRANSACTION, data, reply, 0);
DatabaseUtils.readExceptionWithOperationApplicationExceptionFromParcel(reply);

View File

@@ -24,8 +24,6 @@ import android.os.Parcel;
import java.util.Map;
import java.util.HashMap;
import dalvik.system.VMStack;
public class ContentProviderOperation implements Parcelable {
private final static int TYPE_INSERT = 1;
private final static int TYPE_UPDATE = 2;
@@ -37,7 +35,6 @@ public class ContentProviderOperation implements Parcelable {
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;
@@ -52,7 +49,6 @@ public class ContentProviderOperation implements Parcelable {
mType = builder.mType;
mUri = builder.mUri;
mValues = builder.mValues;
mEntity = builder.mEntity;
mSelection = builder.mSelection;
mSelectionArgs = builder.mSelectionArgs;
mExpectedCount = builder.mExpectedCount;
@@ -60,11 +56,10 @@ public class ContentProviderOperation implements Parcelable {
mValuesBackReferences = builder.mValuesBackReferences;
}
private ContentProviderOperation(Parcel source, ClassLoader classLoader) {
private ContentProviderOperation(Parcel source) {
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;
@@ -92,7 +87,6 @@ public class ContentProviderOperation implements Parcelable {
} else {
dest.writeInt(0);
}
dest.writeParcelable(mEntity, 0);
if (mSelection != null) {
dest.writeInt(1);
dest.writeString(mSelection);
@@ -184,7 +178,7 @@ public class ContentProviderOperation implements Parcelable {
* 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)}.
* {@link Builder#withSelectionBackReference}.
* @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.
@@ -201,12 +195,7 @@ public class ContentProviderOperation implements Parcelable {
resolveSelectionArgsBackReferences(backRefs, numBackRefs);
if (mType == TYPE_INSERT) {
Uri newUri;
if (mEntity != null) {
newUri = provider.insertEntity(mUri, mEntity);
} else {
newUri = provider.insert(mUri, values);
}
Uri newUri = provider.insert(mUri, values);
if (newUri == null) {
throw new OperationApplicationException("insert failed");
}
@@ -217,11 +206,7 @@ public class ContentProviderOperation implements Parcelable {
if (mType == TYPE_DELETE) {
numRows = provider.delete(mUri, mSelection, selectionArgs);
} else if (mType == TYPE_UPDATE) {
if (mEntity != null) {
numRows = provider.updateEntity(mUri, mEntity);
} else {
numRows = provider.update(mUri, values, mSelection, selectionArgs);
}
numRows = provider.update(mUri, values, mSelection, selectionArgs);
} else if (mType == TYPE_COUNT) {
Cursor cursor = provider.query(mUri, COUNT_COLUMNS, mSelection, selectionArgs, null);
try {
@@ -322,7 +307,7 @@ public class ContentProviderOperation implements Parcelable {
*/
private static String backRefToValue(ContentProviderResult[] backRefs, int numBackRefs,
Integer backRefIndex) {
if (backRefIndex > numBackRefs) {
if (backRefIndex >= numBackRefs) {
throw new ArrayIndexOutOfBoundsException("asked for back ref " + backRefIndex
+ " but there are only " + numBackRefs + " back refs");
}
@@ -343,7 +328,7 @@ public class ContentProviderOperation implements Parcelable {
public static final Creator<ContentProviderOperation> CREATOR =
new Creator<ContentProviderOperation>() {
public ContentProviderOperation createFromParcel(Parcel source) {
return new ContentProviderOperation(source, VMStack.getCallingClassLoader2());
return new ContentProviderOperation(source);
}
public ContentProviderOperation[] newArray(int size) {
@@ -368,7 +353,6 @@ public class ContentProviderOperation implements Parcelable {
private String mSelection;
private String[] mSelectionArgs;
private ContentValues mValues;
private Entity mEntity;
private Integer mExpectedCount;
private ContentValues mValuesBackReferences;
private Map<Integer, Integer> mSelectionArgsBackReferences;
@@ -384,14 +368,6 @@ public class ContentProviderOperation implements Parcelable {
/** 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);
}
@@ -414,19 +390,39 @@ public class ContentProviderOperation implements Parcelable {
}
/**
* 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
* Add a ContentValues back reference.
* 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 withValueBackReference(String key, int previousResult) {
if (mType != TYPE_INSERT && mType != TYPE_UPDATE) {
throw new IllegalArgumentException(
"only inserts and updates can have value back-references");
}
if (mValuesBackReferences == null) {
mValuesBackReferences = new ContentValues();
}
mValuesBackReferences.put(key, previousResult);
return this;
}
/**
* Add a back references as a selection arg. Any value at that index of the selection arg
* 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.
*/
public Builder withSelectionBackReferences(Map<Integer, Integer> backReferences) {
public Builder withSelectionBackReference(int selectionArgIndex, int previousResult) {
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;
if (mSelectionArgsBackReferences == null) {
mSelectionArgsBackReferences = new HashMap<Integer, Integer>();
}
mSelectionArgsBackReferences.put(selectionArgIndex, previousResult);
return this;
}
@@ -444,25 +440,11 @@ public class ContentProviderOperation implements Parcelable {
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 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
* specified by {@link #withSelectionBackReferences}.
* specified by {@link #withSelectionBackReference}.
* This can only be used with builders of type update, delete, or count query.
* @return this builder, to allow for chaining.
*/

View File

@@ -74,4 +74,11 @@ public class ContentProviderResult implements Parcelable {
return new ContentProviderResult[size];
}
};
public String toString() {
if (uri != null) {
return "ContentProviderResult(uri=" + uri.toString() + ")";
}
return "ContentProviderResult(count=" + count + ")";
}
}

View File

@@ -41,6 +41,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.ArrayList;
/**
@@ -566,50 +567,6 @@ 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) {
throw new IllegalArgumentException("Unknown URL " + uri);
}
try {
return provider.insertEntity(uri, entity);
} finally {
provider.release();
}
}
/**
* 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) {
throw new IllegalArgumentException("Unknown URL " + uri);
}
try {
return provider.updateEntity(uri, entity);
} finally {
provider.release();
}
}
/**
* Applies each of the {@link ContentProviderOperation} objects and returns an array
* of their results. Passes through OperationApplicationException, which may be thrown
@@ -627,7 +584,7 @@ public abstract class ContentResolver {
* to communicate with a remote provider.
*/
public ContentProviderResult[] applyBatch(String authority,
ContentProviderOperation[] operations)
ArrayList<ContentProviderOperation> operations)
throws RemoteException, OperationApplicationException {
ContentProviderClient provider = acquireContentProviderClient(authority);
if (provider == null) {

View File

@@ -17,10 +17,88 @@
package android.content;
import android.os.Parcelable;
import android.os.Parcel;
import android.net.Uri;
import android.util.Log;
import java.util.ArrayList;
/**
* Objects that pass through the ContentProvider and ContentResolver's methods that deal with
* Entities must implement this abstract base class and thus themselves be Parcelable.
*/
public abstract class Entity implements Parcelable {
public final class Entity implements Parcelable {
final private ContentValues mValues;
final private ArrayList<NamedContentValues> mSubValues;
public Entity(ContentValues values) {
mValues = values;
mSubValues = new ArrayList<NamedContentValues>();
}
public ContentValues getEntityValues() {
return mValues;
}
public ArrayList<NamedContentValues> getSubValues() {
return mSubValues;
}
public void addSubValue(Uri uri, ContentValues values) {
mSubValues.add(new Entity.NamedContentValues(uri, values));
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel dest, int flags) {
mValues.writeToParcel(dest, 0);
dest.writeInt(mSubValues.size());
for (NamedContentValues value : mSubValues) {
value.uri.writeToParcel(dest, 0);
value.values.writeToParcel(dest, 0);
}
}
private Entity(Parcel source) {
mValues = ContentValues.CREATOR.createFromParcel(source);
final int numValues = source.readInt();
mSubValues = new ArrayList<NamedContentValues>(numValues);
for (int i = 0; i < numValues; i++) {
final Uri uri = Uri.CREATOR.createFromParcel(source);
final ContentValues values = ContentValues.CREATOR.createFromParcel(source);
mSubValues.add(new NamedContentValues(uri, values));
}
}
public static final Creator<Entity> CREATOR = new Creator<Entity>() {
public Entity createFromParcel(Parcel source) {
return new Entity(source);
}
public Entity[] newArray(int size) {
return new Entity[size];
}
};
public static class NamedContentValues {
public final Uri uri;
public final ContentValues values;
public NamedContentValues(Uri uri, ContentValues values) {
this.uri = uri;
this.values = values;
}
}
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("Entity: ").append(getEntityValues());
for (Entity.NamedContentValues namedValue : getSubValues()) {
sb.append("\n ").append(namedValue.uri);
sb.append("\n -> ").append(namedValue.values);
}
return sb.toString();
}
}

View File

@@ -18,7 +18,7 @@ package android.content;
import android.os.RemoteException;
public interface EntityIterator {
public interface EntityIterator {
/**
* Returns whether there are more elements to iterate, i.e. whether the
* iterator is positioned in front of an element.

View File

@@ -28,6 +28,7 @@ import android.os.IInterface;
import android.os.ParcelFileDescriptor;
import java.io.FileNotFoundException;
import java.util.ArrayList;
/**
* The ipc interface to talk to a content provider.
@@ -60,7 +61,7 @@ public interface IContentProvider extends IInterface {
throws RemoteException, FileNotFoundException;
public AssetFileDescriptor openAssetFile(Uri url, String mode)
throws RemoteException, FileNotFoundException;
public ContentProviderResult[] applyBatch(ContentProviderOperation[] operations)
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws RemoteException, OperationApplicationException;
/* IPC constants */

View File

@@ -21,6 +21,7 @@ import android.os.IBinder;
import android.os.IInterface;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.Parcelable;
import android.util.Log;
/**
@@ -93,7 +94,7 @@ public interface IEntityIterator extends IInterface {
return true;
}
reply.writeNoException();
reply.writeParcelable(entity, 0);
entity.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
return true;
}
@@ -149,7 +150,7 @@ public interface IEntityIterator extends IInterface {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_next, _data, _reply, 0);
_reply.readException();
return (Entity) _reply.readParcelable(null /* classLoader */);
return Entity.CREATOR.createFromParcel(_reply);
} finally {
_reply.recycle();
_data.recycle();

View File

@@ -0,0 +1,135 @@
/*
* Copyright (C) 2006 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.os.*;
import android.os.Process;
import android.accounts.Account;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @hide
*/
public abstract class SyncAdapterNew {
private static final String TAG = "SyncAdapter";
private final Context mContext;
private final String mAuthority;
/** Kernel event log tag. Also listed in data/etc/event-log-tags. */
public static final int LOG_SYNC_DETAILS = 2743;
public SyncAdapterNew(Context context, String authority) {
mContext = context;
mAuthority = authority;
}
class Transport extends ISyncAdapter.Stub {
private final AtomicInteger mNumSyncStarts = new AtomicInteger(0);
private volatile Thread mSyncThread;
public void startSync(ISyncContext syncContext, Account account, Bundle extras) {
boolean alreadyInProgress;
synchronized (this) {
if (mSyncThread == null) {
mSyncThread = new Thread(
new SyncRunnable(new SyncContext(syncContext), account, extras),
"SyncAdapterThread-" + mNumSyncStarts.incrementAndGet());
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
mSyncThread.start();
alreadyInProgress = false;
} else {
alreadyInProgress = true;
}
}
if (alreadyInProgress) {
try {
syncContext.onFinished(SyncResult.ALREADY_IN_PROGRESS);
} catch (RemoteException e) {
// don't care if the caller is no longer around
}
}
}
public void cancelSync() {
synchronized (this) {
if (mSyncThread != null) {
mSyncThread.interrupt();
}
}
}
private class SyncRunnable implements Runnable {
private final SyncContext mSyncContext;
private final Account mAccount;
private final Bundle mExtras;
private SyncRunnable(SyncContext syncContext, Account account, Bundle extras) {
mSyncContext = syncContext;
mAccount = account;
mExtras = extras;
}
public void run() {
if (isCanceled()) {
return;
}
SyncResult syncResult = new SyncResult();
ContentProviderClient provider = mAuthority != null
? mContext.getContentResolver().acquireContentProviderClient(mAuthority)
: null;
try {
SyncAdapterNew.this.performSync(mAccount, mExtras, provider, syncResult);
} finally {
if (provider != null) {
provider.release();
}
if (!isCanceled()) {
mSyncContext.onFinished(syncResult);
}
mSyncThread = null;
}
}
private boolean isCanceled() {
return Thread.currentThread().isInterrupted();
}
}
}
Transport mTransport = new Transport();
/**
* Get the Transport object.
*/
public final ISyncAdapter getISyncAdapter() {
return mTransport;
}
/**
* Perform a sync for this account. SyncAdapter-specific parameters may
* be specified in extras, which is guaranteed to not be null. Invocations
* of this method are guaranteed to be serialized.
*
* @param account the account that should be synced
* @param extras SyncAdapter-specific parameters
*/
public abstract void performSync(Account account, Bundle extras,
ContentProviderClient provider, SyncResult syncResult);
}

View File

@@ -166,6 +166,48 @@ public abstract class AbstractWindowedCursor extends AbstractCursor
return mWindow.isBlob(mPos, columnIndex);
}
public boolean isString(int columnIndex)
{
checkPosition();
synchronized(mUpdatedRows) {
if (isFieldUpdated(columnIndex)) {
Object object = getUpdatedField(columnIndex);
return object == null || object instanceof String;
}
}
return mWindow.isString(mPos, columnIndex);
}
public boolean isLong(int columnIndex)
{
checkPosition();
synchronized(mUpdatedRows) {
if (isFieldUpdated(columnIndex)) {
Object object = getUpdatedField(columnIndex);
return object != null && (object instanceof Integer || object instanceof Long);
}
}
return mWindow.isLong(mPos, columnIndex);
}
public boolean isFloat(int columnIndex)
{
checkPosition();
synchronized(mUpdatedRows) {
if (isFieldUpdated(columnIndex)) {
Object object = getUpdatedField(columnIndex);
return object != null && (object instanceof Float || object instanceof Double);
}
}
return mWindow.isFloat(mPos, columnIndex);
}
@Override
protected void checkPosition()
{

View File

@@ -263,7 +263,58 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
}
}
/**
* Checks if a field contains a long
*
* @param row the row to read from, row - getStartPosition() being the actual row in the window
* @param col the column to read from
* @return {@code true} if given field is a long
*/
public boolean isLong(int row, int col) {
acquireReference();
try {
return isInteger_native(row - mStartPos, col);
} finally {
releaseReference();
}
}
/**
* Checks if a field contains a float.
*
* @param row the row to read from, row - getStartPosition() being the actual row in the window
* @param col the column to read from
* @return {@code true} if given field is a float
*/
public boolean isFloat(int row, int col) {
acquireReference();
try {
return isFloat_native(row - mStartPos, col);
} finally {
releaseReference();
}
}
/**
* Checks if a field contains either a String or is null.
*
* @param row the row to read from, row - getStartPosition() being the actual row in the window
* @param col the column to read from
* @return {@code true} if given field is {@code NULL} or a String
*/
public boolean isString(int row, int col) {
acquireReference();
try {
return isString_native(row - mStartPos, col);
} finally {
releaseReference();
}
}
private native boolean isBlob_native(int row, int col);
private native boolean isString_native(int row, int col);
private native boolean isInteger_native(int row, int col);
private native boolean isFloat_native(int row, int col);
/**
* Returns a String for the given field.

View File

@@ -31,6 +31,75 @@ public final class ContactsContract {
/** A content:// style uri to the authority for the contacts provider */
public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
public interface AccountsColumns {
/**
* The name of this account data
* <P>Type: TEXT</P>
*/
public static final String NAME = "name";
/**
* The name of this account data
* <P>Type: TEXT</P>
*/
public static final String TYPE = "type";
/**
* The name of this account data
* <P>Type: TEXT</P>
*/
public static final String DATA1 = "data1";
/**
* The value for this account data
* <P>Type: INTEGER</P>
*/
public static final String DATA2 = "data2";
/**
* The value for this account data
* <P>Type: INTEGER</P>
*/
public static final String DATA3 = "data3";
/**
* The value for this account data
* <P>Type: INTEGER</P>
*/
public static final String DATA4 = "data4";
/**
* The value for this account data
* <P>Type: INTEGER</P>
*/
public static final String DATA5 = "data5";
}
/**
* Constants for the aggregates table, which contains a record per group
* of contact representing the same person.
*/
public static final class Accounts implements BaseColumns, AccountsColumns {
/**
* This utility class cannot be instantiated
*/
private Accounts() {}
/**
* The content:// style URI for this table
*/
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "accounts");
/**
* The MIME type of {@link #CONTENT_URI} providing a directory of
* account data.
*/
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contacts_account";
/**
* The MIME type of a {@link #CONTENT_URI} subdirectory of a account
*/
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contacts_account";
}
public interface AggregatesColumns {
/**
* The display name for the contact.
@@ -135,6 +204,11 @@ public final class ContactsContract {
*/
private Contacts() {}
/**
* A reference to the {@link Accounts#_ID} that this data belongs to.
*/
public static final String ACCOUNTS_ID = "accounts_id";
/**
* A reference to the {@link Aggregates#_ID} that this data belongs to.
*/
@@ -166,6 +240,22 @@ public final class ContactsContract {
*/
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/person";
/**
* A string that uniquely identifies this contact to its source, which is referred to
* by the {@link #ACCOUNTS_ID}
*/
public static final String SOURCE_ID = "sourceid";
/**
* An integer that is updated whenever this contact or its data changes.
*/
public static final String VERSION = "version";
/**
* Set to 1 whenever the version changes
*/
public static final String DIRTY = "dirty";
/**
* A sub-directory of a single contact that contains all of their {@link Data} rows.
* To access this directory append

View File

@@ -229,7 +229,7 @@ static jboolean isBlob_native(JNIEnv* env, jobject object, jint row, jint column
{
int32_t err;
CursorWindow * window = GET_WINDOW(env, object);
LOG_WINDOW("Checking if column is a blob for %d,%d from %p", row, column, window);
LOG_WINDOW("Checking if column is a blob or null for %d,%d from %p", row, column, window);
field_slot_t field;
err = window->read_field_slot(row, column, &field);
@@ -241,6 +241,54 @@ LOG_WINDOW("Checking if column is a blob for %d,%d from %p", row, column, window
return field.type == FIELD_TYPE_BLOB || field.type == FIELD_TYPE_NULL;
}
static jboolean isString_native(JNIEnv* env, jobject object, jint row, jint column)
{
int32_t err;
CursorWindow * window = GET_WINDOW(env, object);
LOG_WINDOW("Checking if column is a string or null for %d,%d from %p", row, column, window);
field_slot_t field;
err = window->read_field_slot(row, column, &field);
if (err != 0) {
throwExceptionWithRowCol(env, row, column);
return NULL;
}
return field.type == FIELD_TYPE_STRING || field.type == FIELD_TYPE_NULL;
}
static jboolean isInteger_native(JNIEnv* env, jobject object, jint row, jint column)
{
int32_t err;
CursorWindow * window = GET_WINDOW(env, object);
LOG_WINDOW("Checking if column is an integer for %d,%d from %p", row, column, window);
field_slot_t field;
err = window->read_field_slot(row, column, &field);
if (err != 0) {
throwExceptionWithRowCol(env, row, column);
return NULL;
}
return field.type == FIELD_TYPE_INTEGER;
}
static jboolean isFloat_native(JNIEnv* env, jobject object, jint row, jint column)
{
int32_t err;
CursorWindow * window = GET_WINDOW(env, object);
LOG_WINDOW("Checking if column is a float for %d,%d from %p", row, column, window);
field_slot_t field;
err = window->read_field_slot(row, column, &field);
if (err != 0) {
throwExceptionWithRowCol(env, row, column);
return NULL;
}
return field.type == FIELD_TYPE_FLOAT;
}
static jstring getString_native(JNIEnv* env, jobject object, jint row, jint column)
{
int32_t err;
@@ -326,11 +374,11 @@ LOG_WINDOW("Copying string for %d,%d from %p", row, column, window);
jniThrowException(env, "java/lang/IllegalStateException", "Unable to get field slot");
return NULL;
}
jcharArray buffer = (jcharArray)env->GetObjectField(buf, gBufferField);
if (buffer == NULL) {
jniThrowException(env, "java/lang/IllegalStateException", "buf should not be null");
return NULL;
return NULL;
}
jchar* dst = env->GetCharArrayElements(buffer, NULL);
uint8_t type = field.type;
@@ -338,7 +386,7 @@ LOG_WINDOW("Copying string for %d,%d from %p", row, column, window);
jcharArray newArray = NULL;
if (type == FIELD_TYPE_STRING) {
uint32_t size = field.data.buffer.size;
if (size > 0) {
if (size > 0) {
#if WINDOW_STORAGE_UTF8
// Pass size - 1 since the UTF8 is null terminated and we don't want a null terminator on the UTF16 string
String16 utf16((char const *)window->offsetToPtr(field.data.buffer.offset), size - 1);
@@ -346,7 +394,7 @@ LOG_WINDOW("Copying string for %d,%d from %p", row, column, window);
if (strSize > bufferSize || dst == NULL) {
newArray = env->NewCharArray(strSize);
env->SetCharArrayRegion(newArray, 0, strSize, (jchar const *)utf16.string());
} else {
} else {
memcpy(dst, (jchar const *)utf16.string(), strSize * 2);
}
sizeCopied = strSize;
@@ -359,7 +407,7 @@ LOG_WINDOW("Copying string for %d,%d from %p", row, column, window);
memcpy(dst, (jchar const *)window->offsetToPtr(field.data.buffer.offset), size);
}
#endif
}
}
} else if (type == FIELD_TYPE_INTEGER) {
int64_t value;
if (window->getLong(row, column, &value)) {
@@ -628,6 +676,9 @@ static JNINativeMethod sMethods[] =
{"putDouble_native", "(DII)Z", (void *)putDouble_native},
{"freeLastRow_native", "()V", (void *)freeLastRow},
{"putNull_native", "(II)Z", (void *)putNull_native},
{"isString_native", "(II)Z", (void *)isString_native},
{"isFloat_native", "(II)Z", (void *)isFloat_native},
{"isInteger_native", "(II)Z", (void *)isInteger_native},
};
int register_android_database_CursorWindow(JNIEnv * env)
@@ -646,7 +697,7 @@ int register_android_database_CursorWindow(JNIEnv * env)
LOGE("Error locating fields");
return -1;
}
clazz = env->FindClass("android/database/CharArrayBuffer");
if (clazz == NULL) {
LOGE("Can't find android/database/CharArrayBuffer");

View File

@@ -34,6 +34,7 @@ import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import java.io.FileNotFoundException;
import java.util.ArrayList;
/**
* Mock implementation of IContentProvider that does nothing. All methods are non-functional and
@@ -91,7 +92,8 @@ public class MockContentProvider implements IContentProvider {
throw new UnsupportedOperationException("unimplemented mock method");
}
public ContentProviderResult[] applyBatch(ContentProviderOperation[] operations) throws RemoteException, OperationApplicationException {
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws RemoteException, OperationApplicationException {
throw new UnsupportedOperationException("unimplemented mock method");
}

View File

@@ -92,10 +92,6 @@ public class ContentProviderOperationTest extends TestCase {
}
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);
@@ -103,7 +99,8 @@ public class ContentProviderOperationTest extends TestCase {
previousResults[3] = new ContentProviderResult(103);
ContentProviderOperation op1 = ContentProviderOperation.newInsert(sTestUri1)
.withValues(sTestValues1)
.withValueBackReferences(valuesBackRefs)
.withValueBackReference("a1", 3)
.withValueBackReference("a2", 1)
.build();
ContentProviderResult result = op1.apply(new TestContentProvider() {
public Uri insert(Uri uri, ContentValues values) {
@@ -146,11 +143,6 @@ public class ContentProviderOperationTest extends TestCase {
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");
@@ -158,18 +150,15 @@ public class ContentProviderOperationTest extends TestCase {
ContentProviderOperation op1 = ContentProviderOperation.newInsert(sTestUri1)
.withValues(values)
.withValueBackReferences(valuesBackRefs)
.withValueBackReference("a1", 3)
.withValueBackReference("a2", 1)
.withValueBackReference("a3", 2)
.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);
@@ -179,7 +168,9 @@ public class ContentProviderOperationTest extends TestCase {
String[] selectionArgs = new String[]{"a", null, null, "b", null};
ContentProviderOperation op1 = ContentProviderOperation.newUpdate(sTestUri1)
.withSelectionBackReferences(selectionBackRefs)
.withSelectionBackReference(1, 3)
.withSelectionBackReference(2, 1)
.withSelectionBackReference(4, 2)
.withSelection("unused", selectionArgs)
.build();
String[] s2 = op1.resolveSelectionArgsBackReferences(
@@ -209,7 +200,6 @@ public class ContentProviderOperationTest extends TestCase {
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"});
@@ -224,7 +214,6 @@ public class ContentProviderOperationTest extends TestCase {
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);
@@ -422,35 +411,6 @@ public class ContentProviderOperationTest extends TestCase {
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;