OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / frameworks / base / core / java / android / widget / CursorTreeAdapter.java
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.widget;
18
19 import android.app.Activity;
20 import android.content.Context;
21 import android.database.ContentObserver;
22 import android.database.Cursor;
23 import android.database.DataSetObserver;
24 import android.os.Handler;
25 import android.util.Config;
26 import android.util.Log;
27 import android.util.SparseArray;
28 import android.view.View;
29 import android.view.ViewGroup;
30
31 /**
32  * An adapter that exposes data from a series of {@link Cursor}s to an
33  * {@link ExpandableListView} widget. The top-level {@link Cursor} (that is
34  * given in the constructor) exposes the groups, while subsequent {@link Cursor}s
35  * returned from {@link #getChildrenCursor(Cursor)} expose children within a
36  * particular group. The Cursors must include a column named "_id" or this class
37  * will not work.
38  */
39 public abstract class CursorTreeAdapter extends BaseExpandableListAdapter implements Filterable,
40         CursorFilter.CursorFilterClient {
41     private Context mContext;
42     private Handler mHandler;
43     private boolean mAutoRequery;
44
45     /** The cursor helper that is used to get the groups */
46     MyCursorHelper mGroupCursorHelper;
47     
48     /**
49      * The map of a group position to the group's children cursor helper (the
50      * cursor helper that is used to get the children for that group)
51      */
52     SparseArray<MyCursorHelper> mChildrenCursorHelpers;
53
54     // Filter related
55     CursorFilter mCursorFilter;
56     FilterQueryProvider mFilterQueryProvider;
57     
58     /**
59      * Constructor. The adapter will call {@link Cursor#requery()} on the cursor whenever
60      * it changes so that the most recent data is always displayed.
61      *
62      * @param cursor The cursor from which to get the data for the groups.
63      */
64     public CursorTreeAdapter(Cursor cursor, Context context) {
65         init(cursor, context, true);
66     }
67
68     /**
69      * Constructor.
70      * 
71      * @param cursor The cursor from which to get the data for the groups.
72      * @param context The context
73      * @param autoRequery If true the adapter will call {@link Cursor#requery()}
74      *        on the cursor whenever it changes so the most recent data is
75      *        always displayed.
76      */
77     public CursorTreeAdapter(Cursor cursor, Context context, boolean autoRequery) {
78         init(cursor, context, autoRequery);
79     }
80     
81     private void init(Cursor cursor, Context context, boolean autoRequery) {
82         mContext = context;
83         mHandler = new Handler();
84         mAutoRequery = autoRequery;
85         
86         mGroupCursorHelper = new MyCursorHelper(cursor);
87         mChildrenCursorHelpers = new SparseArray<MyCursorHelper>();
88     }
89
90     /**
91      * Gets the cursor helper for the children in the given group.
92      * 
93      * @param groupPosition The group whose children will be returned
94      * @param requestCursor Whether to request a Cursor via
95      *            {@link #getChildrenCursor(Cursor)} (true), or to assume a call
96      *            to {@link #setChildrenCursor(int, Cursor)} will happen shortly
97      *            (false).
98      * @return The cursor helper for the children of the given group
99      */
100     synchronized MyCursorHelper getChildrenCursorHelper(int groupPosition, boolean requestCursor) {
101         MyCursorHelper cursorHelper = mChildrenCursorHelpers.get(groupPosition);
102         
103         if (cursorHelper == null) {
104             if (mGroupCursorHelper.moveTo(groupPosition) == null) return null;
105             
106             final Cursor cursor = getChildrenCursor(mGroupCursorHelper.getCursor());
107             cursorHelper = new MyCursorHelper(cursor);
108             mChildrenCursorHelpers.put(groupPosition, cursorHelper);
109         }
110         
111         return cursorHelper;
112     }
113
114     /**
115      * Gets the Cursor for the children at the given group. Subclasses must
116      * implement this method to return the children data for a particular group.
117      * <p>
118      * If you want to asynchronously query a provider to prevent blocking the
119      * UI, it is possible to return null and at a later time call
120      * {@link #setChildrenCursor(int, Cursor)}.
121      * <p>
122      * It is your responsibility to manage this Cursor through the Activity
123      * lifecycle. It is a good idea to use {@link Activity#managedQuery} which
124      * will handle this for you. In some situations, the adapter will deactivate
125      * the Cursor on its own, but this will not always be the case, so please
126      * ensure the Cursor is properly managed.
127      * 
128      * @param groupCursor The cursor pointing to the group whose children cursor
129      *            should be returned
130      * @return The cursor for the children of a particular group, or null.
131      */
132     abstract protected Cursor getChildrenCursor(Cursor groupCursor);
133     
134     /**
135      * Sets the group Cursor.
136      * 
137      * @param cursor The Cursor to set for the group. If there is an existing cursor 
138      * it will be closed.
139      */
140     public void setGroupCursor(Cursor cursor) {
141         mGroupCursorHelper.changeCursor(cursor, false);
142     }
143     
144     /**
145      * Sets the children Cursor for a particular group. If there is an existing cursor
146      * it will be closed.
147      * <p>
148      * This is useful when asynchronously querying to prevent blocking the UI.
149      * 
150      * @param groupPosition The group whose children are being set via this Cursor.
151      * @param childrenCursor The Cursor that contains the children of the group.
152      */
153     public void setChildrenCursor(int groupPosition, Cursor childrenCursor) {
154         
155         /*
156          * Don't request a cursor from the subclass, instead we will be setting
157          * the cursor ourselves.
158          */
159         MyCursorHelper childrenCursorHelper = getChildrenCursorHelper(groupPosition, false);
160
161         /*
162          * Don't release any cursor since we know exactly what data is changing
163          * (this cursor, which is still valid).
164          */
165         childrenCursorHelper.changeCursor(childrenCursor, false);
166     }
167     
168     public Cursor getChild(int groupPosition, int childPosition) {
169         // Return this group's children Cursor pointing to the particular child
170         return getChildrenCursorHelper(groupPosition, true).moveTo(childPosition);
171     }
172
173     public long getChildId(int groupPosition, int childPosition) {
174         return getChildrenCursorHelper(groupPosition, true).getId(childPosition);
175     }
176
177     public int getChildrenCount(int groupPosition) {
178         MyCursorHelper helper = getChildrenCursorHelper(groupPosition, true);
179         return (mGroupCursorHelper.isValid() && helper != null) ? helper.getCount() : 0;
180     }
181
182     public Cursor getGroup(int groupPosition) {
183         // Return the group Cursor pointing to the given group
184         return mGroupCursorHelper.moveTo(groupPosition);
185     }
186
187     public int getGroupCount() {
188         return mGroupCursorHelper.getCount();
189     }
190
191     public long getGroupId(int groupPosition) {
192         return mGroupCursorHelper.getId(groupPosition);
193     }
194
195     public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
196             ViewGroup parent) {
197         Cursor cursor = mGroupCursorHelper.moveTo(groupPosition);
198         if (cursor == null) {
199             throw new IllegalStateException("this should only be called when the cursor is valid");
200         }
201         
202         View v;
203         if (convertView == null) {
204             v = newGroupView(mContext, cursor, isExpanded, parent);
205         } else {
206             v = convertView;
207         }
208         bindGroupView(v, mContext, cursor, isExpanded);
209         return v;
210     }
211
212     /**
213      * Makes a new group view to hold the group data pointed to by cursor.
214      * 
215      * @param context Interface to application's global information
216      * @param cursor The group cursor from which to get the data. The cursor is
217      *            already moved to the correct position.
218      * @param isExpanded Whether the group is expanded.
219      * @param parent The parent to which the new view is attached to
220      * @return The newly created view.
221      */
222     protected abstract View newGroupView(Context context, Cursor cursor, boolean isExpanded,
223             ViewGroup parent);
224
225     /**
226      * Bind an existing view to the group data pointed to by cursor.
227      * 
228      * @param view Existing view, returned earlier by newGroupView.
229      * @param context Interface to application's global information
230      * @param cursor The cursor from which to get the data. The cursor is
231      *            already moved to the correct position.
232      * @param isExpanded Whether the group is expanded.
233      */
234     protected abstract void bindGroupView(View view, Context context, Cursor cursor,
235             boolean isExpanded);
236
237     public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
238             View convertView, ViewGroup parent) {
239         MyCursorHelper cursorHelper = getChildrenCursorHelper(groupPosition, true);
240         
241         Cursor cursor = cursorHelper.moveTo(childPosition);
242         if (cursor == null) {
243             throw new IllegalStateException("this should only be called when the cursor is valid");
244         }
245         
246         View v;
247         if (convertView == null) {
248             v = newChildView(mContext, cursor, isLastChild, parent);
249         } else {
250             v = convertView;
251         }
252         bindChildView(v, mContext, cursor, isLastChild);
253         return v;
254     }
255
256     /**
257      * Makes a new child view to hold the data pointed to by cursor.
258      * 
259      * @param context Interface to application's global information
260      * @param cursor The cursor from which to get the data. The cursor is
261      *            already moved to the correct position.
262      * @param isLastChild Whether the child is the last child within its group.
263      * @param parent The parent to which the new view is attached to
264      * @return the newly created view.
265      */
266     protected abstract View newChildView(Context context, Cursor cursor, boolean isLastChild,
267             ViewGroup parent);
268
269     /**
270      * Bind an existing view to the child data pointed to by cursor
271      * 
272      * @param view Existing view, returned earlier by newChildView
273      * @param context Interface to application's global information
274      * @param cursor The cursor from which to get the data. The cursor is
275      *            already moved to the correct position.
276      * @param isLastChild Whether the child is the last child within its group.
277      */
278     protected abstract void bindChildView(View view, Context context, Cursor cursor,
279             boolean isLastChild);
280     
281     public boolean isChildSelectable(int groupPosition, int childPosition) {
282         return true;
283     }
284
285     public boolean hasStableIds() {
286         return true;
287     }
288
289     private synchronized void releaseCursorHelpers() {
290         for (int pos = mChildrenCursorHelpers.size() - 1; pos >= 0; pos--) {
291             mChildrenCursorHelpers.valueAt(pos).deactivate();
292         }
293         
294         mChildrenCursorHelpers.clear();
295     }
296     
297     @Override
298     public void notifyDataSetChanged() {
299         notifyDataSetChanged(true);
300     }
301
302     /**
303      * Notifies a data set change, but with the option of not releasing any
304      * cached cursors.
305      * 
306      * @param releaseCursors Whether to release and deactivate any cached
307      *            cursors.
308      */
309     public void notifyDataSetChanged(boolean releaseCursors) {
310         
311         if (releaseCursors) {
312             releaseCursorHelpers();
313         }
314         
315         super.notifyDataSetChanged();
316     }
317     
318     @Override
319     public void notifyDataSetInvalidated() {
320         releaseCursorHelpers();
321         super.notifyDataSetInvalidated();
322     }
323
324     @Override
325     public void onGroupCollapsed(int groupPosition) {
326         deactivateChildrenCursorHelper(groupPosition);
327     }
328
329     /**
330      * Deactivates the Cursor and removes the helper from cache.
331      * 
332      * @param groupPosition The group whose children Cursor and helper should be
333      *            deactivated.
334      */
335     synchronized void deactivateChildrenCursorHelper(int groupPosition) {
336         MyCursorHelper cursorHelper = getChildrenCursorHelper(groupPosition, true);
337         mChildrenCursorHelpers.remove(groupPosition);
338         cursorHelper.deactivate();
339     }
340
341     /**
342      * @see CursorAdapter#convertToString(Cursor)
343      */
344     public String convertToString(Cursor cursor) {
345         return cursor == null ? "" : cursor.toString();
346     }
347
348     /**
349      * @see CursorAdapter#runQueryOnBackgroundThread(CharSequence)
350      */
351     public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
352         if (mFilterQueryProvider != null) {
353             return mFilterQueryProvider.runQuery(constraint);
354         }
355
356         return mGroupCursorHelper.getCursor();
357     }
358     
359     public Filter getFilter() {
360         if (mCursorFilter == null) {
361             mCursorFilter = new CursorFilter(this);
362         }
363         return mCursorFilter;
364     }
365
366     /**
367      * @see CursorAdapter#getFilterQueryProvider()
368      */
369     public FilterQueryProvider getFilterQueryProvider() {
370         return mFilterQueryProvider;
371     }
372
373     /**
374      * @see CursorAdapter#setFilterQueryProvider(FilterQueryProvider)
375      */
376     public void setFilterQueryProvider(FilterQueryProvider filterQueryProvider) {
377         mFilterQueryProvider = filterQueryProvider;
378     }
379     
380     /**
381      * @see CursorAdapter#changeCursor(Cursor)
382      */
383     public void changeCursor(Cursor cursor) {
384         mGroupCursorHelper.changeCursor(cursor, true);
385     }
386
387     /**
388      * @see CursorAdapter#getCursor()
389      */
390     public Cursor getCursor() {
391         return mGroupCursorHelper.getCursor();
392     }
393
394     /**
395      * Helper class for Cursor management:
396      * <li> Data validity
397      * <li> Funneling the content and data set observers from a Cursor to a
398      *      single data set observer for widgets
399      * <li> ID from the Cursor for use in adapter IDs
400      * <li> Swapping cursors but maintaining other metadata
401      */
402     class MyCursorHelper {
403         private Cursor mCursor;
404         private boolean mDataValid;
405         private int mRowIDColumn;
406         private MyContentObserver mContentObserver;
407         private MyDataSetObserver mDataSetObserver;
408         
409         MyCursorHelper(Cursor cursor) {
410             final boolean cursorPresent = cursor != null;
411             mCursor = cursor;
412             mDataValid = cursorPresent;
413             mRowIDColumn = cursorPresent ? cursor.getColumnIndex("_id") : -1;
414             mContentObserver = new MyContentObserver();
415             mDataSetObserver = new MyDataSetObserver();
416             if (cursorPresent) {
417                 cursor.registerContentObserver(mContentObserver);
418                 cursor.registerDataSetObserver(mDataSetObserver);
419             }
420         }
421         
422         Cursor getCursor() {
423             return mCursor;
424         }
425
426         int getCount() {
427             if (mDataValid && mCursor != null) {
428                 return mCursor.getCount();
429             } else {
430                 return 0;
431             }
432         }
433         
434         long getId(int position) {
435             if (mDataValid && mCursor != null) {
436                 if (mCursor.moveToPosition(position)) {
437                     return mCursor.getLong(mRowIDColumn);
438                 } else {
439                     return 0;
440                 }
441             } else {
442                 return 0;
443             }
444         }
445         
446         Cursor moveTo(int position) {
447             if (mDataValid && (mCursor != null) && mCursor.moveToPosition(position)) {
448                 return mCursor;
449             } else {
450                 return null;
451             }
452         }
453         
454         void changeCursor(Cursor cursor, boolean releaseCursors) {
455             if (cursor == mCursor) return;
456
457             deactivate();
458             mCursor = cursor;
459             if (cursor != null) {
460                 cursor.registerContentObserver(mContentObserver);
461                 cursor.registerDataSetObserver(mDataSetObserver);
462                 mRowIDColumn = cursor.getColumnIndex("_id");
463                 mDataValid = true;
464                 // notify the observers about the new cursor
465                 notifyDataSetChanged(releaseCursors);
466             } else {
467                 mRowIDColumn = -1;
468                 mDataValid = false;
469                 // notify the observers about the lack of a data set
470                 notifyDataSetInvalidated();
471             }
472         }
473
474         void deactivate() {
475             if (mCursor == null) {
476                 return;
477             }
478             
479             mCursor.unregisterContentObserver(mContentObserver);
480             mCursor.unregisterDataSetObserver(mDataSetObserver);
481             mCursor.close();
482             mCursor = null;
483         }
484         
485         boolean isValid() {
486             return mDataValid && mCursor != null;
487         }
488         
489         private class MyContentObserver extends ContentObserver {
490             public MyContentObserver() {
491                 super(mHandler);
492             }
493
494             @Override
495             public boolean deliverSelfNotifications() {
496                 return true;
497             }
498
499             @Override
500             public void onChange(boolean selfChange) {
501                 if (mAutoRequery && mCursor != null) {
502                     if (Config.LOGV) Log.v("Cursor", "Auto requerying " + mCursor +
503                             " due to update");
504                     mDataValid = mCursor.requery();
505                 }
506             }
507         }
508
509         private class MyDataSetObserver extends DataSetObserver {
510             @Override
511             public void onChanged() {
512                 mDataValid = true;
513                 notifyDataSetChanged();
514             }
515
516             @Override
517             public void onInvalidated() {
518                 mDataValid = false;
519                 notifyDataSetInvalidated();
520             }
521         }
522     }
523 }