Home | History | Annotate | Download | only in content
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.support.v4.content;
     18 
     19 import android.content.Context;
     20 import android.database.ContentObserver;
     21 import android.os.Handler;
     22 import android.support.v4.util.DebugUtils;
     23 
     24 import java.io.FileDescriptor;
     25 import java.io.PrintWriter;
     26 
     27 /**
     28  * Static library support version of the framework's {@link android.content.Loader}.
     29  * Used to write apps that run on platforms prior to Android 3.0.  When running
     30  * on Android 3.0 or above, this implementation is still used; it does not try
     31  * to switch to the framework's implementation.  See the framework SDK
     32  * documentation for a class overview.
     33  */
     34 public class Loader<D> {
     35     int mId;
     36     OnLoadCompleteListener<D> mListener;
     37     OnLoadCanceledListener<D> mOnLoadCanceledListener;
     38     Context mContext;
     39     boolean mStarted = false;
     40     boolean mAbandoned = false;
     41     boolean mReset = true;
     42     boolean mContentChanged = false;
     43     boolean mProcessingChange = false;
     44 
     45     /**
     46      * An implementation of a ContentObserver that takes care of connecting
     47      * it to the Loader to have the loader re-load its data when the observer
     48      * is told it has changed.  You do not normally need to use this yourself;
     49      * it is used for you by {@link CursorLoader} to take care of executing
     50      * an update when the cursor's backing data changes.
     51      */
     52     public final class ForceLoadContentObserver extends ContentObserver {
     53         public ForceLoadContentObserver() {
     54             super(new Handler());
     55         }
     56 
     57         @Override
     58         public boolean deliverSelfNotifications() {
     59             return true;
     60         }
     61 
     62         @Override
     63         public void onChange(boolean selfChange) {
     64             onContentChanged();
     65         }
     66     }
     67 
     68     /**
     69      * Interface that is implemented to discover when a Loader has finished
     70      * loading its data.  You do not normally need to implement this yourself;
     71      * it is used in the implementation of {@link android.support.v4.app.LoaderManager}
     72      * to find out when a Loader it is managing has completed so that this can
     73      * be reported to its client.  This interface should only be used if a
     74      * Loader is not being used in conjunction with LoaderManager.
     75      */
     76     public interface OnLoadCompleteListener<D> {
     77         /**
     78          * Called on the thread that created the Loader when the load is complete.
     79          *
     80          * @param loader the loader that completed the load
     81          * @param data the result of the load
     82          */
     83         public void onLoadComplete(Loader<D> loader, D data);
     84     }
     85 
     86     /**
     87      * Interface that is implemented to discover when a Loader has been canceled
     88      * before it finished loading its data.  You do not normally need to implement
     89      * this yourself; it is used in the implementation of {@link android.support.v4.app.LoaderManager}
     90      * to find out when a Loader it is managing has been canceled so that it
     91      * can schedule the next Loader.  This interface should only be used if a
     92      * Loader is not being used in conjunction with LoaderManager.
     93      */
     94     public interface OnLoadCanceledListener<D> {
     95         /**
     96          * Called on the thread that created the Loader when the load is canceled.
     97          *
     98          * @param loader the loader that canceled the load
     99          */
    100         public void onLoadCanceled(Loader<D> loader);
    101     }
    102 
    103     /**
    104      * Stores away the application context associated with context.
    105      * Since Loaders can be used across multiple activities it's dangerous to
    106      * store the context directly; always use {@link #getContext()} to retrieve
    107      * the Loader's Context, don't use the constructor argument directly.
    108      * The Context returned by {@link #getContext} is safe to use across
    109      * Activity instances.
    110      *
    111      * @param context used to retrieve the application context.
    112      */
    113     public Loader(Context context) {
    114         mContext = context.getApplicationContext();
    115     }
    116 
    117     /**
    118      * Sends the result of the load to the registered listener. Should only be called by subclasses.
    119      *
    120      * Must be called from the process's main thread.
    121      *
    122      * @param data the result of the load
    123      */
    124     public void deliverResult(D data) {
    125         if (mListener != null) {
    126             mListener.onLoadComplete(this, data);
    127         }
    128     }
    129 
    130     /**
    131      * Informs the registered {@link OnLoadCanceledListener} that the load has been canceled.
    132      * Should only be called by subclasses.
    133      *
    134      * Must be called from the process's main thread.
    135      */
    136     public void deliverCancellation() {
    137         if (mOnLoadCanceledListener != null) {
    138             mOnLoadCanceledListener.onLoadCanceled(this);
    139         }
    140     }
    141 
    142     /**
    143      * @return an application context retrieved from the Context passed to the constructor.
    144      */
    145     public Context getContext() {
    146         return mContext;
    147     }
    148 
    149     /**
    150      * @return the ID of this loader
    151      */
    152     public int getId() {
    153         return mId;
    154     }
    155 
    156     /**
    157      * Registers a class that will receive callbacks when a load is complete.
    158      * The callback will be called on the process's main thread so it's safe to
    159      * pass the results to widgets.
    160      *
    161      * <p>Must be called from the process's main thread.
    162      */
    163     public void registerListener(int id, OnLoadCompleteListener<D> listener) {
    164         if (mListener != null) {
    165             throw new IllegalStateException("There is already a listener registered");
    166         }
    167         mListener = listener;
    168         mId = id;
    169     }
    170 
    171     /**
    172      * Remove a listener that was previously added with {@link #registerListener}.
    173      *
    174      * Must be called from the process's main thread.
    175      */
    176     public void unregisterListener(OnLoadCompleteListener<D> listener) {
    177         if (mListener == null) {
    178             throw new IllegalStateException("No listener register");
    179         }
    180         if (mListener != listener) {
    181             throw new IllegalArgumentException("Attempting to unregister the wrong listener");
    182         }
    183         mListener = null;
    184     }
    185 
    186     /**
    187      * Registers a listener that will receive callbacks when a load is canceled.
    188      * The callback will be called on the process's main thread so it's safe to
    189      * pass the results to widgets.
    190      *
    191      * Must be called from the process's main thread.
    192      *
    193      * @param listener The listener to register.
    194      */
    195     public void registerOnLoadCanceledListener(OnLoadCanceledListener<D> listener) {
    196         if (mOnLoadCanceledListener != null) {
    197             throw new IllegalStateException("There is already a listener registered");
    198         }
    199         mOnLoadCanceledListener = listener;
    200     }
    201 
    202     /**
    203      * Unregisters a listener that was previously added with
    204      * {@link #registerOnLoadCanceledListener}.
    205      *
    206      * Must be called from the process's main thread.
    207      *
    208      * @param listener The listener to unregister.
    209      */
    210     public void unregisterOnLoadCanceledListener(OnLoadCanceledListener<D> listener) {
    211         if (mOnLoadCanceledListener == null) {
    212             throw new IllegalStateException("No listener register");
    213         }
    214         if (mOnLoadCanceledListener != listener) {
    215             throw new IllegalArgumentException("Attempting to unregister the wrong listener");
    216         }
    217         mOnLoadCanceledListener = null;
    218     }
    219 
    220     /**
    221      * Return whether this load has been started.  That is, its {@link #startLoading()}
    222      * has been called and no calls to {@link #stopLoading()} or
    223      * {@link #reset()} have yet been made.
    224      */
    225     public boolean isStarted() {
    226         return mStarted;
    227     }
    228 
    229     /**
    230      * Return whether this loader has been abandoned.  In this state, the
    231      * loader <em>must not</em> report any new data, and <em>must</em> keep
    232      * its last reported data valid until it is finally reset.
    233      */
    234     public boolean isAbandoned() {
    235         return mAbandoned;
    236     }
    237 
    238     /**
    239      * Return whether this load has been reset.  That is, either the loader
    240      * has not yet been started for the first time, or its {@link #reset()}
    241      * has been called.
    242      */
    243     public boolean isReset() {
    244         return mReset;
    245     }
    246 
    247     /**
    248      * This function will normally be called for you automatically by
    249      * {@link android.support.v4.app.LoaderManager} when the associated fragment/activity
    250      * is being started.  When using a Loader with {@link android.support.v4.app.LoaderManager},
    251      * you <em>must not</em> call this method yourself, or you will conflict
    252      * with its management of the Loader.
    253      *
    254      * Starts an asynchronous load of the Loader's data. When the result
    255      * is ready the callbacks will be called on the process's main thread.
    256      * If a previous load has been completed and is still valid
    257      * the result may be passed to the callbacks immediately.
    258      * The loader will monitor the source of
    259      * the data set and may deliver future callbacks if the source changes.
    260      * Calling {@link #stopLoading} will stop the delivery of callbacks.
    261      *
    262      * <p>This updates the Loader's internal state so that
    263      * {@link #isStarted()} and {@link #isReset()} will return the correct
    264      * values, and then calls the implementation's {@link #onStartLoading()}.
    265      *
    266      * <p>Must be called from the process's main thread.
    267      */
    268     public final void startLoading() {
    269         mStarted = true;
    270         mReset = false;
    271         mAbandoned = false;
    272         onStartLoading();
    273     }
    274 
    275     /**
    276      * Subclasses must implement this to take care of loading their data,
    277      * as per {@link #startLoading()}.  This is not called by clients directly,
    278      * but as a result of a call to {@link #startLoading()}.
    279      */
    280     protected void onStartLoading() {
    281     }
    282 
    283     /**
    284      * Attempt to cancel the current load task.
    285      * Must be called on the main thread of the process.
    286      *
    287      * <p>Cancellation is not an immediate operation, since the load is performed
    288      * in a background thread.  If there is currently a load in progress, this
    289      * method requests that the load be canceled, and notes this is the case;
    290      * once the background thread has completed its work its remaining state
    291      * will be cleared.  If another load request comes in during this time,
    292      * it will be held until the canceled load is complete.
    293      *
    294      * @return Returns <tt>false</tt> if the task could not be canceled,
    295      * typically because it has already completed normally, or
    296      * because {@link #startLoading()} hasn't been called; returns
    297      * <tt>true</tt> otherwise.  When <tt>true</tt> is returned, the task
    298      * is still running and the {@link OnLoadCanceledListener} will be called
    299      * when the task completes.
    300      */
    301     public boolean cancelLoad() {
    302         return onCancelLoad();
    303     }
    304 
    305     /**
    306      * Subclasses must implement this to take care of requests to {@link #cancelLoad()}.
    307      * This will always be called from the process's main thread.
    308      *
    309      * @return Returns <tt>false</tt> if the task could not be canceled,
    310      * typically because it has already completed normally, or
    311      * because {@link #startLoading()} hasn't been called; returns
    312      * <tt>true</tt> otherwise.  When <tt>true</tt> is returned, the task
    313      * is still running and the {@link OnLoadCanceledListener} will be called
    314      * when the task completes.
    315      */
    316     protected boolean onCancelLoad() {
    317         return false;
    318     }
    319 
    320     /**
    321      * Force an asynchronous load. Unlike {@link #startLoading()} this will ignore a previously
    322      * loaded data set and load a new one.  This simply calls through to the
    323      * implementation's {@link #onForceLoad()}.  You generally should only call this
    324      * when the loader is started -- that is, {@link #isStarted()} returns true.
    325      *
    326      * <p>Must be called from the process's main thread.
    327      */
    328     public void forceLoad() {
    329         onForceLoad();
    330     }
    331 
    332     /**
    333      * Subclasses must implement this to take care of requests to {@link #forceLoad()}.
    334      * This will always be called from the process's main thread.
    335      */
    336     protected void onForceLoad() {
    337     }
    338 
    339     /**
    340      * This function will normally be called for you automatically by
    341      * {@link android.support.v4.app.LoaderManager} when the associated fragment/activity
    342      * is being stopped.  When using a Loader with {@link android.support.v4.app.LoaderManager},
    343      * you <em>must not</em> call this method yourself, or you will conflict
    344      * with its management of the Loader.
    345      *
    346      * <p>Stops delivery of updates until the next time {@link #startLoading()} is called.
    347      * Implementations should <em>not</em> invalidate their data at this point --
    348      * clients are still free to use the last data the loader reported.  They will,
    349      * however, typically stop reporting new data if the data changes; they can
    350      * still monitor for changes, but must not report them to the client until and
    351      * if {@link #startLoading()} is later called.
    352      *
    353      * <p>This updates the Loader's internal state so that
    354      * {@link #isStarted()} will return the correct
    355      * value, and then calls the implementation's {@link #onStopLoading()}.
    356      *
    357      * <p>Must be called from the process's main thread.
    358      */
    359     public void stopLoading() {
    360         mStarted = false;
    361         onStopLoading();
    362     }
    363 
    364     /**
    365      * Subclasses must implement this to take care of stopping their loader,
    366      * as per {@link #stopLoading()}.  This is not called by clients directly,
    367      * but as a result of a call to {@link #stopLoading()}.
    368      * This will always be called from the process's main thread.
    369      */
    370     protected void onStopLoading() {
    371     }
    372 
    373     /**
    374      * This function will normally be called for you automatically by
    375      * {@link android.support.v4.app.LoaderManager} when restarting a Loader.  When using
    376      * a Loader with {@link android.support.v4.app.LoaderManager},
    377      * you <em>must not</em> call this method yourself, or you will conflict
    378      * with its management of the Loader.
    379      *
    380      * Tell the Loader that it is being abandoned.  This is called prior
    381      * to {@link #reset} to have it retain its current data but not report
    382      * any new data.
    383      */
    384     public void abandon() {
    385         mAbandoned = true;
    386         onAbandon();
    387     }
    388 
    389     /**
    390      * Subclasses implement this to take care of being abandoned.  This is
    391      * an optional intermediate state prior to {@link #onReset()} -- it means that
    392      * the client is no longer interested in any new data from the loader,
    393      * so the loader must not report any further updates.  However, the
    394      * loader <em>must</em> keep its last reported data valid until the final
    395      * {@link #onReset()} happens.  You can retrieve the current abandoned
    396      * state with {@link #isAbandoned}.
    397      */
    398     protected void onAbandon() {
    399     }
    400 
    401     /**
    402      * This function will normally be called for you automatically by
    403      * {@link android.support.v4.app.LoaderManager} when destroying a Loader.  When using
    404      * a Loader with {@link android.support.v4.app.LoaderManager},
    405      * you <em>must not</em> call this method yourself, or you will conflict
    406      * with its management of the Loader.
    407      *
    408      * Resets the state of the Loader.  The Loader should at this point free
    409      * all of its resources, since it may never be called again; however, its
    410      * {@link #startLoading()} may later be called at which point it must be
    411      * able to start running again.
    412      *
    413      * <p>This updates the Loader's internal state so that
    414      * {@link #isStarted()} and {@link #isReset()} will return the correct
    415      * values, and then calls the implementation's {@link #onReset()}.
    416      *
    417      * <p>Must be called from the process's main thread.
    418      */
    419     public void reset() {
    420         onReset();
    421         mReset = true;
    422         mStarted = false;
    423         mAbandoned = false;
    424         mContentChanged = false;
    425         mProcessingChange = false;
    426     }
    427 
    428     /**
    429      * Subclasses must implement this to take care of resetting their loader,
    430      * as per {@link #reset()}.  This is not called by clients directly,
    431      * but as a result of a call to {@link #reset()}.
    432      * This will always be called from the process's main thread.
    433      */
    434     protected void onReset() {
    435     }
    436 
    437     /**
    438      * Take the current flag indicating whether the loader's content had
    439      * changed while it was stopped.  If it had, true is returned and the
    440      * flag is cleared.
    441      */
    442     public boolean takeContentChanged() {
    443         boolean res = mContentChanged;
    444         mContentChanged = false;
    445         mProcessingChange |= res;
    446         return res;
    447     }
    448 
    449     /**
    450      * Commit that you have actually fully processed a content change that
    451      * was returned by {@link #takeContentChanged}.  This is for use with
    452      * {@link #rollbackContentChanged()} to handle situations where a load
    453      * is cancelled.  Call this when you have completely processed a load
    454      * without it being cancelled.
    455      */
    456     public void commitContentChanged() {
    457         mProcessingChange = false;
    458     }
    459 
    460     /**
    461      * Report that you have abandoned the processing of a content change that
    462      * was returned by {@link #takeContentChanged()} and would like to rollback
    463      * to the state where there is again a pending content change.  This is
    464      * to handle the case where a data load due to a content change has been
    465      * canceled before its data was delivered back to the loader.
    466      */
    467     public void rollbackContentChanged() {
    468         if (mProcessingChange) {
    469             onContentChanged();
    470         }
    471     }
    472 
    473     /**
    474      * Called when {@link ForceLoadContentObserver} detects a change.  The
    475      * default implementation checks to see if the loader is currently started;
    476      * if so, it simply calls {@link #forceLoad()}; otherwise, it sets a flag
    477      * so that {@link #takeContentChanged()} returns true.
    478      *
    479      * <p>Must be called from the process's main thread.
    480      */
    481     public void onContentChanged() {
    482         if (mStarted) {
    483             forceLoad();
    484         } else {
    485             // This loader has been stopped, so we don't want to load
    486             // new data right now...  but keep track of it changing to
    487             // refresh later if we start again.
    488             mContentChanged = true;
    489         }
    490     }
    491 
    492     /**
    493      * For debugging, converts an instance of the Loader's data class to
    494      * a string that can be printed.  Must handle a null data.
    495      */
    496     public String dataToString(D data) {
    497         StringBuilder sb = new StringBuilder(64);
    498         DebugUtils.buildShortClassTag(data, sb);
    499         sb.append("}");
    500         return sb.toString();
    501     }
    502 
    503     @Override
    504     public String toString() {
    505         StringBuilder sb = new StringBuilder(64);
    506         DebugUtils.buildShortClassTag(this, sb);
    507         sb.append(" id=");
    508         sb.append(mId);
    509         sb.append("}");
    510         return sb.toString();
    511     }
    512 
    513     /**
    514      * Print the Loader's state into the given stream.
    515      *
    516      * @param prefix Text to print at the front of each line.
    517      * @param fd The raw file descriptor that the dump is being sent to.
    518      * @param writer A PrintWriter to which the dump is to be set.
    519      * @param args Additional arguments to the dump request.
    520      */
    521     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
    522         writer.print(prefix); writer.print("mId="); writer.print(mId);
    523                 writer.print(" mListener="); writer.println(mListener);
    524         if (mStarted || mContentChanged || mProcessingChange) {
    525             writer.print(prefix); writer.print("mStarted="); writer.print(mStarted);
    526                     writer.print(" mContentChanged="); writer.print(mContentChanged);
    527                     writer.print(" mProcessingChange="); writer.println(mProcessingChange);
    528         }
    529         if (mAbandoned || mReset) {
    530             writer.print(prefix); writer.print("mAbandoned="); writer.print(mAbandoned);
    531                     writer.print(" mReset="); writer.println(mReset);
    532         }
    533     }
    534 }