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

Commit 2d2d7d6f authored by Jeff Brown's avatar Jeff Brown Committed by Android (Google) Code Review
Browse files

Merge "Implement a cancelation mechanism for queries."

parents 30c918ce 75ea64fc
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -94,6 +94,7 @@ LOCAL_SRC_FILES += \
	core/java/android/bluetooth/IBluetoothHealthCallback.aidl \
	core/java/android/bluetooth/IBluetoothPbap.aidl \
	core/java/android/bluetooth/IBluetoothStateChangeCallback.aidl \
	core/java/android/content/ICancelationSignal.aidl \
	core/java/android/content/IClipboard.aidl \
	core/java/android/content/IContentService.aidl \
	core/java/android/content/IIntentReceiver.aidl \
+27 −0
Original line number Diff line number Diff line
@@ -4659,7 +4659,9 @@ package android.content {
  public abstract class AsyncTaskLoader extends android.content.Loader {
    ctor public AsyncTaskLoader(android.content.Context);
    method public boolean cancelLoad();
    method protected boolean isLoadInBackgroundCanceled();
    method public abstract D loadInBackground();
    method protected void onCancelLoadInBackground();
    method public void onCanceled(D);
    method protected D onLoadInBackground();
    method public void setUpdateThrottle(long);
@@ -4701,6 +4703,18 @@ package android.content {
    method public final void setResultExtras(android.os.Bundle);
  }
  public final class CancelationSignal {
    ctor public CancelationSignal();
    method public void cancel();
    method public boolean isCanceled();
    method public void setOnCancelListener(android.content.CancelationSignal.OnCancelListener);
    method public void throwIfCanceled();
  }
  public static abstract interface CancelationSignal.OnCancelListener {
    method public abstract void onCancel();
  }
  public class ClipData implements android.os.Parcelable {
    ctor public ClipData(java.lang.CharSequence, java.lang.String[], android.content.ClipData.Item);
    ctor public ClipData(android.content.ClipDescription, android.content.ClipData.Item);
@@ -4820,6 +4834,7 @@ package android.content {
    method public android.os.ParcelFileDescriptor openPipeHelper(android.net.Uri, java.lang.String, android.os.Bundle, T, android.content.ContentProvider.PipeDataWriter<T>) throws java.io.FileNotFoundException;
    method public android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException;
    method public abstract android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
    method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.content.CancelationSignal);
    method protected final void setPathPermissions(android.content.pm.PathPermission[]);
    method protected final void setReadPermission(java.lang.String);
    method protected final void setWritePermission(java.lang.String);
@@ -4843,6 +4858,7 @@ package android.content {
    method public android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException, android.os.RemoteException;
    method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException, android.os.RemoteException;
    method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String) throws android.os.RemoteException;
    method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.content.CancelationSignal) throws android.os.RemoteException;
    method public boolean release();
    method public int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]) throws android.os.RemoteException;
  }
