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 
ObjectCursorLoader(Context context, Uri uri, String[] projection, CursorCreator<T> factory)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
loadInBackground()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 
getObjectCursor(Cursor inner)99     protected ObjectCursor<T> getObjectCursor(Cursor inner) {
100         return new ObjectCursor<T>(inner, mFactory);
101     }
102 
103     /* Runs on the UI thread */
104     @Override
deliverResult(ObjectCursor<T> cursor)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
onStartLoading()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
onStopLoading()146     protected void onStopLoading() {
147         // Attempt to cancel the current load task if possible.
148         cancelLoad();
149     }
150 
151     @Override
onCanceled(ObjectCursor<T> cursor)152     public void onCanceled(ObjectCursor<T> cursor) {
153         if (cursor != null && !cursor.isClosed()) {
154             cursor.close();
155         }
156     }
157 
158     @Override
onReset()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
dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)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      */
setDebugDelay(int delayMs)191     public ObjectCursorLoader<T> setDebugDelay(int delayMs) {
192         mDebugDelayMs = delayMs;
193         return this;
194     }
195 
getUri()196     public final Uri getUri() {
197         return mUri;
198     }
199 
setUri(Uri uri)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