Yarrn/src/main/java/de/vanmar/android/yarrn/components/ImageDialog.java
public class ImageDialog extends Dialog {
    private String imageUrl;

    public ImageDialog(Context context, String imageUrl) {
        super(context, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        this.imageUrl = imageUrl;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.image_dialog);
        WebView webView = (WebView) findViewById(R.id.webview);

        webView.getSettings().setBuiltInZoomControls(true);
        webView.getSettings().setUseWideViewPort(true);
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
            webView.getSettings().setDisplayZoomControls(false);
        }

        webView.loadUrl(imageUrl);
        webView.setBackgroundColor(Color.BLACK);
    }

}
Yarrn/src/main/java/de/vanmar/android/yarrn/projects/PhotoAdapter.java
public class PhotoAdapter extends ArrayAdapter<Photo> {

    interface PhotoAdapterListener {
        void onMoveLeft(int position);

        void onMoveAllLeft(int position);

        void onMoveRight(int position);

        void onMoveAllRight(int position);
    }

    private boolean editable = false;
    private PhotoAdapterListener listener;

    private class ViewHolder {
        private ImageView photo;
        private View edit_overlay;
        private ImageButton move_left;
        private ImageButton move_all_left;
        private ImageButton move_right;
        private ImageButton move_all_right;
    }

    private final Activity context;

    public PhotoAdapter(final Activity context) {
        super(context, 0);
        this.context = context;
    }

    public void setItems(Collection<? extends Photo> collection) {
        clear();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            super.addAll(collection);
        } else {
            setNotifyOnChange(false);
            clear();
            for (Photo photo : collection) {
                add(photo);
            }
            // this sets notifyOnChange to true, regardless of former state
            notifyDataSetChanged();
        }
    }

    public void setEditable(boolean editable) {
        this.editable = editable;
        notifyDataSetChanged();
    }

    public void setPhotoAdapterListener(PhotoAdapterListener listener) {
        this.listener = listener;
    }

    public PhotoAdapterListener getPhotoAdapterListener() {
        return listener;
    }

    @Override
    public View getView(final int position, final View convertView,
                        final ViewGroup parent) {
        final View view;
        final ViewHolder holder;
        if (convertView != null) {
            view = convertView;
            holder = (ViewHolder) view.getTag();
        } else {
            view = context.getLayoutInflater().inflate(
                    R.layout.photogallery_item, parent, false);
            holder = new ViewHolder();
            holder.photo = (ImageView) view.findViewById(R.id.photo);
            holder.edit_overlay = view.findViewById(R.id.edit_overlay);
            holder.move_left = (ImageButton) view.findViewById(R.id.move_left);
            holder.move_all_left = (ImageButton) view.findViewById(R.id.move_all_left);
            holder.move_right = (ImageButton) view.findViewById(R.id.move_right);
            holder.move_all_right = (ImageButton) view.findViewById(R.id.move_all_right);
            view.setTag(holder);
        }
        final Photo photo = getItem(position);
        new AQuery(view).id(holder.photo).image(photo.getSquareUrl());
        holder.edit_overlay.setVisibility(editable ? View.VISIBLE : View.GONE);
        holder.move_left.setVisibility(position > 0 ? View.VISIBLE : View.GONE);
        holder.move_all_left.setVisibility(position > 0 ? View.VISIBLE : View.GONE);
        holder.move_right.setVisibility(position < getCount() - 1 ? View.VISIBLE : View.GONE);
        holder.move_all_right.setVisibility(position < getCount() - 1 ? View.VISIBLE : View.GONE);
        holder.move_left.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (listener != null) {
                    listener.onMoveLeft(position);
                }
            }
        });
        holder.move_all_left.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (listener != null) {
                    listener.onMoveAllLeft(position);
                }
            }
        });
        holder.move_right.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (listener != null) {
                    listener.onMoveRight(position);
                }
            }
        });
        holder.move_all_right.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (listener != null) {
                    listener.onMoveAllRight(position);
                }
            }
        });

        return view;
    }
}
Yarrn/src/main/java/de/vanmar/android/yarrn/ravelry/dts/Needle.java
public class Needle {

    @SerializedName("name")
    public String name;
}
Yarrn/src/main/java/de/vanmar/android/yarrn/ravelry/dts/QueuedProjectResult.java
public class QueuedProjectResult implements ETaggable {

    @SerializedName("queued_project")
    public QueuedProject queuedProject;
    private String etag;

    @Override
    public void setETag(String etag) {
        this.etag = etag;
    }

    @Override
    public String getETag() {
        return this.etag;
    }
}
Yarrn/src/main/java/de/vanmar/android/yarrn/ravelry/dts/BookmarkShort.java
public class BookmarkShort {

    public static final String PROJECT = "project";
    public static final String PATTERN = "pattern";

    @SerializedName("id")
    public String id;
    @SerializedName("comment")
    public String comment;
    @SerializedName("type")
    public String type;
    @SerializedName("tag_names")
    public List<String> tags;
    @SerializedName("favorited")
    public Favorite favorite;
}
Yarrn/src/main/java/de/vanmar/android/yarrn/requests/AddFavoriteRequest.java
public class AddFavoriteRequest extends AbstractRavelryRequest<BookmarkShort> {
    private JsonObject updateData;

    public AddFavoriteRequest(YarrnPrefs_ prefs, Application application, JsonObject updateData) {
        super(BookmarkShort.class, prefs, application);
        this.updateData = updateData;
    }

    public JsonObject getUpdateData() {
        return updateData;
    }

    @Override
    public BookmarkShort loadDataFromNetwork() throws Exception {
        final OAuthRequest request = new OAuthRequest(Verb.POST,
                application.getString(R.string.ravelry_url)
                        + String.format("/people/%s/favorites/create.json",
                        prefs.username().get())
        );
        request.addBodyParameter("data", updateData.toString());
        Response response = executeRequest(request);
        return new Gson().fromJson(response.getBody(), BookmarkShort.class);
    }
}
Yarrn/src/main/java/de/vanmar/android/yarrn/components/AddEditFavoriteDialog.java
public class AddEditFavoriteDialog extends Dialog {

    private BookmarkShort favorite;

    public interface AddEditFavoriteDialogListener {
        void onSave(String comment, String tags);
    }

    private AddEditFavoriteDialogListener listener;
    private final YarrnPrefs_ prefs;

    public AddEditFavoriteDialog(Context context, AddEditFavoriteDialogListener listener, YarrnPrefs_ prefs) {
        super(context);
        this.listener = listener;
        this.prefs = prefs;
        setTitle(R.string.add_favorite_title);
    }

    public AddEditFavoriteDialog(Context context, AddEditFavoriteDialogListener listener, YarrnPrefs_ prefs, BookmarkShort favorite) {
        this(context, listener, prefs);
        this.favorite = favorite;
        setTitle(R.string.edit_favorite_title);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.add_edit_favorite_dialog);
        initFields();

        ((Button) findViewById(R.id.button_cancel)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
            }
        });

        ((Button) findViewById(R.id.button_save)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String comment = ((EditText) findViewById(R.id.add_favorite_comment)).getText().toString();
                String tags = ((EditText) findViewById(R.id.add_favorite_tags)).getText().toString();
                listener.onSave(comment, tags);
                dismiss();
            }
        });
    }

    private void initFields() {
        if (favorite != null) {
            ((EditText) findViewById(R.id.add_favorite_comment)).setText(favorite.comment);
            ((EditText) findViewById(R.id.add_favorite_tags)).setText(StringUtils.join(favorite.tags, ' '));
        }
    }
}
Yarrn/src/main/java/de/vanmar/android/yarrn/ravelry/dts/QueuedProject.java
public class QueuedProject {
    public int id;
    public String name;
    @SerializedName("pattern_name")
    public String patternName;
    @SerializedName("pattern")
    public Pattern pattern;
    @SerializedName("notes")
    public String notes;
    @SerializedName("created_at")
    public Date created;
}
Yarrn/src/main/java/de/vanmar/android/yarrn/ravelry/RavelryResultListener.java
public abstract class RavelryResultListener<T> implements RequestListener<T> {

