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