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