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
17package com.android.mail.content;
18
19import com.android.mail.utils.LogTag;
20
21import android.content.AsyncTaskLoader;
22import android.content.Context;
23import android.database.Cursor;
24import android.net.Uri;
25
26import java.io.FileDescriptor;
27import java.io.PrintWriter;
28import 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 */
35public 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