Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ce31b236 authored by Fred Quintana's avatar Fred Quintana
Browse files

enhance ContentProvider with the ability to perform batch operations

parent e18b02cc
Loading
Loading
Loading
Loading
+327 −0
Original line number Diff line number Diff line
@@ -25060,6 +25060,21 @@
 visibility="public"
>
</constructor>
<method name="applyBatch"
 return="android.content.ContentProviderResult[]"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="operations" type="android.content.ContentProviderOperation[]">
</parameter>
<exception name="OperationApplicationException" type="android.content.OperationApplicationException">
</exception>
</method>
<method name="attachInfo"
 return="void"
 abstract="false"
@@ -25681,6 +25696,269 @@
</exception>
</method>
</class>
<class name="ContentProviderOperation"
 extends="java.lang.Object"
 abstract="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<method name="apply"
 return="android.content.ContentProviderResult"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="provider" type="android.content.ContentProvider">
</parameter>
<parameter name="backRefs" type="android.content.ContentProviderResult[]">
</parameter>
<parameter name="numBackRefs" type="int">
</parameter>
<exception name="OperationApplicationException" type="android.content.OperationApplicationException">
</exception>
</method>
<method name="backRefToValue"
 return="java.lang.String"
 abstract="false"
 native="false"
 synchronized="false"
 static="true"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="backRefs" type="android.content.ContentProviderResult[]">
</parameter>
<parameter name="numBackRefs" type="int">
</parameter>
<parameter name="backRefIndex" type="java.lang.Integer">
</parameter>
</method>
<method name="newCountQuery"
 return="android.content.ContentProviderOperation.Builder"
 abstract="false"
 native="false"
 synchronized="false"
 static="true"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="uri" type="android.net.Uri">
</parameter>
</method>
<method name="newDelete"
 return="android.content.ContentProviderOperation.Builder"
 abstract="false"
 native="false"
 synchronized="false"
 static="true"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="uri" type="android.net.Uri">
</parameter>
</method>
<method name="newInsert"
 return="android.content.ContentProviderOperation.Builder"
 abstract="false"
 native="false"
 synchronized="false"
 static="true"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="uri" type="android.net.Uri">
</parameter>
</method>
<method name="newUpdate"
 return="android.content.ContentProviderOperation.Builder"
 abstract="false"
 native="false"
 synchronized="false"
 static="true"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="uri" type="android.net.Uri">
</parameter>
</method>
<method name="resolveSelectionArgsBackReferences"
 return="java.lang.String[]"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="backRef" type="android.content.ContentProviderResult[]">
</parameter>
<parameter name="numBackRefs" type="int">
</parameter>
</method>
<method name="resolveValueBackReferences"
 return="android.content.ContentValues"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="backRefs" type="android.content.ContentProviderResult[]">
</parameter>
<parameter name="numBackRefs" type="int">
</parameter>
</method>
</class>
<class name="ContentProviderOperation.Builder"
 extends="java.lang.Object"
 abstract="false"
 static="true"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<method name="build"
 return="android.content.ContentProviderOperation"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
</method>
<method name="withExpectedCount"
 return="android.content.ContentProviderOperation.Builder"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="count" type="int">
</parameter>
</method>
<method name="withSelection"
 return="android.content.ContentProviderOperation.Builder"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="selection" type="java.lang.String">
</parameter>
<parameter name="selectionArgs" type="java.lang.String[]">
</parameter>
</method>
<method name="withSelectionBackReferences"
 return="android.content.ContentProviderOperation.Builder"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="backReferences" type="java.util.Map&lt;java.lang.Integer, java.lang.Integer&gt;">
</parameter>
</method>
<method name="withValueBackReferences"
 return="android.content.ContentProviderOperation.Builder"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="backReferences" type="android.content.ContentValues">
</parameter>
</method>
<method name="withValues"
 return="android.content.ContentProviderOperation.Builder"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="values" type="android.content.ContentValues">
</parameter>
</method>
</class>
<class name="ContentProviderResult"
 extends="java.lang.Object"
 abstract="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<constructor name="ContentProviderResult"
 type="android.content.ContentProviderResult"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="uri" type="android.net.Uri">
</parameter>
</constructor>
<constructor name="ContentProviderResult"
 type="android.content.ContentProviderResult"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="count" type="int">
