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