OSDN Git Service

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