</parameter>
</constructor>
<field name="count"
 type="java.lang.Integer"
 transient="false"
 volatile="false"
 static="false"
 final="true"
 deprecated="not deprecated"
 visibility="public"
>
</field>
<field name="uri"
 type="android.net.Uri"
 transient="false"
 volatile="false"
 static="false"
 final="true"
 deprecated="not deprecated"
 visibility="public"
>
</field>
</class>
<class name="ContentQueryMap"
 extends="java.util.Observable"
 abstract="false"
@@ -33581,6 +33859,55 @@
</parameter>
</method>
</class>
<class name="OperationApplicationException"
 extends="java.lang.Exception"
 abstract="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<constructor name="OperationApplicationException"
 type="android.content.OperationApplicationException"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
</constructor>
<constructor name="OperationApplicationException"
 type="android.content.OperationApplicationException"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="message" type="java.lang.String">
</parameter>
</constructor>
<constructor name="OperationApplicationException"
 type="android.content.OperationApplicationException"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="message" type="java.lang.String">
</parameter>
<parameter name="cause" type="java.lang.Throwable">
</parameter>
</constructor>
<constructor name="OperationApplicationException"
 type="android.content.OperationApplicationException"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="cause" type="java.lang.Throwable">
</parameter>
</constructor>
</class>
<class name="ReceiverCallNotAllowedException"
 extends="android.util.AndroidRuntimeException"
 abstract="false"
+94 −20
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@ import java.util.Collections;
import java.util.Map;
import java.util.Vector;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;

import com.google.android.collect.Maps;

