BaseCursorPagerAdapter.java revision 8122882aaf14820bc2241605b2ab818ad358b9af
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 18package com.android.ex.photo.adapters; 19 20import android.app.Fragment; 21import android.app.FragmentManager; 22import android.content.Context; 23import android.database.Cursor; 24import android.util.Log; 25import android.util.SparseIntArray; 26import android.view.View; 27 28import com.android.ex.photo.provider.PhotoContract; 29 30import 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 */ 37public abstract class BaseCursorPagerAdapter extends BaseFragmentPagerAdapter { 38 private static final String TAG = "BaseCursorPagerAdapter"; 39 40 Context mContext; 41 private boolean mDataValid; 42 private Cursor mCursor; 43 private int mRowIDColumn; 44 /** Mapping of row ID to cursor position */ 45 private SparseIntArray mItemPosition; 46 /** Mapping of instantiated object to row ID */ 47 private HashMap<Object, Integer> mObjectRowMap = new HashMap<Object, Integer>(); 48 49 /** 50 * Constructor that always enables auto-requery. 51 * 52 * @param c The cursor from which to get the data. 53 * @param context The context 54 */ 55 public BaseCursorPagerAdapter(Context context, FragmentManager fm, Cursor c) { 56 super(fm); 57 init(context, c); 58 } 59 60 /** 61 * Makes a fragment for the data pointed to by the cursor 62 * 63 * @param context Interface to application's global information 64 * @param cursor The cursor from which to get the data. The cursor is already 65 * moved to the correct position. 66 * @return the newly created fragment. 67 */ 68 public abstract Fragment getItem(Context context, Cursor cursor); 69 70 @Override 71 public Fragment getItem(int position) { 72 if (mDataValid && moveCursorTo(position)) { 73 return getItem(mContext, mCursor); 74 } 75 return null; 76 } 77 78 @Override 79 public int getCount() { 80 if (mDataValid && mCursor != null) { 81 return mCursor.getCount(); 82 } else { 83 return 0; 84 } 85 } 86 87 @Override 88 public Object instantiateItem(View container, int position) { 89 if (!mDataValid) { 90 throw new IllegalStateException("this should only be called when the cursor is valid"); 91 } 92 93 final Integer rowId; 94 if (moveCursorTo(position)) { 95 rowId = mCursor.getString(mRowIDColumn).hashCode(); 96 } else { 97 rowId = null; 98 } 99 100 // Create the fragment and bind cursor data 101 final Object obj = super.instantiateItem(container, position); 102 if (obj != null) { 103 mObjectRowMap.put(obj, rowId); 104 } 105 return obj; 106 } 107 108 @Override 109 public void destroyItem(View container, int position, Object object) { 110 mObjectRowMap.remove(object); 111 112 super.destroyItem(container, position, object); 113 } 114 115 @Override 116 public int getItemPosition(Object object) { 117 final Integer rowId = mObjectRowMap.get(object); 118 if (rowId == null || mItemPosition == null) { 119 return POSITION_NONE; 120 } 121 122 final int position = mItemPosition.get(rowId, POSITION_NONE); 123 return position; 124 } 125 126 /** 127 * @return true if data is valid 128 */ 129 public boolean isDataValid() { 130 return mDataValid; 131 } 132 133 /** 134 * Returns the cursor. 135 */ 136 public Cursor getCursor() { 137 return mCursor; 138 } 139 140 /** 141 * Returns the data item associated with the specified position in the data set. 142 */ 143 public Object getDataItem(int position) { 144 if (mDataValid && moveCursorTo(position)) { 145 return mCursor; 146 } else { 147 return null; 148 } 149 } 150 151 /** 152 * Returns the row id associated with the specified position in the list. 153 */ 154 public long getItemId(int position) { 155 if (mDataValid && moveCursorTo(position)) { 156 return mCursor.getString(mRowIDColumn).hashCode(); 157 } else { 158 return 0; 159 } 160 } 161 162 /** 163 * Swap in a new Cursor, returning the old Cursor. 164 * 165 * @param newCursor The new cursor to be used. 166 * @return Returns the previously set Cursor, or null if there was not one. 167 * If the given new Cursor is the same instance is the previously set 168 * Cursor, null is also returned. 169 */ 170 public Cursor swapCursor(Cursor newCursor) { 171 if (Log.isLoggable(TAG, Log.VERBOSE)) { 172 Log.v(TAG, "swapCursor old=" + (mCursor == null ? -1 : mCursor.getCount()) + 173 "; new=" + (newCursor == null ? -1 : newCursor.getCount())); 174 } 175 176 if (newCursor == mCursor) { 177 return null; 178 } 179 Cursor oldCursor = mCursor; 180 mCursor = newCursor; 181 if (newCursor != null) { 182 mRowIDColumn = newCursor.getColumnIndex(PhotoContract.PhotoViewColumns.URI); 183 mDataValid = true; 184 } else { 185 mRowIDColumn = -1; 186 mDataValid = false; 187 } 188 189 setItemPosition(); 190 notifyDataSetChanged(); // notify the observers about the new cursor 191 return oldCursor; 192 } 193 194 /** 195 * Converts the cursor into a CharSequence. Subclasses should override this 196 * method to convert their results. The default implementation returns an 197 * empty String for null values or the default String representation of 198 * the value. 199 * 200 * @param cursor the cursor to convert to a CharSequence 201 * @return a CharSequence representing the value 202 */ 203 public CharSequence convertToString(Cursor cursor) { 204 return cursor == null ? "" : cursor.toString(); 205 } 206 207 @Override 208 protected String makeFragmentName(int viewId, int index) { 209 if (moveCursorTo(index)) { 210 return "android:pager:" + viewId + ":" + mCursor.getString(mRowIDColumn).hashCode(); 211 } else { 212 return super.makeFragmentName(viewId, index); 213 } 214 } 215 216 /** 217 * Moves the cursor to the given position 218 * 219 * @return {@code true} if the cursor's position was set. Otherwise, {@code false}. 220 */ 221 private boolean moveCursorTo(int position) { 222 if (mCursor != null && !mCursor.isClosed()) { 223 return mCursor.moveToPosition(position); 224 } 225 return false; 226 } 227 228 /** 229 * Initialize the adapter. 230 */ 231 private void init(Context context, Cursor c) { 232 boolean cursorPresent = c != null; 233 mCursor = c; 234 mDataValid = cursorPresent; 235 mContext = context; 236 mRowIDColumn = cursorPresent 237 ? mCursor.getColumnIndex(PhotoContract.PhotoViewColumns.URI) : -1; 238 } 239 240 /** 241 * Sets the {@link #mItemPosition} instance variable with the current mapping of 242 * row id to cursor position. 243 */ 244 private void setItemPosition() { 245 if (!mDataValid || mCursor == null || mCursor.isClosed()) { 246 mItemPosition = null; 247 return; 248 } 249 250 SparseIntArray itemPosition = new SparseIntArray(mCursor.getCount()); 251 252 mCursor.moveToPosition(-1); 253 while (mCursor.moveToNext()) { 254 final int rowId = mCursor.getString(mRowIDColumn).hashCode(); 255 final int position = mCursor.getPosition(); 256 257 itemPosition.append(rowId, position); 258 } 259 mItemPosition = itemPosition; 260 } 261} 262