Home | History | Annotate | Download | only in content
      1 /*
      2  * Copyright (C) 2007 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 android.content;
     18 
     19 import android.database.ContentObserver;
     20 import android.database.Cursor;
     21 import android.os.Handler;
     22 
     23 import java.util.HashMap;
     24 import java.util.Map;
     25 import java.util.Observable;
     26 
     27 /**
     28  * Caches the contents of a cursor into a Map of String->ContentValues and optionally
     29  * keeps the cache fresh by registering for updates on the content backing the cursor. The column of
     30  * the database that is to be used as the key of the map is user-configurable, and the
     31  * ContentValues contains all columns other than the one that is designated the key.
     32  * <p>
     33  * The cursor data is accessed by row key and column name via getValue().
     34  */
     35 public class ContentQueryMap extends Observable {
     36     private volatile Cursor mCursor;
     37     private String[] mColumnNames;
     38     private int mKeyColumn;
     39 
     40     private Handler mHandlerForUpdateNotifications = null;
     41     private boolean mKeepUpdated = false;
     42 
     43     private Map<String, ContentValues> mValues = null;
     44 
     45     private ContentObserver mContentObserver;
     46 
     47     /** Set when a cursor change notification is received and is cleared on a call to requery(). */
     48     private boolean mDirty = false;
     49 
     50     /**
     51      * Creates a ContentQueryMap that caches the content backing the cursor
     52      *
     53      * @param cursor the cursor whose contents should be cached
     54      * @param columnNameOfKey the column that is to be used as the key of the values map
     55      * @param keepUpdated true if the cursor's ContentProvider should be monitored for changes and
     56      * the map updated when changes do occur
     57      * @param handlerForUpdateNotifications the Handler that should be used to receive
     58      *  notifications of changes (if requested). Normally you pass null here, but if
     59      *  you know that the thread that is creating this isn't a thread that can receive
     60      *  messages then you can create your own handler and use that here.
     61      */
     62     public ContentQueryMap(Cursor cursor, String columnNameOfKey, boolean keepUpdated,
     63             Handler handlerForUpdateNotifications) {
     64         mCursor = cursor;
     65         mColumnNames = mCursor.getColumnNames();
     66         mKeyColumn = mCursor.getColumnIndexOrThrow(columnNameOfKey);
     67         mHandlerForUpdateNotifications = handlerForUpdateNotifications;
     68         setKeepUpdated(keepUpdated);
     69 
     70         // If we aren't keeping the cache updated with the current state of the cursor's
     71         // ContentProvider then read it once into the cache. Otherwise the cache will be filled
     72         // automatically.
     73         if (!keepUpdated) {
     74             readCursorIntoCache(cursor);
     75         }
     76     }
     77 
     78     /**
     79      * Change whether or not the ContentQueryMap will register with the cursor's ContentProvider
     80      * for change notifications. If you use a ContentQueryMap in an activity you should call this
     81      * with false in onPause(), which means you need to call it with true in onResume()
     82      * if want it to be kept updated.
     83      * @param keepUpdated if true the ContentQueryMap should be registered with the cursor's
     84      * ContentProvider, false otherwise
     85      */
     86     public void setKeepUpdated(boolean keepUpdated) {
     87         if (keepUpdated == mKeepUpdated) return;
     88         mKeepUpdated = keepUpdated;
     89 
     90         if (!mKeepUpdated) {
     91             mCursor.unregisterContentObserver(mContentObserver);
     92             mContentObserver = null;
     93         } else {
     94             if (mHandlerForUpdateNotifications == null) {
     95                 mHandlerForUpdateNotifications = new Handler();
     96             }
     97             if (mContentObserver == null) {
     98                 mContentObserver = new ContentObserver(mHandlerForUpdateNotifications) {
     99                     @Override
    100                     public void onChange(boolean selfChange) {
    101                         // If anyone is listening, we need to do this now to broadcast
    102                         // to the observers.  Otherwise, we'll just set mDirty and
    103                         // let it query lazily when they ask for the values.
    104                         if (countObservers() != 0) {
    105                             requery();
    106                         } else {
    107                             mDirty = true;
    108                         }
    109                     }
    110                 };
    111             }
    112             mCursor.registerContentObserver(mContentObserver);
    113             // mark dirty, since it is possible the cursor's backing data had changed before we
    114             // registered for changes
    115             mDirty = true;
    116         }
    117     }
    118 
    119     /**
    120      * Access the ContentValues for the row specified by rowName
    121      * @param rowName which row to read
    122      * @return the ContentValues for the row, or null if the row wasn't present in the cursor
    123      */
    124     public synchronized ContentValues getValues(String rowName) {
    125         if (mDirty) requery();
    126         return mValues.get(rowName);
    127     }
    128 
    129     /** Requeries the cursor and reads the contents into the cache */
    130     public void requery() {
    131         final Cursor cursor = mCursor;
    132         if (cursor == null) {
    133             // If mCursor is null then it means there was a requery() in flight
    134             // while another thread called close(), which nulls out mCursor.
    135             // If this happens ignore the requery() since we are closed anyways.
    136             return;
    137         }
    138         mDirty = false;
    139         if (!cursor.requery()) {
    140             // again, don't do anything if the cursor is already closed
    141             return;
    142         }
    143         readCursorIntoCache(cursor);
    144         setChanged();
    145         notifyObservers();
    146     }
    147 
    148     private synchronized void readCursorIntoCache(Cursor cursor) {
    149         // Make a new map so old values returned by getRows() are undisturbed.
    150         int capacity = mValues != null ? mValues.size() : 0;
    151         mValues = new HashMap<String, ContentValues>(capacity);
    152         while (cursor.moveToNext()) {
    153             ContentValues values = new ContentValues();
    154             for (int i = 0; i < mColumnNames.length; i++) {
    155                 if (i != mKeyColumn) {
    156                     values.put(mColumnNames[i], cursor.getString(i));
    157                 }
    158             }
    159             mValues.put(cursor.getString(mKeyColumn), values);
    160         }
    161     }
    162 
    163     public synchronized Map<String, ContentValues> getRows() {
    164         if (mDirty) requery();
    165         return mValues;
    166     }
    167 
    168     public synchronized void close() {
    169         if (mContentObserver != null) {
    170             mCursor.unregisterContentObserver(mContentObserver);
    171             mContentObserver = null;
    172         }
    173         mCursor.close();
    174         mCursor = null;
    175     }
    176 
    177     @Override
    178     protected void finalize() throws Throwable {
    179         if (mCursor != null) close();
    180         super.finalize();
    181     }
    182 }
    183