OSDN Git Service

Allow failures of applyBatch() operations.
authorJeff Sharkey <jsharkey@android.com>
Tue, 16 Apr 2019 00:54:58 +0000 (18:54 -0600)
committerJeff Sharkey <jsharkey@android.com>
Tue, 16 Apr 2019 00:55:00 +0000 (18:55 -0600)
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

core/java/android/content/ContentProviderOperation.java
core/java/android/content/ContentProviderResult.java

index a41b5d3..a646e49 100644 (file)
@@ -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;
+        }
     }
 }
index d90173c..b301011 100644 (file)
 
 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) {
+            sb.append("uri=" + uri + " ");
+        }
+        if (count != null) {
+            sb.append("count=" + count + " ");
+        }
         if (uri != null) {
-            return "ContentProviderResult(uri=" + uri.toString() + ")";
+            sb.append("failure=" + failure + " ");
         }
-        return "ContentProviderResult(count=" + count + ")";
+        sb.deleteCharAt(sb.length() - 1);
+        sb.append(")");
+        return sb.toString();
     }
 }