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