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.compat.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      */
ContentProviderOperation(Builder builder)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 
ContentProviderOperation(Parcel source)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 */
ContentProviderOperation(ContentProviderOperation cpo, Uri withUri)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 
writeToParcel(Parcel dest, int flags)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      */
newInsert(Uri uri)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      */
newUpdate(Uri uri)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      */
newDelete(Uri uri)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      */
newAssertQuery(Uri uri)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      */
getUri()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      */
isYieldAllowed()215     public boolean isYieldAllowed() {
216         return mYieldAllowed;
217     }
218 
219     /** {@hide} */
isFailureAllowed()220     public boolean isFailureAllowed() {
221         return mFailureAllowed;
222     }
223 
224     /** @hide exposed for unit tests */
225     @UnsupportedAppUsage
getType()226     public int getType() {
227         return mType;
228     }
229 
230     /**
231      * Returns true if the operation represents an insertion.
232      *
233      * @see #newInsert
234      */
isInsert()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      */
isDelete()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      */
isUpdate()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      */
isAssertQuery()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      */
isWriteOperation()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      */
isReadOperation()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      */
apply(ContentProvider provider, ContentProviderResult[] backRefs, int numBackRefs)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 
applyInternal(ContentProvider provider, ContentProviderResult[] backRefs, int numBackRefs)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      */
resolveValueBackReferences( ContentProviderResult[] backRefs, int numBackRefs)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      */
resolveSelectionArgsBackReferences( ContentProviderResult[] backRefs, int numBackRefs)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
toString()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      */
backRefToValue(ContentProviderResult[] backRefs, int numBackRefs, Integer backRefIndex)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 
describeContents()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. */
Builder(int type, Uri uri)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}. */
build()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          */
withValueBackReferences(ContentValues backReferences)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          */
withValueBackReference(String key, int previousResult)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          */
withSelectionBackReference(int selectionArgIndex, int previousResult)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          */
withValues(ContentValues values)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          */
withValue(String key, Object value)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          */
withSelection(String selection, String[] selectionArgs)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          */
withExpectedCount(int count)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          */
withYieldAllowed(boolean yieldAllowed)706         public Builder withYieldAllowed(boolean yieldAllowed) {
707             mYieldAllowed = yieldAllowed;
708             return this;
709         }
710 
711         /** {@hide} */
withFailureAllowed(boolean failureAllowed)712         public Builder withFailureAllowed(boolean failureAllowed) {
713             mFailureAllowed = failureAllowed;
714             return this;
715         }
716     }
717 }
718