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