Home | History | Annotate | Download | only in content
      1 /*
      2  * Copyright 2018 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 androidx.loader.content;
     18 
     19 import android.content.ContentResolver;
     20 import android.content.Context;
     21 import android.database.Cursor;
     22 import android.net.Uri;
     23 
     24 import androidx.annotation.NonNull;
     25 import androidx.annotation.Nullable;
     26 import androidx.core.content.ContentResolverCompat;
     27 import androidx.core.os.CancellationSignal;
     28 import androidx.core.os.OperationCanceledException;
     29 
     30 import java.io.FileDescriptor;
     31 import java.io.PrintWriter;
     32 import java.util.Arrays;
     33 
     34 /**
     35  * Static library support version of the framework's {@link android.content.CursorLoader}.
     36  * Used to write apps that run on platforms prior to Android 3.0.  When running
     37  * on Android 3.0 or above, this implementation is still used; it does not try
     38  * to switch to the framework's implementation.  See the framework SDK
     39  * documentation for a class overview.
     40  */
     41 public class CursorLoader extends AsyncTaskLoader<Cursor> {
     42     final ForceLoadContentObserver mObserver;
     43 
     44     Uri mUri;
     45     String[] mProjection;
     46     String mSelection;
     47     String[] mSelectionArgs;
     48     String mSortOrder;
     49 
     50     Cursor mCursor;
     51     CancellationSignal mCancellationSignal;
     52 
     53     /* Runs on a worker thread */
     54     @Override
     55     public Cursor loadInBackground() {
     56         synchronized (this) {
     57             if (isLoadInBackgroundCanceled()) {
     58                 throw new OperationCanceledException();
     59             }
     60             mCancellationSignal = new CancellationSignal();
     61         }
     62         try {
     63             Cursor cursor = ContentResolverCompat.query(getContext().getContentResolver(),
     64                     mUri, mProjection, mSelection, mSelectionArgs, mSortOrder,
     65                     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(@NonNull 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(@NonNull Context context, @NonNull Uri uri, @Nullable String[] projection,
    134             @Nullable String selection, @Nullable String[] selectionArgs,
    135             @Nullable String sortOrder) {
    136         super(context);
    137         mObserver = new ForceLoadContentObserver();
    138         mUri = uri;
    139         mProjection = projection;
    140         mSelection = selection;
    141         mSelectionArgs = selectionArgs;
    142         mSortOrder = sortOrder;
    143     }
    144 
    145     /**
    146      * Starts an asynchronous load of the contacts list data. When the result is ready the callbacks
    147      * will be called on the UI thread. If a previous load has been completed and is still valid
    148      * the result may be passed to the callbacks immediately.
    149      *
    150      * Must be called from the UI thread
    151      */
    152     @Override
    153     protected void onStartLoading() {
    154         if (mCursor != null) {
    155             deliverResult(mCursor);
    156         }
    157         if (takeContentChanged() || mCursor == null) {
    158             forceLoad();
    159         }
    160     }
    161 
    162     /**
    163      * Must be called from the UI thread
    164      */
    165     @Override
    166     protected void onStopLoading() {
    167         // Attempt to cancel the current load task if possible.
    168         cancelLoad();
    169     }
    170 
    171     @Override
    172     public void onCanceled(Cursor cursor) {
    173         if (cursor != null && !cursor.isClosed()) {
    174             cursor.close();
    175         }
    176     }
    177 
    178     @Override
    179     protected void onReset() {
    180         super.onReset();
    181 
    182         // Ensure the loader is stopped
    183         onStopLoading();
    184 
    185         if (mCursor != null && !mCursor.isClosed()) {
    186             mCursor.close();
    187         }
    188         mCursor = null;
    189     }
    190 
    191     @NonNull
    192     public Uri getUri() {
    193         return mUri;
    194     }
    195 
    196     public void setUri(@NonNull Uri uri) {
    197         mUri = uri;
    198     }
    199 
    200     @Nullable
    201     public String[] getProjection() {
    202         return mProjection;
    203     }
    204 
    205     public void setProjection(@Nullable String[] projection) {
    206         mProjection = projection;
    207     }
    208 
    209     @Nullable
    210     public String getSelection() {
    211         return mSelection;
    212     }
    213 
    214     public void setSelection(@Nullable String selection) {
    215         mSelection = selection;
    216     }
    217 
    218     @Nullable
    219     public String[] getSelectionArgs() {
    220         return mSelectionArgs;
    221     }
    222 
    223     public void setSelectionArgs(@Nullable String[] selectionArgs) {
    224         mSelectionArgs = selectionArgs;
    225     }
    226 
    227     @Nullable
    228     public String getSortOrder() {
    229         return mSortOrder;
    230     }
    231 
    232     public void setSortOrder(@Nullable String sortOrder) {
    233         mSortOrder = sortOrder;
    234     }
    235 
    236     @Override
    237     @Deprecated
    238     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
    239         super.dump(prefix, fd, writer, args);
    240         writer.print(prefix); writer.print("mUri="); writer.println(mUri);
    241         writer.print(prefix); writer.print("mProjection=");
    242                 writer.println(Arrays.toString(mProjection));
    243         writer.print(prefix); writer.print("mSelection="); writer.println(mSelection);
    244         writer.print(prefix); writer.print("mSelectionArgs=");
    245                 writer.println(Arrays.toString(mSelectionArgs));
    246         writer.print(prefix); writer.print("mSortOrder="); writer.println(mSortOrder);
    247         writer.print(prefix); writer.print("mCursor="); writer.println(mCursor);
    248         writer.print(prefix); writer.print("mContentChanged="); writer.println(mContentChanged);
    249     }
    250 }
    251