/* * Copyright (C) 2006 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 org.robolectric.shadows; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.SimpleCursorAdapter; import android.widget.SimpleCursorAdapter.CursorToStringConverter; import android.widget.SimpleCursorAdapter.ViewBinder; import android.widget.TextView; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.RealObject; @Implements(SimpleCursorAdapter.class) public class ShadowSimpleCursorAdapter extends ShadowResourceCursorAdapter { @RealObject private SimpleCursorAdapter realSimpleCursorAdapter; protected int[] mFrom; protected int[] mTo; private int mStringConversionColumn = -1; private CursorToStringConverter mCursorToStringConverter; private ViewBinder mViewBinder; private String[] mOriginalFrom; /** * Constructor. * * @param context The context where the ListView associated with this * SimpleListItemFactory is running * @param layout resource identifier of a layout file that defines the views * for this list item. The layout file should include at least * those named views defined in "to" * @param c The database cursor. Can be null if the cursor is not available yet. * @param from A list of column names representing the data to bind to the UI. Can be null * if the cursor is not available yet. * @param to The views that should display column in the "from" parameter. * These should all be TextViews. The first N views in this list * are given the values of the first N columns in the from * parameter. Can be null if the cursor is not available yet. */ public void __constructor__(Context context, int layout, Cursor c, String[] from, int[] to) { super.__constructor__(context, layout, c); mTo = to; mOriginalFrom = from; findColumns(from); } public void __constructor__(Context context, int layout, Cursor c, String[] from, int[] to, int flags) { this.__constructor__(context, layout, c, from, to); } @Implementation public void bindView(View view, Context context, Cursor cursor) { final ViewBinder binder = mViewBinder; final int count = mTo.length; final int[] from = mFrom; final int[] to = mTo; for (int i = 0; i < count; i++) { final View v = view.findViewById(to[i]); if (v != null) { boolean bound = false; if (binder != null) { bound = binder.setViewValue(v, cursor, from[i]); } if (!bound) { String text = cursor.getString(from[i]); if (text == null) { text = ""; } if (v instanceof TextView) { setViewText((TextView) v, text); } else if (v instanceof ImageView) { setViewImage((ImageView) v, text); } else { throw new IllegalStateException(v.getClass().getName() + " is not a " + " view that can be bounds by this SimpleCursorAdapter"); } } } } } @Implementation public ViewBinder getViewBinder() { return mViewBinder; } @Implementation public void setViewBinder(ViewBinder viewBinder) { mViewBinder = viewBinder; } @Implementation public void setViewImage(ImageView v, String value) { try { v.setImageResource(Integer.parseInt(value)); } catch (NumberFormatException nfe) { v.setImageURI(Uri.parse(value)); } } @Implementation public void setViewText(TextView v, String text) { v.setText(text); } @Implementation public int getStringConversionColumn() { return mStringConversionColumn; } @Implementation public void setStringConversionColumn(int stringConversionColumn) { mStringConversionColumn = stringConversionColumn; } @Implementation public CursorToStringConverter getCursorToStringConverter() { return mCursorToStringConverter; } @Implementation public void setCursorToStringConverter(CursorToStringConverter cursorToStringConverter) { mCursorToStringConverter = cursorToStringConverter; } @Implementation public CharSequence convertToString(Cursor cursor) { if (mCursorToStringConverter != null) { return mCursorToStringConverter.convertToString(cursor); } else if (mStringConversionColumn > -1) { return cursor.getString(mStringConversionColumn); } return realSimpleCursorAdapter.convertToString(cursor); } /** * Create a map from an array of strings to an array of column-id integers in mCursor. * If mCursor is null, the array will be discarded. * * @param from the Strings naming the columns of interest */ private void findColumns(String[] from) { if (mCursor != null) { findColumnsFromCursor(mCursor, from); } else { mFrom = null; } } private void findColumnsFromCursor(Cursor c, String[] from) { // By convention, calling LoaderManager.LoaderCallbacks#onLoaderReset will swap // the current cursor for null. In that case, the current mapping is removed. if(c != null) { int i; int count = from.length; if (mFrom == null || mFrom.length != count) { mFrom = new int[count]; } for (i = 0; i < count; i++) { mFrom[i] = c.getColumnIndexOrThrow(from[i]); } } else { mFrom = null; } } @Implementation public Cursor swapCursor(Cursor c) { // super.swapCursor() will notify observers, so make sure we have a mapping before // this happens findColumnsFromCursor(c, mOriginalFrom); return super.swapCursor(c); } @Implementation public void changeCursor(Cursor c) { findColumnsFromCursor(c, mOriginalFrom); super.changeCursor(c); } @Implementation public void changeCursorAndColumns(Cursor c, String[] from, int[] to) { mOriginalFrom = from; mTo = to; realSimpleCursorAdapter.changeCursor(c); findColumns(mOriginalFrom); } @Implementation public View getView(int position, View convertView, ViewGroup parent) { if (!mDataValid) { throw new IllegalStateException("this should only be called when the cursor is valid"); } if (!mCursor.moveToPosition(position)) { throw new IllegalStateException("couldn't move cursor to position " + position); } View v; if (convertView == null) { v = newView(mContext, mCursor, parent); } else { v = convertView; } bindView(v, mContext, mCursor); return v; } @Implementation public View getDropDownView(int position, View convertView, ViewGroup parent) { if (mDataValid) { mCursor.moveToPosition(position); View v; if (convertView == null) { v = newDropDownView(mContext, mCursor, parent); } else { v = convertView; } bindView(v, mContext, mCursor); return v; } else { return null; } } }