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