Home | History | Annotate | Download | only in content
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.content;
     18 
     19 import android.annotation.UnsupportedAppUsage;
     20 import android.database.Cursor;
     21 import android.net.Uri;
     22 import android.os.Parcel;
     23 import android.os.Parcelable;
     24 import android.text.TextUtils;
     25 import android.util.Log;
     26 
     27 import java.util.ArrayList;
     28 import java.util.HashMap;
     29 import java.util.Map;
     30 
     31 /**
     32  * Represents a single operation to be performed as part of a batch of operations.
     33  *
     34  * @see ContentProvider#applyBatch(ArrayList)
     35  */
     36 public class ContentProviderOperation implements Parcelable {
     37     /** @hide exposed for unit tests */
     38     @UnsupportedAppUsage
     39     public final static int TYPE_INSERT = 1;
     40     /** @hide exposed for unit tests */
     41     @UnsupportedAppUsage
     42     public final static int TYPE_UPDATE = 2;
     43     /** @hide exposed for unit tests */
     44     @UnsupportedAppUsage
     45     public final static int TYPE_DELETE = 3;
     46     /** @hide exposed for unit tests */
     47     public final static int TYPE_ASSERT = 4;
     48 
     49     @UnsupportedAppUsage
     50     private final int mType;
     51     @UnsupportedAppUsage
     52     private final Uri mUri;
     53     @UnsupportedAppUsage
     54     private final String mSelection;
     55     private final String[] mSelectionArgs;
     56     private final ContentValues mValues;
     57     private final Integer mExpectedCount;
     58     private final ContentValues mValuesBackReferences;
     59     private final Map<Integer, Integer> mSelectionArgsBackReferences;
     60     private final boolean mYieldAllowed;
     61     private final boolean mFailureAllowed;
     62 
     63     private final static String TAG = "ContentProviderOperation";
     64 
     65     /**
     66      * Creates a {@link ContentProviderOperation} by copying the contents of a
     67      * {@link Builder}.
     68      */
     69     private ContentProviderOperation(Builder builder) {
     70         mType = builder.mType;
     71         mUri = builder.mUri;
     72         mValues = builder.mValues;
     73         mSelection = builder.mSelection;
     74         mSelectionArgs = builder.mSelectionArgs;
     75         mExpectedCount = builder.mExpectedCount;
     76         mSelectionArgsBackReferences = builder.mSelectionArgsBackReferences;
     77         mValuesBackReferences = builder.mValuesBackReferences;
     78         mYieldAllowed = builder.mYieldAllowed;
     79         mFailureAllowed = builder.mFailureAllowed;
     80     }
     81 
     82     private ContentProviderOperation(Parcel source) {
     83         mType = source.readInt();
     84         mUri = Uri.CREATOR.createFromParcel(source);
     85         mValues = source.readInt() != 0 ? ContentValues.CREATOR.createFromParcel(source) : null;
     86         mSelection = source.readInt() != 0 ? source.readString() : null;
     87         mSelectionArgs = source.readInt() != 0 ? source.readStringArray() : null;
     88         mExpectedCount = source.readInt() != 0 ? source.readInt() : null;
     89         mValuesBackReferences = source.readInt() != 0
     90                 ? ContentValues.CREATOR.createFromParcel(source)
     91                 : null;
     92         mSelectionArgsBackReferences = source.readInt() != 0
     93                 ? new HashMap<Integer, Integer>()
     94                 : null;
     95         if (mSelectionArgsBackReferences != null) {
     96             final int count = source.readInt();
     97             for (int i = 0; i < count; i++) {
     98                 mSelectionArgsBackReferences.put(source.readInt(), source.readInt());
     99             }
    100         }
    101         mYieldAllowed = source.readInt() != 0;
    102         mFailureAllowed = source.readInt() != 0;
    103     }
    104 
    105     /** @hide */
    106     public ContentProviderOperation(ContentProviderOperation cpo, Uri withUri) {
    107         mType = cpo.mType;
    108         mUri = withUri;
    109         mValues = cpo.mValues;
    110         mSelection = cpo.mSelection;
    111         mSelectionArgs = cpo.mSelectionArgs;
    112         mExpectedCount = cpo.mExpectedCount;
    113         mSelectionArgsBackReferences = cpo.mSelectionArgsBackReferences;
    114         mValuesBackReferences = cpo.mValuesBackReferences;
    115         mYieldAllowed = cpo.mYieldAllowed;
    116         mFailureAllowed = cpo.mFailureAllowed;
    117     }
    118 
    119     public void writeToParcel(Parcel dest, int flags) {
    120         dest.writeInt(mType);
    121         Uri.writeToParcel(dest, mUri);
    122         if (mValues != null) {
    123             dest.writeInt(1);
    124             mValues.writeToParcel(dest, 0);
    125         } else {
    126             dest.writeInt(0);
    127         }
    128         if (mSelection != null) {
    129             dest.writeInt(1);
    130             dest.writeString(mSelection);
    131         } else {
    132             dest.writeInt(0);
    133         }
    134         if (mSelectionArgs != null) {
    135             dest.writeInt(1);
    136             dest.writeStringArray(mSelectionArgs);
    137         } else {
    138             dest.writeInt(0);
    139         }
    140         if (mExpectedCount != null) {
    141             dest.writeInt(1);
    142             dest.writeInt(mExpectedCount);
    143         } else {
    144             dest.writeInt(0);
    145         }
    146         if (mValuesBackReferences != null) {
    147             dest.writeInt(1);
    148             mValuesBackReferences.writeToParcel(dest, 0);
    149         } else {
    150             dest.writeInt(0);
    151         }
    152         if (mSelectionArgsBackReferences != null) {
    153             dest.writeInt(1);
    154             dest.writeInt(mSelectionArgsBackReferences.size());
    155             for (Map.Entry<Integer, Integer> entry : mSelectionArgsBackReferences.entrySet()) {
    156                 dest.writeInt(entry.getKey());
    157                 dest.writeInt(entry.getValue());
    158             }
    159         } else {
    160             dest.writeInt(0);
    161         }
    162         dest.writeInt(mYieldAllowed ? 1 : 0);
    163         dest.writeInt(mFailureAllowed ? 1 : 0);
    164     }
    165 
    166     /**
    167      * Create a {@link Builder} suitable for building an insert {@link ContentProviderOperation}.
    168      * @param uri The {@link Uri} that is the target of the insert.
    169      * @return a {@link Builder}
    170      */
    171     public static Builder newInsert(Uri uri) {
    172         return new Builder(TYPE_INSERT, uri);
    173     }
    174 
    175     /**
    176      * Create a {@link Builder} suitable for building an update {@link ContentProviderOperation}.
    177      * @param uri The {@link Uri} that is the target of the update.
    178      * @return a {@link Builder}
    179      */
    180     public static Builder newUpdate(Uri uri) {
    181         return new Builder(TYPE_UPDATE, uri);
    182     }
    183 
    184     /**
    185      * Create a {@link Builder} suitable for building a delete {@link ContentProviderOperation}.
    186      * @param uri The {@link Uri} that is the target of the delete.
    187      * @return a {@link Builder}
    188      */
    189     public static Builder newDelete(Uri uri) {
    190         return new Builder(TYPE_DELETE, uri);
    191     }
    192 
    193     /**
    194      * Create a {@link Builder} suitable for building a
    195      * {@link ContentProviderOperation} to assert a set of values as provided
    196      * through {@link Builder#withValues(ContentValues)}.
    197      */
    198     public static Builder newAssertQuery(Uri uri) {
    199         return new Builder(TYPE_ASSERT, uri);
    200     }
    201 
    202     /**
    203      * Gets the Uri for the target of the operation.
    204      */
    205     public Uri getUri() {
    206         return mUri;
    207     }
    208 
    209     /**
    210      * Returns true if the operation allows yielding the database to other transactions
    211      * if the database is contended.
    212      *
    213      * @see android.database.sqlite.SQLiteDatabase#yieldIfContendedSafely()
    214      */
    215     public boolean isYieldAllowed() {
    216         return mYieldAllowed;
    217     }
    218 
    219     /** {@hide} */
    220     public boolean isFailureAllowed() {
    221         return mFailureAllowed;
    222     }
    223 
    224     /** @hide exposed for unit tests */
    225     @UnsupportedAppUsage
    226     public int getType() {
    227         return mType;
    228     }
    229 
    230     /**
    231      * Returns true if the operation represents an insertion.
    232      *
    233      * @see #newInsert
    234      */
    235     public boolean isInsert() {
    236         return mType == TYPE_INSERT;
    237     }
    238 
    239     /**
    240      * Returns true if the operation represents a deletion.
    241      *
    242      * @see #newDelete
    243      */
    244     public boolean isDelete() {
    245         return mType == TYPE_DELETE;
    246     }
    247 
    248     /**
    249      * Returns true if the operation represents an update.
    250      *
    251      * @see #newUpdate
    252      */
    253     public boolean isUpdate() {
    254         return mType == TYPE_UPDATE;
    255     }
    256 
    257     /**
    258      * Returns true if the operation represents an assert query.
    259      *
    260      * @see #newAssertQuery
    261      */
    262     public boolean isAssertQuery() {
    263         return mType == TYPE_ASSERT;
    264     }
    265 
    266     /**
    267      * Returns true if the operation represents an insertion, deletion, or update.
    268      *
    269      * @see #isInsert
    270      * @see #isDelete
    271      * @see #isUpdate
    272      */
    273     public boolean isWriteOperation() {
    274         return mType == TYPE_DELETE || mType == TYPE_INSERT || mType == TYPE_UPDATE;
    275     }
    276 
    277     /**
    278      * Returns true if the operation represents an assert query.
    279      *
    280      * @see #isAssertQuery
    281      */
    282     public boolean isReadOperation() {
    283         return mType == TYPE_ASSERT;
    284     }
    285 
    286     /**
    287      * Applies this operation using the given provider. The backRefs array is used to resolve any
    288      * back references that were requested using
    289      * {@link Builder#withValueBackReferences(ContentValues)} and
    290      * {@link Builder#withSelectionBackReference}.
    291      * @param provider the {@link ContentProvider} on which this batch is applied
    292      * @param backRefs a {@link ContentProviderResult} array that will be consulted
    293      * to resolve any requested back references.
    294      * @param numBackRefs the number of valid results on the backRefs array.
    295      * @return a {@link ContentProviderResult} that contains either the {@link Uri} of the inserted
    296      * row if this was an insert otherwise the number of rows affected.
    297      * @throws OperationApplicationException thrown if either the insert fails or
    298      * if the number of rows affected didn't match the expected count
    299      */
    300     public ContentProviderResult apply(ContentProvider provider, ContentProviderResult[] backRefs,
    301             int numBackRefs) throws OperationApplicationException {
    302         if (mFailureAllowed) {
    303             try {
    304                 return applyInternal(provider, backRefs, numBackRefs);
    305             } catch (Exception e) {
    306                 return new ContentProviderResult(e.getMessage());
    307             }
    308         } else {
    309             return applyInternal(provider, backRefs, numBackRefs);
    310         }
    311     }
    312 
    313     private ContentProviderResult applyInternal(ContentProvider provider,
    314             ContentProviderResult[] backRefs, int numBackRefs)
    315             throws OperationApplicationException {
    316         ContentValues values = resolveValueBackReferences(backRefs, numBackRefs);
    317         String[] selectionArgs =
    318                 resolveSelectionArgsBackReferences(backRefs, numBackRefs);
    319 
    320         if (mType == TYPE_INSERT) {
    321             final Uri newUri = provider.insert(mUri, values);
    322             if (newUri != null) {
    323                 return new ContentProviderResult(newUri);
    324             } else {
    325                 throw new OperationApplicationException(
    326                         "Insert into " + mUri + " returned no result");
    327             }
    328         }
    329 
    330         final int numRows;
    331         if (mType == TYPE_DELETE) {
    332             numRows = provider.delete(mUri, mSelection, selectionArgs);
    333         } else if (mType == TYPE_UPDATE) {
    334             numRows = provider.update(mUri, values, mSelection, selectionArgs);
    335         } else if (mType == TYPE_ASSERT) {
    336             // Assert that all rows match expected values
    337             String[] projection =  null;
    338             if (values != null) {
    339                 // Build projection map from expected values
    340                 final ArrayList<String> projectionList = new ArrayList<String>();
    341                 for (Map.Entry<String, Object> entry : values.valueSet()) {
    342                     projectionList.add(entry.getKey());
    343                 }
    344                 projection = projectionList.toArray(new String[projectionList.size()]);
    345             }
    346             final Cursor cursor = provider.query(mUri, projection, mSelection, selectionArgs, null);
    347             try {
    348                 numRows = cursor.getCount();
    349                 if (projection != null) {
    350                     while (cursor.moveToNext()) {
    351                         for (int i = 0; i < projection.length; i++) {
    352                             final String cursorValue = cursor.getString(i);
    353                             final String expectedValue = values.getAsString(projection[i]);
    354                             if (!TextUtils.equals(cursorValue, expectedValue)) {
    355                                 // Throw exception when expected values don't match
    356                                 throw new OperationApplicationException("Found value " + cursorValue
    357                                         + " when expected " + expectedValue + " for column "
    358                                         + projection[i]);
    359                             }
    360                         }
    361                     }
    362                 }
    363             } finally {
    364                 cursor.close();
    365             }
    366         } else {
    367             throw new IllegalStateException("bad type, " + mType);
    368         }
    369 
    370         if (mExpectedCount != null && mExpectedCount != numRows) {
    371             throw new OperationApplicationException(
    372                     "Expected " + mExpectedCount + " rows but actual " + numRows);
    373         }
    374 
    375         return new ContentProviderResult(numRows);
    376     }
    377 
    378     /**
    379      * The ContentValues back references are represented as a ContentValues object where the
    380      * key refers to a column and the value is an index of the back reference whose
    381      * valued should be associated with the column.
    382      * <p>
    383      * This is intended to be a private method but it is exposed for
    384      * unit testing purposes
    385      * @param backRefs an array of previous results
    386      * @param numBackRefs the number of valid previous results in backRefs
    387      * @return the ContentValues that should be used in this operation application after
    388      * expansion of back references. This can be called if either mValues or mValuesBackReferences
    389      * is null
    390      */
    391     public ContentValues resolveValueBackReferences(
    392             ContentProviderResult[] backRefs, int numBackRefs) {
    393         if (mValuesBackReferences == null) {
    394             return mValues;
    395         }
    396         final ContentValues values;
    397         if (mValues == null) {
    398             values = new ContentValues();
    399         } else {
    400             values = new ContentValues(mValues);
    401         }
    402         for (Map.Entry<String, Object> entry : mValuesBackReferences.valueSet()) {
    403             String key = entry.getKey();
    404             Integer backRefIndex = mValuesBackReferences.getAsInteger(key);
    405             if (backRefIndex == null) {
    406                 Log.e(TAG, this.toString());
    407                 throw new IllegalArgumentException("values backref " + key + " is not an integer");
    408             }
    409             values.put(key, backRefToValue(backRefs, numBackRefs, backRefIndex));
    410         }
    411         return values;
    412     }
    413 
    414     /**
    415      * The Selection Arguments back references are represented as a Map of Integer->Integer where
    416      * the key is an index into the selection argument array (see {@link Builder#withSelection})
    417      * and the value is the index of the previous result that should be used for that selection
    418      * argument array slot.
    419      * <p>
    420      * This is intended to be a private method but it is exposed for
    421      * unit testing purposes
    422      * @param backRefs an array of previous results
    423      * @param numBackRefs the number of valid previous results in backRefs
    424      * @return the ContentValues that should be used in this operation application after
    425      * expansion of back references. This can be called if either mValues or mValuesBackReferences
    426      * is null
    427      */
    428     public String[] resolveSelectionArgsBackReferences(
    429             ContentProviderResult[] backRefs, int numBackRefs) {
    430         if (mSelectionArgsBackReferences == null) {
    431             return mSelectionArgs;
    432         }
    433         String[] newArgs = new String[mSelectionArgs.length];
    434         System.arraycopy(mSelectionArgs, 0, newArgs, 0, mSelectionArgs.length);
    435         for (Map.Entry<Integer, Integer> selectionArgBackRef
    436                 : mSelectionArgsBackReferences.entrySet()) {
    437             final Integer selectionArgIndex = selectionArgBackRef.getKey();
    438             final int backRefIndex = selectionArgBackRef.getValue();
    439             newArgs[selectionArgIndex] =
    440                     String.valueOf(backRefToValue(backRefs, numBackRefs, backRefIndex));
    441         }
    442         return newArgs;
    443     }
    444 
    445     @Override
    446     public String toString() {
    447         return "mType: " + mType + ", mUri: " + mUri +
    448                 ", mSelection: " + mSelection +
    449                 ", mExpectedCount: " + mExpectedCount +
    450                 ", mYieldAllowed: " + mYieldAllowed +
    451                 ", mValues: " + mValues +
    452                 ", mValuesBackReferences: " + mValuesBackReferences +
    453                 ", mSelectionArgsBackReferences: " + mSelectionArgsBackReferences;
    454     }
    455 
    456     /**
    457      * Return the string representation of the requested back reference.
    458      * @param backRefs an array of results
    459      * @param numBackRefs the number of items in the backRefs array that are valid
    460      * @param backRefIndex which backRef to be used
    461      * @throws ArrayIndexOutOfBoundsException thrown if the backRefIndex is larger than
    462      * the numBackRefs
    463      * @return the string representation of the requested back reference.
    464      */
    465     private long backRefToValue(ContentProviderResult[] backRefs, int numBackRefs,
    466             Integer backRefIndex) {
    467         if (backRefIndex >= numBackRefs) {
    468             Log.e(TAG, this.toString());
    469             throw new ArrayIndexOutOfBoundsException("asked for back ref " + backRefIndex
    470                     + " but there are only " + numBackRefs + " back refs");
    471         }
    472         ContentProviderResult backRef = backRefs[backRefIndex];
    473         long backRefValue;
    474         if (backRef.uri != null) {
    475             backRefValue = ContentUris.parseId(backRef.uri);
    476         } else {
    477             backRefValue = backRef.count;
    478         }
    479         return backRefValue;
    480     }
    481 
    482     public int describeContents() {
    483         return 0;
    484     }
    485 
    486     public static final @android.annotation.NonNull Creator<ContentProviderOperation> CREATOR =
    487             new Creator<ContentProviderOperation>() {
    488         public ContentProviderOperation createFromParcel(Parcel source) {
    489             return new ContentProviderOperation(source);
    490         }
    491 
    492         public ContentProviderOperation[] newArray(int size) {
    493             return new ContentProviderOperation[size];
    494         }
    495     };
    496 
    497     /**
    498      * Used to add parameters to a {@link ContentProviderOperation}. The {@link Builder} is
    499      * first created by calling {@link ContentProviderOperation#newInsert(android.net.Uri)},
    500      * {@link ContentProviderOperation#newUpdate(android.net.Uri)},
    501      * {@link ContentProviderOperation#newDelete(android.net.Uri)} or
    502      * {@link ContentProviderOperation#newAssertQuery(Uri)}. The withXXX methods
    503      * can then be used to add parameters to the builder. See the specific methods to find for
    504      * which {@link Builder} type each is allowed. Call {@link #build} to create the
    505      * {@link ContentProviderOperation} once all the parameters have been supplied.
    506      */
    507     public static class Builder {
    508         private final int mType;
    509         private final Uri mUri;
    510         private String mSelection;
    511         private String[] mSelectionArgs;
    512         private ContentValues mValues;
    513         private Integer mExpectedCount;
    514         private ContentValues mValuesBackReferences;
    515         private Map<Integer, Integer> mSelectionArgsBackReferences;
    516         private boolean mYieldAllowed;
    517         private boolean mFailureAllowed;
    518 
    519         /** Create a {@link Builder} of a given type. The uri must not be null. */
    520         private Builder(int type, Uri uri) {
    521             if (uri == null) {
    522                 throw new IllegalArgumentException("uri must not be null");
    523             }
    524             mType = type;
    525             mUri = uri;
    526         }
    527 
    528         /** Create a ContentProviderOperation from this {@link Builder}. */
    529         public ContentProviderOperation build() {
    530             if (mType == TYPE_UPDATE) {
    531                 if ((mValues == null || mValues.isEmpty())
    532                       && (mValuesBackReferences == null || mValuesBackReferences.isEmpty())) {
    533                     throw new IllegalArgumentException("Empty values");
    534                 }
    535             }
    536             if (mType == TYPE_ASSERT) {
    537                 if ((mValues == null || mValues.isEmpty())
    538                       && (mValuesBackReferences == null || mValuesBackReferences.isEmpty())
    539                         && (mExpectedCount == null)) {
    540                     throw new IllegalArgumentException("Empty values");
    541                 }
    542             }
    543             return new ContentProviderOperation(this);
    544         }
    545 
    546         /**
    547          * Add a {@link ContentValues} of back references. The key is the name of the column
    548          * and the value is an integer that is the index of the previous result whose
    549          * value should be used for the column. The value is added as a {@link String}.
    550          * A column value from the back references takes precedence over a value specified in
    551          * {@link #withValues}.
    552          * This can only be used with builders of type insert, update, or assert.
    553          * @return this builder, to allow for chaining.
    554          */
    555         public Builder withValueBackReferences(ContentValues backReferences) {
    556             if (mType != TYPE_INSERT && mType != TYPE_UPDATE && mType != TYPE_ASSERT) {
    557                 throw new IllegalArgumentException(
    558                         "only inserts, updates, and asserts can have value back-references");
    559             }
    560             mValuesBackReferences = backReferences;
    561             return this;
    562         }
    563 
    564         /**
    565          * Add a ContentValues back reference.
    566          * A column value from the back references takes precedence over a value specified in
    567          * {@link #withValues}.
    568          * This can only be used with builders of type insert, update, or assert.
    569          * @return this builder, to allow for chaining.
    570          */
    571         public Builder withValueBackReference(String key, int previousResult) {
    572             if (mType != TYPE_INSERT && mType != TYPE_UPDATE && mType != TYPE_ASSERT) {
    573                 throw new IllegalArgumentException(
    574                         "only inserts, updates, and asserts can have value back-references");
    575             }
    576             if (mValuesBackReferences == null) {
    577                 mValuesBackReferences = new ContentValues();
    578             }
    579             mValuesBackReferences.put(key, previousResult);
    580             return this;
    581         }
    582 
    583         /**
    584          * Add a back references as a selection arg. Any value at that index of the selection arg
    585          * that was specified by {@link #withSelection} will be overwritten.
    586          * This can only be used with builders of type update, delete, or assert.
    587          * @return this builder, to allow for chaining.
    588          */
    589         public Builder withSelectionBackReference(int selectionArgIndex, int previousResult) {
    590             if (mType != TYPE_UPDATE && mType != TYPE_DELETE && mType != TYPE_ASSERT) {
    591                 throw new IllegalArgumentException("only updates, deletes, and asserts "
    592                         + "can have selection back-references");
    593             }
    594             if (mSelectionArgsBackReferences == null) {
    595                 mSelectionArgsBackReferences = new HashMap<Integer, Integer>();
    596             }
    597             mSelectionArgsBackReferences.put(selectionArgIndex, previousResult);
    598             return this;
    599         }
    600 
    601         /**
    602          * The ContentValues to use. This may be null. These values may be overwritten by
    603          * the corresponding value specified by {@link #withValueBackReference} or by
    604          * future calls to {@link #withValues} or {@link #withValue}.
    605          * This can only be used with builders of type insert, update, or assert.
    606          * @return this builder, to allow for chaining.
    607          */
    608         public Builder withValues(ContentValues values) {
    609             if (mType != TYPE_INSERT && mType != TYPE_UPDATE && mType != TYPE_ASSERT) {
    610                 throw new IllegalArgumentException(
    611                         "only inserts, updates, and asserts can have values");
    612             }
    613             if (mValues == null) {
    614                 mValues = new ContentValues();
    615             }
    616             mValues.putAll(values);
    617             return this;
    618         }
    619 
    620         /**
    621          * A value to insert or update. This value may be overwritten by
    622          * the corresponding value specified by {@link #withValueBackReference}.
    623          * This can only be used with builders of type insert, update, or assert.
    624          * @param key the name of this value
    625          * @param value the value itself. the type must be acceptable for insertion by
    626          * {@link ContentValues#put}
    627          * @return this builder, to allow for chaining.
    628          */
    629         public Builder withValue(String key, Object value) {
    630             if (mType != TYPE_INSERT && mType != TYPE_UPDATE && mType != TYPE_ASSERT) {
    631                 throw new IllegalArgumentException("only inserts and updates can have values");
    632             }
    633             if (mValues == null) {
    634                 mValues = new ContentValues();
    635             }
    636             if (value == null) {
    637                 mValues.putNull(key);
    638             } else if (value instanceof String) {
    639                 mValues.put(key, (String) value);
    640             } else if (value instanceof Byte) {
    641                 mValues.put(key, (Byte) value);
    642             } else if (value instanceof Short) {
    643                 mValues.put(key, (Short) value);
    644             } else if (value instanceof Integer) {
    645                 mValues.put(key, (Integer) value);
    646             } else if (value instanceof Long) {
    647                 mValues.put(key, (Long) value);
    648             } else if (value instanceof Float) {
    649                 mValues.put(key, (Float) value);
    650             } else if (value instanceof Double) {
    651                 mValues.put(key, (Double) value);
    652             } else if (value instanceof Boolean) {
    653                 mValues.put(key, (Boolean) value);
    654             } else if (value instanceof byte[]) {
    655                 mValues.put(key, (byte[]) value);
    656             } else {
    657                 throw new IllegalArgumentException("bad value type: " + value.getClass().getName());
    658             }
    659             return this;
    660         }
    661 
    662         /**
    663          * The selection and arguments to use. An occurrence of '?' in the selection will be
    664          * replaced with the corresponding occurrence of the selection argument. Any of the
    665          * selection arguments may be overwritten by a selection argument back reference as
    666          * specified by {@link #withSelectionBackReference}.
    667          * This can only be used with builders of type update, delete, or assert.
    668          * @return this builder, to allow for chaining.
    669          */
    670         public Builder withSelection(String selection, String[] selectionArgs) {
    671             if (mType != TYPE_UPDATE && mType != TYPE_DELETE && mType != TYPE_ASSERT) {
    672                 throw new IllegalArgumentException(
    673                         "only updates, deletes, and asserts can have selections");
    674             }
    675             mSelection = selection;
    676             if (selectionArgs == null) {
    677                 mSelectionArgs = null;
    678             } else {
    679                 mSelectionArgs = new String[selectionArgs.length];
    680                 System.arraycopy(selectionArgs, 0, mSelectionArgs, 0, selectionArgs.length);
    681             }
    682             return this;
    683         }
    684 
    685         /**
    686          * If set then if the number of rows affected by this operation does not match
    687          * this count {@link OperationApplicationException} will be throw.
    688          * This can only be used with builders of type update, delete, or assert.
    689          * @return this builder, to allow for chaining.
    690          */
    691         public Builder withExpectedCount(int count) {
    692             if (mType != TYPE_UPDATE && mType != TYPE_DELETE && mType != TYPE_ASSERT) {
    693                 throw new IllegalArgumentException(
    694                         "only updates, deletes, and asserts can have expected counts");
    695             }
    696             mExpectedCount = count;
    697             return this;
    698         }
    699 
    700         /**
    701          * If set to true then the operation allows yielding the database to other transactions
    702          * if the database is contended.
    703          * @return this builder, to allow for chaining.
    704          * @see android.database.sqlite.SQLiteDatabase#yieldIfContendedSafely()
    705          */
    706         public Builder withYieldAllowed(boolean yieldAllowed) {
    707             mYieldAllowed = yieldAllowed;
    708             return this;
    709         }
    710 
    711         /** {@hide} */
    712         public Builder withFailureAllowed(boolean failureAllowed) {
    713             mFailureAllowed = failureAllowed;
    714             return this;
    715         }
    716     }
    717 }
    718