Home | History | Annotate | Download | only in content
      1 /*
      2  * Copyright (C) 2013 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 com.android.mail.content;
     18 
     19 import com.android.mail.utils.LogTag;
     20 
     21 import android.content.AsyncTaskLoader;
     22 import android.content.Context;
     23 import android.database.Cursor;
     24 import android.net.Uri;
     25 
     26 import java.io.FileDescriptor;
     27 import java.io.PrintWriter;
     28 import java.util.Arrays;
     29 
     30 /**
     31  * A copy of the framework's {@link android.content.CursorLoader} class. Copied because
     32  * CursorLoader is not parameterized, and we want to parameterize over the underlying cursor type.
     33  * @param <T>
     34  */
     35 public class ObjectCursorLoader<T> extends AsyncTaskLoader<ObjectCursor<T>> {
     36     final ForceLoadContentObserver mObserver;
     37     protected static final String LOG_TAG = LogTag.getLogTag();
     38 
     39     private Uri mUri;
     40     final String[] mProjection;
     41     // Copied over from CursorLoader, but none of our uses specify this. So these are hardcoded to
     42     // null right here.
     43     final String mSelection = null;
     44     final String[] mSelectionArgs = null;
     45     final String mSortOrder = null;
     46 
     47     /** The underlying cursor that contains the data. */
     48     ObjectCursor<T> mCursor;
     49 
     50     /** The factory that knows how to create T objects from cursors: one object per row. */
     51     private final CursorCreator<T> mFactory;
     52 
     53     private int mDebugDelayMs = 0;
     54 
     55     public ObjectCursorLoader(Context context, Uri uri, String[] projection,
     56             CursorCreator<T> factory) {
     57         super(context);
     58 
     59         /*
     60          * If these are null, it's going to crash anyway in loadInBackground(), but this stack trace
     61          * is much more useful.
     62          */
     63         if (factory == null) {
     64             throw new NullPointerException("The factory cannot be null");
     65         }
     66 
     67         mObserver = new ForceLoadContentObserver();
     68         setUri(uri);
     69         mProjection = projection;
     70         mFactory = factory;
     71     }
     72 
     73     /* Runs on a worker thread */
     74     @Override
     75     public ObjectCursor<T> loadInBackground() {
     76         final Cursor inner = getContext().getContentResolver().query(mUri, mProjection,
     77                 mSelection, mSelectionArgs, mSortOrder);
     78         if (inner == null) {
     79             // If there's no underlying cursor, there's nothing to do.
     80             return null;
     81         }
     82         // Ensure the cursor window is filled
     83         inner.getCount();
     84         inner.registerContentObserver(mObserver);
     85 
     86         // Modifications to the ObjectCursor, create an Object Cursor and fill the cache.
     87         final ObjectCursor<T> cursor = getObjectCursor(inner);
     88         cursor.fillCache();
     89 
     90         try {
     91             if (mDebugDelayMs > 0) {
     92                 Thread.sleep(mDebugDelayMs);
     93             }
     94         } catch (InterruptedException e) {}
     95 
     96         return cursor;
     97     }
     98 
     99     protected ObjectCursor<T> getObjectCursor(Cursor inner) {
    100         return new ObjectCursor<T>(inner, mFactory);
    101     }
    102 
    103     /* Runs on the UI thread */
    104     @Override
    105     public void deliverResult(ObjectCursor<T> cursor) {
    106         if (isReset()) {
    107             // An async query came in while the loader is stopped
    108             if (cursor != null) {
    109                 cursor.close();
    110             }
    111             return;
    112         }
    113         final Cursor oldCursor = mCursor;
    114         mCursor = cursor;
    115 
    116         if (isStarted()) {
    117             super.deliverResult(cursor);
    118         }
    119 
    120         if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
    121             oldCursor.close();
    122         }
    123     }
    124 
    125     /**
    126      * Starts an asynchronous load of the contacts list data. When the result is ready the callbacks
    127      * will be called on the UI thread. If a previous load has been completed and is still valid
    128      * the result may be passed to the callbacks immediately.
    129      *
    130      * Must be called from the UI thread
    131      */
    132     @Override
    133     protected void onStartLoading() {
    134         if (mCursor != null) {
    135             deliverResult(mCursor);
    136         }
    137         if (takeContentChanged() || mCursor == null) {
    138             forceLoad();
    139         }
    140     }
    141 
    142     /**
    143      * Must be called from the UI thread
    144      */
    145     @Override
    146     protected void onStopLoading() {
    147         // Attempt to cancel the current load task if possible.
    148         cancelLoad();
    149     }
    150 
    151     @Override
    152     public void onCanceled(ObjectCursor<T> cursor) {
    153         if (cursor != null && !cursor.isClosed()) {
    154             cursor.close();
    155         }
    156     }
    157 
    158     @Override
    159     protected void onReset() {
    160         super.onReset();
    161 
    162         // Ensure the loader is stopped
    163         onStopLoading();
    164 
    165         if (mCursor != null && !mCursor.isClosed()) {
    166             mCursor.close();
    167         }
    168         mCursor = null;
    169     }
    170 
    171     @Override
    172     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
    173         super.dump(prefix, fd, writer, args);
    174         writer.print(prefix); writer.print("mUri="); writer.println(mUri);
    175         writer.print(prefix); writer.print("mProjection=");
    176         writer.println(Arrays.toString(mProjection));
    177         writer.print(prefix); writer.print("mSelection="); writer.println(mSelection);
    178         writer.print(prefix); writer.print("mSelectionArgs=");
    179         writer.println(Arrays.toString(mSelectionArgs));
    180         writer.print(prefix); writer.print("mSortOrder="); writer.println(mSortOrder);
    181         writer.print(prefix); writer.print("mCursor="); writer.println(mCursor);
    182     }
    183 
    184     /**
    185      * For debugging loader-related race conditions. Delays the background thread load. The delay is
    186      * currently run after the query is complete.
    187      *
    188      * @param delayMs additional delay (in ms) to add to the background load operation
    189      * @return this object itself, for fluent chaining
    190      */
    191     public ObjectCursorLoader<T> setDebugDelay(int delayMs) {
    192         mDebugDelayMs = delayMs;
    193         return this;
    194     }
    195 
    196     public final Uri getUri() {
    197         return mUri;
    198     }
    199 
    200     public final void setUri(Uri uri) {
    201         if (uri == null) {
    202             throw new NullPointerException("The uri cannot be null");
    203         }
    204         mUri = uri;
    205     }
    206 }
    207