    private final IRavelryActivity activity;

    protected RavelryResultListener(IRavelryActivity activity) {
        this.activity = activity;
    }

    @Override
    public void onRequestFailure(SpiceException spiceException) {
        if (spiceException.getCause() instanceof RavelryException) {
            RavelryException ravelryException = (RavelryException) spiceException.getCause();
            if (ravelryException.getStatusCode() == 401 || ravelryException.getStatusCode() == 403) {
                activity.requestToken();
                return;
            }
        }
        AQUtility.report(spiceException);
    }
}
Yarrn/src/main/java/de/vanmar/android/yarrn/requests/GetQueuedProjectRequest.java
public class GetQueuedProjectRequest extends AbstractRavelryGetRequest<QueuedProjectResult> {

    private final int projectId;
    private String username;

    public GetQueuedProjectRequest(Application application, YarrnPrefs_ prefs, int projectId, String username) {
        super(QueuedProjectResult.class, application, prefs);
        this.projectId = projectId;
        this.username = username;
    }

    protected QueuedProjectResult parseResult(String responseBody) {
        return new GsonBuilder().setDateFormat("yyyy/MM/dd").create().fromJson(responseBody, QueuedProjectResult.class);
    }

    protected OAuthRequest getRequest() {
        return new OAuthRequest(Verb.GET, String.format(
                application.getString(R.string.ravelry_url) + "/people/%s/queue/%s.json", username, projectId));
    }
}
Yarrn/src/main/java/de/vanmar/android/yarrn/ravelry/dts/Pattern.java
public class Pattern {
    public int id;
    public String name;
    @SerializedName("photos")
    public List<Photo> photos = new LinkedList<Photo>();
    @SerializedName("pattern_author")
    public PatternAuthor patternAuthor;
    @SerializedName("notes_html")
    public String notes_html;
    @SerializedName("url")
    public String url;
    @SerializedName("yarn_weight_description")
    public String yarn_weight_description;
    @SerializedName("yardage_description")
    public String yardage_description;
    @SerializedName("gauge_description")
    public String gauge_description;
    @SerializedName("pattern_needle_sizes")
    public List<Needle> pattern_needle_sizes;
}
Yarrn/src/main/java/com/meetme/android/horizontallistview/HorizontalListView.java
public class HorizontalListView extends AdapterView<ListAdapter> {
    /**
     * Defines where to insert items into the ViewGroup, as defined in
     * {@code ViewGroup #addViewInLayout(View, int, LayoutParams, boolean)}
     */
    private static final int INSERT_AT_END_OF_LIST = -1;
    private static final int INSERT_AT_START_OF_LIST = 0;

    /**
     * The velocity to use for overscroll absorption
     */
    private static final float FLING_DEFAULT_ABSORB_VELOCITY = 30f;

    /**
     * The friction amount to use for the fling tracker
     */
    private static final float FLING_FRICTION = 0.009f;

    /**
     * Used for tracking the state data necessary to restore the
     * HorizontalListView to its previous state after a rotation occurs
     */
    private static final String BUNDLE_ID_CURRENT_X = "BUNDLE_ID_CURRENT_X";

    /**
     * The bundle id of the parents state. Used to restore the parent's state
     * after a rotation occurs
     */
    private static final String BUNDLE_ID_PARENT_STATE = "BUNDLE_ID_PARENT_STATE";

    /**
     * Tracks ongoing flings
     */
    protected Scroller mFlingTracker = new Scroller(getContext());

    /**
     * Gesture listener to receive callbacks when gestures are detected
     */
    private final GestureListener mGestureListener = new GestureListener();

    /**
     * Used for detecting gestures within this view so they can be handled
     */
    private final GestureDetector mGestureDetector;

    /**
     * This tracks the starting layout position of the leftmost view
     */
    private int mDisplayOffset;

    /**
     * Holds a reference to the adapter bound to this view
     */
    protected ListAdapter mAdapter;

    /**
     * Holds a cache of recycled views to be reused as needed
     */
    private final List<Queue<View>> mRemovedViewsCache = new ArrayList<Queue<View>>();

    /**
     * Flag used to mark when the adapters data has changed, so the view can be
     * relaid out
     */
    private boolean mDataChanged = false;

    /**
     * Temporary rectangle to be used for measurements
     */
    private final Rect mRect = new Rect();

    /**
     * Tracks the currently touched view, used to delegate touches to the view
     * being touched
     */
    private View mViewBeingTouched = null;

    /**
     * The width of the divider that will be used between list items
     */
    private int mDividerWidth = 0;

    /**
     * The drawable that will be used as the list divider
     */
    private Drawable mDivider = null;

    /**
     * The x position of the currently rendered view
     */
    protected int mCurrentX;

    /**
     * The x position of the next to be rendered view
     */
    protected int mNextX;

    /**
     * Used to hold the scroll position to restore to post rotate
     */
    private Integer mRestoreX = null;

    /**
     * Tracks the maximum possible X position, stays at max value until last
     * item is laid out and it can be determined
     */
    private int mMaxX = Integer.MAX_VALUE;

    /**
     * The adapter index of the leftmost view currently visible
     */
    private int mLeftViewAdapterIndex;

    /**
     * The adapter index of the rightmost view currently visible
     */
    private int mRightViewAdapterIndex;

    /**
     * This tracks the currently selected accessibility item
     */
    private int mCurrentlySelectedAdapterIndex;

    /**
     * Callback interface to notify listener that the user has scrolled this
     * view to the point that it is low on data.
     */
    private RunningOutOfDataListener mRunningOutOfDataListener = null;

    /**
     * This tracks the user value set of how many items from the end will be
     * considered running out of data.
     */
    private int mRunningOutOfDataThreshold = 0;

    /**
     * Tracks if we have told the listener that we are running low on data. We
     * only want to tell them once.
     */
    private boolean mHasNotifiedRunningLowOnData = false;

    /**
     * Callback interface to be invoked when the scroll state has changed.
     */
    private OnScrollStateChangedListener mOnScrollStateChangedListener = null;

    /**
     * Represents the current scroll state of this view. Needed so we can detect
     * when the state changes so scroll listener can be notified.
     */
    private OnScrollStateChangedListener.ScrollState mCurrentScrollState = OnScrollStateChangedListener.ScrollState.SCROLL_STATE_IDLE;

    /**
     * Tracks the state of the left edge glow.
     */
    private final EdgeEffectCompat mEdgeGlowLeft;

    /**
     * Tracks the state of the right edge glow.
     */
    private final EdgeEffectCompat mEdgeGlowRight;

    /**
     * The height measure spec for this view, used to help size children views
     */
    private int mHeightMeasureSpec;

    /**
     * Used to track if a view touch should be blocked because it stopped a
     * fling
     */
    private boolean mBlockTouchAction = false;

    /**
     * Used to track if the parent vertically scrollable view has been told to
     * DisallowInterceptTouchEvent
     */
    private boolean mIsParentVerticiallyScrollableViewDisallowingInterceptTouchEvent = false;

