Home | History | Annotate | Download | only in adapters
      1 /*
      2  * Copyright (C) 2011 Google Inc.
      3  * Licensed to The Android Open Source Project.
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package com.android.ex.photo.adapters;
     19 
     20 import android.content.Context;
     21 import android.database.Cursor;
     22 import android.support.v4.app.Fragment;
     23 import android.support.v4.app.FragmentManager;
     24 import android.util.Log;
     25 import android.util.SparseIntArray;
     26 import android.view.View;
     27 
     28 import com.android.ex.photo.provider.PhotoContract;
     29 
     30 import java.util.HashMap;
     31 
     32 /**
     33  * Page adapter for use with an BaseCursorLoader. Unlike other cursor adapters, this has no
     34  * observers for automatic refresh. Instead, it depends upon external mechanisms to provide
     35  * the update signal.
     36  */
     37 public abstract class BaseCursorPagerAdapter extends BaseFragmentPagerAdapter {
     38     private static final String TAG = "BaseCursorPagerAdapter";
     39 
     40     protected Context mContext;
     41     protected Cursor mCursor;
     42     protected int mRowIDColumn;
     43     /** Mapping of row ID to cursor position */
     44     protected SparseIntArray mItemPosition;
     45     /** Mapping of instantiated object to row ID */
     46     protected final HashMap<Object, Integer> mObjectRowMap = new HashMap<Object, Integer>();
     47 
     48     /**
     49      * Constructor that always enables auto-requery.
     50      *
     51      * @param c The cursor from which to get the data.
     52      * @param context The context
     53      */
     54     public BaseCursorPagerAdapter(Context context, FragmentManager fm, Cursor c) {
     55         super(fm);
     56         init(context, c);
     57     }
     58 
     59     /**
     60      * Makes a fragment for the data pointed to by the cursor
     61      *
     62      * @param context Interface to application's global information
     63      * @param cursor The cursor from which to get the data. The cursor is already
     64      * moved to the correct position.
     65      * @return the newly created fragment.
     66      */
     67     public abstract Fragment getItem(Context context, Cursor cursor, int position);
     68 
     69     // TODO: This shouldn't just return null - maybe it needs to wait for a cursor to be supplied?
     70     //       See b/7103023
     71     @Override
     72     public Fragment getItem(int position) {
     73         if (mCursor != null && moveCursorTo(position)) {
     74             return getItem(mContext, mCursor, position);
     75         }
     76         return null;
     77     }
     78 
     79     @Override
     80     public int getCount() {
     81         if (mCursor != null) {
     82             return mCursor.getCount();
     83         } else {
     84             return 0;
     85         }
     86     }
     87 
     88     @Override
     89     public Object instantiateItem(View container, int position) {
     90         if (mCursor == null) {
     91             throw new IllegalStateException("this should only be called when the cursor is valid");
     92         }
     93 
     94         final Integer rowId;
     95         if (moveCursorTo(position)) {
     96             rowId = mCursor.getString(mRowIDColumn).hashCode();
     97         } else {
     98             rowId = null;
     99         }
    100 
    101         // Create the fragment and bind cursor data
    102         final Object obj = super.instantiateItem(container, position);
    103         if (obj != null) {
    104             mObjectRowMap.put(obj, rowId);
    105         }
    106         return obj;
    107     }
    108 
    109     @Override
    110     public void destroyItem(View container, int position, Object object) {
    111         mObjectRowMap.remove(object);
    112 
    113         super.destroyItem(container, position, object);
    114     }
    115 
    116     @Override
    117     public int getItemPosition(Object object) {
    118         final Integer rowId = mObjectRowMap.get(object);
    119         if (rowId == null || mItemPosition == null) {
    120             return POSITION_NONE;
    121         }
    122 
    123         final int position = mItemPosition.get(rowId, POSITION_NONE);
    124         return position;
    125     }
    126 
    127     /**
    128      * @return true if data is valid
    129      */
    130     public boolean isDataValid() {
    131         return mCursor != null;
    132     }
    133 
    134     /**
    135      * Returns the cursor.
    136      */
    137     public Cursor getCursor() {
    138         return mCursor;
    139     }
    140 
    141     /**
    142      * Returns the data item associated with the specified position in the data set.
    143      */
    144     public Object getDataItem(int position) {
    145         if (mCursor != null && moveCursorTo(position)) {
    146             return mCursor;
    147         } else {
    148             return null;
    149         }
    150     }
    151 
    152     /**
    153      * Returns the row id associated with the specified position in the list.
    154      */
    155     public long getItemId(int position) {
    156         if (mCursor != null && moveCursorTo(position)) {
    157             return mCursor.getString(mRowIDColumn).hashCode();
    158         } else {
    159             return 0;
    160         }
    161     }
    162 
    163     /**
    164      * Swap in a new Cursor, returning the old Cursor.
    165      *
    166      * @param newCursor The new cursor to be used.
    167      * @return Returns the previously set Cursor, or null if there was not one.
    168      * If the given new Cursor is the same instance is the previously set
    169      * Cursor, null is also returned.
    170      */
    171     public Cursor swapCursor(Cursor newCursor) {
    172         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    173             Log.v(TAG, "swapCursor old=" + (mCursor == null ? -1 : mCursor.getCount()) +
    174                     "; new=" + (newCursor == null ? -1 : newCursor.getCount()));
    175         }
    176 
    177         if (newCursor == mCursor) {
    178             return null;
    179         }
    180         Cursor oldCursor = mCursor;
    181         mCursor = newCursor;
    182         if (newCursor != null) {
    183             mRowIDColumn = newCursor.getColumnIndex(PhotoContract.PhotoViewColumns.URI);
    184         } else {
    185             mRowIDColumn = -1;
    186         }
    187 
    188         setItemPosition();
    189         notifyDataSetChanged();     // notify the observers about the new cursor
    190         return oldCursor;
    191     }
    192 
    193     /**
    194      * Converts the cursor into a CharSequence. Subclasses should override this
    195      * method to convert their results. The default implementation returns an
    196      * empty String for null values or the default String representation of
    197      * the value.
    198      *
    199      * @param cursor the cursor to convert to a CharSequence
    200      * @return a CharSequence representing the value
    201      */
    202     public CharSequence convertToString(Cursor cursor) {
    203         return cursor == null ? "" : cursor.toString();
    204     }
    205 
    206     @Override
    207     protected String makeFragmentName(int viewId, int index) {
    208         if (moveCursorTo(index)) {
    209             return "android:pager:" + viewId + ":" + mCursor.getString(mRowIDColumn).hashCode();
    210         } else {
    211             return super.makeFragmentName(viewId, index);
    212         }
    213     }
    214 
    215     /**
    216      * Moves the cursor to the given position
    217      *
    218      * @return {@code true} if the cursor's position was set. Otherwise, {@code false}.
    219      */
    220     private boolean moveCursorTo(int position) {
    221         if (mCursor != null && !mCursor.isClosed()) {
    222             return mCursor.moveToPosition(position);
    223         }
    224         return false;
    225     }
    226 
    227     /**
    228      * Initialize the adapter.
    229      */
    230     private void init(Context context, Cursor c) {
    231         boolean cursorPresent = c != null;
    232         mCursor = c;
    233         mContext = context;
    234         mRowIDColumn = cursorPresent
    235                 ? mCursor.getColumnIndex(PhotoContract.PhotoViewColumns.URI) : -1;
    236     }
    237 
    238     /**
    239      * Sets the {@link #mItemPosition} instance variable with the current mapping of
    240      * row id to cursor position.
    241      */
    242     private void setItemPosition() {
    243         if (mCursor == null || mCursor.isClosed()) {
    244             mItemPosition = null;
    245             return;
    246         }
    247 
    248         SparseIntArray itemPosition = new SparseIntArray(mCursor.getCount());
    249 
    250         mCursor.moveToPosition(-1);
    251         while (mCursor.moveToNext()) {
    252             final int rowId = mCursor.getString(mRowIDColumn).hashCode();
    253             final int position = mCursor.getPosition();
    254 
    255             itemPosition.append(rowId, position);
    256         }
    257         mItemPosition = itemPosition;
    258     }
    259 }
    260