1/* 2 * Copyright (C) 2011 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.support.v4.content; 18 19import android.content.Context; 20import android.database.ContentObserver; 21import android.os.Handler; 22import android.support.v4.util.DebugUtils; 23 24import java.io.FileDescriptor; 25import java.io.PrintWriter; 26 27/** 28 * Static library support version of the framework's {@link android.content.Loader}. 29 * Used to write apps that run on platforms prior to Android 3.0. When running 30 * on Android 3.0 or above, this implementation is still used; it does not try 31 * to switch to the framework's implementation. See the framework SDK 32 * documentation for a class overview. 33 */ 34public class Loader<D> { 35 int mId; 36 OnLoadCompleteListener<D> mListener; 37 OnLoadCanceledListener<D> mOnLoadCanceledListener; 38 Context mContext; 39 boolean mStarted = false; 40 boolean mAbandoned = false; 41 boolean mReset = true; 42 boolean mContentChanged = false; 43 boolean mProcessingChange = false; 44 45 /** 46 * An implementation of a ContentObserver that takes care of connecting 47 * it to the Loader to have the loader re-load its data when the observer 48 * is told it has changed. You do not normally need to use this yourself; 49 * it is used for you by {@link CursorLoader} to take care of executing 50 * an update when the cursor's backing data changes. 51 */ 52 public final class ForceLoadContentObserver extends ContentObserver { 53 public ForceLoadContentObserver() { 54 super(new Handler()); 55 } 56 57 @Override 58 public boolean deliverSelfNotifications() { 59 return true; 60 } 61 62 @Override 63 public void onChange(boolean selfChange) { 64 onContentChanged(); 65 } 66 } 67 68 /** 69 * Interface that is implemented to discover when a Loader has finished 70 * loading its data. You do not normally need to implement this yourself; 71 * it is used in the implementation of {@link android.support.v4.app.LoaderManager} 72 * to find out when a Loader it is managing has completed so that this can 73 * be reported to its client. This interface should only be used if a 74 * Loader is not being used in conjunction with LoaderManager. 75 */ 76 public interface OnLoadCompleteListener<D> { 77 /** 78 * Called on the thread that created the Loader when the load is complete. 79 * 80 * @param loader the loader that completed the load 81 * @param data the result of the load 82 */ 83 public void onLoadComplete(Loader<D> loader, D data); 84 } 85 86 /** 87 * Interface that is implemented to discover when a Loader has been canceled 88 * before it finished loading its data. You do not normally need to implement 89 * this yourself; it is used in the implementation of {@link android.support.v4.app.LoaderManager} 90 * to find out when a Loader it is managing has been canceled so that it 91 * can schedule the next Loader. This interface should only be used if a 92 * Loader is not being used in conjunction with LoaderManager. 93 */ 94 public interface OnLoadCanceledListener<D> { 95 /** 96 * Called on the thread that created the Loader when the load is canceled. 97 * 98 * @param loader the loader that canceled the load 99 */ 100 public void onLoadCanceled(Loader<D> loader); 101 } 102 103 /** 104 * Stores away the application context associated with context. 105 * Since Loaders can be used across multiple activities it's dangerous to 106 * store the context directly; always use {@link #getContext()} to retrieve 107 * the Loader's Context, don't use the constructor argument directly. 108 * The Context returned by {@link #getContext} is safe to use across 109 * Activity instances. 110 * 111 * @param context used to retrieve the application context. 112 */ 113 public Loader(Context context) { 114 mContext = context.getApplicationContext(); 115 } 116 117 /** 118 * Sends the result of the load to the registered listener. Should only be called by subclasses. 119 * 120 * Must be called from the process's main thread. 121 * 122 * @param data the result of the load 123 */ 124 public void deliverResult(D data) { 125 if (mListener != null) { 126 mListener.onLoadComplete(this, data); 127 } 128 } 129 130 /** 131 * Informs the registered {@link OnLoadCanceledListener} that the load has been canceled. 132 * Should only be called by subclasses. 133 * 134 * Must be called from the process's main thread. 135 */ 136 public void deliverCancellation() { 137 if (mOnLoadCanceledListener != null) { 138 mOnLoadCanceledListener.onLoadCanceled(this); 139 } 140 } 141 142 /** 143 * @return an application context retrieved from the Context passed to the constructor. 144 */ 145 public Context getContext() { 146 return mContext; 147 } 148 149 /** 150 * @return the ID of this loader 151 */ 152 public int getId() { 153 return mId; 154 } 155 156 /** 157 * Registers a class that will receive callbacks when a load is complete. 158 * The callback will be called on the process's main thread so it's safe to 159 * pass the results to widgets. 160 * 161 * <p>Must be called from the process's main thread. 162 */ 163 public void registerListener(int id, OnLoadCompleteListener<D> listener) { 164 if (mListener != null) { 165 throw new IllegalStateException("There is already a listener registered"); 166 } 167 mListener = listener; 168 mId = id; 169 } 170 171 /** 172 * Remove a listener that was previously added with {@link #registerListener}. 173 * 174 * Must be called from the process's main thread. 175 */ 176 public void unregisterListener(OnLoadCompleteListener<D> listener) { 177 if (mListener == null) { 178 throw new IllegalStateException("No listener register"); 179 } 180 if (mListener != listener) { 181 throw new IllegalArgumentException("Attempting to unregister the wrong listener"); 182 } 183 mListener = null; 184 } 185 186 /** 187 * Registers a listener that will receive callbacks when a load is canceled. 188 * The callback will be called on the process's main thread so it's safe to 189 * pass the results to widgets. 190 * 191 * Must be called from the process's main thread. 192 * 193 * @param listener The listener to register. 194 */ 195 public void registerOnLoadCanceledListener(OnLoadCanceledListener<D> listener) { 196 if (mOnLoadCanceledListener != null) { 197 throw new IllegalStateException("There is already a listener registered"); 198 } 199 mOnLoadCanceledListener = listener; 200 } 201 202 /** 203 * Unregisters a listener that was previously added with 204 * {@link #registerOnLoadCanceledListener}. 205 * 206 * Must be called from the process's main thread. 207 * 208 * @param listener The listener to unregister. 209 */ 210 public void unregisterOnLoadCanceledListener(OnLoadCanceledListener<D> listener) { 211 if (mOnLoadCanceledListener == null) { 212 throw new IllegalStateException("No listener register"); 213 } 214 if (mOnLoadCanceledListener != listener) { 215 throw new IllegalArgumentException("Attempting to unregister the wrong listener"); 216 } 217 mOnLoadCanceledListener = null; 218 } 219 220 /** 221 * Return whether this load has been started. That is, its {@link #startLoading()} 222 * has been called and no calls to {@link #stopLoading()} or 223 * {@link #reset()} have yet been made. 224 */ 225 public boolean isStarted() { 226 return mStarted; 227 } 228 229 /** 230 * Return whether this loader has been abandoned. In this state, the 231 * loader <em>must not</em> report any new data, and <em>must</em> keep 232 * its last reported data valid until it is finally reset. 233 */ 234 public boolean isAbandoned() { 235 return mAbandoned; 236 } 237 238 /** 239 * Return whether this load has been reset. That is, either the loader 240 * has not yet been started for the first time, or its {@link #reset()} 241 * has been called. 242 */ 243 public boolean isReset() { 244 return mReset; 245 } 246 247 /** 248 * This function will normally be called for you automatically by 249 * {@link android.support.v4.app.LoaderManager} when the associated fragment/activity 250 * is being started. When using a Loader with {@link android.support.v4.app.LoaderManager}, 251 * you <em>must not</em> call this method yourself, or you will conflict 252 * with its management of the Loader. 253 * 254 * Starts an asynchronous load of the Loader's data. When the result 255 * is ready the callbacks will be called on the process's main thread. 256 * If a previous load has been completed and is still valid 257 * the result may be passed to the callbacks immediately. 258 * The loader will monitor the source of 259 * the data set and may deliver future callbacks if the source changes. 260 * Calling {@link #stopLoading} will stop the delivery of callbacks. 261 * 262 * <p>This updates the Loader's internal state so that 263 * {@link #isStarted()} and {@link #isReset()} will return the correct 264 * values, and then calls the implementation's {@link #onStartLoading()}. 265 * 266 * <p>Must be called from the process's main thread. 267 */ 268 public final void startLoading() { 269 mStarted = true; 270 mReset = false; 271 mAbandoned = false; 272 onStartLoading(); 273 } 274 275 /** 276 * Subclasses must implement this to take care of loading their data, 277 * as per {@link #startLoading()}. This is not called by clients directly, 278 * but as a result of a call to {@link #startLoading()}. 279 */ 280 protected void onStartLoading() { 281 } 282 283 /** 284 * Attempt to cancel the current load task. 285 * Must be called on the main thread of the process. 286 * 287 * <p>Cancellation is not an immediate operation, since the load is performed 288 * in a background thread. If there is currently a load in progress, this 289 * method requests that the load be canceled, and notes this is the case; 290 * once the background thread has completed its work its remaining state 291 * will be cleared. If another load request comes in during this time, 292 * it will be held until the canceled load is complete. 293 * 294 * @return Returns <tt>false</tt> if the task could not be canceled, 295 * typically because it has already completed normally, or 296 * because {@link #startLoading()} hasn't been called; returns 297 * <tt>true</tt> otherwise. When <tt>true</tt> is returned, the task 298 * is still running and the {@link OnLoadCanceledListener} will be called 299 * when the task completes. 300 */ 301 public boolean cancelLoad() { 302 return onCancelLoad(); 303 } 304 305 /** 306 * Subclasses must implement this to take care of requests to {@link #cancelLoad()}. 307 * This will always be called from the process's main thread. 308 * 309 * @return Returns <tt>false</tt> if the task could not be canceled, 310 * typically because it has already completed normally, or 311 * because {@link #startLoading()} hasn't been called; returns 312 * <tt>true</tt> otherwise. When <tt>true</tt> is returned, the task 313 * is still running and the {@link OnLoadCanceledListener} will be called 314 * when the task completes. 315 */ 316 protected boolean onCancelLoad() { 317 return false; 318 } 319 320 /** 321 * Force an asynchronous load. Unlike {@link #startLoading()} this will ignore a previously 322 * loaded data set and load a new one. This simply calls through to the 323 * implementation's {@link #onForceLoad()}. You generally should only call this 324 * when the loader is started -- that is, {@link #isStarted()} returns true. 325 * 326 * <p>Must be called from the process's main thread. 327 */ 328 public void forceLoad() { 329 onForceLoad(); 330 } 331 332 /** 333 * Subclasses must implement this to take care of requests to {@link #forceLoad()}. 334 * This will always be called from the process's main thread. 335 */ 336 protected void onForceLoad() { 337 } 338 339 /** 340 * This function will normally be called for you automatically by 341 * {@link android.support.v4.app.LoaderManager} when the associated fragment/activity 342 * is being stopped. When using a Loader with {@link android.support.v4.app.LoaderManager}, 343 * you <em>must not</em> call this method yourself, or you will conflict 344 * with its management of the Loader. 345 * 346 * <p>Stops delivery of updates until the next time {@link #startLoading()} is called. 347 * Implementations should <em>not</em> invalidate their data at this point -- 348 * clients are still free to use the last data the loader reported. They will, 349 * however, typically stop reporting new data if the data changes; they can 350 * still monitor for changes, but must not report them to the client until and 351 * if {@link #startLoading()} is later called. 352 * 353 * <p>This updates the Loader's internal state so that 354 * {@link #isStarted()} will return the correct 355 * value, and then calls the implementation's {@link #onStopLoading()}. 356 * 357 * <p>Must be called from the process's main thread. 358 */ 359 public void stopLoading() { 360 mStarted = false; 361 onStopLoading(); 362 } 363 364 /** 365 * Subclasses must implement this to take care of stopping their loader, 366 * as per {@link #stopLoading()}. This is not called by clients directly, 367 * but as a result of a call to {@link #stopLoading()}. 368 * This will always be called from the process's main thread. 369 */ 370 protected void onStopLoading() { 371 } 372 373 /** 374 * This function will normally be called for you automatically by 375 * {@link android.support.v4.app.LoaderManager} when restarting a Loader. When using 376 * a Loader with {@link android.support.v4.app.LoaderManager}, 377 * you <em>must not</em> call this method yourself, or you will conflict 378 * with its management of the Loader. 379 * 380 * Tell the Loader that it is being abandoned. This is called prior 381 * to {@link #reset} to have it retain its current data but not report 382 * any new data. 383 */ 384 public void abandon() { 385 mAbandoned = true; 386 onAbandon(); 387 } 388 389 /** 390 * Subclasses implement this to take care of being abandoned. This is 391 * an optional intermediate state prior to {@link #onReset()} -- it means that 392 * the client is no longer interested in any new data from the loader, 393 * so the loader must not report any further updates. However, the 394 * loader <em>must</em> keep its last reported data valid until the final 395 * {@link #onReset()} happens. You can retrieve the current abandoned 396 * state with {@link #isAbandoned}. 397 */ 398 protected void onAbandon() { 399 } 400 401 /** 402 * This function will normally be called for you automatically by 403 * {@link android.support.v4.app.LoaderManager} when destroying a Loader. When using 404 * a Loader with {@link android.support.v4.app.LoaderManager}, 405 * you <em>must not</em> call this method yourself, or you will conflict 406 * with its management of the Loader. 407 * 408 * Resets the state of the Loader. The Loader should at this point free 409 * all of its resources, since it may never be called again; however, its 410 * {@link #startLoading()} may later be called at which point it must be 411 * able to start running again. 412 * 413 * <p>This updates the Loader's internal state so that 414 * {@link #isStarted()} and {@link #isReset()} will return the correct 415 * values, and then calls the implementation's {@link #onReset()}. 416 * 417 * <p>Must be called from the process's main thread. 418 */ 419 public void reset() { 420 onReset(); 421 mReset = true; 422 mStarted = false; 423 mAbandoned = false; 424 mContentChanged = false; 425 mProcessingChange = false; 426 } 427 428 /** 429 * Subclasses must implement this to take care of resetting their loader, 430 * as per {@link #reset()}. This is not called by clients directly, 431 * but as a result of a call to {@link #reset()}. 432 * This will always be called from the process's main thread. 433 */ 434 protected void onReset() { 435 } 436 437 /** 438 * Take the current flag indicating whether the loader's content had 439 * changed while it was stopped. If it had, true is returned and the 440 * flag is cleared. 441 */ 442 public boolean takeContentChanged() { 443 boolean res = mContentChanged; 444 mContentChanged = false; 445 mProcessingChange |= res; 446 return res; 447 } 448 449 /** 450 * Commit that you have actually fully processed a content change that 451 * was returned by {@link #takeContentChanged}. This is for use with 452 * {@link #rollbackContentChanged()} to handle situations where a load 453 * is cancelled. Call this when you have completely processed a load 454 * without it being cancelled. 455 */ 456 public void commitContentChanged() { 457 mProcessingChange = false; 458 } 459 460 /** 461 * Report that you have abandoned the processing of a content change that 462 * was returned by {@link #takeContentChanged()} and would like to rollback 463 * to the state where there is again a pending content change. This is 464 * to handle the case where a data load due to a content change has been 465 * canceled before its data was delivered back to the loader. 466 */ 467 public void rollbackContentChanged() { 468 if (mProcessingChange) { 469 onContentChanged(); 470 } 471 } 472 473 /** 474 * Called when {@link ForceLoadContentObserver} detects a change. The 475 * default implementation checks to see if the loader is currently started; 476 * if so, it simply calls {@link #forceLoad()}; otherwise, it sets a flag 477 * so that {@link #takeContentChanged()} returns true. 478 * 479 * <p>Must be called from the process's main thread. 480 */ 481 public void onContentChanged() { 482 if (mStarted) { 483 forceLoad(); 484 } else { 485 // This loader has been stopped, so we don't want to load 486 // new data right now... but keep track of it changing to 487 // refresh later if we start again. 488 mContentChanged = true; 489 } 490 } 491 492 /** 493 * For debugging, converts an instance of the Loader's data class to 494 * a string that can be printed. Must handle a null data. 495 */ 496 public String dataToString(D data) { 497 StringBuilder sb = new StringBuilder(64); 498 DebugUtils.buildShortClassTag(data, sb); 499 sb.append("}"); 500 return sb.toString(); 501 } 502 503 @Override 504 public String toString() { 505 StringBuilder sb = new StringBuilder(64); 506 DebugUtils.buildShortClassTag(this, sb); 507 sb.append(" id="); 508 sb.append(mId); 509 sb.append("}"); 510 return sb.toString(); 511 } 512 513 /** 514 * Print the Loader's state into the given stream. 515 * 516 * @param prefix Text to print at the front of each line. 517 * @param fd The raw file descriptor that the dump is being sent to. 518 * @param writer A PrintWriter to which the dump is to be set. 519 * @param args Additional arguments to the dump request. 520 */ 521 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 522 writer.print(prefix); writer.print("mId="); writer.print(mId); 523 writer.print(" mListener="); writer.println(mListener); 524 if (mStarted || mContentChanged || mProcessingChange) { 525 writer.print(prefix); writer.print("mStarted="); writer.print(mStarted); 526 writer.print(" mContentChanged="); writer.print(mContentChanged); 527 writer.print(" mProcessingChange="); writer.println(mProcessingChange); 528 } 529 if (mAbandoned || mReset) { 530 writer.print(prefix); writer.print("mAbandoned="); writer.print(mAbandoned); 531 writer.print(" mReset="); writer.println(mReset); 532 } 533 } 534}