    /**
     * The listener that receives notifications when this view is clicked.
     */
    private OnClickListener mOnClickListener;

    public HorizontalListView(final Context context, final AttributeSet attrs) {
        super(context, attrs);
        mEdgeGlowLeft = new EdgeEffectCompat(context);
        mEdgeGlowRight = new EdgeEffectCompat(context);
        mGestureDetector = new GestureDetector(context, mGestureListener);
        bindGestureDetector();
        initView();
        retrieveXmlConfiguration(context, attrs);
        setWillNotDraw(false);

        // If the OS version is high enough then set the friction on the fling
        // tracker */
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            HoneycombPlus.setFriction(mFlingTracker, FLING_FRICTION);
        }
    }

    /**
     * Registers the gesture detector to receive gesture notifications for this
     * view
     */
    private void bindGestureDetector() {
        // Generic touch listener that can be applied to any view that needs to
        // process gestures
        final View.OnTouchListener gestureListenerHandler = new View.OnTouchListener() {
            @Override
            public boolean onTouch(final View v, final MotionEvent event) {
                // Delegate the touch event to our gesture detector
                return mGestureDetector.onTouchEvent(event);
            }
        };

        setOnTouchListener(gestureListenerHandler);
    }

    /**
     * When this HorizontalListView is embedded within a vertical scrolling view
     * it is important to disable the parent view from interacting with any
     * touch events while the user is scrolling within this HorizontalListView.
     * This will start at this view and go up the view tree looking for a
     * vertical scrolling view. If one is found it will enable or disable parent
     * touch interception.
     *
     * @param disallowIntercept If true the parent will be prevented from intercepting child
     *                          touch events
     */
    private void requestParentListViewToNotInterceptTouchEvents(
            final Boolean disallowIntercept) {
        // Prevent calling this more than once needlessly
        if (mIsParentVerticiallyScrollableViewDisallowingInterceptTouchEvent != disallowIntercept) {
            View view = this;

            while (view.getParent() instanceof View) {
                // If the parent is a ListView or ScrollView then disallow
                // intercepting of touch events
                if (view.getParent() instanceof ListView
                        || view.getParent() instanceof ScrollView) {
                    view.getParent().requestDisallowInterceptTouchEvent(
                            disallowIntercept);
                    mIsParentVerticiallyScrollableViewDisallowingInterceptTouchEvent = disallowIntercept;
                    return;
                }

                view = (View) view.getParent();
            }
        }
    }

    /**
     * Parse the XML configuration for this widget
     *
     * @param context Context used for extracting attributes
     * @param attrs   The Attribute Set containing the ColumnView attributes
     */
    private void retrieveXmlConfiguration(final Context context,
                                          final AttributeSet attrs) {
        if (attrs != null) {
            final TypedArray a = context.obtainStyledAttributes(attrs,
                    R.styleable.HorizontalListView);

            // Get the provided drawable from the XML
            final Drawable d = a
                    .getDrawable(R.styleable.HorizontalListView_android_divider);
            if (d != null) {
                // If a drawable is provided to use as the divider then use its
                // intrinsic width for the divider width
                setDivider(d);
            }

            // If a width is explicitly specified then use that width
            final int dividerWidth = a.getDimensionPixelSize(
                    R.styleable.HorizontalListView_dividerWidth, 0);
            if (dividerWidth != 0) {
                setDividerWidth(dividerWidth);
            }

            a.recycle();
        }
    }

    @Override
    public Parcelable onSaveInstanceState() {
        final Bundle bundle = new Bundle();

        // Add the parent state to the bundle
        bundle.putParcelable(BUNDLE_ID_PARENT_STATE,
                super.onSaveInstanceState());

        // Add our state to the bundle
        bundle.putInt(BUNDLE_ID_CURRENT_X, mCurrentX);

        return bundle;
    }

    @Override
    public void onRestoreInstanceState(final Parcelable state) {
        if (state instanceof Bundle) {
            final Bundle bundle = (Bundle) state;

            // Restore our state from the bundle
            mRestoreX = Integer.valueOf((bundle.getInt(BUNDLE_ID_CURRENT_X)));

            // Restore out parent's state from the bundle
            super.onRestoreInstanceState(bundle
                    .getParcelable(BUNDLE_ID_PARENT_STATE));
        }
    }

    /**
     * Sets the drawable that will be drawn between each item in the list. If
     * the drawable does not have an intrinsic width, you should also call
     * {@link #setDividerWidth(int)}
     *
     * @param divider The drawable to use.
     */
    public void setDivider(final Drawable divider) {
        mDivider = divider;

        if (divider != null) {
            setDividerWidth(divider.getIntrinsicWidth());
        } else {
            setDividerWidth(0);
        }
    }

    /**
     * Sets the width of the divider that will be drawn between each item in the
     * list. Calling this will override the intrinsic width as set by
     * {@link #setDivider(Drawable)}
     *
     * @param width The width of the divider in pixels.
     */
    public void setDividerWidth(final int width) {
        mDividerWidth = width;

        // Force the view to rerender itself
        requestLayout();
        invalidate();
    }

    private void initView() {
        mLeftViewAdapterIndex = -1;
        mRightViewAdapterIndex = -1;
        mDisplayOffset = 0;
        mCurrentX = 0;
        mNextX = 0;
        mMaxX = Integer.MAX_VALUE;
        setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_IDLE);
    }

    /**
     * Will re-initialize the HorizontalListView to remove all child views
     * rendered and reset to initial configuration.
     */
    private void reset() {
        initView();
        removeAllViewsInLayout();
        requestLayout();
    }

    /**
     * DataSetObserver used to capture adapter data change events
     */
    private final DataSetObserver mAdapterDataObserver = new DataSetObserver() {
        @Override
        public void onChanged() {
            mDataChanged = true;

            // Clear so we can notify again as we run out of data
            mHasNotifiedRunningLowOnData = false;

            unpressTouchedChild();

            // Invalidate and request layout to force this view to completely
            // redraw itself
            invalidate();
            requestLayout();
        }

        @Override
        public void onInvalidated() {
            // Clear so we can notify again as we run out of data
            mHasNotifiedRunningLowOnData = false;

            unpressTouchedChild();
            reset();

            // Invalidate and request layout to force this view to completely
            // redraw itself
            invalidate();
            requestLayout();
        }
    };

    @Override
    public void setSelection(final int position) {
        mCurrentlySelectedAdapterIndex = position;
    }

    @Override
    public View getSelectedView() {
        return getChild(mCurrentlySelectedAdapterIndex);
    }

    @Override
    public void setAdapter(final ListAdapter adapter) {
        if (mAdapter != null) {
            mAdapter.unregisterDataSetObserver(mAdapterDataObserver);
        }

        if (adapter != null) {
            // Clear so we can notify again as we run out of data
            mHasNotifiedRunningLowOnData = false;

            mAdapter = adapter;
            mAdapter.registerDataSetObserver(mAdapterDataObserver);
        }

        initializeRecycledViewCache(mAdapter.getViewTypeCount());
        reset();
    }

    @Override
    public ListAdapter getAdapter() {
        return mAdapter;
    }

    /**
     * Will create and initialize a cache for the given number of different
     * types of views.
     *
     * @param viewTypeCount - The total number of different views supported
     */
    private void initializeRecycledViewCache(final int viewTypeCount) {
        // The cache is created such that the response from
        // mAdapter.getItemViewType is the array index to the correct cache for
        // that item.
        mRemovedViewsCache.clear();
        for (int i = 0; i < viewTypeCount; i++) {
            mRemovedViewsCache.add(new LinkedList<View>());
        }
    }

    /**
     * Returns a recycled view from the cache that can be reused, or null if one
     * is not available.
     *
     * @param adapterIndex
     * @return
     */
    private View getRecycledView(final int adapterIndex) {
        final int itemViewType = mAdapter.getItemViewType(adapterIndex);

        if (isItemViewTypeValid(itemViewType)) {
            return mRemovedViewsCache.get(itemViewType).poll();
        }

        return null;
    }

    /**
     * Adds the provided view to a recycled views cache.
     *
     * @param adapterIndex
     * @param view
     */
    private void recycleView(final int adapterIndex, final View view) {
        // There is one Queue of views for each different type of view.
        // Just add the view to the pile of other views of the same type.
        // The order they are added and removed does not matter.
        final int itemViewType = mAdapter.getItemViewType(adapterIndex);
        if (isItemViewTypeValid(itemViewType)) {
            mRemovedViewsCache.get(itemViewType).offer(view);
        }
    }

    private boolean isItemViewTypeValid(final int itemViewType) {
        return itemViewType < mRemovedViewsCache.size();
    }

    /**
     * Adds a child to this viewgroup and measures it so it renders the correct
     * size
     */
    private void addAndMeasureChild(final View child, final int viewPos) {
        final LayoutParams params = getLayoutParams(child);
        addViewInLayout(child, viewPos, params, true);
        measureChild(child);
    }

    /**
     * Measure the provided child.
     *
     * @param child The child.
     */
    private void measureChild(final View child) {
        final ViewGroup.LayoutParams childLayoutParams = getLayoutParams(child);
        final int childHeightSpec = ViewGroup.getChildMeasureSpec(
                mHeightMeasureSpec, getPaddingTop() + getPaddingBottom(),
                childLayoutParams.height);

        int childWidthSpec;
        if (childLayoutParams.width > 0) {
            childWidthSpec = MeasureSpec.makeMeasureSpec(
                    childLayoutParams.width, MeasureSpec.EXACTLY);
        } else {
            childWidthSpec = MeasureSpec.makeMeasureSpec(0,
                    MeasureSpec.UNSPECIFIED);
        }

        child.measure(childWidthSpec, childHeightSpec);
    }

    /**
     * Gets a child's layout parameters, defaults if not available.
     */
    private ViewGroup.LayoutParams getLayoutParams(final View child) {
        ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
        if (layoutParams == null) {
            // Since this is a horizontal list view default to matching the
            // parents height, and wrapping the width
            layoutParams = new ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.WRAP_CONTENT,
                    ViewGroup.LayoutParams.MATCH_PARENT);
        }

        return layoutParams;
    }

    @SuppressLint("WrongCall")
    @Override
    protected void onLayout(final boolean changed, final int left,
                            final int top, final int right, final int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        if (mAdapter == null) {
            return;
        }

        // Force the OS to redraw this view
        invalidate();

        // If the data changed then reset everything and render from scratch at
        // the same offset as last time
        if (mDataChanged) {
            final int oldCurrentX = mCurrentX;
            initView();
            removeAllViewsInLayout();
            mNextX = oldCurrentX;
            mDataChanged = false;
        }

        // If restoring from a rotation
        if (mRestoreX != null) {
            mNextX = mRestoreX;
            mRestoreX = null;
        }

        // If in a fling
        if (mFlingTracker.computeScrollOffset()) {
            // Compute the next position
            mNextX = mFlingTracker.getCurrX();
        }

        // Prevent scrolling past 0 so you can't scroll past the end of the list
        // to the left
        if (mNextX < 0) {
            mNextX = 0;

            // Show an edge effect absorbing the current velocity
            if (mEdgeGlowLeft.isFinished()) {
                mEdgeGlowLeft.onAbsorb((int) determineFlingAbsorbVelocity());
            }

            mFlingTracker.forceFinished(true);
            setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_IDLE);
        } else if (mNextX > mMaxX) {
            // Clip the maximum scroll position at mMaxX so you can't scroll
            // past the end of the list to the right
            mNextX = mMaxX;

            // Show an edge effect absorbing the current velocity
            if (mEdgeGlowRight.isFinished()) {
                mEdgeGlowRight.onAbsorb((int) determineFlingAbsorbVelocity());
            }

            mFlingTracker.forceFinished(true);
            setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_IDLE);
        }

        // Calculate our delta from the last time the view was drawn
        final int dx = mCurrentX - mNextX;
        removeNonVisibleChildren(dx);
        fillList(dx);
        positionChildren(dx);

        // Since the view has now been drawn, update our current position
        mCurrentX = mNextX;

        // If we have scrolled enough to lay out all views, then determine the
        // maximum scroll position now
        if (determineMaxX()) {
            // Redo the layout pass since we now know the maximum scroll
            // position
            onLayout(changed, left, top, right, bottom);
            return;
        }

        // If the fling has finished
        if (mFlingTracker.isFinished()) {
            // If the fling just ended
            if (mCurrentScrollState == OnScrollStateChangedListener.ScrollState.SCROLL_STATE_FLING) {
                setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_IDLE);
            }
        } else {
            // Still in a fling so schedule the next frame
            postDelayed(mDelayedLayout, 10L);
        }
    }

    @Override
    protected float getLeftFadingEdgeStrength() {
        final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();

        // If completely at the edge then disable the fading edge
        if (mCurrentX == 0) {
            return 0;
        } else if (mCurrentX < horizontalFadingEdgeLength) {
            // We are very close to the edge, so enable the fading edge
            // proportional to the distance from the edge, and the width of the
            // edge effect
            return (float) mCurrentX / horizontalFadingEdgeLength;
        } else {
            // The current x position is more then the width of the fading edge
            // so enable it fully.
            return 1;
        }
    }

    @Override
    protected float getRightFadingEdgeStrength() {
        final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();

        // If completely at the edge then disable the fading edge
        if (mCurrentX == mMaxX) {
            return 0;
        } else if ((mMaxX - mCurrentX) < horizontalFadingEdgeLength) {
            // We are very close to the edge, so enable the fading edge
            // proportional to the distance from the ednge, and the width of the
            // edge effect
            return (float) (mMaxX - mCurrentX) / horizontalFadingEdgeLength;
        } else {
            // The distance from the maximum x position is more then the width
            // of the fading edge so enable it fully.
            return 1;
        }
    }

    /**
     * Determines the current fling absorb velocity
     */
    private float determineFlingAbsorbVelocity() {
        // If the OS version is high enough get the real velocity */
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            return IceCreamSandwichPlus.getCurrVelocity(mFlingTracker);
        } else {
            // Unable to get the velocity so just return a default.
            // In actuality this is never used since EdgeEffectCompat does not
            // draw anything unless the device is ICS+.
            // Less then ICS EdgeEffectCompat essentially performs a NOP.
            return FLING_DEFAULT_ABSORB_VELOCITY;
        }
    }

    /**
     * Use to schedule a request layout via a runnable
     */
    private final Runnable mDelayedLayout = new Runnable() {
        @Override
        public void run() {
            requestLayout();
        }
    };

    @Override
    protected void onMeasure(final int widthMeasureSpec,
                             final int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        // Cache off the measure spec
        mHeightMeasureSpec = heightMeasureSpec;
    }

    ;

    /**
     * Determine the Max X position. This is the farthest that the user can
     * scroll the screen. Until the last adapter item has been laid out it is
     * impossible to calculate; once that has occurred this will perform the
     * calculation, and if necessary force a redraw and relayout of this view.
     *
     * @return true if the maxx position was just determined
     */
    private boolean determineMaxX() {
        // If the last view has been laid out, then we can determine the maximum
        // x position
        if (isLastItemInAdapter(mRightViewAdapterIndex)) {
            final View rightView = getRightmostChild();

            if (rightView != null) {
                final int oldMaxX = mMaxX;

                // Determine the maximum x position
                mMaxX = mCurrentX + (rightView.getRight() - getPaddingLeft())
                        - getRenderWidth();

                // Handle the case where the views do not fill at least 1 screen
                if (mMaxX < 0) {
                    mMaxX = 0;
                }

                if (mMaxX != oldMaxX) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Adds children views to the left and right of the current views until the
     * screen is full
     */
    private void fillList(final int dx) {
        // Get the rightmost child and determine its right edge
        int edge = 0;
        View child = getRightmostChild();
        if (child != null) {
            edge = child.getRight();
        }

        // Add new children views to the right, until past the edge of the
        // screen
        fillListRight(edge, dx);

        // Get the leftmost child and determine its left edge
        edge = 0;
        child = getLeftmostChild();
        if (child != null) {
            edge = child.getLeft();
        }

        // Add new children views to the left, until past the edge of the screen
        fillListLeft(edge, dx);
    }

    private void removeNonVisibleChildren(final int dx) {
        View child = getLeftmostChild();

        // Loop removing the leftmost child, until that child is on the screen
        while (child != null && child.getRight() + dx <= 0) {
            // The child is being completely removed so remove its width from
            // the display offset and its divider if it has one.
            // To remove add the size of the child and its divider (if it has
            // one) to the offset.
            // You need to add since its being removed from the left side, i.e.
            // shifting the offset to the right.
            mDisplayOffset += isLastItemInAdapter(mLeftViewAdapterIndex) ? child
                    .getMeasuredWidth() : mDividerWidth
                    + child.getMeasuredWidth();

            // Add the removed view to the cache
            recycleView(mLeftViewAdapterIndex, child);

            // Actually remove the view
            removeViewInLayout(child);

            // Keep track of the adapter index of the left most child
            mLeftViewAdapterIndex++;

            // Get the new leftmost child
            child = getLeftmostChild();
        }

        child = getRightmostChild();

        // Loop removing the rightmost child, until that child is on the screen
        while (child != null && child.getLeft() + dx >= getWidth()) {
            recycleView(mRightViewAdapterIndex, child);
            removeViewInLayout(child);
            mRightViewAdapterIndex--;
            child = getRightmostChild();
        }
    }

    private void fillListRight(int rightEdge, final int dx) {
        // Loop adding views to the right until the screen is filled
        while (rightEdge + dx + mDividerWidth < getWidth()
                && mRightViewAdapterIndex + 1 < mAdapter.getCount()) {
            mRightViewAdapterIndex++;

            // If mLeftViewAdapterIndex < 0 then this is the first time a view
            // is being added, and left == right
            if (mLeftViewAdapterIndex < 0) {
                mLeftViewAdapterIndex = mRightViewAdapterIndex;
            }

            // Get the view from the adapter, utilizing a cached view if one is
            // available
            final View child = mAdapter.getView(mRightViewAdapterIndex,
                    getRecycledView(mRightViewAdapterIndex), this);
            addAndMeasureChild(child, INSERT_AT_END_OF_LIST);

            // If first view, then no divider to the left of it, otherwise add
            // the space for the divider width
            rightEdge += (mRightViewAdapterIndex == 0 ? 0 : mDividerWidth)
                    + child.getMeasuredWidth();

            // Check if we are running low on data so we can tell listeners to
            // go get more
            determineIfLowOnData();
        }
    }

    private void fillListLeft(int leftEdge, final int dx) {
        // Loop adding views to the left until the screen is filled
        while (leftEdge + dx - mDividerWidth > 0 && mLeftViewAdapterIndex >= 1) {
            mLeftViewAdapterIndex--;
            final View child = mAdapter.getView(mLeftViewAdapterIndex,
                    getRecycledView(mLeftViewAdapterIndex), this);
            addAndMeasureChild(child, INSERT_AT_START_OF_LIST);

            // If first view, then no divider to the left of it
            leftEdge -= mLeftViewAdapterIndex == 0 ? child.getMeasuredWidth()
                    : mDividerWidth + child.getMeasuredWidth();

            // If on a clean edge then just remove the child, otherwise remove
            // the divider as well
            mDisplayOffset -= leftEdge + dx == 0 ? child.getMeasuredWidth()
                    : mDividerWidth + child.getMeasuredWidth();
        }
    }

    /**
     * Loops through each child and positions them onto the screen
     */
    private void positionChildren(final int dx) {
        final int childCount = getChildCount();

        if (childCount > 0) {
            mDisplayOffset += dx;
            int leftOffset = mDisplayOffset;

            // Loop each child view
            for (int i = 0; i < childCount; i++) {
                final View child = getChildAt(i);
                final int left = leftOffset + getPaddingLeft();
                final int top = getPaddingTop();
                final int right = left + child.getMeasuredWidth();
                final int bottom = top + child.getMeasuredHeight();

                // Layout the child
                child.layout(left, top, right, bottom);

                // Increment our offset by added child's size and divider width
                leftOffset += child.getMeasuredWidth() + mDividerWidth;
            }
        }
    }

    /**
     * Gets the current child that is leftmost on the screen.
     */
    private View getLeftmostChild() {
        return getChildAt(0);
    }

    /**
     * Gets the current child that is rightmost on the screen.
     */
    private View getRightmostChild() {
        return getChildAt(getChildCount() - 1);
    }

    /**
     * Finds a child view that is contained within this view, given the adapter
     * index.
     *
     * @return View The child view, or or null if not found.
     */
    private View getChild(final int adapterIndex) {
        if (adapterIndex >= mLeftViewAdapterIndex
                && adapterIndex <= mRightViewAdapterIndex) {
            getChildAt(adapterIndex - mLeftViewAdapterIndex);
        }

        return null;
    }

    /**
     * Returns the index of the child that contains the coordinates given. This
     * is useful to determine which child has been touched. This can be used for
     * a call to {@link #getChildAt(int)}
     *
     * @param x X-coordinate
     * @param y Y-coordinate
     * @return The index of the child that contains the coordinates. If no child
     * is found then returns -1
     */
    private int getChildIndex(final int x, final int y) {
        final int childCount = getChildCount();

        for (int index = 0; index < childCount; index++) {
            getChildAt(index).getHitRect(mRect);
            if (mRect.contains(x, y)) {
                return index;
            }
        }

        return -1;
    }

    /**
     * Simple convenience method for determining if this index is the last index
     * in the adapter
     */
    private boolean isLastItemInAdapter(final int index) {
        return index == mAdapter.getCount() - 1;
    }

    /**
     * Gets the height in px this view will be rendered. (padding removed)
     */
    private int getRenderHeight() {
        return getHeight() - getPaddingTop() - getPaddingBottom();
    }

    /**
     * Gets the width in px this view will be rendered. (padding removed)
     */
    private int getRenderWidth() {
        return getWidth() - getPaddingLeft() - getPaddingRight();
    }

    /**
     * Scroll to the provided offset
     */
    public void scrollTo(final int x) {
        mFlingTracker.startScroll(mNextX, 0, x - mNextX, 0);
        setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_FLING);
        requestLayout();
    }

    /**
     * Draws the overscroll edge glow effect on the left and right sides of the
     * horizontal list
     */
    private void drawEdgeGlow(final Canvas canvas) {
        if (mEdgeGlowLeft != null && !mEdgeGlowLeft.isFinished()
                && isEdgeGlowEnabled()) {
            // The Edge glow is meant to come from the top of the screen, so
            // rotate it to draw on the left side.
            final int restoreCount = canvas.save();
            final int height = getHeight();

            canvas.rotate(-90, 0, 0);
            canvas.translate(-height + getPaddingBottom(), 0);

            mEdgeGlowLeft.setSize(getRenderHeight(), getRenderWidth());
            if (mEdgeGlowLeft.draw(canvas)) {
                invalidate();
            }

            canvas.restoreToCount(restoreCount);
        } else if (mEdgeGlowRight != null && !mEdgeGlowRight.isFinished()
                && isEdgeGlowEnabled()) {
            // The Edge glow is meant to come from the top of the screen, so
            // rotate it to draw on the right side.
            final int restoreCount = canvas.save();
            final int width = getWidth();

            canvas.rotate(90, 0, 0);
            canvas.translate(getPaddingTop(), -width);
            mEdgeGlowRight.setSize(getRenderHeight(), getRenderWidth());
            if (mEdgeGlowRight.draw(canvas)) {
                invalidate();
            }

            canvas.restoreToCount(restoreCount);
        }
    }

    /**
     * Draws the dividers that go in between the horizontal list view items
     */
    private void drawDividers(final Canvas canvas) {
        final int count = getChildCount();

        // Only modify the left and right in the loop, we set the top and bottom
        // here since they are always the same
        final Rect bounds = mRect;
        mRect.top = getPaddingTop();
        mRect.bottom = mRect.top + getRenderHeight();

        // Draw the list dividers
        for (int i = 0; i < count; i++) {
            // Don't draw a divider to the right of the last item in the adapter
            if (!(i == count - 1 && isLastItemInAdapter(mRightViewAdapterIndex))) {
                final View child = getChildAt(i);

                bounds.left = child.getRight();
                bounds.right = child.getRight() + mDividerWidth;

                // Clip at the left edge of the screen
                if (bounds.left < getPaddingLeft()) {
                    bounds.left = getPaddingLeft();
                }

                // Clip at the right edge of the screen
                if (bounds.right > getWidth() - getPaddingRight()) {
                    bounds.right = getWidth() - getPaddingRight();
                }

                // Draw a divider to the right of the child
                drawDivider(canvas, bounds);

                // If the first view, determine if a divider should be shown to
                // the left of it.
                // A divider should be shown if the left side of this view does
                // not fill to the left edge of the screen.
                if (i == 0 && child.getLeft() > getPaddingLeft()) {
                    bounds.left = getPaddingLeft();
                    bounds.right = child.getLeft();
                    drawDivider(canvas, bounds);
                }
            }
        }
    }

    /**
     * Draws a divider in the given bounds.
     *
     * @param canvas The canvas to draw to.
     * @param bounds The bounds of the divider.
     */
    private void drawDivider(final Canvas canvas, final Rect bounds) {
        if (mDivider != null) {
            mDivider.setBounds(bounds);
            mDivider.draw(canvas);
        }
    }

    @Override
    protected void onDraw(final Canvas canvas) {
        super.onDraw(canvas);
        drawDividers(canvas);
    }

    @Override
    protected void dispatchDraw(final Canvas canvas) {
        super.dispatchDraw(canvas);
        drawEdgeGlow(canvas);
    }

    @Override
    protected void dispatchSetPressed(final boolean pressed) {
        // Don't dispatch setPressed to our children. We call setPressed on
        // ourselves to
        // get the selector in the right state, but we don't want to press each
        // child.
    }

    protected boolean onFling(final MotionEvent e1, final MotionEvent e2,
                              final float velocityX, final float velocityY) {
        mFlingTracker.fling(mNextX, 0, (int) -velocityX, 0, 0, mMaxX, 0, 0);
        setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_FLING);
        requestLayout();
        return true;
    }

    protected boolean onDown(final MotionEvent e) {
        // If the user just caught a fling, then disable all touch actions until
        // they release their finger
        mBlockTouchAction = !mFlingTracker.isFinished();

        // Allow a finger down event to catch a fling
        mFlingTracker.forceFinished(true);
        setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_IDLE);

        unpressTouchedChild();

        if (!mBlockTouchAction) {
            // Find the child that was pressed
            final int index = getChildIndex((int) e.getX(), (int) e.getY());
            if (index >= 0) {
                // Save off view being touched so it can later be released
                mViewBeingTouched = getChildAt(index);

                if (mViewBeingTouched != null) {
                    // Set the view as pressed
                    mViewBeingTouched.setPressed(true);
                    refreshDrawableState();
                }
            }
        }

        return true;
    }

    /**
     * If a view is currently pressed then unpress it
     */
    private void unpressTouchedChild() {
        if (mViewBeingTouched != null) {
            // Set the view as not pressed
            mViewBeingTouched.setPressed(false);
            refreshDrawableState();

            // Null out the view so we don't leak it
            mViewBeingTouched = null;
        }
    }

    private class GestureListener extends
            GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onDown(final MotionEvent e) {
            return HorizontalListView.this.onDown(e);
        }

        @Override
        public boolean onFling(final MotionEvent e1, final MotionEvent e2,
                               final float velocityX, final float velocityY) {
            return HorizontalListView.this
                    .onFling(e1, e2, velocityX, velocityY);
        }

        @Override
        public boolean onScroll(final MotionEvent e1, final MotionEvent e2,
                                final float distanceX, final float distanceY) {
            // Lock the user into interacting just with this view
            requestParentListViewToNotInterceptTouchEvents(true);

            setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_TOUCH_SCROLL);
            unpressTouchedChild();
            mNextX += (int) distanceX;
            updateOverscrollAnimation(Math.round(distanceX));
            requestLayout();

            return true;
        }

        @Override
        public boolean onSingleTapConfirmed(final MotionEvent e) {
            unpressTouchedChild();
            final OnItemClickListener onItemClickListener = getOnItemClickListener();

            final int index = getChildIndex((int) e.getX(), (int) e.getY());

            // If the tap is inside one of the child views, and we are not
            // blocking touches
            if (index >= 0 && !mBlockTouchAction) {
                final View child = getChildAt(index);
                final int adapterIndex = mLeftViewAdapterIndex + index;

                if (onItemClickListener != null) {
                    onItemClickListener.onItemClick(HorizontalListView.this,
                            child, adapterIndex,
                            mAdapter.getItemId(adapterIndex));
                    return true;
                }
            }

            if (mOnClickListener != null && !mBlockTouchAction) {
                mOnClickListener.onClick(HorizontalListView.this);
            }

            return false;
        }

        @Override
        public void onLongPress(final MotionEvent e) {
            unpressTouchedChild();

            final int index = getChildIndex((int) e.getX(), (int) e.getY());
            if (index >= 0 && !mBlockTouchAction) {
                final View child = getChildAt(index);
                final OnItemLongClickListener onItemLongClickListener = getOnItemLongClickListener();
                if (onItemLongClickListener != null) {
                    final int adapterIndex = mLeftViewAdapterIndex + index;
                    final boolean handled = onItemLongClickListener
                            .onItemLongClick(HorizontalListView.this, child,
                                    adapterIndex,
                                    mAdapter.getItemId(adapterIndex));

                    if (handled) {
                        // BZZZTT!!1!
                        performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
                    }
                }
            }
        }
    }

    ;

    @Override
    public boolean onTouchEvent(final MotionEvent event) {
        // Detect when the user lifts their finger off the screen after a touch
        if (event.getAction() == MotionEvent.ACTION_UP) {
            // If not flinging then we are idle now. The user just finished a
            // finger scroll.
            if (mFlingTracker == null || mFlingTracker.isFinished()) {
                setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_IDLE);
            }

            // Allow the user to interact with parent views
            requestParentListViewToNotInterceptTouchEvents(false);

            releaseEdgeGlow();
        } else if (event.getAction() == MotionEvent.ACTION_CANCEL) {
            unpressTouchedChild();
            releaseEdgeGlow();

            // Allow the user to interact with parent views
            requestParentListViewToNotInterceptTouchEvents(false);
        }

        return super.onTouchEvent(event);
    }

    /**
     * Release the EdgeGlow so it animates
     */
    private void releaseEdgeGlow() {
        if (mEdgeGlowLeft != null) {
            mEdgeGlowLeft.onRelease();
        }

        if (mEdgeGlowRight != null) {
            mEdgeGlowRight.onRelease();
        }
    }

    /**
     * Sets a listener to be called when the HorizontalListView has been
     * scrolled to a point where it is running low on data. An example use case
     * is wanting to auto download more data when the user has scrolled to the
     * point where only 10 items are left to be rendered off the right of the
     * screen. To get called back at that point just register with this function
     * with a numberOfItemsLeftConsideredLow value of 10. <br>
     * <br>
     * This will only be called once to notify that the HorizontalListView is
     * running low on data. Calling notifyDataSetChanged on the adapter will
     * allow this to be called again once low on data.
     *
     * @param listener                       The listener to be notified when the number of array adapters
     *                                       items left to be shown is running low.
     * @param numberOfItemsLeftConsideredLow The number of array adapter items that have not yet been
     *                                       displayed that is considered too low.
     */
    public void setRunningOutOfDataListener(
            final RunningOutOfDataListener listener,
            final int numberOfItemsLeftConsideredLow) {
        mRunningOutOfDataListener = listener;
        mRunningOutOfDataThreshold = numberOfItemsLeftConsideredLow;
    }

    /**
     * This listener is used to allow notification when the HorizontalListView
     * is running low on data to display.
     */
    public static interface RunningOutOfDataListener {
        /**
         * Called when the HorizontalListView is running out of data and has
         * reached at least the provided threshold.
         */
        void onRunningOutOfData();
    }

    /**
     * Determines if we are low on data and if so will call to notify the
     * listener, if there is one, that we are running low on data.
     */
    private void determineIfLowOnData() {
        // Check if the threshold has been reached and a listener is registered
        if (mRunningOutOfDataListener != null
                && mAdapter != null
                && mAdapter.getCount() - (mRightViewAdapterIndex + 1) < mRunningOutOfDataThreshold) {

            // Prevent notification more than once
            if (!mHasNotifiedRunningLowOnData) {
                mHasNotifiedRunningLowOnData = true;
                mRunningOutOfDataListener.onRunningOutOfData();
            }
        }
    }

    /**
     * Register a callback to be invoked when the HorizontalListView has been
     * clicked.
     *
     * @param listener The callback that will be invoked.
     */
    @Override
    public void setOnClickListener(final OnClickListener listener) {
        mOnClickListener = listener;
    }

    /**
     * Interface definition for a callback to be invoked when the view scroll
     * state has changed.
     */
    public interface OnScrollStateChangedListener {
        public enum ScrollState {
            /**
             * The view is not scrolling. Note navigating the list using the
             * trackball counts as being in the idle state since these
             * transitions are not animated.
             */
            SCROLL_STATE_IDLE,

            /**
             * The user is scrolling using touch, and their finger is still on
             * the screen
             */
            SCROLL_STATE_TOUCH_SCROLL,

            /**
             * The user had previously been scrolling using touch and had
             * performed a fling. The animation is now coasting to a stop
             */
            SCROLL_STATE_FLING
        }

        /**
         * Callback method to be invoked when the scroll state changes.
         *
         * @param scrollState The current scroll state.
         */
        public void onScrollStateChanged(ScrollState scrollState);
    }

    /**
     * Sets a listener to be invoked when the scroll state has changed.
     *
     * @param listener The listener to be invoked.
     */
    public void setOnScrollStateChangedListener(
            final OnScrollStateChangedListener listener) {
        mOnScrollStateChangedListener = listener;
    }

    /**
     * Call to set the new scroll state. If it has changed and a listener is
     * registered then it will be notified.
     */
    private void setCurrentScrollState(
            final OnScrollStateChangedListener.ScrollState newScrollState) {
        // If the state actually changed then notify listener if there is one
        if (mCurrentScrollState != newScrollState
                && mOnScrollStateChangedListener != null) {
            mOnScrollStateChangedListener.onScrollStateChanged(newScrollState);
        }

        mCurrentScrollState = newScrollState;
    }

    /**
     * Updates the over scroll animation based on the scrolled offset.
     *
     * @param scrolledOffset The scroll offset
     */
    private void updateOverscrollAnimation(final int scrolledOffset) {
        if (mEdgeGlowLeft == null || mEdgeGlowRight == null) {
            return;
        }

        // Calculate where the next scroll position would be
        final int nextScrollPosition = mCurrentX + scrolledOffset;

        // If not currently in a fling (Don't want to allow fling offset updates
        // to cause over scroll animation)
        if (mFlingTracker == null || mFlingTracker.isFinished()) {
            // If currently scrolled off the left side of the list and the
            // adapter is not empty
            if (nextScrollPosition < 0) {

                // Calculate the amount we have scrolled since last frame
                final int overscroll = Math.abs(scrolledOffset);

                // Tell the edge glow to redraw itself at the new offset
                mEdgeGlowLeft.onPull((float) overscroll / getRenderWidth());

                // Cancel animating right glow
                if (!mEdgeGlowRight.isFinished()) {
                    mEdgeGlowRight.onRelease();
                }
            } else if (nextScrollPosition > mMaxX) {
                // Scrolled off the right of the list

                // Calculate the amount we have scrolled since last frame
                final int overscroll = Math.abs(scrolledOffset);

                // Tell the edge glow to redraw itself at the new offset
                mEdgeGlowRight.onPull((float) overscroll / getRenderWidth());

                // Cancel animating left glow
                if (!mEdgeGlowLeft.isFinished()) {
                    mEdgeGlowLeft.onRelease();
                }
            }
        }
    }

    /**
     * Checks if the edge glow should be used enabled. The glow is not enabled
     * unless there are more views than can fit on the screen at one time.
     */
    private boolean isEdgeGlowEnabled() {
        if (mAdapter == null || mAdapter.isEmpty()) {
            return false;
        }

        // If the maxx is more then zero then the user can scroll, so the edge
        // effects should be shown
        return mMaxX > 0;
    }

    @TargetApi(11)
    /** Wrapper class to protect access to API version 11 and above features */
    private static final class HoneycombPlus {
        static {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
                throw new RuntimeException(
                        "Should not get to HoneycombPlus class unless sdk is >= 11!");
            }
        }

        /**
         * Sets the friction for the provided scroller
         */
        public static void setFriction(final Scroller scroller,
                                       final float friction) {
            if (scroller != null) {
                scroller.setFriction(friction);
            }
        }
    }

    @TargetApi(14)
    /** Wrapper class to protect access to API version 14 and above features */
    private static final class IceCreamSandwichPlus {
        static {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                throw new RuntimeException(
                        "Should not get to IceCreamSandwichPlus class unless sdk is >= 14!");
            }
        }

        /**
         * Gets the velocity for the provided scroller
         */
        public static float getCurrVelocity(final Scroller scroller) {
            return scroller.getCurrVelocity();
        }
    }
}
Yarrn/src/main/java/de/vanmar/android/yarrn/YarrnSpiceService.java
public class YarrnSpiceService extends GsonSpringAndroidSpiceService {
    @Override
    public int getThreadCount() {
        return 3;
    }
}
Yarrn/src/main/java/de/vanmar/android/yarrn/ravelry/IRavelryActivity.java
public interface IRavelryActivity {

