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