1/*
2 * Copyright (C) 2010 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
17package android.content;
18
19import android.database.Cursor;
20import android.net.Uri;
21import android.os.CancellationSignal;
22import android.os.OperationCanceledException;
23
24import java.io.FileDescriptor;
25import java.io.PrintWriter;
26import java.util.Arrays;
27
28/**
29 * A loader that queries the {@link ContentResolver} and returns a {@link Cursor}.
30 * This class implements the {@link Loader} protocol in a standard way for
31 * querying cursors, building on {@link AsyncTaskLoader} to perform the cursor
32 * query on a background thread so that it does not block the application's UI.
33 *
34 * <p>A CursorLoader must be built with the full information for the query to
35 * perform, either through the
36 * {@link #CursorLoader(Context, Uri, String[], String, String[], String)} or
37 * creating an empty instance with {@link #CursorLoader(Context)} and filling
38 * in the desired paramters with {@link #setUri(Uri)}, {@link #setSelection(String)},
39 * {@link #setSelectionArgs(String[])}, {@link #setSortOrder(String)},
40 * and {@link #setProjection(String[])}.
41 */
42public class CursorLoader extends AsyncTaskLoader<Cursor> {
43    final ForceLoadContentObserver mObserver;
44
45    Uri mUri;
46    String[] mProjection;
47    String mSelection;
48    String[] mSelectionArgs;
49    String mSortOrder;
50
51    Cursor mCursor;
52    CancellationSignal mCancellationSignal;
53
54    /* Runs on a worker thread */
55    @Override
56    public Cursor loadInBackground() {
57        synchronized (this) {
58            if (isLoadInBackgroundCanceled()) {
59                throw new OperationCanceledException();
60            }
61            mCancellationSignal = new CancellationSignal();
62        }
63        try {
64            Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,
65                    mSelectionArgs, mSortOrder, mCancellationSignal);
66            if (cursor != null) {
67                try {
68                    // Ensure the cursor window is filled.
69                    cursor.getCount();
70                    cursor.registerContentObserver(mObserver);
71                } catch (RuntimeException ex) {
72                    cursor.close();
73                    throw ex;
74                }
75            }
76            return cursor;
77        } finally {
78            synchronized (this) {
79                mCancellationSignal = null;
80            }
81        }
82    }
83
84    @Override
85    public void cancelLoadInBackground() {
86        super.cancelLoadInBackground();
87
88        synchronized (this) {
89            if (mCancellationSignal != null) {
90                mCancellationSignal.cancel();
91            }
92        }
93    }
94
95    /* Runs on the UI thread */
96    @Override
97    public void deliverResult(Cursor cursor) {
98        if (isReset()) {
99            // An async query came in while the loader is stopped
100            if (cursor != null) {
101                cursor.close();
102            }
103            return;
104        }
105        Cursor oldCursor = mCursor;
106        mCursor = cursor;
107
108        if (isStarted()) {
109            super.deliverResult(cursor);
110        }
111
112        if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
113            oldCursor.close();
114        }
115    }
116
117    /**
118     * Creates an empty unspecified CursorLoader.  You must follow this with
119     * calls to {@link #setUri(Uri)}, {@link #setSelection(String)}, etc
120     * to specify the query to perform.
121     */
122    public CursorLoader(Context context) {
123        super(context);
124        mObserver = new ForceLoadContentObserver();
125    }
126
127    /**
128     * Creates a fully-specified CursorLoader.  See
129     * {@link ContentResolver#query(Uri, String[], String, String[], String)
130     * ContentResolver.query()} for documentation on the meaning of the
131     * parameters.  These will be passed as-is to that call.
132     */
133    public CursorLoader(Context context, Uri uri, String[] projection, String selection,
134            String[] selectionArgs, String sortOrder) {
135        super(context);
136        mObserver = new ForceLoadContentObserver();
137        mUri = uri;
138        mProjection = projection;
139        mSelection = selection;
140        mSelectionArgs = selectionArgs;
141        mSortOrder = sortOrder;
142    }
143
144    /**
145     * Starts an asynchronous load of the contacts list data. When the result is ready the callbacks
146     * will be called on the UI thread. If a previous load has been completed and is still valid
147     * the result may be passed to the callbacks immediately.
148     *
149     * Must be called from the UI thread
150     */
151    @Override
152    protected void onStartLoading() {
153        if (mCursor != null) {
154            deliverResult(mCursor);
155        }
156        if (takeContentChanged() || mCursor == null) {
157            forceLoad();
158        }
159    }
160
161    /**
162     * Must be called from the UI thread
163     */
164    @Override
165    protected void onStopLoading() {
166        // Attempt to cancel the current load task if possible.
167        cancelLoad();
168    }
169
170    @Override
171    public void onCanceled(Cursor cursor) {
172        if (cursor != null && !cursor.isClosed()) {
173            cursor.close();
174        }
175    }
176
177    @Override
178    protected void onReset() {
179        super.onReset();
180
181        // Ensure the loader is stopped
182        onStopLoading();
183
184        if (mCursor != null && !mCursor.isClosed()) {
185            mCursor.close();
186        }
187        mCursor = null;
188    }
189
190    public Uri getUri() {
191        return mUri;
192    }
193
194    public void setUri(Uri uri) {
195        mUri = uri;
196    }
197
198    public String[] getProjection() {
199        return mProjection;
200    }
201
202    public void setProjection(String[] projection) {
203        mProjection = projection;
204    }
205
206    public String getSelection() {
207        return mSelection;
208    }
209
210    public void setSelection(String selection) {
211        mSelection = selection;
212    }
213
214    public String[] getSelectionArgs() {
215        return mSelectionArgs;
216    }
217
218    public void setSelectionArgs(String[] selectionArgs) {
219        mSelectionArgs = selectionArgs;
220    }
221
222    public String getSortOrder() {
223        return mSortOrder;
224    }
225
226    public void setSortOrder(String sortOrder) {
227        mSortOrder = sortOrder;
228    }
229
230    @Override
231    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
232        super.dump(prefix, fd, writer, args);
233        writer.print(prefix); writer.print("mUri="); writer.println(mUri);
234        writer.print(prefix); writer.print("mProjection=");
235                writer.println(Arrays.toString(mProjection));
236        writer.print(prefix); writer.print("mSelection="); writer.println(mSelection);
237        writer.print(prefix); writer.print("mSelectionArgs=");
238                writer.println(Arrays.toString(mSelectionArgs));
239        writer.print(prefix); writer.print("mSortOrder="); writer.println(mSortOrder);
240        writer.print(prefix); writer.print("mCursor="); writer.println(mCursor);
241        writer.print(prefix); writer.print("mContentChanged="); writer.println(mContentChanged);
242    }
243}
244