    void requestToken();
}
Yarrn/src/main/java/de/vanmar/android/yarrn/requests/AbstractRavelryGetRequest.java
public abstract class AbstractRavelryGetRequest<T extends ETaggable> extends AbstractRavelryRequest<T> {

    public static final long CACHE_DURATION = DurationInMillis.ONE_MINUTE;
    private final GsonObjectPersisterFactory persisterFactory;


    public AbstractRavelryGetRequest(Class<T> clazz, Application application, YarrnPrefs_ prefs) {
        super(clazz, prefs, application);
        try {
            persisterFactory = new GsonObjectPersisterFactory(application);
        } catch (SpiceException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    @Override
    public T loadDataFromNetwork() throws Exception {
        final OAuthRequest request = getRequest();
        ACRA.getErrorReporter().putCustomData("lastUrl", getCacheKey().toString());
        /*T dataFromCache = persisterFactory.createObjectPersister(getResultType()).loadDataFromCache(getCacheKey(), DurationInMillis.ALWAYS_RETURNED);
        if (dataFromCache != null) {
            request.addHeader("If-None-Match", dataFromCache.getETag());
        }*/
        final Response response = executeRequest(request);
        /*if (response.getCode() == 304) {
            return dataFromCache;
        } else {*/
        T result = parseResult(response.getBody());
        result.setETag(response.getHeader("ETag"));
        return result;
        /*}*/
    }


    protected abstract T parseResult(String responseBody);

    protected abstract OAuthRequest getRequest();

    public Object getCacheKey() {
        OAuthRequest request = getRequest();
        return request.getCompleteUrl();
    }

}


Question: package de.vanmar.android.yarrn.queues;
import java.util.Collection;
import java.util.List;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.EFragment;
import org.androidannotations.annotations.FragmentArg;
import org.androidannotations.annotations.OptionsItem;
import org.androidannotations.annotations.OptionsMenu;
import org.androidannotations.annotations.UiThread;
import org.androidannotations.annotations.ViewById;
import org.androidannotations.annotations.sharedpreferences.Pref;
import com.androidquery.util.AQUtility;
import com.google.gson.JsonObject;
import com.meetme.android.horizontallistview.HorizontalListView;
import com.octo.android.robospice.SpiceManager;
import com.octo.android.robospice.persistence.exception.SpiceException;
import com.octo.android.robospice.request.listener.RequestListener;
import android.app.Activity;
import android.support.v4.app.Fragment;
import android.view.View;
import android.webkit.WebView;
import android.widget.AdapterView;
import android.widget.TextView;
import android.widget.Toast;
import de.vanmar.android.yarrn.R;
import de.vanmar.android.yarrn.YarrnPrefs_;
import de.vanmar.android.yarrn.YarrnSpiceService;
import de.vanmar.android.yarrn.components.AddEditFavoriteDialog;
import de.vanmar.android.yarrn.components.ImageDialog;
import de.vanmar.android.yarrn.projects.PhotoAdapter;
import de.vanmar.android.yarrn.ravelry.IRavelryActivity;
import de.vanmar.android.yarrn.ravelry.RavelryResultListener;
import de.vanmar.android.yarrn.ravelry.dts.BookmarkShort;
import de.vanmar.android.yarrn.ravelry.dts.Needle;
import de.vanmar.android.yarrn.ravelry.dts.Pattern;
import de.vanmar.android.yarrn.ravelry.dts.QueuedProject;
import de.vanmar.android.yarrn.ravelry.dts.QueuedProjectResult;
import de.vanmar.android.yarrn.requests.AbstractRavelryGetRequest;
import de.vanmar.android.yarrn.requests.AddFavoriteRequest;
import de.vanmar.android.yarrn.requests.GetQueuedProjectRequest;





@EFragment(R.layout.fragment_queue_detail)
@OptionsMenu(R.menu.fragment_menu)
public class QueuedProjectFragment extends Fragment {

    public static final String ARG_QUEUED_PROJECT_ID = "queuedProjectId";
    public static final String ARG_USERNAME = "username";

    protected SpiceManager spiceManager;
    private ImageDialog dialog;

    public interface QueuedProjectFragmentListener extends IRavelryActivity {
    }

    @ViewById(R.id.gallery)
    HorizontalListView gallery;

    @ViewById(R.id.name)
    TextView name;

    @ViewById(R.id.author)
    TextView author;

    @ViewById(R.id.gauge_description)
    TextView gauge_description;

    @ViewById(R.id.yarn_weight_description)
    TextView yarn_weight_description;

    @ViewById(R.id.yardage_description)
    TextView yardage_description;

    @ViewById(R.id.needles)
    TextView needles;

    @ViewById(R.id.notes)
    WebView notes;

    @FragmentArg(ARG_QUEUED_PROJECT_ID)
    int queuedProjectId;

    @FragmentArg(ARG_USERNAME)
    String username;

    private QueuedProjectFragmentListener listener;

    private PhotoAdapter adapter;

    @Pref
    YarrnPrefs_ prefs;

    @AfterViews
    public void afterViews() {
        if (spiceManager == null) {
            spiceManager = new SpiceManager(YarrnSpiceService.class);
        }

        adapter = new PhotoAdapter(getActivity());
        gallery.setAdapter(adapter);
        gallery.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                dialog = new ImageDialog(getActivity(), adapter.getItem(position).getMediumUrl());
                dialog.show();
            }
        });
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (dialog != null && dialog.isShowing()) {
            dialog.dismiss();
            dialog = null;
        }
    }

    @Override
    public void onAttach(final Activity activity) {
        super.onAttach(activity);
        if (activity instanceof QueuedProjectFragmentListener) {
            listener = (QueuedProjectFragmentListener) activity;
        } else {
            throw new ClassCastException(activity.toString()
                    + " must implement QueuedProjectFragmentListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        listener = null;
    }

    public void onQueuedProjectSelected(final int queuedProjectId) {
        clearPattern();
        if (queuedProjectId != 0) {
            GetQueuedProjectRequest request = new GetQueuedProjectRequest(this.getActivity().getApplication(), prefs, queuedProjectId, username);
            spiceManager.execute(request, request.getCacheKey(), AbstractRavelryGetRequest.CACHE_DURATION, new QueuedProjectListener(listener));
        }
    }

    @Override
    public void onStart() {
        super.onStart();
        spiceManager.start(this.getActivity());

        onQueuedProjectSelected(queuedProjectId);
    }

    @Override
    public void onResume() {
        super.onResume();
    }

    @Override
    public void onStop() {
        if (spiceManager.isStarted()) {
            spiceManager.shouldStop();
        }
        super.onStop();
    }

    @UiThread
    protected void clearPattern() {
        View view = getView();
        if (view != null) {
            view.setVisibility(View.GONE);
        }
    }

    protected void displayQueuedProject(final QueuedProjectResult queuedProjectResult) {
Answer:         QueuedProject queuedProject = queuedProjectResult.queuedProject;