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

Commit b19a71a2 authored by Jeff Brown's avatar Jeff Brown
Browse files

Support automatic cancellation of Loaders.

Change-Id: I18d3f49e413f48fcdd519d15e99c238ad54d35b9
parent f10d69f3
Loading
Loading
Loading
Loading
+11 −3
Original line number Diff line number Diff line
@@ -4660,10 +4660,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 void cancelLoadInBackground();
    method public 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);
@@ -5754,7 +5753,9 @@ package android.content {
  public class Loader {
    ctor public Loader(android.content.Context);
    method public void abandon();
    method public boolean cancelLoad();
    method public java.lang.String dataToString(D);
    method public void deliverCancellation();
    method public void deliverResult(D);
    method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
    method public void forceLoad();
@@ -5764,23 +5765,30 @@ package android.content {
    method public boolean isReset();
    method public boolean isStarted();
    method protected void onAbandon();
    method protected boolean onCancelLoad();
    method public void onContentChanged();
    method protected void onForceLoad();
    method protected void onReset();
    method protected void onStartLoading();
    method protected void onStopLoading();
    method public void registerListener(int, android.content.Loader.OnLoadCompleteListener<D>);
    method public void registerOnLoadCanceledListener(android.content.Loader.OnLoadCanceledListener<D>);
    method public void reset();
    method public final void startLoading();
    method public void stopLoading();
    method public boolean takeContentChanged();
    method public void unregisterListener(android.content.Loader.OnLoadCompleteListener<D>);
    method public void unregisterOnLoadCanceledListener(android.content.Loader.OnLoadCanceledListener<D>);
  }
  public final class Loader.ForceLoadContentObserver extends android.database.ContentObserver {
    ctor public Loader.ForceLoadContentObserver();
  }
  public static abstract interface Loader.OnLoadCanceledListener {
    method public abstract void onLoadCanceled(android.content.Loader<D>);
  }
  public static abstract interface Loader.OnLoadCompleteListener {
    method public abstract void onLoadComplete(android.content.Loader<D>, D);
  }
+51 −5
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.app;

import android.content.Loader;
import android.content.Loader.OnLoadCanceledListener;
import android.os.Bundle;
import android.util.DebugUtils;
import android.util.Log;
@@ -219,7 +220,8 @@ class LoaderManagerImpl extends LoaderManager {
    
    boolean mCreatingLoader;

    final class LoaderInfo implements Loader.OnLoadCompleteListener<Object> {
    final class LoaderInfo implements Loader.OnLoadCompleteListener<Object>,
            Loader.OnLoadCanceledListener<Object> {
        final int mId;
        final Bundle mArgs;
        LoaderManager.LoaderCallbacks<Object> mCallbacks;
@@ -271,6 +273,7 @@ class LoaderManagerImpl extends LoaderManager {
                }
                if (!mListenerRegistered) {
                    mLoader.registerListener(mId, this);
                    mLoader.registerOnLoadCanceledListener(this);
                    mListenerRegistered = true;
                }
                mLoader.startLoading();
@@ -329,11 +332,21 @@ class LoaderManagerImpl extends LoaderManager {
                    // Let the loader know we're done with it
                    mListenerRegistered = false;
                    mLoader.unregisterListener(this);
                    mLoader.unregisterOnLoadCanceledListener(this);
                    mLoader.stopLoading();
                }
            }
        }

        void cancel() {
            if (DEBUG) Log.v(TAG, "  Canceling: " + this);
            if (mStarted && mLoader != null && mListenerRegistered) {
                if (!mLoader.cancelLoad()) {
                    onLoadCanceled(mLoader);
                }
            }
        }

        void destroy() {
            if (DEBUG) Log.v(TAG, "  Destroying: " + this);
            mDestroyed = true;
@@ -361,6 +374,7 @@ class LoaderManagerImpl extends LoaderManager {
                if (mListenerRegistered) {
                    mListenerRegistered = false;
                    mLoader.unregisterListener(this);
                    mLoader.unregisterOnLoadCanceledListener(this);
                }
                mLoader.reset();
            }
@@ -369,7 +383,37 @@ class LoaderManagerImpl extends LoaderManager {
            }
        }

        @Override public void onLoadComplete(Loader<Object> loader, Object data) {
        @Override
        public void onLoadCanceled(Loader<Object> loader) {
            if (DEBUG) Log.v(TAG, "onLoadCanceled: " + this);

            if (mDestroyed) {
                if (DEBUG) Log.v(TAG, "  Ignoring load canceled -- destroyed");
                return;
            }

            if (mLoaders.get(mId) != this) {
                // This cancellation message is not coming from the current active loader.
                // We don't care about it.
                if (DEBUG) Log.v(TAG, "  Ignoring load canceled -- not active");
                return;
            }

            LoaderInfo pending = mPendingLoader;
            if (pending != null) {
                // There is a new request pending and we were just
                // waiting for the old one to cancel or complete before starting
                // it.  So now it is time, switch over to the new loader.
                if (DEBUG) Log.v(TAG, "  Switching to pending loader: " + pending);
                mPendingLoader = null;
                mLoaders.put(mId, null);
                destroy();
                installLoader(pending);
            }
        }

        @Override
        public void onLoadComplete(Loader<Object> loader, Object data) {
            if (DEBUG) Log.v(TAG, "onLoadComplete: " + this);
            
            if (mDestroyed) {
@@ -632,7 +676,9 @@ class LoaderManagerImpl extends LoaderManager {
                    } else {
                        // Now we have three active loaders... we'll queue
                        // up this request to be processed once one of the other loaders
                        // finishes.
                        // finishes or is canceled.
                        if (DEBUG) Log.v(TAG, "  Current loader is running; attempting to cancel");
                        info.cancel();
                        if (info.mPendingLoader != null) {
                            if (DEBUG) Log.v(TAG, "  Removing pending loader: " + info.mPendingLoader);
                            info.mPendingLoader.destroy();
+96 −50
Original line number Diff line number Diff line
@@ -53,19 +53,33 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
    static final boolean DEBUG = false;

    final class LoadTask extends AsyncTask<Void, Void, D> implements Runnable {
        private final CountDownLatch mDone = new CountDownLatch(1);

        D result;
        // Set to true to indicate that the task has been posted to a handler for
        // execution at a later time.  Used to throttle updates.
        boolean waiting;

        private CountDownLatch done = new CountDownLatch(1);

        /* Runs on a worker thread */
        @Override
        protected D doInBackground(Void... params) {
            if (DEBUG) Slog.v(TAG, this + " >>> doInBackground");
            result = AsyncTaskLoader.this.onLoadInBackground();
            try {
                D data = AsyncTaskLoader.this.onLoadInBackground();
                if (DEBUG) Slog.v(TAG, this + "  <<< doInBackground");
            return result;
                return data;
            } catch (OperationCanceledException ex) {
                if (!isCancelled()) {
                    // onLoadInBackground threw a canceled exception spuriously.
                    // This is problematic because it means that the LoaderManager did not
                    // cancel the Loader itself and still expects to receive a result.
                    // Additionally, the Loader's own state will not have been updated to
                    // reflect the fact that the task was being canceled.
                    // So we treat this case as an unhandled exception.
                    throw ex;
                }
                if (DEBUG) Slog.v(TAG, this + "  <<< doInBackground (was canceled)");
                return null;
            }
        }

        /* Runs on the UI thread */
@@ -75,25 +89,37 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
            try {
                AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
            } finally {
                done.countDown();
                mDone.countDown();
            }
        }

        /* Runs on the UI thread */
        @Override
        protected void onCancelled() {
        protected void onCancelled(D data) {
            if (DEBUG) Slog.v(TAG, this + " onCancelled");
            try {
                AsyncTaskLoader.this.dispatchOnCancelled(this, result);
                AsyncTaskLoader.this.dispatchOnCancelled(this, data);
            } finally {
                done.countDown();
                mDone.countDown();
            }
        }

        /* Runs on the UI thread, when the waiting task is posted to a handler.
         * This method is only executed when task execution was deferred (waiting was true). */
        @Override
        public void run() {
            waiting = false;
            AsyncTaskLoader.this.executePendingTask();
        }

        /* Used for testing purposes to wait for the task to complete. */
        public void waitForLoader() {
            try {
                mDone.await();
            } catch (InterruptedException e) {
                // Ignore
            }
        }
    }

    volatile LoadTask mTask;
@@ -109,7 +135,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {

    /**
     * Set amount to throttle updates by.  This is the minimum time from
     * when the last {@link #onLoadInBackground()} call has completed until
     * when the last {@link #loadInBackground()} call has completed until
     * a new load is scheduled.
     *
     * @param delayMS Amount of delay, in milliseconds.
@@ -130,24 +156,9 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
        executePendingTask();
    }

    /**
     * Attempt to cancel the current load task. See {@link AsyncTask#cancel(boolean)}
     * for more info.  Must be called on the main thread of the process.
     *
     * <p>Cancelling is not an immediate operation, since the load is performed
     * in a background thread.  If there is currently a load in progress, this
     * method requests that the load be cancelled, and notes this is the case;
     * once the background thread has completed its work its remaining state
     * will be cleared.  If another load request comes in during this time,
     * it will be held until the cancelled load is complete.
     *
     * @return Returns <tt>false</tt> if the task could not be cancelled,
     *         typically because it has already completed normally, or
     *         because {@link #startLoading()} hasn't been called; returns
     *         <tt>true</tt> otherwise.
     */
    public boolean cancelLoad() {
        if (DEBUG) Slog.v(TAG, "cancelLoad: mTask=" + mTask);
    @Override
    protected boolean onCancelLoad() {
        if (DEBUG) Slog.v(TAG, "onCancelLoad: mTask=" + mTask);
        if (mTask != null) {
            if (mCancellingTask != null) {
                // There was a pending task already waiting for a previous
@@ -173,7 +184,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
                if (DEBUG) Slog.v(TAG, "cancelLoad: cancelled=" + cancelled);
                if (cancelled) {
                    mCancellingTask = mTask;
                    onCancelLoadInBackground();
                    cancelLoadInBackground();
                }
                mTask = null;
                return cancelled;
@@ -184,7 +195,10 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {

    /**
     * Called if the task was canceled before it was completed.  Gives the class a chance
     * to properly dispose of the result.
     * to clean up post-cancellation and to properly dispose of the result.
     *
     * @param data The value that was returned by {@link #loadInBackground}, or null
     * if the task threw {@link OperationCanceledException}.
     */
    public void onCanceled(D data) {
    }
@@ -218,6 +232,8 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
            if (DEBUG) Slog.v(TAG, "Cancelled task is now canceled!");
            mLastLoadCompleteTime = SystemClock.uptimeMillis();
            mCancellingTask = null;
            if (DEBUG) Slog.v(TAG, "Delivering cancellation");
            deliverCancellation();
            executePendingTask();
        }
    }
@@ -240,38 +256,72 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
    }

    /**
     * Called on a worker thread to perform the actual load and to return
     * the result of the load operation.
     *
     * Implementations should not deliver the result directly, but should return them
     * from this method, which will eventually end up calling {@link #deliverResult} on
     * the UI thread.  If implementations need to process the results on the UI thread
     * they may override {@link #deliverResult} and do so there.
     *
     * To support cancellation, this method should periodically check the value of
     * {@link #isLoadInBackgroundCanceled} and terminate when it returns true.
     * Subclasses may also override {@link #cancelLoadInBackground} to interrupt the load
     * directly instead of polling {@link #isLoadInBackgroundCanceled}.
     *
     * When the load is canceled, this method may either return normally or throw
     * {@link OperationCanceledException}.  In either case, the {@link Loader} will
     * call {@link #onCanceled} to perform post-cancellation cleanup and to dispose of the
     * result object, if any.
     *
     * @return The result of the load operation.
     *
     * @throws OperationCanceledException if the load is canceled during execution.
     *
     * @see #isLoadInBackgroundCanceled
     * @see #cancelLoadInBackground
     * @see #onCanceled
     */
    public abstract D loadInBackground();

    /**
     * Called on a worker thread to perform the actual load. Implementations should not deliver the
     * result directly, but should return them from this method, which will eventually end up
     * calling {@link #deliverResult} on the UI thread. If implementations need to process
     * the results on the UI thread they may override {@link #deliverResult} and do so
     * there.
     * Calls {@link #loadInBackground()}.
     *
     * This method is reserved for use by the loader framework.
     * Subclasses should override {@link #loadInBackground} instead of this method.
     *
     * @return The result of the load operation.
     *
     * @return Implementations must return the result of their load operation.
     * @throws OperationCanceledException if the load is canceled during execution.
     *
     * @see #loadInBackground
     */
    protected D onLoadInBackground() {
        return loadInBackground();
    }

    /**
     * Override this method to try to abort the computation currently taking
     * place on a background thread.
     * Called on the main thread to abort a load in progress.
     *
     * Override this method to abort the current invocation of {@link #loadInBackground}
     * that is running in the background on a worker thread.
     *
     * Note that when this method is called, it is possible that {@link #loadInBackground}
     * has not started yet or has already completed.
     * This method should do nothing if {@link #loadInBackground} has not started
     * running or if it has already finished.
     *
     * @see #loadInBackground
     */
    protected void onCancelLoadInBackground() {
    public void cancelLoadInBackground() {
    }

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

@@ -288,11 +338,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
    public void waitForLoader() {
        LoadTask task = mTask;
        if (task != null) {
            try {
                task.done.await();
            } catch (InterruptedException e) {
                // Ignore
            }
            task.waitForLoader();
        }
    }

+2 −2
Original line number Diff line number Diff line
@@ -76,8 +76,8 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> {
    }

    @Override
    protected void onCancelLoadInBackground() {
        super.onCancelLoadInBackground();
    public void cancelLoadInBackground() {
        super.cancelLoadInBackground();

        synchronized (this) {
            if (mCancelationSignal != null) {
+101 −0
Original line number Diff line number Diff line
@@ -52,6 +52,7 @@ import java.io.PrintWriter;
public class Loader<D> {
    int mId;
    OnLoadCompleteListener<D> mListener;
    OnLoadCanceledListener<D> mOnLoadCanceledListener;
    Context mContext;
    boolean mStarted = false;
    boolean mAbandoned = false;
@@ -99,6 +100,23 @@ public class Loader<D> {
        public void onLoadComplete(Loader<D> loader, D data);
    }

    /**
     * Interface that is implemented to discover when a Loader has been canceled
     * before it finished loading its data.  You do not normally need to implement
     * this yourself; it is used in the implementation of {@link android.app.LoaderManager}
     * to find out when a Loader it is managing has been canceled so that it
     * can schedule the next Loader.  This interface should only be used if a
     * Loader is not being used in conjunction with LoaderManager.
     */
    public interface OnLoadCanceledListener<D> {
        /**
         * Called on the thread that created the Loader when the load is canceled.
         *
         * @param loader the loader that canceled the load
         */
        public void onLoadCanceled(Loader<D> loader);
    }

    /**
     * Stores away the application context associated with context.
     * Since Loaders can be used across multiple activities it's dangerous to
@@ -126,6 +144,18 @@ public class Loader<D> {
        }
    }

    /**
     * Informs the registered {@link OnLoadCanceledListener} that the load has been canceled.
     * Should only be called by subclasses.
     *
     * Must be called from the process's main thread.
     */
    public void deliverCancellation() {
        if (mOnLoadCanceledListener != null) {
            mOnLoadCanceledListener.onLoadCanceled(this);
        }
    }

    /**
     * @return an application context retrieved from the Context passed to the constructor.
     */
@@ -170,6 +200,40 @@ public class Loader<D> {
        mListener = null;
    }

    /**
     * Registers a listener that will receive callbacks when a load is canceled.
     * The callback will be called on the process's main thread so it's safe to
     * pass the results to widgets.
     *
     * Must be called from the process's main thread.
     *
     * @param listener The listener to register.
     */
    public void registerOnLoadCanceledListener(OnLoadCanceledListener<D> listener) {
        if (mOnLoadCanceledListener != null) {
            throw new IllegalStateException("There is already a listener registered");
        }
        mOnLoadCanceledListener = listener;
    }

    /**
     * Unregisters a listener that was previously added with
     * {@link #registerOnLoadCanceledListener}.
     *
     * Must be called from the process's main thread.
     *
     * @param listener The listener to unregister.
     */
    public void unregisterOnLoadCanceledListener(OnLoadCanceledListener<D> listener) {
        if (mOnLoadCanceledListener == null) {
            throw new IllegalStateException("No listener register");
        }
        if (mOnLoadCanceledListener != listener) {
            throw new IllegalArgumentException("Attempting to unregister the wrong listener");
        }
        mOnLoadCanceledListener = null;
    }

    /**
     * Return whether this load has been started.  That is, its {@link #startLoading()}
     * has been called and no calls to {@link #stopLoading()} or
@@ -233,6 +297,43 @@ public class Loader<D> {
    protected void onStartLoading() {
    }

    /**
     * Attempt to cancel the current load task.
     * Must be called on the main thread of the process.
     *
     * <p>Cancellation is not an immediate operation, since the load is performed
     * in a background thread.  If there is currently a load in progress, this
     * method requests that the load be canceled, and notes this is the case;
     * once the background thread has completed its work its remaining state
     * will be cleared.  If another load request comes in during this time,
     * it will be held until the canceled load is complete.
     *
     * @return Returns <tt>false</tt> if the task could not be canceled,
     * typically because it has already completed normally, or
     * because {@link #startLoading()} hasn't been called; returns
     * <tt>true</tt> otherwise.  When <tt>true</tt> is returned, the task
     * is still running and the {@link OnLoadCanceledListener} will be called
     * when the task completes.
     */
    public boolean cancelLoad() {
        return onCancelLoad();
    }

    /**
     * Subclasses must implement this to take care of requests to {@link #cancelLoad()}.
     * This will always be called from the process's main thread.
     *
     * @return Returns <tt>false</tt> if the task could not be canceled,
     * typically because it has already completed normally, or
     * because {@link #startLoading()} hasn't been called; returns
     * <tt>true</tt> otherwise.  When <tt>true</tt> is returned, the task
     * is still running and the {@link OnLoadCanceledListener} will be called
     * when the task completes.
     */
    protected boolean onCancelLoad() {
        return false;
    }

    /**
     * Force an asynchronous load. Unlike {@link #startLoading()} this will ignore a previously
     * loaded data set and load a new one.  This simply calls through to the
Loading