@@ -55,6 +57,9 @@ public abstract class AbstractSyncableContentProvider extends SyncableContentPro
        return mIsTemporary;
    }

    private final ThreadLocal<Boolean> mApplyingBatch = new ThreadLocal<Boolean>();
    private final ThreadLocal<Set<Uri>> mPendingBatchNotifications = new ThreadLocal<Set<Uri>>();

    /**
     * Indicates whether or not this ContentProvider contains a full
     * set of data or just diffs. This knowledge comes in handy when
@@ -243,73 +248,114 @@ public abstract class AbstractSyncableContentProvider extends SyncableContentPro
    public final int update(final Uri url, final ContentValues values,
            final String selection, final String[] selectionArgs) {
        mDb = mOpenHelper.getWritableDatabase();
        final boolean notApplyingBatch = !applyingBatch();
        if (notApplyingBatch) {
            mDb.beginTransaction();
        }
        try {
            if (isTemporary() && mSyncState.matches(url)) {
                int numRows = mSyncState.asContentProvider().update(
                        url, values, selection, selectionArgs);
                if (notApplyingBatch) {
                    mDb.setTransactionSuccessful();
                }
                return numRows;
            }

            int result = updateInternal(url, values, selection, selectionArgs);
            if (notApplyingBatch) {
                mDb.setTransactionSuccessful();

            }
            if (!isTemporary() && result > 0) {
                if (notApplyingBatch) {
                    getContext().getContentResolver().notifyChange(url, null /* observer */,
                            changeRequiresLocalSync(url));
                } else {
                    mPendingBatchNotifications.get().add(url);
                }
            }

            return result;
        } finally {
            if (notApplyingBatch) {
                mDb.endTransaction();
            }
        }
    }

    @Override
    public final int delete(final Uri url, final String selection,
            final String[] selectionArgs) {
        mDb = mOpenHelper.getWritableDatabase();
        final boolean notApplyingBatch = !applyingBatch();
        if (notApplyingBatch) {
            mDb.beginTransaction();
        }
        try {
            if (isTemporary() && mSyncState.matches(url)) {
                int numRows = mSyncState.asContentProvider().delete(url, selection, selectionArgs);
                if (notApplyingBatch) {
                    mDb.setTransactionSuccessful();
                }
                return numRows;
            }
            int result = deleteInternal(url, selection, selectionArgs);
            if (notApplyingBatch) {
                mDb.setTransactionSuccessful();
            }
            if (!isTemporary() && result > 0) {
                if (notApplyingBatch) {
                    getContext().getContentResolver().notifyChange(url, null /* observer */,
                            changeRequiresLocalSync(url));
                } else {
                    mPendingBatchNotifications.get().add(url);
                }
            }
            return result;
        } finally {
            if (notApplyingBatch) {
                mDb.endTransaction();
            }
        }
    }

    private boolean applyingBatch() {
        return mApplyingBatch.get() != null && mApplyingBatch.get();
    }

    @Override
    public final Uri insert(final Uri url, final ContentValues values) {
        mDb = mOpenHelper.getWritableDatabase();
        final boolean notApplyingBatch = !applyingBatch();
        if (notApplyingBatch) {
            mDb.beginTransaction();
        }
        try {
            if (isTemporary() && mSyncState.matches(url)) {
                Uri result = mSyncState.asContentProvider().insert(url, values);
                if (notApplyingBatch) {
                    mDb.setTransactionSuccessful();
                }
                return result;
            }
            Uri result = insertInternal(url, values);
            if (notApplyingBatch) {
                mDb.setTransactionSuccessful();
            }
            if (!isTemporary() && result != null) {
                if (notApplyingBatch) {
                    getContext().getContentResolver().notifyChange(url, null /* observer */,
                            changeRequiresLocalSync(url));
                } else {
                    mPendingBatchNotifications.get().add(url);
                }
            }
            return result;
        } finally {
            if (notApplyingBatch) {
                mDb.endTransaction();
            }
        }
    }

    @Override
    public final int bulkInsert(final Uri uri, final ContentValues[] values) {
@@ -342,6 +388,34 @@ public abstract class AbstractSyncableContentProvider extends SyncableContentPro
        return completed;
    }

    public ContentProviderResult[] applyBatch(ContentProviderOperation[] operations)
            throws OperationApplicationException {
        // initialize if this is the first time this thread has applied a batch
        if (mApplyingBatch.get() == null) {
            mApplyingBatch.set(false);
            mPendingBatchNotifications.set(new HashSet<Uri>());
        }

        if (applyingBatch()) {
            throw new IllegalStateException(
                    "applyBatch is not reentrant but mApplyingBatch is already set");
        }
        getDatabase().beginTransaction();
        try {
            mApplyingBatch.set(true);
            ContentProviderResult[] results = super.applyBatch(operations);
            getDatabase().setTransactionSuccessful();
            return results;
        } finally {
            mApplyingBatch.set(false);
            getDatabase().endTransaction();
            for (Uri url : mPendingBatchNotifications.get()) {
                getContext().getContentResolver().notifyChange(url, null /* observer */,
                        changeRequiresLocalSync(url));
            }
        }
    }
    
    /**
     * Check if changes to this URI can be syncable changes.
     * @param uri the URI of the resource that was changed
+23 −1
Original line number Diff line number Diff line
@@ -629,4 +629,26 @@ public abstract class ContentProvider implements ComponentCallbacks {
            ContentProvider.this.onCreate();
        }
    }

    /**
     * Applies each of the {@link ContentProviderOperation} objects and returns an array
     * of their results. Passes through OperationApplicationException, which may be thrown
     * by the call to {@link ContentProviderOperation#apply}.
     * If all the applications succeed then a {@link ContentProviderResult} array with the
     * same number of elements as the operations will be returned. It is implementation-specific
     * how many, if any, operations will have been successfully applied if a call to
     * apply results in a {@link OperationApplicationException}.
     * @param operations the operations to apply
     * @return the results of the applications
     * @throws OperationApplicationException thrown if an application fails.
     * See {@link ContentProviderOperation#apply} for more information.
     */
    public ContentProviderResult[] applyBatch(ContentProviderOperation[] operations)
            throws OperationApplicationException {
        ContentProviderResult[] results = new ContentProviderResult[operations.length];
        for (int i = 0; i < operations.length; i++) {
            results[i] = operations[i].apply(this, results, i);
        }
        return results;
    }
}
 No newline at end of file
+358 −0

File added.

Preview size limit exceeded, changes collapsed.

+39 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.content;

import android.net.Uri;

/**
 * Contains the result of the application of a {@link ContentProviderOperation}. It is guaranteed
 * to have exactly one of {@link #uri} or {@link #count} set.
 */
public class ContentProviderResult {
    public final Uri uri;
    public final Integer count;

    public ContentProviderResult(Uri uri) {
        if (uri == null) throw new IllegalArgumentException("uri must not be null");
        this.uri = uri;
        this.count = null;
    }

    public ContentProviderResult(int count) {
        this.count = count;
        this.uri = null;
    }
}
 No newline at end of file
Loading