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.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.compat.annotation.UnsupportedAppUsage; 22 import android.database.Cursor; 23 import android.net.Uri; 24 import android.os.Bundle; 25 import android.os.Parcel; 26 import android.os.Parcelable; 27 import android.text.TextUtils; 28 import android.util.ArrayMap; 29 import android.util.Log; 30 import android.util.SparseArray; 31 32 import java.util.ArrayList; 33 import java.util.Map; 34 import java.util.Objects; 35 36 /** 37 * Represents a single operation to be performed as part of a batch of operations. 38 * 39 * @see ContentProvider#applyBatch(ArrayList) 40 */ 41 public class ContentProviderOperation implements Parcelable { 42 /** @hide exposed for unit tests */ 43 @UnsupportedAppUsage 44 public final static int TYPE_INSERT = 1; 45 /** @hide exposed for unit tests */ 46 @UnsupportedAppUsage 47 public final static int TYPE_UPDATE = 2; 48 /** @hide exposed for unit tests */ 49 @UnsupportedAppUsage 50 public final static int TYPE_DELETE = 3; 51 /** @hide exposed for unit tests */ 52 public final static int TYPE_ASSERT = 4; 53 /** @hide exposed for unit tests */ 54 public final static int TYPE_CALL = 5; 55 56 @UnsupportedAppUsage 57 private final int mType; 58 @UnsupportedAppUsage 59 private final Uri mUri; 60 private final String mMethod; 61 private final String mArg; 62 private final ArrayMap<String, Object> mValues; 63 private final ArrayMap<String, Object> mExtras; 64 @UnsupportedAppUsage 65 private final String mSelection; 66 private final SparseArray<Object> mSelectionArgs; 67 private final Integer mExpectedCount; 68 private final boolean mYieldAllowed; 69 private final boolean mExceptionAllowed; 70 71 private final static String TAG = "ContentProviderOperation"; 72 73 /** 74 * Creates a {@link ContentProviderOperation} by copying the contents of a 75 * {@link Builder}. 76 */ ContentProviderOperation(Builder builder)77 private ContentProviderOperation(Builder builder) { 78 mType = builder.mType; 79 mUri = builder.mUri; 80 mMethod = builder.mMethod; 81 mArg = builder.mArg; 82 mValues = builder.mValues; 83 mExtras = builder.mExtras; 84 mSelection = builder.mSelection; 85 mSelectionArgs = builder.mSelectionArgs; 86 mExpectedCount = builder.mExpectedCount; 87 mYieldAllowed = builder.mYieldAllowed; 88 mExceptionAllowed = builder.mExceptionAllowed; 89 } 90 ContentProviderOperation(Parcel source)91 private ContentProviderOperation(Parcel source) { 92 mType = source.readInt(); 93 mUri = Uri.CREATOR.createFromParcel(source); 94 mMethod = source.readInt() != 0 ? source.readString8() : null; 95 mArg = source.readInt() != 0 ? source.readString8() : null; 96 final int valuesSize = source.readInt(); 97 if (valuesSize != -1) { 98 mValues = new ArrayMap<>(valuesSize); 99 source.readArrayMap(mValues, null); 100 } else { 101 mValues = null; 102 } 103 final int extrasSize = source.readInt(); 104 if (extrasSize != -1) { 105 mExtras = new ArrayMap<>(extrasSize); 106 source.readArrayMap(mExtras, null); 107 } else { 108 mExtras = null; 109 } 110 mSelection = source.readInt() != 0 ? source.readString8() : null; 111 mSelectionArgs = source.readSparseArray(null); 112 mExpectedCount = source.readInt() != 0 ? source.readInt() : null; 113 mYieldAllowed = source.readInt() != 0; 114 mExceptionAllowed = source.readInt() != 0; 115 } 116 117 /** @hide */ ContentProviderOperation(ContentProviderOperation cpo, Uri withUri)118 public ContentProviderOperation(ContentProviderOperation cpo, Uri withUri) { 119 mType = cpo.mType; 120 mUri = withUri; 121 mMethod = cpo.mMethod; 122 mArg = cpo.mArg; 123 mValues = cpo.mValues; 124 mExtras = cpo.mExtras; 125 mSelection = cpo.mSelection; 126 mSelectionArgs = cpo.mSelectionArgs; 127 mExpectedCount = cpo.mExpectedCount; 128 mYieldAllowed = cpo.mYieldAllowed; 129 mExceptionAllowed = cpo.mExceptionAllowed; 130 } 131 132 @Override writeToParcel(Parcel dest, int flags)133 public void writeToParcel(Parcel dest, int flags) { 134 dest.writeInt(mType); 135 Uri.writeToParcel(dest, mUri); 136 if (mMethod != null) { 137 dest.writeInt(1); 138 dest.writeString8(mMethod); 139 } else { 140 dest.writeInt(0); 141 } 142 if (mArg != null) { 143 dest.writeInt(1); 144 dest.writeString8(mArg); 145 } else { 146 dest.writeInt(0); 147 } 148 if (mValues != null) { 149 dest.writeInt(mValues.size()); 150 dest.writeArrayMap(mValues); 151 } else { 152 dest.writeInt(-1); 153 } 154 if (mExtras != null) { 155 dest.writeInt(mExtras.size()); 156 dest.writeArrayMap(mExtras); 157 } else { 158 dest.writeInt(-1); 159 } 160 if (mSelection != null) { 161 dest.writeInt(1); 162 dest.writeString8(mSelection); 163 } else { 164 dest.writeInt(0); 165 } 166 dest.writeSparseArray(mSelectionArgs); 167 if (mExpectedCount != null) { 168 dest.writeInt(1); 169 dest.writeInt(mExpectedCount); 170 } else { 171 dest.writeInt(0); 172 } 173 dest.writeInt(mYieldAllowed ? 1 : 0); 174 dest.writeInt(mExceptionAllowed ? 1 : 0); 175 } 176 177 /** 178 * Create a {@link Builder} suitable for building an operation that will 179 * invoke {@link ContentProvider#insert}. 180 * 181 * @param uri The {@link Uri} that is the target of the operation. 182 */ newInsert(@onNull Uri uri)183 public static @NonNull Builder newInsert(@NonNull Uri uri) { 184 return new Builder(TYPE_INSERT, uri); 185 } 186 187 /** 188 * Create a {@link Builder} suitable for building an operation that will 189 * invoke {@link ContentProvider#update}. 190 * 191 * @param uri The {@link Uri} that is the target of the operation. 192 */ newUpdate(@onNull Uri uri)193 public static @NonNull Builder newUpdate(@NonNull Uri uri) { 194 return new Builder(TYPE_UPDATE, uri); 195 } 196 197 /** 198 * Create a {@link Builder} suitable for building an operation that will 199 * invoke {@link ContentProvider#delete}. 200 * 201 * @param uri The {@link Uri} that is the target of the operation. 202 */ newDelete(@onNull Uri uri)203 public static @NonNull Builder newDelete(@NonNull Uri uri) { 204 return new Builder(TYPE_DELETE, uri); 205 } 206 207 /** 208 * Create a {@link Builder} suitable for building a 209 * {@link ContentProviderOperation} to assert a set of values as provided 210 * through {@link Builder#withValues(ContentValues)}. 211 */ newAssertQuery(@onNull Uri uri)212 public static @NonNull Builder newAssertQuery(@NonNull Uri uri) { 213 return new Builder(TYPE_ASSERT, uri); 214 } 215 216 /** 217 * Create a {@link Builder} suitable for building an operation that will 218 * invoke {@link ContentProvider#call}. 219 * 220 * @param uri The {@link Uri} that is the target of the operation. 221 */ newCall(@onNull Uri uri, @Nullable String method, @Nullable String arg)222 public static @NonNull Builder newCall(@NonNull Uri uri, @Nullable String method, 223 @Nullable String arg) { 224 return new Builder(TYPE_CALL, uri, method, arg); 225 } 226 227 /** 228 * Gets the Uri for the target of the operation. 229 */ getUri()230 public @NonNull Uri getUri() { 231 return mUri; 232 } 233 234 /** 235 * Returns true if the operation allows yielding the database to other transactions 236 * if the database is contended. 237 * 238 * @see android.database.sqlite.SQLiteDatabase#yieldIfContendedSafely() 239 */ isYieldAllowed()240 public boolean isYieldAllowed() { 241 return mYieldAllowed; 242 } 243 244 /** 245 * Returns true if this operation allows subsequent operations to continue 246 * even if this operation throws an exception. When true, any encountered 247 * exception is returned via {@link ContentProviderResult#exception}. 248 */ isExceptionAllowed()249 public boolean isExceptionAllowed() { 250 return mExceptionAllowed; 251 } 252 253 /** @hide exposed for unit tests */ 254 @UnsupportedAppUsage getType()255 public int getType() { 256 return mType; 257 } 258 259 /** 260 * Returns true if the operation represents a {@link ContentProvider#insert} 261 * operation. 262 * 263 * @see #newInsert 264 */ isInsert()265 public boolean isInsert() { 266 return mType == TYPE_INSERT; 267 } 268 269 /** 270 * Returns true if the operation represents a {@link ContentProvider#delete} 271 * operation. 272 * 273 * @see #newDelete 274 */ isDelete()275 public boolean isDelete() { 276 return mType == TYPE_DELETE; 277 } 278 279 /** 280 * Returns true if the operation represents a {@link ContentProvider#update} 281 * operation. 282 * 283 * @see #newUpdate 284 */ isUpdate()285 public boolean isUpdate() { 286 return mType == TYPE_UPDATE; 287 } 288 289 /** 290 * Returns true if the operation represents an assert query. 291 * 292 * @see #newAssertQuery 293 */ isAssertQuery()294 public boolean isAssertQuery() { 295 return mType == TYPE_ASSERT; 296 } 297 298 /** 299 * Returns true if the operation represents a {@link ContentProvider#call} 300 * operation. 301 * 302 * @see #newCall 303 */ isCall()304 public boolean isCall() { 305 return mType == TYPE_CALL; 306 } 307 308 /** 309 * Returns true if the operation represents an insertion, deletion, or update. 310 * 311 * @see #isInsert 312 * @see #isDelete 313 * @see #isUpdate 314 */ isWriteOperation()315 public boolean isWriteOperation() { 316 return mType == TYPE_DELETE || mType == TYPE_INSERT || mType == TYPE_UPDATE; 317 } 318 319 /** 320 * Returns true if the operation represents an assert query. 321 * 322 * @see #isAssertQuery 323 */ isReadOperation()324 public boolean isReadOperation() { 325 return mType == TYPE_ASSERT; 326 } 327 328 /** 329 * Applies this operation using the given provider. The backRefs array is used to resolve any 330 * back references that were requested using 331 * {@link Builder#withValueBackReferences(ContentValues)} and 332 * {@link Builder#withSelectionBackReference}. 333 * @param provider the {@link ContentProvider} on which this batch is applied 334 * @param backRefs a {@link ContentProviderResult} array that will be consulted 335 * to resolve any requested back references. 336 * @param numBackRefs the number of valid results on the backRefs array. 337 * @return a {@link ContentProviderResult} that contains either the {@link Uri} of the inserted 338 * row if this was an insert otherwise the number of rows affected. 339 * @throws OperationApplicationException thrown if either the insert fails or 340 * if the number of rows affected didn't match the expected count 341 */ apply(@onNull ContentProvider provider, @NonNull ContentProviderResult[] backRefs, int numBackRefs)342 public @NonNull ContentProviderResult apply(@NonNull ContentProvider provider, 343 @NonNull ContentProviderResult[] backRefs, int numBackRefs) 344 throws OperationApplicationException { 345 if (mExceptionAllowed) { 346 try { 347 return applyInternal(provider, backRefs, numBackRefs); 348 } catch (Exception e) { 349 return new ContentProviderResult(e); 350 } 351 } else { 352 return applyInternal(provider, backRefs, numBackRefs); 353 } 354 } 355 applyInternal(ContentProvider provider, ContentProviderResult[] backRefs, int numBackRefs)356 private ContentProviderResult applyInternal(ContentProvider provider, 357 ContentProviderResult[] backRefs, int numBackRefs) 358 throws OperationApplicationException { 359 final ContentValues values = resolveValueBackReferences(backRefs, numBackRefs); 360 361 // If the creator requested explicit selection or selectionArgs, it 362 // should take precedence over similar values they defined in extras 363 Bundle extras = resolveExtrasBackReferences(backRefs, numBackRefs); 364 if (mSelection != null) { 365 extras = (extras != null) ? extras : new Bundle(); 366 extras.putString(ContentResolver.QUERY_ARG_SQL_SELECTION, mSelection); 367 } 368 if (mSelectionArgs != null) { 369 extras = (extras != null) ? extras : new Bundle(); 370 extras.putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, 371 resolveSelectionArgsBackReferences(backRefs, numBackRefs)); 372 } 373 374 if (mType == TYPE_INSERT) { 375 final Uri newUri = provider.insert(mUri, values, extras); 376 if (newUri != null) { 377 return new ContentProviderResult(newUri); 378 } else { 379 throw new OperationApplicationException( 380 "Insert into " + mUri + " returned no result"); 381 } 382 } else if (mType == TYPE_CALL) { 383 final Bundle res = provider.call(mUri.getAuthority(), mMethod, mArg, extras); 384 return new ContentProviderResult(res); 385 } 386 387 final int numRows; 388 if (mType == TYPE_DELETE) { 389 numRows = provider.delete(mUri, extras); 390 } else if (mType == TYPE_UPDATE) { 391 numRows = provider.update(mUri, values, extras); 392 } else if (mType == TYPE_ASSERT) { 393 // Assert that all rows match expected values 394 String[] projection = null; 395 if (values != null) { 396 // Build projection map from expected values 397 final ArrayList<String> projectionList = new ArrayList<String>(); 398 for (Map.Entry<String, Object> entry : values.valueSet()) { 399 projectionList.add(entry.getKey()); 400 } 401 projection = projectionList.toArray(new String[projectionList.size()]); 402 } 403 final Cursor cursor = provider.query(mUri, projection, extras, null); 404 try { 405 numRows = cursor.getCount(); 406 if (projection != null) { 407 while (cursor.moveToNext()) { 408 for (int i = 0; i < projection.length; i++) { 409 final String cursorValue = cursor.getString(i); 410 final String expectedValue = values.getAsString(projection[i]); 411 if (!TextUtils.equals(cursorValue, expectedValue)) { 412 // Throw exception when expected values don't match 413 throw new OperationApplicationException("Found value " + cursorValue 414 + " when expected " + expectedValue + " for column " 415 + projection[i]); 416 } 417 } 418 } 419 } 420 } finally { 421 cursor.close(); 422 } 423 } else { 424 throw new IllegalStateException("bad type, " + mType); 425 } 426 427 if (mExpectedCount != null && mExpectedCount != numRows) { 428 throw new OperationApplicationException( 429 "Expected " + mExpectedCount + " rows but actual " + numRows); 430 } 431 432 return new ContentProviderResult(numRows); 433 } 434 435 /** 436 * Return the values for this operation after resolving any requested 437 * back-references using the given results. 438 * 439 * @param backRefs the results to use when resolving any back-references 440 * @param numBackRefs the number of results which are valid 441 */ resolveValueBackReferences( @onNull ContentProviderResult[] backRefs, int numBackRefs)442 public @Nullable ContentValues resolveValueBackReferences( 443 @NonNull ContentProviderResult[] backRefs, int numBackRefs) { 444 if (mValues != null) { 445 final ContentValues values = new ContentValues(); 446 for (int i = 0; i < mValues.size(); i++) { 447 final Object value = mValues.valueAt(i); 448 final Object resolved; 449 if (value instanceof BackReference) { 450 resolved = ((BackReference) value).resolve(backRefs, numBackRefs); 451 } else { 452 resolved = value; 453 } 454 values.putObject(mValues.keyAt(i), resolved); 455 } 456 return values; 457 } else { 458 return null; 459 } 460 } 461 462 /** 463 * Return the extras for this operation after resolving any requested 464 * back-references using the given results. 465 * 466 * @param backRefs the results to use when resolving any back-references 467 * @param numBackRefs the number of results which are valid 468 */ resolveExtrasBackReferences( @onNull ContentProviderResult[] backRefs, int numBackRefs)469 public @Nullable Bundle resolveExtrasBackReferences( 470 @NonNull ContentProviderResult[] backRefs, int numBackRefs) { 471 if (mExtras != null) { 472 final Bundle extras = new Bundle(); 473 for (int i = 0; i < mExtras.size(); i++) { 474 final Object value = mExtras.valueAt(i); 475 final Object resolved; 476 if (value instanceof BackReference) { 477 resolved = ((BackReference) value).resolve(backRefs, numBackRefs); 478 } else { 479 resolved = value; 480 } 481 extras.putObject(mExtras.keyAt(i), resolved); 482 } 483 return extras; 484 } else { 485 return null; 486 } 487 } 488 489 /** 490 * Return the selection arguments for this operation after resolving any 491 * requested back-references using the given results. 492 * 493 * @param backRefs the results to use when resolving any back-references 494 * @param numBackRefs the number of results which are valid 495 */ resolveSelectionArgsBackReferences( @onNull ContentProviderResult[] backRefs, int numBackRefs)496 public @Nullable String[] resolveSelectionArgsBackReferences( 497 @NonNull ContentProviderResult[] backRefs, int numBackRefs) { 498 if (mSelectionArgs != null) { 499 int max = -1; 500 for (int i = 0; i < mSelectionArgs.size(); i++) { 501 max = Math.max(max, mSelectionArgs.keyAt(i)); 502 } 503 504 final String[] selectionArgs = new String[max + 1]; 505 for (int i = 0; i < mSelectionArgs.size(); i++) { 506 final Object value = mSelectionArgs.valueAt(i); 507 final Object resolved; 508 if (value instanceof BackReference) { 509 resolved = ((BackReference) value).resolve(backRefs, numBackRefs); 510 } else { 511 resolved = value; 512 } 513 selectionArgs[mSelectionArgs.keyAt(i)] = String.valueOf(resolved); 514 } 515 return selectionArgs; 516 } else { 517 return null; 518 } 519 } 520 521 /** {@hide} */ typeToString(int type)522 public static String typeToString(int type) { 523 switch (type) { 524 case TYPE_INSERT: return "insert"; 525 case TYPE_UPDATE: return "update"; 526 case TYPE_DELETE: return "delete"; 527 case TYPE_ASSERT: return "assert"; 528 case TYPE_CALL: return "call"; 529 default: return Integer.toString(type); 530 } 531 } 532 533 @Override toString()534 public String toString() { 535 final StringBuilder sb = new StringBuilder("ContentProviderOperation("); 536 sb.append("type=" + typeToString(mType) + " "); 537 if (mUri != null) { 538 sb.append("uri=" + mUri + " "); 539 } 540 if (mValues != null) { 541 sb.append("values=" + mValues + " "); 542 } 543 if (mSelection != null) { 544 sb.append("selection=" + mSelection + " "); 545 } 546 if (mSelectionArgs != null) { 547 sb.append("selectionArgs=" + mSelectionArgs + " "); 548 } 549 if (mExpectedCount != null) { 550 sb.append("expectedCount=" + mExpectedCount + " "); 551 } 552 if (mYieldAllowed) { 553 sb.append("yieldAllowed "); 554 } 555 if (mExceptionAllowed) { 556 sb.append("exceptionAllowed "); 557 } 558 sb.deleteCharAt(sb.length() - 1); 559 sb.append(")"); 560 return sb.toString(); 561 } 562 563 @Override describeContents()564 public int describeContents() { 565 return 0; 566 } 567 568 public static final @android.annotation.NonNull Creator<ContentProviderOperation> CREATOR = 569 new Creator<ContentProviderOperation>() { 570 @Override 571 public ContentProviderOperation createFromParcel(Parcel source) { 572 return new ContentProviderOperation(source); 573 } 574 575 @Override 576 public ContentProviderOperation[] newArray(int size) { 577 return new ContentProviderOperation[size]; 578 } 579 }; 580 581 /** {@hide} */ 582 public static class BackReference implements Parcelable { 583 private final int fromIndex; 584 private final String fromKey; 585 BackReference(int fromIndex, String fromKey)586 private BackReference(int fromIndex, String fromKey) { 587 this.fromIndex = fromIndex; 588 this.fromKey = fromKey; 589 } 590 BackReference(Parcel src)591 public BackReference(Parcel src) { 592 this.fromIndex = src.readInt(); 593 if (src.readInt() != 0) { 594 this.fromKey = src.readString8(); 595 } else { 596 this.fromKey = null; 597 } 598 } 599 resolve(ContentProviderResult[] backRefs, int numBackRefs)600 public Object resolve(ContentProviderResult[] backRefs, int numBackRefs) { 601 if (fromIndex >= numBackRefs) { 602 Log.e(TAG, this.toString()); 603 throw new ArrayIndexOutOfBoundsException("asked for back ref " + fromIndex 604 + " but there are only " + numBackRefs + " back refs"); 605 } 606 ContentProviderResult backRef = backRefs[fromIndex]; 607 Object backRefValue; 608 if (backRef.extras != null) { 609 backRefValue = backRef.extras.get(fromKey); 610 } else if (backRef.uri != null) { 611 backRefValue = ContentUris.parseId(backRef.uri); 612 } else { 613 backRefValue = (long) backRef.count; 614 } 615 return backRefValue; 616 } 617 618 @Override writeToParcel(Parcel dest, int flags)619 public void writeToParcel(Parcel dest, int flags) { 620 dest.writeInt(fromIndex); 621 if (fromKey != null) { 622 dest.writeInt(1); 623 dest.writeString8(fromKey); 624 } else { 625 dest.writeInt(0); 626 } 627 } 628 629 @Override describeContents()630 public int describeContents() { 631 return 0; 632 } 633 634 public static final @android.annotation.NonNull Creator<BackReference> CREATOR = 635 new Creator<BackReference>() { 636 @Override 637 public BackReference createFromParcel(Parcel source) { 638 return new BackReference(source); 639 } 640 641 @Override 642 public BackReference[] newArray(int size) { 643 return new BackReference[size]; 644 } 645 }; 646 } 647 648 /** 649 * Used to add parameters to a {@link ContentProviderOperation}. The {@link Builder} is 650 * first created by calling {@link ContentProviderOperation#newInsert(android.net.Uri)}, 651 * {@link ContentProviderOperation#newUpdate(android.net.Uri)}, 652 * {@link ContentProviderOperation#newDelete(android.net.Uri)} or 653 * {@link ContentProviderOperation#newAssertQuery(Uri)}. The withXXX methods 654 * can then be used to add parameters to the builder. See the specific methods to find for 655 * which {@link Builder} type each is allowed. Call {@link #build} to create the 656 * {@link ContentProviderOperation} once all the parameters have been supplied. 657 */ 658 public static class Builder { 659 private final int mType; 660 private final Uri mUri; 661 private final String mMethod; 662 private final String mArg; 663 private ArrayMap<String, Object> mValues; 664 private ArrayMap<String, Object> mExtras; 665 private String mSelection; 666 private SparseArray<Object> mSelectionArgs; 667 private Integer mExpectedCount; 668 private boolean mYieldAllowed; 669 private boolean mExceptionAllowed; 670 Builder(int type, Uri uri)671 private Builder(int type, Uri uri) { 672 this(type, uri, null, null); 673 } 674 Builder(int type, Uri uri, String method, String arg)675 private Builder(int type, Uri uri, String method, String arg) { 676 mType = type; 677 mUri = Objects.requireNonNull(uri); 678 mMethod = method; 679 mArg = arg; 680 } 681 682 /** Create a ContentProviderOperation from this {@link Builder}. */ build()683 public @NonNull ContentProviderOperation build() { 684 if (mType == TYPE_UPDATE) { 685 if ((mValues == null || mValues.isEmpty())) { 686 throw new IllegalArgumentException("Empty values"); 687 } 688 } 689 if (mType == TYPE_ASSERT) { 690 if ((mValues == null || mValues.isEmpty()) 691 && (mExpectedCount == null)) { 692 throw new IllegalArgumentException("Empty values"); 693 } 694 } 695 return new ContentProviderOperation(this); 696 } 697 ensureValues()698 private void ensureValues() { 699 if (mValues == null) { 700 mValues = new ArrayMap<>(); 701 } 702 } 703 ensureExtras()704 private void ensureExtras() { 705 if (mExtras == null) { 706 mExtras = new ArrayMap<>(); 707 } 708 } 709 ensureSelectionArgs()710 private void ensureSelectionArgs() { 711 if (mSelectionArgs == null) { 712 mSelectionArgs = new SparseArray<>(); 713 } 714 } 715 setValue(@onNull String key, @NonNull Object value)716 private void setValue(@NonNull String key, @NonNull Object value) { 717 ensureValues(); 718 final boolean oldReference = mValues.get(key) instanceof BackReference; 719 final boolean newReference = value instanceof BackReference; 720 if (!oldReference || newReference) { 721 mValues.put(key, value); 722 } 723 } 724 setExtra(@onNull String key, @NonNull Object value)725 private void setExtra(@NonNull String key, @NonNull Object value) { 726 ensureExtras(); 727 final boolean oldReference = mExtras.get(key) instanceof BackReference; 728 final boolean newReference = value instanceof BackReference; 729 if (!oldReference || newReference) { 730 mExtras.put(key, value); 731 } 732 } 733 setSelectionArg(int index, @NonNull Object value)734 private void setSelectionArg(int index, @NonNull Object value) { 735 ensureSelectionArgs(); 736 final boolean oldReference = mSelectionArgs.get(index) instanceof BackReference; 737 final boolean newReference = value instanceof BackReference; 738 if (!oldReference || newReference) { 739 mSelectionArgs.put(index, value); 740 } 741 } 742 743 /** 744 * Configure the values to use for this operation. This method will 745 * replace any previously defined values for the contained keys, but it 746 * will not replace any back-reference requests. 747 * <p> 748 * Any value may be dynamically overwritten using the result of a 749 * previous operation by using methods such as 750 * {@link #withValueBackReference(String, int)}. 751 */ withValues(@onNull ContentValues values)752 public @NonNull Builder withValues(@NonNull ContentValues values) { 753 assertValuesAllowed(); 754 ensureValues(); 755 final ArrayMap<String, Object> rawValues = values.getValues(); 756 for (int i = 0; i < rawValues.size(); i++) { 757 setValue(rawValues.keyAt(i), rawValues.valueAt(i)); 758 } 759 return this; 760 } 761 762 /** 763 * Configure the given value to use for this operation. This method will 764 * replace any previously defined value for this key. 765 * 766 * @param key the key indicating which value to configure 767 */ withValue(@onNull String key, @Nullable Object value)768 public @NonNull Builder withValue(@NonNull String key, @Nullable Object value) { 769 assertValuesAllowed(); 770 if (!ContentValues.isSupportedValue(value)) { 771 throw new IllegalArgumentException("bad value type: " + value.getClass().getName()); 772 } 773 setValue(key, value); 774 return this; 775 } 776 777 /** 778 * Configure the given values to be dynamically overwritten using the 779 * result of a previous operation. This method will replace any 780 * previously defined values for these keys. 781 * 782 * @param backReferences set of values where the key indicates which 783 * value to configure and the value the index indicating 784 * which historical {@link ContentProviderResult} should 785 * overwrite the value 786 */ withValueBackReferences(@onNull ContentValues backReferences)787 public @NonNull Builder withValueBackReferences(@NonNull ContentValues backReferences) { 788 assertValuesAllowed(); 789 final ArrayMap<String, Object> rawValues = backReferences.getValues(); 790 for (int i = 0; i < rawValues.size(); i++) { 791 setValue(rawValues.keyAt(i), 792 new BackReference((int) rawValues.valueAt(i), null)); 793 } 794 return this; 795 } 796 797 /** 798 * Configure the given value to be dynamically overwritten using the 799 * result of a previous operation. This method will replace any 800 * previously defined value for this key. 801 * 802 * @param key the key indicating which value to configure 803 * @param fromIndex the index indicating which historical 804 * {@link ContentProviderResult} should overwrite the value 805 */ withValueBackReference(@onNull String key, int fromIndex)806 public @NonNull Builder withValueBackReference(@NonNull String key, int fromIndex) { 807 assertValuesAllowed(); 808 setValue(key, new BackReference(fromIndex, null)); 809 return this; 810 } 811 812 /** 813 * Configure the given value to be dynamically overwritten using the 814 * result of a previous operation. This method will replace any 815 * previously defined value for this key. 816 * 817 * @param key the key indicating which value to configure 818 * @param fromIndex the index indicating which historical 819 * {@link ContentProviderResult} should overwrite the value 820 * @param fromKey the key of indicating which 821 * {@link ContentProviderResult#extras} value should 822 * overwrite the value 823 */ withValueBackReference(@onNull String key, int fromIndex, @NonNull String fromKey)824 public @NonNull Builder withValueBackReference(@NonNull String key, int fromIndex, 825 @NonNull String fromKey) { 826 assertValuesAllowed(); 827 setValue(key, new BackReference(fromIndex, fromKey)); 828 return this; 829 } 830 831 /** 832 * Configure the extras to use for this operation. This method will 833 * replace any previously defined values for the contained keys, but it 834 * will not replace any back-reference requests. 835 * <p> 836 * Any value may be dynamically overwritten using the result of a 837 * previous operation by using methods such as 838 * {@link #withExtraBackReference(String, int)}. 839 */ withExtras(@onNull Bundle extras)840 public @NonNull Builder withExtras(@NonNull Bundle extras) { 841 assertExtrasAllowed(); 842 ensureExtras(); 843 for (String key : extras.keySet()) { 844 setExtra(key, extras.get(key)); 845 } 846 return this; 847 } 848 849 /** 850 * Configure the given extra to use for this operation. This method will 851 * replace any previously defined extras for this key. 852 * 853 * @param key the key indicating which extra to configure 854 */ withExtra(@onNull String key, @Nullable Object value)855 public @NonNull Builder withExtra(@NonNull String key, @Nullable Object value) { 856 assertExtrasAllowed(); 857 setExtra(key, value); 858 return this; 859 } 860 861 /** 862 * Configure the given extra to be dynamically overwritten using the 863 * result of a previous operation. This method will replace any 864 * previously defined extras for this key. 865 * 866 * @param key the key indicating which extra to configure 867 * @param fromIndex the index indicating which historical 868 * {@link ContentProviderResult} should overwrite the extra 869 */ withExtraBackReference(@onNull String key, int fromIndex)870 public @NonNull Builder withExtraBackReference(@NonNull String key, int fromIndex) { 871 assertExtrasAllowed(); 872 setExtra(key, new BackReference(fromIndex, null)); 873 return this; 874 } 875 876 /** 877 * Configure the given extra to be dynamically overwritten using the 878 * result of a previous operation. This method will replace any 879 * previously defined extras for this key. 880 * 881 * @param key the key indicating which extra to configure 882 * @param fromIndex the index indicating which historical 883 * {@link ContentProviderResult} should overwrite the extra 884 * @param fromKey the key of indicating which 885 * {@link ContentProviderResult#extras} value should 886 * overwrite the extra 887 */ withExtraBackReference(@onNull String key, int fromIndex, @NonNull String fromKey)888 public @NonNull Builder withExtraBackReference(@NonNull String key, int fromIndex, 889 @NonNull String fromKey) { 890 assertExtrasAllowed(); 891 setExtra(key, new BackReference(fromIndex, fromKey)); 892 return this; 893 } 894 895 /** 896 * Configure the selection and selection arguments to use for this 897 * operation. This method will replace any previously defined selection 898 * and selection arguments, but it will not replace any back-reference 899 * requests. 900 * <p> 901 * An occurrence of {@code ?} in the selection will be replaced with the 902 * corresponding selection argument when the operation is executed. 903 * <p> 904 * Any selection argument may be dynamically overwritten using the 905 * result of a previous operation by using methods such as 906 * {@link #withSelectionBackReference(int, int)}. 907 */ withSelection(@ullable String selection, @Nullable String[] selectionArgs)908 public @NonNull Builder withSelection(@Nullable String selection, 909 @Nullable String[] selectionArgs) { 910 assertSelectionAllowed(); 911 mSelection = selection; 912 if (selectionArgs != null) { 913 ensureSelectionArgs(); 914 for (int i = 0; i < selectionArgs.length; i++) { 915 setSelectionArg(i, selectionArgs[i]); 916 } 917 } 918 return this; 919 } 920 921 /** 922 * Configure the given selection argument to be dynamically overwritten 923 * using the result of a previous operation. This method will replace 924 * any previously defined selection argument at this index. 925 * 926 * @param index the index indicating which selection argument to 927 * configure 928 * @param fromIndex the index indicating which historical 929 * {@link ContentProviderResult} should overwrite the 930 * selection argument 931 */ withSelectionBackReference(int index, int fromIndex)932 public @NonNull Builder withSelectionBackReference(int index, int fromIndex) { 933 assertSelectionAllowed(); 934 setSelectionArg(index, new BackReference(fromIndex, null)); 935 return this; 936 } 937 938 /** 939 * Configure the given selection argument to be dynamically overwritten 940 * using the result of a previous operation. This method will replace 941 * any previously defined selection argument at this index. 942 * 943 * @param index the index indicating which selection argument to 944 * configure 945 * @param fromIndex the index indicating which historical 946 * {@link ContentProviderResult} should overwrite the 947 * selection argument 948 * @param fromKey the key of indicating which 949 * {@link ContentProviderResult#extras} value should 950 * overwrite the selection argument 951 */ withSelectionBackReference(int index, int fromIndex, @NonNull String fromKey)952 public @NonNull Builder withSelectionBackReference(int index, int fromIndex, 953 @NonNull String fromKey) { 954 assertSelectionAllowed(); 955 setSelectionArg(index, new BackReference(fromIndex, fromKey)); 956 return this; 957 } 958 959 /** 960 * If set then if the number of rows affected by this operation does not match 961 * this count {@link OperationApplicationException} will be throw. 962 * This can only be used with builders of type update, delete, or assert. 963 * @return this builder, to allow for chaining. 964 */ withExpectedCount(int count)965 public @NonNull Builder withExpectedCount(int count) { 966 if (mType != TYPE_UPDATE && mType != TYPE_DELETE && mType != TYPE_ASSERT) { 967 throw new IllegalArgumentException( 968 "only updates, deletes, and asserts can have expected counts"); 969 } 970 mExpectedCount = count; 971 return this; 972 } 973 974 /** 975 * If set to true then the operation allows yielding the database to other transactions 976 * if the database is contended. 977 * @return this builder, to allow for chaining. 978 * @see android.database.sqlite.SQLiteDatabase#yieldIfContendedSafely() 979 */ withYieldAllowed(boolean yieldAllowed)980 public @NonNull Builder withYieldAllowed(boolean yieldAllowed) { 981 mYieldAllowed = yieldAllowed; 982 return this; 983 } 984 985 /** 986 * If set to true, this operation allows subsequent operations to 987 * continue even if this operation throws an exception. When true, any 988 * encountered exception is returned via 989 * {@link ContentProviderResult#exception}. 990 */ withExceptionAllowed(boolean exceptionAllowed)991 public @NonNull Builder withExceptionAllowed(boolean exceptionAllowed) { 992 mExceptionAllowed = exceptionAllowed; 993 return this; 994 } 995 996 /** {@hide} */ withFailureAllowed(boolean failureAllowed)997 public @NonNull Builder withFailureAllowed(boolean failureAllowed) { 998 return withExceptionAllowed(failureAllowed); 999 } 1000 assertValuesAllowed()1001 private void assertValuesAllowed() { 1002 switch (mType) { 1003 case TYPE_INSERT: 1004 case TYPE_UPDATE: 1005 case TYPE_ASSERT: 1006 break; 1007 default: 1008 throw new IllegalArgumentException( 1009 "Values not supported for " + typeToString(mType)); 1010 } 1011 } 1012 assertSelectionAllowed()1013 private void assertSelectionAllowed() { 1014 switch (mType) { 1015 case TYPE_UPDATE: 1016 case TYPE_DELETE: 1017 case TYPE_ASSERT: 1018 break; 1019 default: 1020 throw new IllegalArgumentException( 1021 "Selection not supported for " + typeToString(mType)); 1022 } 1023 } 1024 assertExtrasAllowed()1025 private void assertExtrasAllowed() { 1026 switch (mType) { 1027 case TYPE_INSERT: 1028 case TYPE_UPDATE: 1029 case TYPE_DELETE: 1030 case TYPE_ASSERT: 1031 case TYPE_CALL: 1032 break; 1033 default: 1034 throw new IllegalArgumentException( 1035 "Extras not supported for " + typeToString(mType)); 1036 } 1037 } 1038 } 1039 } 1040