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