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