@@ -4929,6 +4945,7 @@ package android.content {
    method public final java.io.OutputStream openOutputStream(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
    method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException;
    method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
    method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.content.CancelationSignal);
    method public final void registerContentObserver(android.net.Uri, boolean, android.database.ContentObserver);
    method public static void removePeriodicSync(android.accounts.Account, java.lang.String, android.os.Bundle);
    method public static void removeStatusChangeListener(java.lang.Object);
@@ -5781,6 +5798,11 @@ package android.content {
    method public int getNumSuccessfulYieldPoints();
  }
  public class OperationCanceledException extends java.lang.RuntimeException {
    ctor public OperationCanceledException();
    ctor public OperationCanceledException(java.lang.String);
  }
  public class PeriodicSync implements android.os.Parcelable {
    ctor public PeriodicSync(android.accounts.Account, java.lang.String, android.os.Bundle, long);
    method public int describeContents();
@@ -7225,11 +7247,15 @@ package android.database.sqlite {
    method public static android.database.sqlite.SQLiteDatabase openOrCreateDatabase(java.lang.String, android.database.sqlite.SQLiteDatabase.CursorFactory);
    method public static android.database.sqlite.SQLiteDatabase openOrCreateDatabase(java.lang.String, android.database.sqlite.SQLiteDatabase.CursorFactory, android.database.DatabaseErrorHandler);
    method public android.database.Cursor query(boolean, java.lang.String, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String, java.lang.String);
    method public android.database.Cursor query(boolean, java.lang.String, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String, java.lang.String, android.content.CancelationSignal);
    method public android.database.Cursor query(java.lang.String, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String);
    method public android.database.Cursor query(java.lang.String, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String, java.lang.String);
    method public android.database.Cursor queryWithFactory(android.database.sqlite.SQLiteDatabase.CursorFactory, boolean, java.lang.String, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String, java.lang.String);
    method public android.database.Cursor queryWithFactory(android.database.sqlite.SQLiteDatabase.CursorFactory, boolean, java.lang.String, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String, java.lang.String, android.content.CancelationSignal);
    method public android.database.Cursor rawQuery(java.lang.String, java.lang.String[]);
    method public android.database.Cursor rawQuery(java.lang.String, java.lang.String[], android.content.CancelationSignal);
    method public android.database.Cursor rawQueryWithFactory(android.database.sqlite.SQLiteDatabase.CursorFactory, java.lang.String, java.lang.String[], java.lang.String);
    method public android.database.Cursor rawQueryWithFactory(android.database.sqlite.SQLiteDatabase.CursorFactory, java.lang.String, java.lang.String[], java.lang.String, android.content.CancelationSignal);
    method public static int releaseMemory();
    method public long replace(java.lang.String, java.lang.String, android.content.ContentValues);
    method public long replaceOrThrow(java.lang.String, java.lang.String, android.content.ContentValues) throws android.database.SQLException;
@@ -7351,6 +7377,7 @@ package android.database.sqlite {
    method public java.lang.String getTables();
    method public android.database.Cursor query(android.database.sqlite.SQLiteDatabase, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String);
    method public android.database.Cursor query(android.database.sqlite.SQLiteDatabase, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String, java.lang.String);
    method public android.database.Cursor query(android.database.sqlite.SQLiteDatabase, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String, java.lang.String, android.content.CancelationSignal);
    method public void setCursorFactory(android.database.sqlite.SQLiteDatabase.CursorFactory);
    method public void setDistinct(boolean);
    method public void setProjectionMap(java.util.Map<java.lang.String, java.lang.String>);
+20 −0
Original line number Diff line number Diff line
@@ -173,6 +173,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
                if (DEBUG) Slog.v(TAG, "cancelLoad: cancelled=" + cancelled);
                if (cancelled) {
                    mCancellingTask = mTask;
                    onCancelLoadInBackground();
                }
                mTask = null;
                return cancelled;
@@ -255,6 +256,25 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
        return loadInBackground();
    }

    /**
     * Override this method to try to abort the computation currently taking
     * place on a background thread.
     *
     * Note that when this method is called, it is possible that {@link #loadInBackground}
     * has not started yet or has already completed.
     */
    protected void onCancelLoadInBackground() {
    }

    /**
     * Returns true if the current execution of {@link #loadInBackground()} is being canceled.
     *
     * @return True if the current execution of {@link #loadInBackground()} is being canceled.
     */
    protected boolean isLoadInBackgroundCanceled() {
        return mCancellingTask != null;
    }

    /**
     * Locks the current thread until the loader completes the current load
     * operation. Returns immediately if there is no load operation running.
+164 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2012 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.os.RemoteException;

/**
 * Provides the ability to cancel an operation in progress.
 */
public final class CancelationSignal {
    private boolean mIsCanceled;
    private OnCancelListener mOnCancelListener;
    private ICancelationSignal mRemote;

    /**
     * Creates a cancelation signal, initially not canceled.
     */
    public CancelationSignal() {
    }

    /**
     * Returns true if the operation has been canceled.
     *
     * @return True if the operation has been canceled.
     */
    public boolean isCanceled() {
        synchronized (this) {
            return mIsCanceled;
        }
    }

    /**
     * Throws {@link OperationCanceledException} if the operation has been canceled.
     *
     * @throws OperationCanceledException if the operation has been canceled.
     */
    public void throwIfCanceled() {
        if (isCanceled()) {
            throw new OperationCanceledException();
        }
    }

    /**
     * Cancels the operation and signals the cancelation listener.
     * If the operation has not yet started, then it will be canceled as soon as it does.
     */
    public void cancel() {
        synchronized (this) {
            if (!mIsCanceled) {
                mIsCanceled = true;
                if (mOnCancelListener != null) {
                    mOnCancelListener.onCancel();
                }
                if (mRemote != null) {
                    try {
                        mRemote.cancel();
                    } catch (RemoteException ex) {
                    }
                }
            }
        }
    }

    /**
     * Sets the cancelation listener to be called when canceled.
     * If {@link CancelationSignal#cancel} has already been called, then the provided
     * listener is invoked immediately.
     *
     * The listener is called while holding the cancelation signal's lock which is
     * also held while registering or unregistering the listener.  Because of the lock,
     * it is not possible for the listener to run after it has been unregistered.
     * This design choice makes it easier for clients of {@link CancelationSignal} to
     * prevent race conditions related to listener registration and unregistration.
     *
     * @param listener The cancelation listener, or null to remove the current listener.
     */
    public void setOnCancelListener(OnCancelListener listener) {
        synchronized (this) {
            mOnCancelListener = listener;
            if (mIsCanceled && listener != null) {
                listener.onCancel();
            }
        }
    }

    /**
     * Sets the remote transport.
     *
     * @param remote The remote transport, or null to remove.
     *
     * @hide
     */
    public void setRemote(ICancelationSignal remote) {
        synchronized (this) {
            mRemote = remote;
            if (mIsCanceled && remote != null) {
                try {
                    remote.cancel();
                } catch (RemoteException ex) {
                }
            }
        }
    }

    /**
     * Creates a transport that can be returned back to the caller of
     * a Binder function and subsequently used to dispatch a cancelation signal.
     *
     * @return The new cancelation signal transport.
     *
     * @hide
     */
    public static ICancelationSignal createTransport() {
        return new Transport();
    }

    /**
     * Given a locally created transport, returns its associated cancelation signal.
     *
     * @param transport The locally created transport, or null if none.
     * @return The associated cancelation signal, or null if none.
     *
     * @hide
     */
    public static CancelationSignal fromTransport(ICancelationSignal transport) {
        if (transport instanceof Transport) {
            return ((Transport)transport).mCancelationSignal;
        }
        return null;
    }

    /**
     * Listens for cancelation.
     */
    public interface OnCancelListener {
        /**
         * Called when {@link CancelationSignal#cancel} is invoked.
         */
        void onCancel();
    }

    private static final class Transport extends ICancelationSignal.Stub {
        final CancelationSignal mCancelationSignal = new CancelationSignal();

        @Override
        public void cancel() throws RemoteException {
            mCancelationSignal.cancel();
        }
    }
}
+89 −4
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import android.os.Binder;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.util.Log;

import java.io.File;
@@ -174,28 +175,33 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
            return getContentProvider().getClass().getName();
        }

        @Override
        public Cursor query(Uri uri, String[] projection,
                String selection, String[] selectionArgs, String sortOrder) {
                String selection, String[] selectionArgs, String sortOrder,
                ICancelationSignal cancelationSignal) {
            enforceReadPermission(uri);
            return ContentProvider.this.query(uri, projection, selection,
                    selectionArgs, sortOrder);
            return ContentProvider.this.query(uri, projection, selection, selectionArgs, sortOrder,
                    CancelationSignal.fromTransport(cancelationSignal));
        }

        @Override
        public String getType(Uri uri) {
            return ContentProvider.this.getType(uri);
        }


        @Override
        public Uri insert(Uri uri, ContentValues initialValues) {
            enforceWritePermission(uri);
            return ContentProvider.this.insert(uri, initialValues);
        }

        @Override
        public int bulkInsert(Uri uri, ContentValues[] initialValues) {
            enforceWritePermission(uri);
            return ContentProvider.this.bulkInsert(uri, initialValues);
        }

        @Override
        public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
                throws OperationApplicationException {
            for (ContentProviderOperation operation : operations) {
@@ -210,17 +216,20 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
            return ContentProvider.this.applyBatch(operations);
        }

        @Override
        public int delete(Uri uri, String selection, String[] selectionArgs) {
            enforceWritePermission(uri);
            return ContentProvider.this.delete(uri, selection, selectionArgs);
        }

        @Override
        public int update(Uri uri, ContentValues values, String selection,
                String[] selectionArgs) {
            enforceWritePermission(uri);
            return ContentProvider.this.update(uri, values, selection, selectionArgs);
        }

        @Override
        public ParcelFileDescriptor openFile(Uri uri, String mode)
                throws FileNotFoundException {
            if (mode != null && mode.startsWith("rw")) enforceWritePermission(uri);
@@ -228,6 +237,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
            return ContentProvider.this.openFile(uri, mode);
        }

        @Override
        public AssetFileDescriptor openAssetFile(Uri uri, String mode)
                throws FileNotFoundException {
            if (mode != null && mode.startsWith("rw")) enforceWritePermission(uri);
@@ -235,6 +245,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
            return ContentProvider.this.openAssetFile(uri, mode);
        }

        @Override
        public Bundle call(String method, String arg, Bundle extras) {
            return ContentProvider.this.call(method, arg, extras);
        }
@@ -251,6 +262,11 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
            return ContentProvider.this.openTypedAssetFile(uri, mimeType, opts);
        }

        @Override
        public ICancelationSignal createCancelationSignal() throws RemoteException {
            return CancelationSignal.createTransport();
        }

        private void enforceReadPermission(Uri uri) {
            final int uid = Binder.getCallingUid();
            if (uid == mMyUid) {
@@ -540,6 +556,75 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
    public abstract Cursor query(Uri uri, String[] projection,
            String selection, String[] selectionArgs, String sortOrder);

    /**
     * Implement this to handle query requests from clients with support for cancelation.
     * This method can be called from multiple threads, as described in
     * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
     * and Threads</a>.
     * <p>
     * Example client call:<p>
     * <pre>// Request a specific record.
     * Cursor managedCursor = managedQuery(
                ContentUris.withAppendedId(Contacts.People.CONTENT_URI, 2),
                projection,    // Which columns to return.
                null,          // WHERE clause.
                null,          // WHERE clause value substitution
                People.NAME + " ASC");   // Sort order.</pre>
     * Example implementation:<p>
     * <pre>// SQLiteQueryBuilder is a helper class that creates the
        // proper SQL syntax for us.
        SQLiteQueryBuilder qBuilder = new SQLiteQueryBuilder();

        // Set the table we're querying.
        qBuilder.setTables(DATABASE_TABLE_NAME);

        // If the query ends in a specific record number, we're
        // being asked for a specific record, so set the
        // WHERE clause in our query.
        if((URI_MATCHER.match(uri)) == SPECIFIC_MESSAGE){
            qBuilder.appendWhere("_id=" + uri.getPathLeafId());
        }

        // Make the query.
        Cursor c = qBuilder.query(mDb,
                projection,
                selection,
                selectionArgs,
                groupBy,
                having,
                sortOrder);
        c.setNotificationUri(getContext().getContentResolver(), uri);
        return c;</pre>
     * <p>
     * If you implement this method then you must also implement the version of
     * {@link #query(Uri, String[], String, String[], String)} that does not take a cancelation
     * provider to ensure correct operation on older versions of the Android Framework in
     * which the cancelation signal overload was not available.
     *
     * @param uri The URI to query. This will be the full URI sent by the client;
     *      if the client is requesting a specific record, the URI will end in a record number
     *      that the implementation should parse and add to a WHERE or HAVING clause, specifying
     *      that _id value.
     * @param projection The list of columns to put into the cursor. If
     *      null all columns are included.
     * @param selection A selection criteria to apply when filtering rows.
     *      If null then all rows are included.
     * @param selectionArgs You may include ?s in selection, which will be replaced by
     *      the values from selectionArgs, in order that they appear in the selection.
     *      The values will be bound as Strings.
     * @param sortOrder How the rows in the cursor should be sorted.
     *      If null then the provider is free to define the sort order.
     * @param cancelationSignal A signal to cancel the operation in progress, or null if none.
     * If the operation is canceled, then {@link OperationCanceledException} will be thrown
     * when the query is executed.
     * @return a Cursor or null.
     */
    public Cursor query(Uri uri, String[] projection,
            String selection, String[] selectionArgs, String sortOrder,
            CancelationSignal cancelationSignal) {
        return query(uri, projection, selection, selectionArgs, sortOrder);
    }

    /**
     * Implement this to handle requests for the MIME type of the data at the
     * given URI.  The returned MIME type should start with
Loading