All Downloads are FREE. Search and download functionalities are using the official Maven repository.

android.widget.CursorTreeAdapter Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 14-robolectric-10818077
Show newest version
/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.widget;

import android.app.Activity;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.os.Handler;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;

/**
 * An adapter that exposes data from a series of {@link Cursor}s to an
 * {@link ExpandableListView} widget. The top-level {@link Cursor} (that is
 * given in the constructor) exposes the groups, while subsequent {@link Cursor}s
 * returned from {@link #getChildrenCursor(Cursor)} expose children within a
 * particular group. The Cursors must include a column named "_id" or this class
 * will not work.
 */
public abstract class CursorTreeAdapter extends BaseExpandableListAdapter implements Filterable,
        CursorFilter.CursorFilterClient {
    private Context mContext;
    private Handler mHandler;
    private boolean mAutoRequery;

    /** The cursor helper that is used to get the groups */
    MyCursorHelper mGroupCursorHelper;
    
    /**
     * The map of a group position to the group's children cursor helper (the
     * cursor helper that is used to get the children for that group)
     */
    SparseArray mChildrenCursorHelpers;

    // Filter related
    CursorFilter mCursorFilter;
    FilterQueryProvider mFilterQueryProvider;
    
    /**
     * Constructor. The adapter will call {@link Cursor#requery()} on the cursor whenever
     * it changes so that the most recent data is always displayed.
     *
     * @param cursor The cursor from which to get the data for the groups.
     */
    public CursorTreeAdapter(Cursor cursor, Context context) {
        init(cursor, context, true);
    }

    /**
     * Constructor.
     * 
     * @param cursor The cursor from which to get the data for the groups.
     * @param context The context
     * @param autoRequery If true the adapter will call {@link Cursor#requery()}
     *        on the cursor whenever it changes so the most recent data is
     *        always displayed.
     */
    public CursorTreeAdapter(Cursor cursor, Context context, boolean autoRequery) {
        init(cursor, context, autoRequery);
    }
    
    private void init(Cursor cursor, Context context, boolean autoRequery) {
        mContext = context;
        mHandler = new Handler();
        mAutoRequery = autoRequery;
        
        mGroupCursorHelper = new MyCursorHelper(cursor);
        mChildrenCursorHelpers = new SparseArray();
    }

    /**
     * Gets the cursor helper for the children in the given group.
     * 
     * @param groupPosition The group whose children will be returned
     * @param requestCursor Whether to request a Cursor via
     *            {@link #getChildrenCursor(Cursor)} (true), or to assume a call
     *            to {@link #setChildrenCursor(int, Cursor)} will happen shortly
     *            (false).
     * @return The cursor helper for the children of the given group
     */
    synchronized MyCursorHelper getChildrenCursorHelper(int groupPosition, boolean requestCursor) {
        MyCursorHelper cursorHelper = mChildrenCursorHelpers.get(groupPosition);
        
        if (cursorHelper == null) {
            if (mGroupCursorHelper.moveTo(groupPosition) == null) return null;
            
            final Cursor cursor = getChildrenCursor(mGroupCursorHelper.getCursor());
            cursorHelper = new MyCursorHelper(cursor);
            mChildrenCursorHelpers.put(groupPosition, cursorHelper);
        }
        
        return cursorHelper;
    }

    /**
     * Gets the Cursor for the children at the given group. Subclasses must
     * implement this method to return the children data for a particular group.
     * 

* If you want to asynchronously query a provider to prevent blocking the * UI, it is possible to return null and at a later time call * {@link #setChildrenCursor(int, Cursor)}. *

* It is your responsibility to manage this Cursor through the Activity * lifecycle. It is a good idea to use {@link Activity#managedQuery} which * will handle this for you. In some situations, the adapter will deactivate * the Cursor on its own, but this will not always be the case, so please * ensure the Cursor is properly managed. * * @param groupCursor The cursor pointing to the group whose children cursor * should be returned * @return The cursor for the children of a particular group, or null. */ abstract protected Cursor getChildrenCursor(Cursor groupCursor); /** * Sets the group Cursor. * * @param cursor The Cursor to set for the group. If there is an existing cursor * it will be closed. */ public void setGroupCursor(Cursor cursor) { mGroupCursorHelper.changeCursor(cursor, false); } /** * Sets the children Cursor for a particular group. If there is an existing cursor * it will be closed. *

* This is useful when asynchronously querying to prevent blocking the UI. * * @param groupPosition The group whose children are being set via this Cursor. * @param childrenCursor The Cursor that contains the children of the group. */ public void setChildrenCursor(int groupPosition, Cursor childrenCursor) { /* * Don't request a cursor from the subclass, instead we will be setting * the cursor ourselves. */ MyCursorHelper childrenCursorHelper = getChildrenCursorHelper(groupPosition, false); /* * Don't release any cursor since we know exactly what data is changing * (this cursor, which is still valid). */ childrenCursorHelper.changeCursor(childrenCursor, false); } public Cursor getChild(int groupPosition, int childPosition) { // Return this group's children Cursor pointing to the particular child return getChildrenCursorHelper(groupPosition, true).moveTo(childPosition); } public long getChildId(int groupPosition, int childPosition) { return getChildrenCursorHelper(groupPosition, true).getId(childPosition); } public int getChildrenCount(int groupPosition) { MyCursorHelper helper = getChildrenCursorHelper(groupPosition, true); return (mGroupCursorHelper.isValid() && helper != null) ? helper.getCount() : 0; } public Cursor getGroup(int groupPosition) { // Return the group Cursor pointing to the given group return mGroupCursorHelper.moveTo(groupPosition); } public int getGroupCount() { return mGroupCursorHelper.getCount(); } public long getGroupId(int groupPosition) { return mGroupCursorHelper.getId(groupPosition); } public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { Cursor cursor = mGroupCursorHelper.moveTo(groupPosition); if (cursor == null) { throw new IllegalStateException("this should only be called when the cursor is valid"); } View v; if (convertView == null) { v = newGroupView(mContext, cursor, isExpanded, parent); } else { v = convertView; } bindGroupView(v, mContext, cursor, isExpanded); return v; } /** * Makes a new group view to hold the group data pointed to by cursor. * * @param context Interface to application's global information * @param cursor The group cursor from which to get the data. The cursor is * already moved to the correct position. * @param isExpanded Whether the group is expanded. * @param parent The parent to which the new view is attached to * @return The newly created view. */ protected abstract View newGroupView(Context context, Cursor cursor, boolean isExpanded, ViewGroup parent); /** * Bind an existing view to the group data pointed to by cursor. * * @param view Existing view, returned earlier by newGroupView. * @param context Interface to application's global information * @param cursor The cursor from which to get the data. The cursor is * already moved to the correct position. * @param isExpanded Whether the group is expanded. */ protected abstract void bindGroupView(View view, Context context, Cursor cursor, boolean isExpanded); public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { MyCursorHelper cursorHelper = getChildrenCursorHelper(groupPosition, true); Cursor cursor = cursorHelper.moveTo(childPosition); if (cursor == null) { throw new IllegalStateException("this should only be called when the cursor is valid"); } View v; if (convertView == null) { v = newChildView(mContext, cursor, isLastChild, parent); } else { v = convertView; } bindChildView(v, mContext, cursor, isLastChild); return v; } /** * Makes a new child view to hold the data pointed to by cursor. * * @param context Interface to application's global information * @param cursor The cursor from which to get the data. The cursor is * already moved to the correct position. * @param isLastChild Whether the child is the last child within its group. * @param parent The parent to which the new view is attached to * @return the newly created view. */ protected abstract View newChildView(Context context, Cursor cursor, boolean isLastChild, ViewGroup parent); /** * Bind an existing view to the child data pointed to by cursor * * @param view Existing view, returned earlier by newChildView * @param context Interface to application's global information * @param cursor The cursor from which to get the data. The cursor is * already moved to the correct position. * @param isLastChild Whether the child is the last child within its group. */ protected abstract void bindChildView(View view, Context context, Cursor cursor, boolean isLastChild); public boolean isChildSelectable(int groupPosition, int childPosition) { return true; } public boolean hasStableIds() { return true; } private synchronized void releaseCursorHelpers() { for (int pos = mChildrenCursorHelpers.size() - 1; pos >= 0; pos--) { mChildrenCursorHelpers.valueAt(pos).deactivate(); } mChildrenCursorHelpers.clear(); } @Override public void notifyDataSetChanged() { notifyDataSetChanged(true); } /** * Notifies a data set change, but with the option of not releasing any * cached cursors. * * @param releaseCursors Whether to release and deactivate any cached * cursors. */ public void notifyDataSetChanged(boolean releaseCursors) { if (releaseCursors) { releaseCursorHelpers(); } super.notifyDataSetChanged(); } @Override public void notifyDataSetInvalidated() { releaseCursorHelpers(); super.notifyDataSetInvalidated(); } @Override public void onGroupCollapsed(int groupPosition) { deactivateChildrenCursorHelper(groupPosition); } /** * Deactivates the Cursor and removes the helper from cache. * * @param groupPosition The group whose children Cursor and helper should be * deactivated. */ synchronized void deactivateChildrenCursorHelper(int groupPosition) { MyCursorHelper cursorHelper = getChildrenCursorHelper(groupPosition, true); mChildrenCursorHelpers.remove(groupPosition); cursorHelper.deactivate(); } /** * @see CursorAdapter#convertToString(Cursor) */ public String convertToString(Cursor cursor) { return cursor == null ? "" : cursor.toString(); } /** * @see CursorAdapter#runQueryOnBackgroundThread(CharSequence) */ public Cursor runQueryOnBackgroundThread(CharSequence constraint) { if (mFilterQueryProvider != null) { return mFilterQueryProvider.runQuery(constraint); } return mGroupCursorHelper.getCursor(); } public Filter getFilter() { if (mCursorFilter == null) { mCursorFilter = new CursorFilter(this); } return mCursorFilter; } /** * @see CursorAdapter#getFilterQueryProvider() */ public FilterQueryProvider getFilterQueryProvider() { return mFilterQueryProvider; } /** * @see CursorAdapter#setFilterQueryProvider(FilterQueryProvider) */ public void setFilterQueryProvider(FilterQueryProvider filterQueryProvider) { mFilterQueryProvider = filterQueryProvider; } /** * @see CursorAdapter#changeCursor(Cursor) */ public void changeCursor(Cursor cursor) { mGroupCursorHelper.changeCursor(cursor, true); } /** * @see CursorAdapter#getCursor() */ public Cursor getCursor() { return mGroupCursorHelper.getCursor(); } /** * Helper class for Cursor management: *

  • Data validity *
  • Funneling the content and data set observers from a Cursor to a * single data set observer for widgets *
  • ID from the Cursor for use in adapter IDs *
  • Swapping cursors but maintaining other metadata */ class MyCursorHelper { private Cursor mCursor; private boolean mDataValid; private int mRowIDColumn; private MyContentObserver mContentObserver; private MyDataSetObserver mDataSetObserver; MyCursorHelper(Cursor cursor) { final boolean cursorPresent = cursor != null; mCursor = cursor; mDataValid = cursorPresent; mRowIDColumn = cursorPresent ? cursor.getColumnIndex("_id") : -1; mContentObserver = new MyContentObserver(); mDataSetObserver = new MyDataSetObserver(); if (cursorPresent) { cursor.registerContentObserver(mContentObserver); cursor.registerDataSetObserver(mDataSetObserver); } } Cursor getCursor() { return mCursor; } int getCount() { if (mDataValid && mCursor != null) { return mCursor.getCount(); } else { return 0; } } long getId(int position) { if (mDataValid && mCursor != null) { if (mCursor.moveToPosition(position)) { return mCursor.getLong(mRowIDColumn); } else { return 0; } } else { return 0; } } Cursor moveTo(int position) { if (mDataValid && (mCursor != null) && mCursor.moveToPosition(position)) { return mCursor; } else { return null; } } void changeCursor(Cursor cursor, boolean releaseCursors) { if (cursor == mCursor) return; deactivate(); mCursor = cursor; if (cursor != null) { cursor.registerContentObserver(mContentObserver); cursor.registerDataSetObserver(mDataSetObserver); mRowIDColumn = cursor.getColumnIndex("_id"); mDataValid = true; // notify the observers about the new cursor notifyDataSetChanged(releaseCursors); } else { mRowIDColumn = -1; mDataValid = false; // notify the observers about the lack of a data set notifyDataSetInvalidated(); } } void deactivate() { if (mCursor == null) { return; } mCursor.unregisterContentObserver(mContentObserver); mCursor.unregisterDataSetObserver(mDataSetObserver); mCursor.close(); mCursor = null; } boolean isValid() { return mDataValid && mCursor != null; } private class MyContentObserver extends ContentObserver { public MyContentObserver() { super(mHandler); } @Override public boolean deliverSelfNotifications() { return true; } @Override public void onChange(boolean selfChange) { if (mAutoRequery && mCursor != null && !mCursor.isClosed()) { if (false) Log.v("Cursor", "Auto requerying " + mCursor + " due to update"); mDataValid = mCursor.requery(); } } } private class MyDataSetObserver extends DataSetObserver { @Override public void onChanged() { mDataValid = true; notifyDataSetChanged(); } @Override public void onInvalidated() { mDataValid = false; notifyDataSetInvalidated(); } } } }




  • © 2015 - 2024 Weber Informatics LLC | Privacy Policy