From 295ddbae78e2719964fcb2c17a166affecf7354a Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Mon, 15 Apr 2019 18:54:58 -0600 Subject: [PATCH] Allow failures of applyBatch() operations. In some cases, the supplier of ContentProviderOperation is okay if certain operations fail, and they'd like ContentProviderResult to tell them about the failures instead of aborting the remainder of the transaction. Start using this for ModernMediaScanner, where we probably raced with someone when building an UPSERT-style operation. We'll pick up any changes to those files during the next scan. Bug: 128494336 Test: atest --test-mapping packages/providers/MediaProvider Change-Id: Ida8230ff2bbb3bab56eb83928e49e7097bfbc9fd --- .../content/ContentProviderOperation.java | 33 +++++++- .../content/ContentProviderResult.java | 75 +++++++++++++++---- 2 files changed, 89 insertions(+), 19 deletions(-) diff --git a/core/java/android/content/ContentProviderOperation.java b/core/java/android/content/ContentProviderOperation.java index a41b5d3fb7818..a646e49f4f61e 100644 --- a/core/java/android/content/ContentProviderOperation.java +++ b/core/java/android/content/ContentProviderOperation.java @@ -17,7 +17,6 @@ package android.content; import android.annotation.UnsupportedAppUsage; -import android.content.ContentProvider; import android.database.Cursor; import android.net.Uri; import android.os.Parcel; @@ -59,6 +58,7 @@ public class ContentProviderOperation implements Parcelable { private final ContentValues mValuesBackReferences; private final Map mSelectionArgsBackReferences; private final boolean mYieldAllowed; + private final boolean mFailureAllowed; private final static String TAG = "ContentProviderOperation"; @@ -76,6 +76,7 @@ public class ContentProviderOperation implements Parcelable { mSelectionArgsBackReferences = builder.mSelectionArgsBackReferences; mValuesBackReferences = builder.mValuesBackReferences; mYieldAllowed = builder.mYieldAllowed; + mFailureAllowed = builder.mFailureAllowed; } private ContentProviderOperation(Parcel source) { @@ -98,6 +99,7 @@ public class ContentProviderOperation implements Parcelable { } } mYieldAllowed = source.readInt() != 0; + mFailureAllowed = source.readInt() != 0; } /** @hide */ @@ -111,6 +113,7 @@ public class ContentProviderOperation implements Parcelable { mSelectionArgsBackReferences = cpo.mSelectionArgsBackReferences; mValuesBackReferences = cpo.mValuesBackReferences; mYieldAllowed = cpo.mYieldAllowed; + mFailureAllowed = cpo.mFailureAllowed; } public void writeToParcel(Parcel dest, int flags) { @@ -157,6 +160,7 @@ public class ContentProviderOperation implements Parcelable { dest.writeInt(0); } dest.writeInt(mYieldAllowed ? 1 : 0); + dest.writeInt(mFailureAllowed ? 1 : 0); } /** @@ -212,6 +216,11 @@ public class ContentProviderOperation implements Parcelable { return mYieldAllowed; } + /** {@hide} */ + public boolean isFailureAllowed() { + return mFailureAllowed; + } + /** @hide exposed for unit tests */ @UnsupportedAppUsage public int getType() { @@ -274,6 +283,14 @@ public class ContentProviderOperation implements Parcelable { return mType == TYPE_ASSERT; } + private ContentProviderResult fail(String msg) throws OperationApplicationException { + if (mFailureAllowed) { + return new ContentProviderResult(msg); + } else { + throw new OperationApplicationException(msg); + } + } + /** * Applies this operation using the given provider. The backRefs array is used to resolve any * back references that were requested using @@ -297,7 +314,8 @@ public class ContentProviderOperation implements Parcelable { if (mType == TYPE_INSERT) { Uri newUri = provider.insert(mUri, values); if (newUri == null) { - throw new OperationApplicationException("insert failed"); + Log.e(TAG, this.toString()); + return fail("Insert into " + mUri + " returned no result"); } return new ContentProviderResult(newUri); } @@ -329,7 +347,7 @@ public class ContentProviderOperation implements Parcelable { if (!TextUtils.equals(cursorValue, expectedValue)) { // Throw exception when expected values don't match Log.e(TAG, this.toString()); - throw new OperationApplicationException("Found value " + cursorValue + return fail("Found value " + cursorValue + " when expected " + expectedValue + " for column " + projection[i]); } @@ -346,7 +364,7 @@ public class ContentProviderOperation implements Parcelable { if (mExpectedCount != null && mExpectedCount != numRows) { Log.e(TAG, this.toString()); - throw new OperationApplicationException("wrong number of rows: " + numRows); + return fail("Expected " + mExpectedCount + " rows but actual " + numRows); } return new ContentProviderResult(numRows); @@ -491,6 +509,7 @@ public class ContentProviderOperation implements Parcelable { private ContentValues mValuesBackReferences; private Map mSelectionArgsBackReferences; private boolean mYieldAllowed; + private boolean mFailureAllowed; /** Create a {@link Builder} of a given type. The uri must not be null. */ private Builder(int type, Uri uri) { @@ -683,5 +702,11 @@ public class ContentProviderOperation implements Parcelable { mYieldAllowed = yieldAllowed; return this; } + + /** {@hide} */ + public Builder withFailureAllowed(boolean failureAllowed) { + mFailureAllowed = failureAllowed; + return this; + } } } diff --git a/core/java/android/content/ContentProviderResult.java b/core/java/android/content/ContentProviderResult.java index d90173ceb3cd6..b3010116a115c 100644 --- a/core/java/android/content/ContentProviderResult.java +++ b/core/java/android/content/ContentProviderResult.java @@ -16,10 +16,11 @@ package android.content; -import android.content.ContentProvider; import android.net.Uri; -import android.os.Parcelable; import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; /** * Contains the result of the application of a {@link ContentProviderOperation}. It is guaranteed @@ -28,26 +29,44 @@ import android.os.Parcel; public class ContentProviderResult implements Parcelable { public final Uri uri; public final Integer count; + /** {@hide} */ + public final String failure; public ContentProviderResult(Uri uri) { - if (uri == null) throw new IllegalArgumentException("uri must not be null"); - this.uri = uri; - this.count = null; + this(Preconditions.checkNotNull(uri), null, null); } public ContentProviderResult(int count) { + this(null, count, null); + } + + /** {@hide} */ + public ContentProviderResult(String failure) { + this(null, null, failure); + } + + /** {@hide} */ + public ContentProviderResult(Uri uri, Integer count, String failure) { + this.uri = uri; this.count = count; - this.uri = null; + this.failure = failure; } public ContentProviderResult(Parcel source) { - int type = source.readInt(); - if (type == 1) { - count = source.readInt(); + if (source.readInt() != 0) { + uri = Uri.CREATOR.createFromParcel(source); + } else { uri = null; + } + if (source.readInt() != 0) { + count = source.readInt(); } else { count = null; - uri = Uri.CREATOR.createFromParcel(source); + } + if (source.readInt() != 0) { + failure = source.readString(); + } else { + failure = null; } } @@ -55,37 +74,63 @@ public class ContentProviderResult implements Parcelable { public ContentProviderResult(ContentProviderResult cpr, int userId) { uri = ContentProvider.maybeAddUserId(cpr.uri, userId); count = cpr.count; + failure = cpr.failure; } + @Override public void writeToParcel(Parcel dest, int flags) { - if (uri == null) { + if (uri != null) { + dest.writeInt(1); + uri.writeToParcel(dest, flags); + } else { + dest.writeInt(0); + } + if (count != null) { dest.writeInt(1); dest.writeInt(count); } else { - dest.writeInt(2); - uri.writeToParcel(dest, 0); + dest.writeInt(0); + } + if (failure != null) { + dest.writeInt(1); + dest.writeString(failure); + } else { + dest.writeInt(0); } } + @Override public int describeContents() { return 0; } public static final @android.annotation.NonNull Creator CREATOR = new Creator() { + @Override public ContentProviderResult createFromParcel(Parcel source) { return new ContentProviderResult(source); } + @Override public ContentProviderResult[] newArray(int size) { return new ContentProviderResult[size]; } }; + @Override public String toString() { + final StringBuilder sb = new StringBuilder("ContentProviderResult("); if (uri != null) { - return "ContentProviderResult(uri=" + uri.toString() + ")"; + sb.append("uri=" + uri + " "); } - return "ContentProviderResult(count=" + count + ")"; + if (count != null) { + sb.append("count=" + count + " "); + } + if (uri != null) { + sb.append("failure=" + failure + " "); + } + sb.deleteCharAt(sb.length() - 1); + sb.append(")"); + return sb.toString(); } }