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