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 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();
     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         mDirty = false;
    132         mCursor.requery();
    133         readCursorIntoCache();
    134         setChanged();
    135         notifyObservers();
    136     }
    137 
    138     private synchronized void readCursorIntoCache() {
    139         // Make a new map so old values returned by getRows() are undisturbed.
    140         int capacity = mValues != null ? mValues.size() : 0;
    141         mValues = new HashMap<String, ContentValues>(capacity);
    142         while (mCursor.moveToNext()) {
    143             ContentValues values = new ContentValues();
    144             for (int i = 0; i < mColumnNames.length; i++) {
    145                 if (i != mKeyColumn) {
    146                     values.put(mColumnNames[i], mCursor.getString(i));
    147                 }
    148             }
    149             mValues.put(mCursor.getString(mKeyColumn), values);
    150         }
    151     }
    152 
    153     public synchronized Map<String, ContentValues> getRows() {
    154         if (mDirty) requery();
    155         return mValues;
    156     }
    157 
    158     public synchronized void close() {
    159         if (mContentObserver != null) {
    160             mCursor.unregisterContentObserver(mContentObserver);
    161             mContentObserver = null;
    162         }
    163         mCursor.close();
    164         mCursor = null;
    165     }
    166 
    167     @Override
    168     protected void finalize() throws Throwable {
    169         if (mCursor != null) close();
    170         super.finalize();
    171     }
    172 }
    173