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
This commit is contained in:
@@ -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<Integer, Integer> 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<Integer, Integer> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ContentProviderResult> CREATOR =
|
||||
new Creator<ContentProviderResult>() {
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user