public class StickyListHeadersListView extends FrameLayout {

	public interface OnHeaderClickListener {
		public void onHeaderClick(StickyListHeadersListView l, View header,
				int itemPosition, long headerId, boolean currentlySticky);
	}

	/* --- Children --- */
	private WrapperViewList mList;
	private View mHeader;

	/* --- Header state --- */
	private Long mHeaderId;
	// used to not have to call getHeaderId() all the time
	private Integer mHeaderPosition;
	private Integer mHeaderOffset;

	/* --- Delegates --- */
	private OnScrollListener mOnScrollListenerDelegate;

	/* --- Settings --- */
	private boolean mAreHeadersSticky = true;
	private boolean mClippingToPadding = true;
	private boolean mIsDrawingListUnderStickyHeader = true;
	private int mPaddingLeft = 0;
	private int mPaddingTop = 0;
	private int mPaddingRight = 0;
	private int mPaddingBottom = 0;

	/* --- Other --- */
	private AdapterWrapper mAdapter;
	private OnHeaderClickListener mOnHeaderClickListener;
	private Drawable mDivider;
	private int mDividerHeight;
	private AdapterWrapperDataSetObserver mDataSetObserver;

	public StickyListHeadersListView(Context context) {
		this(context, null);
	}

	public StickyListHeadersListView(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	@TargetApi(Build.VERSION_CODES.HONEYCOMB)
	public StickyListHeadersListView(Context context, AttributeSet attrs,
			int defStyle) {
		super(context, attrs, defStyle);

		// Initialize the list
		mList = new WrapperViewList(context);
		mDivider = mList.getDivider();
		mDividerHeight = mList.getDividerHeight();

		// null out divider, dividers are handled by adapter so they look good
		// with headers
		mList.setDivider(null);
		mList.setDividerHeight(0);

		mList.setLifeCycleListener(new WrapperViewListLifeCycleListener());
		mList.setOnScrollListener(new WrapperListScrollListener());
		addView(mList);

		if (attrs != null) {
			TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
					R.styleable.StickyListHeadersListView, 0, 0);

			try {
				// Android attributes
				if (a.hasValue(R.styleable.StickyListHeadersListView_android_padding)) {
					int padding = a
							.getDimensionPixelSize(
									R.styleable.StickyListHeadersListView_android_padding,
									0);
					mPaddingLeft = padding;
					mPaddingTop = padding;
					mPaddingRight = padding;
					mPaddingBottom = padding;
				} else {
					mPaddingLeft = a
							.getDimensionPixelSize(
									R.styleable.StickyListHeadersListView_android_paddingLeft,
									0);
					mPaddingTop = a
							.getDimensionPixelSize(
									R.styleable.StickyListHeadersListView_android_paddingTop,
									0);
					mPaddingRight = a
							.getDimensionPixelSize(
									R.styleable.StickyListHeadersListView_android_paddingRight,
									0);
					mPaddingBottom = a
							.getDimensionPixelSize(
									R.styleable.StickyListHeadersListView_android_paddingBottom,
									0);
				}
				setPadding(mPaddingLeft, mPaddingTop, mPaddingRight,
						mPaddingBottom);

				// Set clip to padding on the list and reset value to default on
				// wrapper
				mClippingToPadding = a
						.getBoolean(
								R.styleable.StickyListHeadersListView_android_clipToPadding,
								true);
				super.setClipToPadding(true);
				mList.setClipToPadding(mClippingToPadding);

				// ListView attributes
				mList.setFadingEdgeLength(a
						.getDimensionPixelSize(
								R.styleable.StickyListHeadersListView_android_fadingEdgeLength,
								mList.getVerticalFadingEdgeLength()));
				final int fadingEdge = a
						.getInt(R.styleable.StickyListHeadersListView_android_requiresFadingEdge,
								0);
				if (fadingEdge == 0x00001000) {
					mList.setVerticalFadingEdgeEnabled(false);
					mList.setHorizontalFadingEdgeEnabled(true);
				} else if (fadingEdge == 0x00002000) {
					mList.setVerticalFadingEdgeEnabled(true);
					mList.setHorizontalFadingEdgeEnabled(false);
				} else {
					mList.setVerticalFadingEdgeEnabled(false);
					mList.setHorizontalFadingEdgeEnabled(false);
				}
				mList.setCacheColorHint(a
						.getColor(
								R.styleable.StickyListHeadersListView_android_cacheColorHint,
								mList.getCacheColorHint()));
				if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
					mList.setChoiceMode(a
							.getInt(R.styleable.StickyListHeadersListView_android_choiceMode,
									mList.getChoiceMode()));
				}
				mList.setDrawSelectorOnTop(a
						.getBoolean(
								R.styleable.StickyListHeadersListView_android_drawSelectorOnTop,
								false));
				mList.setFastScrollEnabled(a
						.getBoolean(
								R.styleable.StickyListHeadersListView_android_fastScrollEnabled,
								mList.isFastScrollEnabled()));
				if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
					mList.setFastScrollAlwaysVisible(a
							.getBoolean(
									R.styleable.StickyListHeadersListView_android_fastScrollAlwaysVisible,
									mList.isFastScrollAlwaysVisible()));
				}
				mList.setScrollBarStyle(a
						.getInt(R.styleable.StickyListHeadersListView_android_scrollbarStyle,
								0));
				final Drawable selector = a
						.getDrawable(R.styleable.StickyListHeadersListView_android_listSelector);
				if (selector != null) {
					mList.setSelector(selector);
				}
				mList.setScrollingCacheEnabled(a
						.getBoolean(
								R.styleable.StickyListHeadersListView_android_scrollingCache,
								mList.isScrollingCacheEnabled()));
				final Drawable divider = a
						.getDrawable(R.styleable.StickyListHeadersListView_android_divider);
				if (divider != null) {
					mDivider = divider;
				}
				mDividerHeight = a
						.getDimensionPixelSize(
								R.styleable.StickyListHeadersListView_android_dividerHeight,
								mDividerHeight);

				// StickyListHeaders attributes
				mAreHeadersSticky = a.getBoolean(
						R.styleable.StickyListHeadersListView_hasStickyHeaders,
						true);
				mIsDrawingListUnderStickyHeader = a
						.getBoolean(
								R.styleable.StickyListHeadersListView_isDrawingListUnderStickyHeader,
								true);
            } finally {
				a.recycle();
			}
		}

        mList.setVerticalScrollBarEnabled(isVerticalScrollBarEnabled());
        mList.setHorizontalScrollBarEnabled(isHorizontalScrollBarEnabled());
    }

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		measureHeader(mHeader);
	}

	private void ensureHeaderHasCorrectLayoutParams(View header) {
		ViewGroup.LayoutParams lp = header.getLayoutParams();
		if (lp == null) {
			lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
		} else if (lp.height == LayoutParams.MATCH_PARENT) {
			lp.height = LayoutParams.WRAP_CONTENT;
		}
		header.setLayoutParams(lp);
	}

	private void measureHeader(View header) {
		if (header != null) {
			final int width = getMeasuredWidth() - mPaddingLeft - mPaddingRight;
			final int parentWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
					width, MeasureSpec.EXACTLY);
			final int parentHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0,
					MeasureSpec.UNSPECIFIED);
			measureChild(header, parentWidthMeasureSpec,
					parentHeightMeasureSpec);
		}
	}

	@Override
	protected void onLayout(boolean changed, int left, int top, int right,
			int bottom) {
		mList.layout(0, 0, mList.getMeasuredWidth(), getHeight());
		if (mHeader != null) {
			MarginLayoutParams lp = (MarginLayoutParams) mHeader
					.getLayoutParams();
			int headerTop = lp.topMargin
					+ (mClippingToPadding ? mPaddingTop : 0);
			// The left parameter must for some reason be set to 0.
			// I think it should be set to mPaddingLeft but apparently not
			mHeader.layout(mPaddingLeft, headerTop, mHeader.getMeasuredWidth()
					+ mPaddingLeft, headerTop + mHeader.getMeasuredHeight());
		}
	}

	@Override
	protected void dispatchDraw(Canvas canvas) {
		// Only draw the list here.
		// The header should be drawn right after the lists children are drawn.
		// This is done so that the header is above the list items
		// but below the list decorators (scroll bars etc).
		drawChild(canvas, mList, 0);
	}

	// Reset values tied the header. also remove header form layout
	// This is called in response to the data set or the adapter changing
	private void clearHeader() {
		if (mHeader != null) {
			removeView(mHeader);
			mHeader = null;
			mHeaderId = null;
			mHeaderPosition = null;
			mHeaderOffset = null;

			// reset the top clipping length
			mList.setTopClippingLength(0);
			updateHeaderVisibilities();
		}
	}

	private void updateOrClearHeader(int firstVisiblePosition) {
		final int adapterCount = mAdapter == null ? 0 : mAdapter.getCount();
		if (adapterCount == 0 || !mAreHeadersSticky) {
			return;
		}

		final int headerViewCount = mList.getHeaderViewsCount();
		final int realFirstVisibleItem = firstVisiblePosition - headerViewCount;

		// It is not a mistake to call getFirstVisiblePosition() here.
		// Most of the time getFixedFirstVisibleItem() should be called
		// but that does not work great together with getChildAt()
		final boolean doesListHaveChildren = mList.getChildCount() != 0;
		final boolean isFirstViewBelowTop = doesListHaveChildren && mList.getFirstVisiblePosition() == 0
				&& mList.getChildAt(0).getTop() > 0;
		final boolean isFirstVisibleItemOutsideAdapterRange = realFirstVisibleItem > adapterCount - 1
				|| realFirstVisibleItem < 0;
		if (!doesListHaveChildren || isFirstVisibleItemOutsideAdapterRange
				|| isFirstViewBelowTop) {
			clearHeader();
			return;
		}

		updateHeader(realFirstVisibleItem);
	}

	private void updateHeader(int firstVisiblePosition) {

		// check if there is a new header should be sticky
		if (mHeaderPosition == null || mHeaderPosition != firstVisiblePosition) {
			mHeaderPosition = firstVisiblePosition;
			final long headerId = mAdapter.getHeaderId(firstVisiblePosition);
			if (mHeaderId == null || mHeaderId != headerId) {
				mHeaderId = headerId;
				final View header = mAdapter.getHeaderView(mHeaderPosition,
						mHeader, this);
				if (mHeader != header) {
					if (header == null) {
						throw new NullPointerException("header may not be null");
					}
					swapHeader(header);
				}
				
				ensureHeaderHasCorrectLayoutParams(mHeader);
				measureHeader(mHeader);

				// Reset mHeaderOffset to null ensuring
				// that it will be set on the header and
				// not skipped for performance reasons.
				mHeaderOffset = null;
			}
		}

		int headerOffset = 0;

		// Calculate new header offset
		// Skip looking at the first view. it never matters because it always
		// results in a headerOffset = 0
		int headerBottom = mHeader.getMeasuredHeight()
				+ (mClippingToPadding ? mPaddingTop : 0);
		for (int i = 0; i < mList.getChildCount(); i++) {
			final View child = mList.getChildAt(i);
			final boolean doesChildHaveHeader = child instanceof WrapperView
					&& ((WrapperView) child).hasHeader();
			final boolean isChildFooter = mList.containsFooterView(child);
			if (child.getTop() >= (mClippingToPadding ? mPaddingTop : 0)
					&& (doesChildHaveHeader || isChildFooter)) {
				headerOffset = Math.min(child.getTop() - headerBottom, 0);
				break;
			}
		}

		setHeaderOffet(headerOffset);

		if (!mIsDrawingListUnderStickyHeader) {
			mList.setTopClippingLength(mHeader.getMeasuredHeight()
					+ mHeaderOffset);
		}

		updateHeaderVisibilities();
	}

	private void swapHeader(View newHeader) {
		if (mHeader != null) {
			removeView(mHeader);
		}
		mHeader = newHeader;
		addView(mHeader);
		mHeader.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				if (mOnHeaderClickListener != null) {
					mOnHeaderClickListener.onHeaderClick(
							StickyListHeadersListView.this, mHeader,
							mHeaderPosition, mHeaderId, true);
				}
			}

		});
	}

	// hides the headers in the list under the sticky header.
	// Makes sure the other ones are showing
	private void updateHeaderVisibilities() {
		int top;
		if (mHeader != null) {
			top = mHeader.getMeasuredHeight()
					+ (mHeaderOffset != null ? mHeaderOffset : 0);
		} else {
			top = mClippingToPadding ? mPaddingTop : 0;
		}
		int childCount = mList.getChildCount();
		for (int i = 0; i < childCount; i++) {
			View child = mList.getChildAt(i);
			if (child instanceof WrapperView) {
				WrapperView wrapperViewChild = (WrapperView) child;
				if (wrapperViewChild.hasHeader()) {
					View childHeader = wrapperViewChild.mHeader;
					if (wrapperViewChild.getTop() < top) {
						if (childHeader.getVisibility() != View.INVISIBLE) {
							childHeader.setVisibility(View.INVISIBLE);
						}
					} else {
						if (childHeader.getVisibility() != View.VISIBLE) {
							childHeader.setVisibility(View.VISIBLE);
						}
					}
				}
			}
		}
	}

	// Wrapper around setting the header offset in different ways depending on
	// the API version
	@SuppressLint("NewApi")
	private void setHeaderOffet(int offset) {
		if (mHeaderOffset == null || mHeaderOffset != offset) {
			mHeaderOffset = offset;
			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
				mHeader.setTranslationY(mHeaderOffset);
			} else {
				MarginLayoutParams params = (MarginLayoutParams) mHeader
						.getLayoutParams();
				params.topMargin = mHeaderOffset;
				mHeader.setLayoutParams(params);
			}
		}
	}

	private class AdapterWrapperDataSetObserver extends DataSetObserver {

		@Override
		public void onChanged() {
			clearHeader();
		}

		@Override
		public void onInvalidated() {
			clearHeader();
		}

	}

	private class WrapperListScrollListener implements OnScrollListener {

		@Override
		public void onScroll(AbsListView view, int firstVisibleItem,
				int visibleItemCount, int totalItemCount) {
			if (mOnScrollListenerDelegate != null) {
				mOnScrollListenerDelegate.onScroll(view, firstVisibleItem,
						visibleItemCount, totalItemCount);
			}
			updateOrClearHeader(mList.getFixedFirstVisibleItem());
		}

		@Override
		public void onScrollStateChanged(AbsListView view, int scrollState) {
			if (mOnScrollListenerDelegate != null) {
				mOnScrollListenerDelegate.onScrollStateChanged(view,
						scrollState);
			}
		}

	}

	private class WrapperViewListLifeCycleListener implements LifeCycleListener {

		@Override
		public void onDispatchDrawOccurred(Canvas canvas) {
			// onScroll is not called often at all before froyo
			// therefor we need to update the header here as well.
			if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) {
				updateOrClearHeader(mList.getFixedFirstVisibleItem());
			}
			if (mHeader != null) {
				if (mClippingToPadding) {
					canvas.save();
					canvas.clipRect(0, mPaddingTop, getRight(), getBottom());
					drawChild(canvas, mHeader, 0);
					canvas.restore();
				} else {
					drawChild(canvas, mHeader, 0);
				}
			}
		}

	}

	private class AdapterWrapperHeaderClickHandler implements
			AdapterWrapper.OnHeaderClickListener {

		@Override
		public void onHeaderClick(View header, int itemPosition, long headerId) {
			mOnHeaderClickListener.onHeaderClick(
					StickyListHeadersListView.this, header, itemPosition,
					headerId, false);
		}

	}

	private boolean isStartOfSection(int position) {
		return position == 0
				|| mAdapter.getHeaderId(position) != mAdapter
						.getHeaderId(position - 1);
	}

	private int getHeaderOverlap(int position) {
		boolean isStartOfSection = isStartOfSection(position);
		if (!isStartOfSection) {
			View header = mAdapter.getHeaderView(position, null, mList);
			if (header == null) {
				throw new NullPointerException("header may not be null");
			}
			ensureHeaderHasCorrectLayoutParams(header);
			measureHeader(header);
			return header.getMeasuredHeight();
		}
		return 0;
	}

	/* ---------- StickyListHeaders specific API ---------- */

	public void setAreHeadersSticky(boolean areHeadersSticky) {
		mAreHeadersSticky = areHeadersSticky;
		if (!areHeadersSticky) {
			clearHeader();
		} else {
			updateOrClearHeader(mList.getFixedFirstVisibleItem());
		}
		// invalidating the list will trigger dispatchDraw()
		mList.invalidate();
	}

	public boolean areHeadersSticky() {
		return mAreHeadersSticky;
	}

	/**
	 * Use areHeadersSticky() method instead
	 */
	@Deprecated
	public boolean getAreHeadersSticky() {
		return areHeadersSticky();
	}

	public void setDrawingListUnderStickyHeader(
			boolean drawingListUnderStickyHeader) {
		mIsDrawingListUnderStickyHeader = drawingListUnderStickyHeader;
		// reset the top clipping length
		mList.setTopClippingLength(0);
	}

	public boolean isDrawingListUnderStickyHeader() {
		return mIsDrawingListUnderStickyHeader;
	}

	public void setOnHeaderClickListener(
			OnHeaderClickListener onHeaderClickListener) {
		mOnHeaderClickListener = onHeaderClickListener;
		if (mAdapter != null) {
			if (mOnHeaderClickListener != null) {
				mAdapter.setOnHeaderClickListener(new AdapterWrapperHeaderClickHandler());
			} else {
				mAdapter.setOnHeaderClickListener(null);
			}
		}
	}

	public View getListChildAt(int index) {
		return mList.getChildAt(index);
	}

	public int getListChildCount() {
		return mList.getChildCount();
	}

	/**
	 * Use the method with extreme caution!! Changing any values on the
	 * underlying ListView might break everything.
	 * 
	 * @return the ListView backing this view.
	 */
	public ListView getWrappedList() {
		return mList;
	}

	/* ---------- ListView delegate methods ---------- */

	public void setAdapter(StickyListHeadersAdapter adapter) {
		if (adapter == null) {
			mList.setAdapter(null);
			clearHeader();
			return;
		}
		if (mAdapter != null) {
			mAdapter.unregisterDataSetObserver(mDataSetObserver);
		}

		if (adapter instanceof SectionIndexer) {
			mAdapter = new SectionIndexerAdapterWrapper(getContext(), adapter);
		} else {
			mAdapter = new AdapterWrapper(getContext(), adapter);
		}
		mDataSetObserver = new AdapterWrapperDataSetObserver();
		mAdapter.registerDataSetObserver(mDataSetObserver);

		if (mOnHeaderClickListener != null) {
			mAdapter.setOnHeaderClickListener(new AdapterWrapperHeaderClickHandler());
		} else {
			mAdapter.setOnHeaderClickListener(null);
		}

		mAdapter.setDivider(mDivider, mDividerHeight);

		mList.setAdapter(mAdapter);
		clearHeader();
	}

	public StickyListHeadersAdapter getAdapter() {
		return mAdapter == null ? null : mAdapter.mDelegate;
	}

	public void setDivider(Drawable divider) {
		mDivider = divider;
		if (mAdapter != null) {
			mAdapter.setDivider(mDivider, mDividerHeight);
		}
	}

	public void setDividerHeight(int dividerHeight) {
		mDividerHeight = dividerHeight;
		if (mAdapter != null) {
			mAdapter.setDivider(mDivider, mDividerHeight);
		}
	}

	public Drawable getDivider() {
		return mDivider;
	}

	public int getDividerHeight() {
		return mDividerHeight;
	}

	public void setOnScrollListener(OnScrollListener onScrollListener) {
		mOnScrollListenerDelegate = onScrollListener;
	}

	public void setOnItemClickListener(OnItemClickListener listener) {
		mList.setOnItemClickListener(listener);
	}

	public void setOnItemLongClickListener(OnItemLongClickListener listener) {
		mList.setOnItemLongClickListener(listener);
	}

    public void addHeaderView(View v, Object data, boolean isSelectable) {
        mList.addHeaderView(v, data, isSelectable);
    }

	public void addHeaderView(View v) {
		mList.addHeaderView(v);
	}

	public void removeHeaderView(View v) {
		mList.removeHeaderView(v);
	}

	public int getHeaderViewsCount() {
		return mList.getHeaderViewsCount();
	}

	public void addFooterView(View v) {
		mList.addFooterView(v);
	}

	public void removeFooterView(View v) {
		mList.removeFooterView(v);
	}

	public int getFooterViewsCount() {
		return mList.getFooterViewsCount();
	}

	public void setEmptyView(View v) {
		mList.setEmptyView(v);
	}

	public View getEmptyView() {
		return mList.getEmptyView();
	}

    @Override
    public void setVerticalScrollBarEnabled(boolean verticalScrollBarEnabled) {
        mList.setVerticalScrollBarEnabled(verticalScrollBarEnabled);
    }

    @Override
    public void setHorizontalScrollBarEnabled(boolean horizontalScrollBarEnabled) {
        mList.setHorizontalScrollBarEnabled(horizontalScrollBarEnabled);
    }

	@TargetApi(Build.VERSION_CODES.FROYO)
	public void smoothScrollBy(int distance, int duration) {
		requireSdkVersion(Build.VERSION_CODES.FROYO);
		mList.smoothScrollBy(distance, duration);
	}

	@TargetApi(Build.VERSION_CODES.HONEYCOMB)
	public void smoothScrollByOffset(int offset) {
		requireSdkVersion(Build.VERSION_CODES.HONEYCOMB);
		mList.smoothScrollByOffset(offset);
	}

	@SuppressLint("NewApi")
	@TargetApi(Build.VERSION_CODES.FROYO)
	public void smoothScrollToPosition(int position) {
		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
			mList.smoothScrollToPosition(position);
		} else {
			int offset = mAdapter == null ? 0 : getHeaderOverlap(position);
			offset -= mClippingToPadding ? 0 : mPaddingTop;
			mList.smoothScrollToPositionFromTop(position, offset);
		}
	}

	@TargetApi(Build.VERSION_CODES.FROYO)
	public void smoothScrollToPosition(int position, int boundPosition) {
		requireSdkVersion(Build.VERSION_CODES.FROYO);
		mList.smoothScrollToPosition(position, boundPosition);
	}

	@TargetApi(Build.VERSION_CODES.HONEYCOMB)
	public void smoothScrollToPositionFromTop(int position, int offset) {
		requireSdkVersion(Build.VERSION_CODES.HONEYCOMB);
		offset += mAdapter == null ? 0 : getHeaderOverlap(position);
		offset -= mClippingToPadding ? 0 : mPaddingTop;
		mList.smoothScrollToPositionFromTop(position, offset);
	}

	@TargetApi(Build.VERSION_CODES.HONEYCOMB)
	public void smoothScrollToPositionFromTop(int position, int offset,
			int duration) {
		requireSdkVersion(Build.VERSION_CODES.HONEYCOMB);
		offset += mAdapter == null ? 0 : getHeaderOverlap(position);
		offset -= mClippingToPadding ? 0 : mPaddingTop;
		mList.smoothScrollToPositionFromTop(position, offset, duration);
	}

	public void setSelection(int position) {
		setSelectionFromTop(position, 0);
	}

	public void setSelectionAfterHeaderView() {
		mList.setSelectionAfterHeaderView();
	}

	public void setSelectionFromTop(int position, int y) {
		y += mAdapter == null ? 0 : getHeaderOverlap(position);
		y -= mClippingToPadding ? 0 : mPaddingTop;
		mList.setSelectionFromTop(position, y);
	}

	public void setSelector(Drawable sel) {
		mList.setSelector(sel);
	}

	public void setSelector(int resID) {
		mList.setSelector(resID);
	}

	public int getFirstVisiblePosition() {
		return mList.getFirstVisiblePosition();
	}

	public int getLastVisiblePosition() {
		return mList.getLastVisiblePosition();
	}

	public void setChoiceMode(int choiceMode) {
		mList.setChoiceMode(choiceMode);
	}

	public void setItemChecked(int position, boolean value) {
		mList.setItemChecked(position, value);
	}

	@TargetApi(Build.VERSION_CODES.HONEYCOMB)
	public int getCheckedItemCount() {
		requireSdkVersion(Build.VERSION_CODES.HONEYCOMB);
		return mList.getCheckedItemCount();
	}

	@TargetApi(Build.VERSION_CODES.FROYO)
	public long[] getCheckedItemIds() {
		requireSdkVersion(Build.VERSION_CODES.FROYO);
		return mList.getCheckedItemIds();
	}

	public int getCheckedItemPosition() {
		return mList.getCheckedItemPosition();
	}

	public SparseBooleanArray getCheckedItemPositions() {
		return mList.getCheckedItemPositions();
	}

	public int getCount() {
		return mList.getCount();
	}

	public Object getItemAtPosition(int position) {
		return mList.getItemAtPosition(position);
	}

	public long getItemIdAtPosition(int position) {
		return mList.getItemIdAtPosition(position);
	}

	public void setOnCreateContextMenuListener(OnCreateContextMenuListener l) {
		mList.setOnCreateContextMenuListener(l);
	}

	public boolean showContextMenu() {
		return mList.showContextMenu();
	}

	public void invalidateViews() {
		mList.invalidateViews();
	}

	@Override
	public void setClipToPadding(boolean clipToPadding) {
		if (mList != null) {
			mList.setClipToPadding(clipToPadding);
		}
		mClippingToPadding = clipToPadding;
	}

	@Override
	public void setPadding(int left, int top, int right, int bottom) {
		mPaddingLeft = left;
		mPaddingTop = top;
		mPaddingRight = right;
		mPaddingBottom = bottom;

		if (mList != null) {
			mList.setPadding(left, top, right, bottom);
		}
		super.setPadding(0, 0, 0, 0);
		requestLayout();
	}

	@Override
	public int getPaddingLeft() {
		return mPaddingLeft;
	}

	@Override
	public int getPaddingTop() {
		return mPaddingTop;
	}

	@Override
	public int getPaddingRight() {
		return mPaddingRight;
	}

	@Override
	public int getPaddingBottom() {
		return mPaddingBottom;
	}

	public void setFastScrollEnabled(boolean fastScrollEnabled) {
		mList.setFastScrollEnabled(fastScrollEnabled);
	}

	/**
	 * @see android.widget.AbsListView#setFastScrollAlwaysVisible(boolean)
	 * @throws ApiLevelTooLowException on pre-Honeycomb device.
	 */
	@TargetApi(Build.VERSION_CODES.HONEYCOMB)
	public void setFastScrollAlwaysVisible(boolean alwaysVisible) {
		requireSdkVersion(Build.VERSION_CODES.HONEYCOMB);
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
			mList.setFastScrollAlwaysVisible(alwaysVisible);
		}
	}

	/**
	 * @see android.widget.AbsListView#isFastScrollAlwaysVisible()
	 * @return true if the fast scroller will always show. False on pre-Honeycomb devices.
	 */
	@TargetApi(Build.VERSION_CODES.HONEYCOMB)
	public boolean isFastScrollAlwaysVisible(){
		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
			return false;
		}
		return mList.isFastScrollAlwaysVisible();
	}

	private void requireSdkVersion(int versionCode) {
		if (Build.VERSION.SDK_INT < versionCode) {
			throw new ApiLevelTooLowException(versionCode);
		}
	}

	public int getPositionForView(View view) {
		return mList.getPositionForView(view);
	}

} public class FootInfo implements Serializable {

	private static final long serialVersionUID = 1L;
	public String id;
	public String text;
	public GeoInfo geo;
	public int comments_count;

	public String created_at;
	public String[] picIds;
	public String thumbnail_pic;
	public String bmiddle_pic;
	public String original_pic;
	public String defaultPicId;

	public String formate_loaction;
	public String city;
	public String province;
	public String country;
	public String city_area;// 区县

	public int headerId;
	public boolean isChecked = false;
	public Date date;

	public UserInfo userInfo;

	// "annotations": [
	// {
	// "place": {
	// "poiid": "B209465CD66EA3F9429E",
	// "title": "丽江三义机场",
	// "lon": 100.24685,
	// "lat": 26.66926,
	// "type": "checkin"
	// }
	// }
	// ],

	// "thumbnail_pic":
	// "http://ww3.sinaimg.cn/thumbnail/590473f6jw1dwmgp7b8tjj.jpg",
	// "bmiddle_pic":
	// "http://ww3.sinaimg.cn/bmiddle/590473f6jw1dwmgp7b8tjj.jpg",
	// "original_pic": "http://ww3.sinaimg.cn/large/590473f6jw1dwmgp7b8tjj.jpg",

	// "pic_ids": [
	// "43653df6jw1eabqyexqw7j20hs0dcdh8",
	// "43653df6jw1eabqyhdbc8j20hs0dcgmq",
	// "43653df6jw1eabqyio9o6j20hs0dc0tn",
	// "43653df6jw1eabqywiuodj20hs0dcq44",
	// "43653df6jw1eabqz6tszuj20hs0dct9p"
	// ],

	public static ArrayList<FootInfo> create(JSONArray array, int size)
			throws JSONException {

		ArrayList<FootInfo> list = new ArrayList<FootInfo>();
		FootInfo info;
		FootInfo preInfo = null;

		int currentHeaderId = size;

		JSONObject jo;
		for (int i = 0; i < array.length(); i++) {
			jo = (JSONObject) array.get(i);
			info = new FootInfo();

			String date = jo.getString("created_at");
			info.date = getDate(date);

			if (info.date != null) {
				info.created_at = getFormateTime(info.date);
			}

			info.id = jo.getString("id");
			info.text = weiboTextFilter(jo.optString("text"));
			info.comments_count = jo.optInt("comments_count", 0);

			try {
				info.geo = GeoInfo.create(jo.getJSONObject("geo"));
			} catch (Exception e) {
				e.printStackTrace();
				continue;
			}

			info.thumbnail_pic = jo.optString("thumbnail_pic");
			info.bmiddle_pic = jo.optString("bmiddle_pic");
			info.original_pic = jo.optString("original_pic");
			try {
				if (!TextUtils.isEmpty(info.original_pic)) {
					info.defaultPicId = info.original_pic.substring(
							info.original_pic.lastIndexOf("/") + 1,
							info.original_pic.lastIndexOf("."));
				}
			} catch (Exception e) {
				e.printStackTrace();
			}

			String picIds = jo.optString("pic_ids");
			info.picIds = getPicIds(picIds);

			if (preInfo != null) {
				if (info.created_at != null && preInfo.created_at != null) {
					if (!info.created_at.equals(preInfo.created_at)) {
						currentHeaderId = i + size;
					}
				}
			}

			preInfo = info;

			info.headerId = currentHeaderId;

			list.add(info);
		}

		return list;
	}

	// "pic_urls": [
	// {
	// "thumbnail_pic":
	// "http://ww3.sinaimg.cn/thumbnail/66f61948jw1e61z5gfubxj218g0xcq8w.jpg"
	// },
	// {
	// "thumbnail_pic":
	// "http://ww4.sinaimg.cn/thumbnail/66f61948jw1e61z5kx45sj218g0xcwkv.jpg"
	// }
	// ],
	public static FootInfo create(JSONObject jo, boolean needUserInfo)
			throws JSONException {
		FootInfo info = new FootInfo();
		String date = jo.getString("created_at");
		info.date = getDate(date);

		if (info.date != null) {
			info.created_at = getFormateTime(info.date);
		}

		info.id = jo.getString("id");
		info.text = weiboTextFilter(jo.optString("text"));
		info.comments_count = jo.optInt("comments_count", 0);

		JSONArray array = jo.getJSONArray("pic_urls");
		info.picIds = makePicIds(array);

		try {
			info.geo = GeoInfo.create(jo.getJSONObject("geo"));
		} catch (Exception e) {
			e.printStackTrace();
		}

		info.thumbnail_pic = jo.optString("thumbnail_pic");
		info.bmiddle_pic = jo.optString("bmiddle_pic");
		info.original_pic = jo.optString("original_pic");

		try {
			if (!TextUtils.isEmpty(info.original_pic)) {
				info.defaultPicId = info.original_pic.substring(
						info.original_pic.lastIndexOf("/") + 1,
						info.original_pic.lastIndexOf("."));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		if (needUserInfo) {
			JSONObject userJo = jo.getJSONObject("user");
			UserInfo user = UserInfo.create(userJo);
			info.userInfo = user;
		}

		return info;
	}

	private static String[] makePicIds(JSONArray array) {
		try {
			String[] str = new String[array.length()];
			for (int i = 0; i < array.length(); i++) {
				JSONObject j = array.getJSONObject(i);
				String url = j.getString("thumbnail_pic");
				if (!TextUtils.isEmpty(url)) {
					String id = url.substring(url.lastIndexOf("/") + 1,
							url.lastIndexOf("."));
					str[i] = id;

					Logger.d("FootInfo", "make pic ids-->" + id);
				}
			}

			return str;
		} catch (Exception e) {
			e.printStackTrace();
		}

		return null;
	}

	private static String[] getPicIds(String ids) {
		Logger.d("FootInfo", "pic ids-->" + ids);
		try {
			if (!TextUtils.isEmpty(ids) && !ids.equals("[]")) {
				ids = ids.substring(1, ids.length() - 1);

				String[] str = ids.split(",");

				if (str != null) {
					for (int i = 0; i < str.length; i++) {
						str[i] = str[i].substring(1, str[i].length() - 1);
					}
				}

				return str;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		return null;
	}

	public static void copy(FootInfo src, FootInfo target) {
		target.bmiddle_pic = src.bmiddle_pic;
		target.city = src.city;
		target.city_area = src.city_area;
		target.country = src.country;
		target.created_at = src.created_at;
		target.formate_loaction = src.formate_loaction;
		target.geo = src.geo;
		target.headerId = src.headerId;
		target.id = src.id;
		target.original_pic = src.original_pic;
		target.picIds = src.picIds;
		target.province = src.province;
		target.text = src.text;
		target.thumbnail_pic = src.thumbnail_pic;
		target.date = src.date;
		target.comments_count = src.comments_count;
	}

	public static FootInfo copy(FootInfo src) {
		FootInfo target = new FootInfo();

		target.bmiddle_pic = src.bmiddle_pic;
		target.city = src.city;
		target.city_area = src.city_area;
		target.country = src.country;
		target.created_at = src.created_at;
		target.formate_loaction = src.formate_loaction;
		target.geo = src.geo;
		target.headerId = src.headerId;
		target.id = src.id;
		target.original_pic = src.original_pic;
		target.picIds = src.picIds;
		target.province = src.province;
		target.text = src.text;
		target.thumbnail_pic = src.thumbnail_pic;
		target.date = src.date;
		target.defaultPicId = src.defaultPicId;
		target.comments_count = src.comments_count;

		return target;
	}

	public static final SimpleDateFormat sDateFormat = new SimpleDateFormat(
			"yyyy年MM月dd日 EEE", Locale.CHINA);

	public static final SimpleDateFormat sDateFormat2 = new SimpleDateFormat(
			"yyyy年MM月dd日", Locale.CHINA);

	public static final SimpleDateFormat sDateFormat3 = new SimpleDateFormat(
			"MM月dd日", Locale.CHINA);

	// 2012.04.22 22:09
	public static final SimpleDateFormat sDateFormat4 = new SimpleDateFormat(
			"kk:mm", Locale.CHINA);

	public static final SimpleDateFormat sDateFormat5 = new SimpleDateFormat(
			"yyyy.MM.dd kk:mm", Locale.CHINA);

	static final DateFormat dateFormat = new SimpleDateFormat(
			"EEE MMM dd kk:mm:ss ZZZ yyyy", Locale.US);

	private static Date getDate(String dateStr) {
		// "Thu Nov 07 00:11:26 +0800 2013"
		try {
			Date date = dateFormat.parse(dateStr);
			return date;
		} catch (ParseException e) {
			e.printStackTrace();
		}

		return null;
	}

	private static String getFormateTime(Date date) {
		return sDateFormat.format(date);
	}

	private static String weiboTextFilter(String text) {
		if (text != null) {
			int index = text.indexOf("我在:http://");

			if (index > 0) {
				return text.substring(0, index);
			} else {
				index = text.indexOf("我在这里:http://");
				if (index > 0) {
					return text.substring(0, index);
				}
			}
		}

		return text;
	}

	@Override
	public int hashCode() {
		return (id + original_pic).hashCode();
	}

	@Override
	public boolean equals(Object o) {
		if (o == null) {
			return false;
		}

		if (o instanceof FootInfo) {
			FootInfo info = (FootInfo) o;

			if (info.id != null && info.defaultPicId != null) {
				return info.id.equals(id)
						&& info.defaultPicId.equals(defaultPicId);
			} else if (info.id != null) {
				return info.id.equals(id);
			}

			return false;
		} else {
			return false;
		}
	}

	public int getPicNum() {
		if (picIds != null) {
			return picIds.length;
		}

		return 0;
	}

	public String getFormatTime() {
		String time = "";

		if (date.getYear() == new Date().getYear()) {
			time = FootInfo.sDateFormat3.format(date);
		} else {
			time = FootInfo.sDateFormat2.format(date);
		}
		return time;
	}

	public String getDetailFormatTime() {
		return FootInfo.sDateFormat4.format(date);
	}

	public String getDetailFormatTime2() {
		return FootInfo.sDateFormat5.format(date);
	}

	public static String getTimeSort(Date firstDay, Date currentDay) {
		if (currentDay != null && firstDay != null) {
			String f = sDateFormat2.format(firstDay);
			String c = sDateFormat2.format(currentDay);
			try {
				Date first = sDateFormat2.parse(f);
				Date current = sDateFormat2.parse(c);

				double day = current.getTime() - first.getTime();
				day = day / 1000 / 60 / 60 / 24;

				if (day > 0 && day < 1
						&& (current.getDate() != first.getDate())) {
					day++;
				}

				day++;

				String num = "第" + (int) day + "天";
				return num;
			} catch (ParseException e) {
				e.printStackTrace();
			}
		}

		return "";
	}

	public String getFormatLoaciton() {
		if (formate_loaction != null) {
			if (!formate_loaction.contains("中国")) {
				return formate_loaction;
			} else {
				String location = formate_loaction.replace("中国", "");

				if (location.length() > 12) {

					if (province != null && city_area != null) {
						location = province + city_area;
					}
				}

				return location;
			}
		}

		return formate_loaction;
	}
} public class NoteInfo implements Serializable {

	public String note_id;
	public String note_uid;
	public String note_title;
	public String note_time_from;
	public String note_time_to;
	public String note_location;
	public String note_folder_url;
	public int note_is_suggest;
	public String[] weiboIds;
	public String note_create_at;// 以秒为单位

	public UserInfo userIno;

	public static ArrayList<NoteInfo> create(JSONArray array)
			throws JSONException {
		ArrayList<NoteInfo> list = new ArrayList<NoteInfo>();

		NoteInfo info;
		for (int i = 0; i < array.length(); i++) {
			JSONObject jo = (JSONObject) array.get(i);
			info = create(jo);
			list.add(info);
		}

		return list;
	}

	public static NoteInfo create(JSONObject jo) throws JSONException {
		NoteInfo info = new NoteInfo();
		info.note_id = jo.getString("note_id");
		info.note_uid = jo.getString("note_uid");
		info.note_title = jo.getString("note_title");
		// info.note_time = jo.getString("note_time");
		info.note_location = jo.getString("note_location");
		info.note_folder_url = jo.getString("note_folder_url");
		info.note_is_suggest = jo.getInt("note_is_suggest");
		String weibo_ids = jo.optString("weibo_ids");
		info.weiboIds = getIds(weibo_ids);
		info.note_time_from = jo.getString("note_time_from");
		info.note_time_to = jo.getString("note_time_to");
		info.note_create_at = jo.getString("note_create_at");
		return info;
	}

	private static String[] getIds(String ids) {
		try {
			if (!TextUtils.isEmpty(ids) && !ids.equals("[]")) {
				ids = ids.substring(1, ids.length() - 1);

				String[] str = ids.split(",");

				if (str != null) {
					for (int i = 0; i < str.length; i++) {
						str[i] = str[i].substring(1, str[i].length() - 1);
					}
				}

				return str;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		return null;
	}

	public static final SimpleDateFormat sDateFormat = new SimpleDateFormat(
			"yyyy.MM.dd", Locale.CHINA);

	public String getFormatNoteFromTime() {
		if (!TextUtils.isEmpty(note_time_from)) {
			long from = Long.parseLong(note_time_from);

			Date before = new Date(from);

			return sDateFormat.format(before);
		}
		return "";
	}

	public String getTotalDays() {
		if (!TextUtils.isEmpty(note_time_from)
				&& !TextUtils.isEmpty(note_time_to)) {
			long from = Long.parseLong(note_time_from);
			long to = Long.parseLong(note_time_to);

			Date before = new Date(from);
			Date after = new Date(to);

			return getTotalDays(before, after) + "天";
		}

		return "";
	}

	public int getTotalDays(Date before, Date after) {
		if (before.equals(after)) {
			return 1;
		}

		String f = sDateFormat.format(before);
		String c = sDateFormat.format(after);
		try {
			Date first = sDateFormat.parse(f);
			Date current = sDateFormat.parse(c);

			double day = current.getTime() - first.getTime();
			day = day / 1000 / 60 / 60 / 24;

			if (day > 0 && day < 1 && (current.getDate() != first.getDate())) {
				day++;
			}

			day++;
			return (int) day;
		} catch (ParseException e) {
			e.printStackTrace();
		}

		return 0;
	}

	@Override
	public boolean equals(Object o) {
		if (o == null) {
			return false;
		}

		if (o instanceof NoteInfo) {
			NoteInfo info = (NoteInfo) o;
			return info.note_id.equals(this.note_id);

		} else {
			return false;
		}
	}

	@Override
	public int hashCode() {
		return note_id.hashCode();
	}

	public static Comparator<NoteInfo> comparator = new Comparator<NoteInfo>() {

		@Override
		public int compare(NoteInfo lhs, NoteInfo rhs) {
			long ltime = Long.parseLong(lhs.note_create_at);
			long rtime = Long.parseLong(rhs.note_create_at);

			int delta = (int) (rtime - ltime);
			return delta;
		}
	};

	public String getFirstLocation() {
		if (!TextUtils.isEmpty(note_location)) {
			int i = note_location.indexOf(",");

			if (i > 0) {
				String str = note_location.substring(0, i);
				return subLocation(str);
			}
		}

		return subLocation(note_location);
	}

	public static String subLocation(String location) {
		if (location != null) {
			try {
				location = location.replace("中国", "");
				int startIndex = location.indexOf("省");
				if (startIndex < 0) {
					startIndex = location.indexOf("自治区");

					if (startIndex > 0) {
						startIndex += 3;
					} else {
						startIndex = 0;
					}
				} else {
					startIndex++;
				}

				int index = location.indexOf("市");

				if (index < 0) {
					index = location.indexOf("自治州");
					if (index > 0) {
						index += 2;
					}
				}

				if (index > 0) {
					location = location.substring(startIndex, index + 1);
				}

				return location;
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		return "";
	}
} public class UserInfo extends BaseInfo implements Serializable {
	
	/*各个字段的含义，参见  http://open.weibo.com/wiki/2/users/show  */

	private static final long serialVersionUID = 1L;
	public String id;
	public String screen_name;
	public String name;
	public String province;
	public String city;
	public String location;
	public String description;
	public String url;
	public String profile_image_url;
	public String domain;
	public String gender;
	public String followers_count;
	public String friends_count;
	public String statuses_count;
	public String favourites_count;
	public String created_at;

	public UserInfo(){
	}
	
	public static UserInfo create(JSONObject jsonObject) throws JSONException {

		UserInfo info = new UserInfo();

		String id = jsonObject.optString("id", null);
		if (id != null) {
			info.id = id;
		}

		String screen_name = jsonObject.optString("screen_name");
		if (screen_name != null) {
			info.screen_name = screen_name;
		}
		String name = jsonObject.optString("name");
		if (name != null) {
			info.name = name;
		}
		String province = jsonObject.optString("province");
		if (province != null) {
			info.province = province;
		}
		String city = jsonObject.optString("city");
		if (city != null) {
			info.city = city;
		}
		String location = jsonObject.optString("location");
		if (location != null) {
			info.location = location;
		}
		String description = jsonObject.optString("description");
		if (description != null) {
			info.description = description;
		}
		String profile_image_url = jsonObject.optString("profile_image_url");
		if (profile_image_url != null) {
			info.profile_image_url = profile_image_url;
		}

		return info;
	}

} public interface Constants {
	
	public static final String APP_KEY = "1710941608"; 
	public static final String REDIRECT_URL = "https://api.weibo.com/oauth2/default.html"; 
	public static final String SCOPE = 
	"email,direct_messages_read,direct_messages_write,"
	+ "friendships_groups_read,friendships_groups_write,statuses_to_me_read,"
	+ "follow_app_official_microblog," + "invitation_write";
	
	
	 public static final String CLIENT_ID         = "client_id";
	    public static final String RESPONSE_TYPE     = "response_type";
	    public static final String USER_REDIRECT_URL = "redirect_uri";
	    public static final String DISPLAY           = "display";
	    public static final String USER_SCOPE        = "scope";
	    public static final String PACKAGE_NAME      = "packagename";
	    public static final String KEY_HASH          = "key_hash";
	    
	    public static final String THUMNAIL_CACHE_PATH = "dandelion_thumnail";
	    public static final String THUMNAIL_CACHE_PROFILE_PATH = "dandelion_thumnail/.profile";
	    public static final String THUMNAIL_CACHE_PROFILE_BIG_PATH = "dandelion_thumnail/.profile_big";
	    public static final String THUMNAIL_CACHE_SMALL_PATH = "dandelion_thumnail/.small_thumnail";
	    public static final String ACTION_CREATE_NOTE_SUCCESS = "com.ustc.dystu.dandelion.CREATE_NOTE_SUCCESS";
	    public static final String ACTION_EDIT_NOTE_SUCCESS = "com.ustc.dystu.dandelion.EDIT_NOTE_SUCCESS";
	    
	    public static final int BUFFER_SIZE = 128 * 1024;

} public abstract class BaseFragment extends Fragment {

	private final String TAG = BaseFragment.class.getSimpleName();
	
	public static final int ERROR_RESPONSE = 0x99;

	@Override
	public void onAttach(Activity activity) {
		super.onAttach(activity);
	}

	@Override
	public void onCreate(Bundle savedInstanceState) {
		setHasOptionsMenu(true);
		setMenuVisibility(true);
		super.onCreate(savedInstanceState);
	}

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
		return super.onCreateView(inflater, container, savedInstanceState);
	}

	@Override
	public void onActivityCreated(Bundle savedInstanceState) {
		// onAttach(getActivity());
		afterActivityCreated();
		super.onActivityCreated(savedInstanceState);
		onFragmentShow();
	}
	
	@Override
	public void onActivityResult(int requestCode, int resultCode, Intent data) {
		Logger.d(TAG, "requestCode:"+requestCode);
		super.onActivityResult(requestCode, resultCode, data);
	}

	public void onFragmentShow() {
		Logger.d(TAG, "onFragmentShow: " + this.getClass().getName());
	}

	public void onFragmentHide() {
		Logger.d(TAG, "onFragmentHide: " + this.getClass().getName());
	}

	@Override
	public void onHiddenChanged(boolean hidden) {
		super.onHiddenChanged(hidden);

		Logger.d(TAG, "onHiddenChanged: " + this.getClass().getName());

		if (!hidden) {
			onFragmentShow();
		} else {
			onFragmentHide();
		}
	}

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

	@Override
	public void onResume() {
		Logger.d(TAG, "onResume: " + this.getClass().getName());
		super.onResume();
		// onFragmentResume();
	}

	protected void afterActivityCreated() {
	}

	@Override
	public void onPause() {
		Logger.d(TAG, "onPause: " + this.getClass().getName());
		super.onPause();
		// onFragmentPause();
	}

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

	public boolean onBackPressed() {
		return false;
	}

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

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

	@Override
	public void onDetach() {
		super.onDetach();
	}
} public class DandelionAPI extends AbsOpenAPI{
	
	public static final String DANDELION_SERVER = "http://1.infotravel.sinaapp.com";
	
	private static final String TAG = DandelionAPI.class.getSimpleName();
	
	private static DandelionAPI sInstance;
	private static Oauth2AccessToken sAccessToken;
	

	private static String sUid;
	private static Context sContext;
	
	public synchronized static DandelionAPI getInstance(Context ctx) {
		if (sInstance == null) {
			Oauth2AccessToken accessToken = AccessTokenKeeper
					.readAccessToken(ctx);
			sInstance = new DandelionAPI(accessToken);
			sAccessToken = accessToken;
			sUid = SharePrefUtils.getUid(ctx);
			sContext = ctx;
			Logger.d(TAG, "accessToken-->" + sAccessToken.getToken());
		}

		return sInstance;
	}

	public Oauth2AccessToken getAccessToken() {
		return sAccessToken;
	}

	public String getUid() {
		return sUid;
	}

	public void logout() {
		sInstance = null;
	}

	private DandelionAPI(Oauth2AccessToken oauth2AccessToken) {
		super(sContext, sUid, oauth2AccessToken);
	}
	
	public void getLocationStatus(RequestListener listener, String uid, int page) {
		String url = API_SERVER + "/place/user_timeline.json";
		

		WeiboParameters params = new WeiboParameters(Constants.APP_KEY);
		params.put("uid", uid);
		params.put("count", String.valueOf(50));
		params.put("page", page);

		requestAsync(url, params, "GET", listener);
	}

	public void getUserInfo(DandRequestListener listener, String uid) {
		String url = API_SERVER + "/users/show.json";

		String res = CacheUtils.getCache(
				sContext,
				CacheUtils.getKey(CacheUtils.CACHE_USER_INFO, new String[] {
						"uid", uid }), 3 * 24 * 60 * 60 * 1000);

		if (res != null) {
			listener.onCache(res);
		} else {
			WeiboParameters params = new WeiboParameters(Constants.APP_KEY);
			params.put("uid", uid);
			requestAsync(url, params, "GET", listener);
		}
	}

	public String[] getUserInfo(String uid) throws WeiboException {
		String url = API_SERVER + "/users/show.json";

		String res = CacheUtils.getCache(
				sContext,
				CacheUtils.getKey(CacheUtils.CACHE_USER_INFO, new String[] {
						"uid", uid }), 3 * 24 * 60 * 60 * 1000);

		if (res != null) {
			return new String[] { "true", res };
		}
		
			
		WeiboParameters params = new WeiboParameters(Constants.APP_KEY);
		params.put("uid", uid);
		params.put("access_token", sAccessToken.getToken());
		
		
		return new String[] { "false",
				new AsyncWeiboRunner(sContext).request(url, params, "GET") };
	}

	public void getFansList(RequestListener listener, int page, int count) {
		String url = API_SERVER + "/friendships/friends/bilateral.json";

		WeiboParameters params = new WeiboParameters(Constants.APP_KEY);
		params.put("uid", sUid);
		params.put("count", String.valueOf(count));
		params.put("page", String.valueOf(page));

		requestAsync(url, params, "GET", listener);
	}

	public String searchFans(String q, String count) throws WeiboException {
		String url = API_SERVER + "/search/suggestions/at_users.json";
		WeiboParameters params = new WeiboParameters(Constants.APP_KEY);
		params.put("access_token", sAccessToken.getToken());
		params.put("q", q);
		params.put("count", count);
		params.put("type", "3");
		
		
		return new AsyncWeiboRunner(sContext).request(url, params, "GET");
	}

	public String getAddress(GeoInfo geo) throws WeiboException {
		if (geo == null) {
			return null;
		}
		String url = API_SERVER + "/location/geo/geo_to_address.json";
		WeiboParameters params = new WeiboParameters(Constants.APP_KEY);
		params.put("access_token", sAccessToken.getToken());
		params.put("coordinate", geo.longitude + "," + geo.latitude);

		return new AsyncWeiboRunner(sContext).request(url, params, "GET");
	}

	/*public String getGoogleAddress(GeoInfo geo) throws WeiboException {
		if (geo == null) {
			return null;  
		}

		//   http://ditu.google.com/maps/api/geocode/json
		String url = "http://ditu.google.com/maps/api/geocode/json";
		WeiboParameters params = new WeiboParameters(Constants.APP_KEY);
		params.put("latlng", geo.latitude + "," + geo.longitude);
		params.put("language", "zh-CN");
		params.put("sensor", false);

		Logger.d(TAG, "lat:" + geo.latitude + "; long:" + geo.longitude);
		
		

		return new AsyncWeiboRunner(sContext).request(url, params, "GET");
	}*/
	
	
	public void getSelectTravelNotes(DandRequestListener listener,
			boolean useCache) {
		String url = DANDELION_SERVER + "/travel_select.php";

		String res = null;
		if (useCache) {
			res = CacheUtils
					.getCache(sContext, CacheUtils.getKey(
							CacheUtils.CACHE_SELECTED_NOTES, null), 0);
		}

		if (res != null) {
			listener.onCache(res);
		} else {
			WeiboParameters params = new WeiboParameters(Constants.APP_KEY);
			requestAsync(url, params, "GET", listener);
		}
	}

	public void getLatestTravelNotes(RequestListener listener, int num) {
		String url = "http://1.infotravel.sinaapp.com/travel_latest.php";
		WeiboParameters params = new WeiboParameters(Constants.APP_KEY);
		params.put("note_num", num);
		requestAsync(url, params, "GET", listener);
	}

	public void getTravelNotes(RequestListener listener, String uid) {
		String url = "http://1.infotravel.sinaapp.com/travel_me.php";
		WeiboParameters params = new WeiboParameters(Constants.APP_KEY);
		params.put("uid", uid);
		requestAsync(url, params, "POST", listener);
	}

	public void getWeiboBatchInfo(RequestListener listener, String weiboIds) {
		// 需要高级别的权限
		String url = API_SERVER + "/statuses/show_batch.json";
		WeiboParameters params = new WeiboParameters(Constants.APP_KEY);
		params.put("ids", weiboIds);
		params.put("trim_user", "1");
		requestAsync(url, params, "GET", listener);
	}

	public ArrayList<FootInfo> getWeiboInfo(String[] weiboIds) {
		ArrayList<FootInfo> list = new ArrayList<FootInfo>();

		FootInfo info;
		for (int i = 0; i < weiboIds.length; i++) {
			try {
				String[] rs = getWeiboInfo(weiboIds[i]);

				if (rs != null) {
					JSONObject jo = new JSONObject(rs[1]);
					info = FootInfo.create(jo, true);
					list.add(info);

					if ("false".equals(rs[0])) {
						CacheUtils.updateCache(sContext, CacheUtils.getKey(
								CacheUtils.CACHE_WEIBO_INFO, new String[] {
										"id", weiboIds[i] }), rs[1]);
					}
				}

			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		return list;
	}

	public String[] getWeiboInfo(String weiboId) throws WeiboException {
		String url = API_SERVER + "/statuses/show.json";

		String res = CacheUtils.getCache(
				sContext,
				CacheUtils.getKey(CacheUtils.CACHE_WEIBO_INFO, new String[] {
						"id", weiboId }), 3 * 24 * 60 * 60 * 1000);

		if (res != null) {
			return new String[] { "true", res };
		}

		WeiboParameters params = new WeiboParameters(Constants.APP_KEY);
		params.put("id", weiboId);
		params.put("access_token", sAccessToken.getToken());
		return new String[] { "false",
				new AsyncWeiboRunner(sContext).request(url, params, "GET") };
		
	}

	public void createTravelNote(RequestListener listener, NoteInfo info,
			String weiboInfo) {
		String url = "http://1.infotravel.sinaapp.com/travel_create.php";
		WeiboParameters params = new WeiboParameters(Constants.APP_KEY);
		params.put("note_folder_url", info.note_folder_url);
		params.put("note_is_suggest", info.note_is_suggest);
		params.put("note_location", info.note_location);
		params.put("note_title", info.note_title);
		params.put("note_uid", info.note_uid);
		params.put("note_time_from", info.note_time_from);
		params.put("note_time_to", info.note_time_to);

		params.put("weiboInfo", weiboInfo);
		requestAsync(url, params, "POST", listener); 
	}

	public ArrayList<NearInfo> getNearTravelNote(double lat, double lon) {
		String url = "http://1.infotravel.sinaapp.com/travel_near.php";
		WeiboParameters params = new WeiboParameters(Constants.APP_KEY);
		params.put("lat", String.valueOf(lat));
		params.put("lon", String.valueOf(lon));

		ArrayList<NearInfo> data;
		try {
			String response = new AsyncWeiboRunner(sContext).request(url, params, "GET");
			JSONArray array = new JSONArray(response);
			data = NearInfo.create(array);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}

		if (data != null) {
			for (NearInfo nearInfo : data) {
				try {
					String[] rs = getWeiboInfo(nearInfo.weiboId);

					if (!"[]".equals(rs) && rs != null) {
						JSONObject jo = new JSONObject(rs[1]);
						nearInfo.footInfo = FootInfo.create(jo, true);

						if ("false".equals(rs[0])) {
							CacheUtils.updateCache(sContext, CacheUtils.getKey(
									CacheUtils.CACHE_WEIBO_INFO, new String[] {
											"id", nearInfo.weiboId }), rs[1]);
						}
					}
				} catch (Exception e) {
					e.printStackTrace();
					continue;
				}
			}
		}

		return data;
	}

	public NoteInfo getRandomTravelNote() {
		String url = "http://1.infotravel.sinaapp.com/random_note_info.php";
		WeiboParameters params = new WeiboParameters(Constants.APP_KEY);
		try {
			String rs = new AsyncWeiboRunner(sContext).request(url, params, "GET");

			NoteInfo info = NoteInfo.create(new JSONObject(rs));

			String[] userInfo = getUserInfo(info.note_uid);

			if (userInfo != null) {
				info.userIno = UserInfo.create(new JSONObject(userInfo[1]));

				if ("false".equals(userInfo[0])) {
					CacheUtils.updateCache(sContext, CacheUtils.getKey(
							CacheUtils.CACHE_USER_INFO, new String[] { "uid",
									info.note_uid }), userInfo[1]);
				}
			}

			return info;
		} catch (WeiboException e) {
			e.printStackTrace();
		} catch (JSONException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return null;
	}

	public void deleteNote(RequestListener listener, NoteInfo info) {
		String url = "http://1.infotravel.sinaapp.com/travel_delete.php";
		WeiboParameters params = new WeiboParameters(Constants.APP_KEY);
		params.put("note_id", info.note_id);
		requestAsync(url, params, "POST", listener);
	}

	public void updateNote(RequestListener listener, NoteInfo info) {
		String url = "http://1.infotravel.sinaapp.com/travel_update.php";
		WeiboParameters params = new WeiboParameters(Constants.APP_KEY);  

		Logger.d(TAG, "note titl-->" + info.note_title);

		params.put("note_id", info.note_id);
		params.put("note_title", info.note_title);
		params.put("note_folder_url", info.note_folder_url);
		requestAsync(url, params, "POST", listener);
	}

	public void getComments(RequestListener listener, String weiboId) {
		String url = API_SERVER + "/comments/show.json";
		WeiboParameters params = new WeiboParameters(Constants.APP_KEY);
		params.put("id", weiboId);
		requestAsync(url, params, "GET", listener);
	}

	public void setComments(RequestListener listener, String weiboId,
			String comment) {
		String url = API_SERVER + "/comments/create.json";
		WeiboParameters params = new WeiboParameters(Constants.APP_KEY);
		params.put("id", weiboId);
		params.put("comment", comment);
		requestAsync(url, params, "POST", listener);
	}
} public class LocationTask {

	private static final int MAX_TASK_NUM = 20;
	private ArrayList<WeakReference<LoactionAsycTask>> mTaskList = new ArrayList<WeakReference<LoactionAsycTask>>();
	private Context mContext;

	public LocationTask(Context ctx) {
		mContext = ctx;
	}

	public void loadLocation(FootInfo geo, TextView tvLocation) {
		final LoactionAsycTask task = new LoactionAsycTask(tvLocation);
		tvLocation.setTag(task);

		executeTask(task, geo);
	}

	private void executeTask(LoactionAsycTask task, Object data) {
		if (mTaskList.size() >= MAX_TASK_NUM) {
			LoactionAsycTask bitmapWorkerTask = mTaskList.get(0).get();
			if (bitmapWorkerTask != null) {
				bitmapWorkerTask.cancel(true);
			}

			mTaskList.remove(0);
		}
		mTaskList.add(new WeakReference<LoactionAsycTask>(task));

		try {
			task.execute(data);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	class LoactionAsycTask extends DandAsyncTask<Object, Void, FootInfo> {
		private FootInfo data;
		private final WeakReference<TextView> textViewReference;

		public LoactionAsycTask(TextView textView) {
			textViewReference = new WeakReference<TextView>(textView);
		}

		@Override
		protected FootInfo doInBackground(Object... params) {
			data = (FootInfo) params[0];

			// 先从微博接口访问地址信息
			try {
				String response = DandelionAPI.getInstance(mContext)
						.getAddress(data.geo);
				if (TextUtils.isEmpty(response)
						|| response.contains("error_code")) {
					JSONObject obj = new JSONObject(response);
					String error = obj.getString("error");

					Logger.d("LocationUtils", "error-->" + error);
				} else {
					JSONObject obj = new JSONObject(response);
					JSONArray array = obj.getJSONArray("geos");
					JSONObject jo = (JSONObject) array.get(0);
					
					data.city = jo.getString("city_name");
					data.province = jo.getString("province_name");
					//data.formate_loaction = jo.getString("address");
					data.formate_loaction = data.province + data.city;
					return data;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}

			/*// 如果微博接口返回为空, 再从google访问
			try {
				String response = DandelionAPI.getInstance(mContext)
						.getGoogleAddress(data.geo);
				if (response != null) {
					JSONObject jo = new JSONObject(response);
					JSONArray array = jo.getJSONArray("results");

					GoogleGeoInfo info = GoogleGeoInfo.getBestLocation(array);
					if (info != null) {
						data.formate_loaction = info.formatted_address;
						data.city = info.getComponent(GoogleGeoInfo.TYPE_CITY);
						data.city_area = info
								.getComponent(GoogleGeoInfo.TYPE_CITY_AREA);
						data.province = info
								.getComponent(GoogleGeoInfo.TYPE_PROVINCE);
						data.country = info
								.getComponent(GoogleGeoInfo.TYPE_COUNTRY);

						Logger.d("Test", "city:" + data.city + "; province:"
								+ data.province + ";country:" + data.country
								+ "; city_area:" + data.city_area);
						return data;
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}*/

			return null;

			// {
			// "geos": [
			// {
			// "longitude": "116.39794",
			// "latitude": "39.90817",
			// "city": "11",
			// "province": "32",
			// "city_name": "北京",
			// "province_name": "朝阳区",
			// "address": "中国北京市海淀区中关村"
			// }
			// ]
			// }
		}

		@Override
		protected void onPostExecute(FootInfo result) {
			// if cancel was called on this task or the "exit early" flag is set
			// then we're done
			if (isCancelled()) {
				result = null;
			}

			final TextView textView = getAttachedTextView();
			if (result != null && textView != null) {
				textView.setText(result.getFormatLoaciton());
			}
		}

		private TextView getAttachedTextView() {
			final TextView textView = textViewReference.get();
			final LoactionAsycTask bitmapWorkerTask = getWorkerTask(textView);

			if (this == bitmapWorkerTask) {
				return textView;
			}

			return null;
		}
	}

	private static LoactionAsycTask getWorkerTask(TextView textView) {
		if (textView != null) {
			final Object task = textView.getTag();
			if (task instanceof LoactionAsycTask) {
				return (LoactionAsycTask) task;
			}
		}
		return null;
	}

} public class Logger {
	
	public static int DEBUG_LEVEL = 6;// ÆÁ±Îlog,ÐÞ¸ÄÎª0

	private static final int VERBOSE = 5;
	private static final int DEBUG = 4;
	private static final int INFO = 3;
	private static final int WARN = 2;
	private static final int ERROR = 1;

	public static int v(String tag, String msg) {
		if (DEBUG_LEVEL > VERBOSE) {
			return Log.v(tag, msg);
		} else {
			return 0;
		}
	}

	public static int v(String tag, String msg, Throwable e) {
		if (DEBUG_LEVEL > VERBOSE) {			return Log.v(tag, msg, e);
		} else {
			return 0;
		}
	}

	public static int d(String tag, String msg) {
		if (DEBUG_LEVEL > DEBUG) {
			return Log.d(tag, msg);
		} else {
			return 0;
		}
	}

	public static int d(String tag, String msg, Throwable e) {
		if (DEBUG_LEVEL > DEBUG) {
			return Log.d(tag, msg, e);
		} else {
			return 0;
		}
	}

	public static int i(String tag, String msg) {
		if (DEBUG_LEVEL > INFO) {
			return Log.i(tag, msg);
		} else {
			return 0;
		}
	}

	public static int i(String tag, String msg, Throwable e) {
		if (DEBUG_LEVEL > INFO) {
			return Log.i(tag, msg, e);
		} else {
			return 0;
		}
	}

	public static int w(String tag, String msg) {
		if (DEBUG_LEVEL > WARN) {
			return Log.w(tag, msg);
		} else {
			return 0;
		}
	}

	public static int w(String tag, String msg, Throwable e) {
		if (DEBUG_LEVEL > WARN) {
			return Log.w(tag, msg, e);
		} else {
			return 0;
		}
	}

	public static int e(String tag, String msg) {
		if (DEBUG_LEVEL > ERROR) {
			return Log.e(tag, msg);
		} else {
			return 0;
		}
	}

	public static int e(String tag, String msg, Throwable e) {
		if (DEBUG_LEVEL > ERROR) {
			return Log.e(tag, msg, e);
		} else {
			return 0;
		}
	}

} public class ImageCache {
    private static final String TAG = "ImageCache";

    // Default memory cache size
    private static final int DEFAULT_MEM_CACHE_SIZE = 1024 * 1024 * 5; // 5MB

    // Default disk cache size
    private static final int DEFAULT_DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB

    // Compression settings when writing images to disk cache
    private static final CompressFormat DEFAULT_COMPRESS_FORMAT = CompressFormat.JPEG;
    private static final int DEFAULT_COMPRESS_QUALITY = 70;

    // Constants to easily toggle various caches
    private static final boolean DEFAULT_MEM_CACHE_ENABLED = true;
    private static final boolean DEFAULT_DISK_CACHE_ENABLED = true;
    private static final boolean DEFAULT_CLEAR_DISK_CACHE_ON_START = false;

    private DiskLruCache mDiskCache;
    private LruCache<String, Bitmap> mMemoryCache;

    /**
     * Creating a new ImageCache object using the specified parameters.
     *
     * @param context The context to use
     * @param cacheParams The cache parameters to use to initialize the cache
     */
    public ImageCache(Context context, ImageCacheParams cacheParams) {
        init(context, cacheParams);
    }

    /**
     * Creating a new ImageCache object using the default parameters.
     *
     * @param context The context to use
     * @param uniqueName A unique name that will be appended to the cache directory
     */
    public ImageCache(Context context, String uniqueName) {
        init(context, new ImageCacheParams(uniqueName));
    }

    /**
     * Find and return an existing ImageCache stored in a {@link RetainFragment}, if not found a new
     * one is created with defaults and saved to a {@link RetainFragment}.
     *
     * @param activity The calling {@link FragmentActivity}
     * @param uniqueName A unique name to append to the cache directory
     * @return An existing retained ImageCache object or a new one if one did not exist.
     */
    public static ImageCache findOrCreateCache(
            final FragmentActivity activity, final String uniqueName) {
        return findOrCreateCache(activity, new ImageCacheParams(uniqueName));
    }

    /**
     * Find and return an existing ImageCache stored in a {@link RetainFragment}, if not found a new
     * one is created using the supplied params and saved to a {@link RetainFragment}.
     *
     * @param activity The calling {@link FragmentActivity}
     * @param cacheParams The cache parameters to use if creating the ImageCache
     * @return An existing retained ImageCache object or a new one if one did not exist
     */
    public static ImageCache findOrCreateCache(
            final FragmentActivity activity, ImageCacheParams cacheParams) {

        // Search for, or create an instance of the non-UI RetainFragment
        final RetainFragment mRetainFragment = RetainFragment.findOrCreateRetainFragment(
                activity.getSupportFragmentManager());

        // See if we already have an ImageCache stored in RetainFragment
        ImageCache imageCache = (ImageCache) mRetainFragment.getObject();

        // No existing ImageCache, create one and store it in RetainFragment
        if (imageCache == null) {
            imageCache = new ImageCache(activity, cacheParams);
            mRetainFragment.setObject(imageCache);
        }

        return imageCache;
    }

    /**
     * Initialize the cache, providing all parameters.
     *
     * @param context The context to use
     * @param cacheParams The cache parameters to initialize the cache
     */
    private void init(Context context, ImageCacheParams cacheParams) {
        final File diskCacheDir = DiskLruCache.getDiskCacheDir(context, cacheParams.uniqueName);

        // Set up disk cache
        if (cacheParams.diskCacheEnabled) {
            mDiskCache = DiskLruCache.openCache(context, diskCacheDir, cacheParams.diskCacheSize);
            if (mDiskCache == null) {
            	return;
            }
            mDiskCache.setCompressParams(cacheParams.compressFormat, cacheParams.compressQuality);
            if (cacheParams.clearDiskCacheOnStart) {
                mDiskCache.clearCache();
            }
        }

        // Set up memory cache
        if (cacheParams.memoryCacheEnabled) {
            mMemoryCache = new LruCache<String, Bitmap>(cacheParams.memCacheSize) {
                /**
                 * Measure item size in bytes rather than units which is more practical for a bitmap
                 * cache
                 */
                @Override
                protected int sizeOf(String key, Bitmap bitmap) {
                    return Utils.getBitmapSize(bitmap);
                }
            };
        }
    }

    public void addBitmapToCache(String data, Bitmap bitmap, boolean needDiskCache) {
        if (data == null || bitmap == null) {
            return;
        }

        // Add to memory cache
        if (mMemoryCache != null && mMemoryCache.get(data) == null) {
            mMemoryCache.put(data, bitmap);
        }

        // Add to disk cache
        if (needDiskCache && mDiskCache != null && !mDiskCache.containsKey(data)) {
            mDiskCache.put(data, bitmap);
        }
    }

    /**
     * Get from memory cache.
     *
     * @param data Unique identifier for which item to get
     * @return The bitmap if found in cache, null otherwise
     */
    public Bitmap getBitmapFromMemCache(String data) {
        if (mMemoryCache != null) {
            final Bitmap memBitmap = mMemoryCache.get(data);
            if (memBitmap != null) {
                if (BuildConfig.DEBUG) {
					// Log.d(TAG, "Memory cache hit");
                }
                return memBitmap;
            }
        }
        return null;
    }

    /**
     * Get from disk cache.
     *
     * @param data Unique identifier for which item to get
     * @return The bitmap if found in cache, null otherwise
     */
    public Bitmap getBitmapFromDiskCache(String data) {
        if (mDiskCache != null) {
            return mDiskCache.get(data);
        }
        return null;
    }

    public void clearCaches() {
    	if(mDiskCache != null) {
			mDiskCache.clearCache();
		}
		
		if(mMemoryCache!=null) {
			mMemoryCache.evictAll();
		}
    }

    public LruCache<String, Bitmap> getMemCache(){
    	return mMemoryCache;
    }
    
    /**
     * A holder class that contains cache parameters.
     */
    public static class ImageCacheParams {
        public String uniqueName;
        public int memCacheSize = DEFAULT_MEM_CACHE_SIZE;
        public int diskCacheSize = DEFAULT_DISK_CACHE_SIZE;
        public CompressFormat compressFormat = DEFAULT_COMPRESS_FORMAT;
        public int compressQuality = DEFAULT_COMPRESS_QUALITY;
        public boolean memoryCacheEnabled = DEFAULT_MEM_CACHE_ENABLED;
        public boolean diskCacheEnabled = DEFAULT_DISK_CACHE_ENABLED;
        public boolean clearDiskCacheOnStart = DEFAULT_CLEAR_DISK_CACHE_ON_START;

        public ImageCacheParams(String uniqueName) {
            this.uniqueName = uniqueName;
        }
    }
} public class ImageFetcher extends ImageResizer {
	private static final String TAG = "ImageFetcher";
	private static final int HTTP_CACHE_SIZE = 10 * 1024 * 1024; // 10MB
	public static final String HTTP_CACHE_DIR = "http";

	/**
	 * Initialize providing a target image width and height for the processing
	 * images.
	 * 
	 * @param context
	 * @param imageWidth
	 * @param imageHeight
	 */
	public ImageFetcher(Context context, int imageWidth, int imageHeight) {
		super(context, imageWidth, imageHeight);
		init(context);
	}

	/**
	 * Initialize providing a single target image size (used for both width and
	 * height);
	 * 
	 * @param context
	 * @param imageSize
	 */
	public ImageFetcher(Context context, int imageSize) {
		super(context, imageSize);
		init(context);
	}

	private void init(Context context) {
		checkConnection(context);
	}

	/**
	 * Simple network connection check.
	 * 
	 * @param context
	 */
	private void checkConnection(Context context) {
		final ConnectivityManager cm = (ConnectivityManager) context
				.getSystemService(Context.CONNECTIVITY_SERVICE);
		final NetworkInfo networkInfo = cm.getActiveNetworkInfo();
		if (networkInfo == null || !networkInfo.isConnectedOrConnecting()) {
			/*
			 * Toast.makeText(context, "No network connection found.",
			 * Toast.LENGTH_LONG).show(); Log.e(TAG,
			 * "checkConnection - no connection found");
			 */
		}
	}

	/**
	 * The main process method, which will be called by the ImageWorker in the
	 * VdiskAsyncTask background thread.
	 * 
	 * @param data
	 *            The data to load the bitmap, in this case, a regular http URL
	 * @return The downloaded and resized bitmap
	 */
	private Bitmap processBitmap(String data) {
		if (BuildConfig.DEBUG) {
			// Log.d(TAG, "processBitmap - " + data);
		}

		// Download a bitmap, write it to a file
		final File f = downloadBitmap(mContext, data);

		if (f != null) {
			// Return a sampled down version
			return decodeSampledBitmapFromFile(f.toString(), mImageWidth,
					mImageHeight);
		}

		return null;
	}

	@Override
	protected Bitmap processBitmap(Object data) {
		return processBitmap(String.valueOf(data));
	}

	/**
	 * Download a bitmap from a URL, write it to a disk and return the File
	 * pointer. This implementation uses a simple disk cache.
	 * 
	 * @param context
	 *            The context to use
	 * @param urlString
	 *            The URL to fetch
	 * @return A File pointing to the fetched bitmap
	 */
	public static File downloadBitmap(Context context, String urlString) {
		final File cacheDir = DiskLruCache.getDiskCacheDir(context,
				HTTP_CACHE_DIR);

		final DiskLruCache cache = DiskLruCache.openCache(context, cacheDir,
				HTTP_CACHE_SIZE);

		if (cache == null) {
			return null;
		}

		Logger.d(TAG, "downloadBitmap urlString: " + urlString);

		final File cacheFile = new File(cache.createFilePath(urlString));

		if (cache.containsKey(urlString)) {
			if (BuildConfig.DEBUG) {
				// Log.d(TAG, "downloadBitmap - found in http cache - "
				// + urlString);
			}
			return cacheFile;
		}

		if (BuildConfig.DEBUG) {
			// Log.d(TAG, "downloadBitmap - downloading - " + urlString);
		}

		if (urlString.startsWith("file://")) {
			BufferedOutputStream out = null;
			InputStream in = null;
			try {
				in = new BufferedInputStream(new FileInputStream(new File(
						urlString.substring("file://".length()))),
						Utils.IO_BUFFER_SIZE);
				out = new BufferedOutputStream(new FileOutputStream(cacheFile),
						Utils.IO_BUFFER_SIZE);

				int b;
				while ((b = in.read()) != -1) {
					out.write(b);
				}
				out.flush();

				return cacheFile;

			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				if (in != null) {
					try {
						in.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
				if (out != null) {
					try {
						out.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		} else {
			String downloadUrl = urlString;

			Logger.d(TAG, "thumbnail downloadurl: " + downloadUrl);

			Utils.disableConnectionReuseIfNecessary();
			HttpURLConnection urlConnection = null;
			InputStream in = null;
			BufferedOutputStream out = null;

			try {
				Logger.d(TAG, "thumbnail downloadurl: " + downloadUrl);
				final URL url = new URL(downloadUrl);
				urlConnection = (HttpURLConnection) url.openConnection();
				urlConnection.setReadTimeout(100 * 1000);
				urlConnection.setConnectTimeout(100 * 1000);
				urlConnection.setUseCaches(false);
				in = new BufferedInputStream(urlConnection.getInputStream(),
						Utils.IO_BUFFER_SIZE);
				out = new BufferedOutputStream(new FileOutputStream(cacheFile),
						Utils.IO_BUFFER_SIZE);

				int b;
				while ((b = in.read()) != -1) {
					out.write(b);
				}

				return cacheFile;

			} catch (Exception e) {
				Logger.e(TAG, "Error in downloadBitmap - " + e);
			} finally {
				if (urlConnection != null) {
					try {
						urlConnection.disconnect();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				if (in != null) {
					try {
						in.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
				if (out != null) {
					try {
						out.close();
					} catch (Exception e) {
						Logger.e(TAG, "Error in downloadBitmap - " + e);
					}
				}
			}
		}

		return null;
	}
} public class RoundedImageView extends ImageView {

	public RoundedImageView(Context context) {
		super(context);
	}

	public RoundedImageView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public RoundedImageView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}

	@Override
	protected void onDraw(Canvas canvas) {

		Drawable drawable = getDrawable();

		if (drawable == null) {
			return;
		}

		if (getWidth() == 0 || getHeight() == 0) {
			return;
		}
		Bitmap b = ((BitmapDrawable) drawable).getBitmap();
		Bitmap bitmap = b.copy(Bitmap.Config.ARGB_8888, true);

		int w = getWidth(), h = getHeight();

		Bitmap roundBitmap = getCroppedBitmap(bitmap, w);
		canvas.drawBitmap(roundBitmap, 0, 0, null);

	}

	public static Bitmap getCroppedBitmap(Bitmap bmp, int radius) {
		Bitmap sbmp;
		if (bmp.getWidth() != radius || bmp.getHeight() != radius)
			sbmp = Bitmap.createScaledBitmap(bmp, radius, radius, false);
		else
			sbmp = bmp;
		Bitmap output = Bitmap.createBitmap(sbmp.getWidth(), sbmp.getHeight(),
				Config.ARGB_8888);
		Canvas canvas = new Canvas(output);

		final int color = 0xffa19774;
		final Paint paint = new Paint();
		final Rect rect = new Rect(0, 0, sbmp.getWidth(), sbmp.getHeight());

		paint.setAntiAlias(true);
		paint.setFilterBitmap(true);
		paint.setDither(true);
		canvas.drawARGB(0, 0, 0, 0);
		paint.setColor(Color.parseColor("#BAB399"));
		canvas.drawCircle(sbmp.getWidth() / 2 + 0.7f,
				sbmp.getHeight() / 2 + 0.7f, sbmp.getWidth() / 2 + 0.1f, paint);
		paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
		canvas.drawBitmap(sbmp, rect, rect, paint);

		return output;
	}

} public class Utils {
	public static final int IO_BUFFER_SIZE = 128 * 1024;

	private Utils() {
	};

	/**
	 * Workaround for bug pre-Froyo, see here for more info:
	 * http://android-developers.blogspot.com/2011/09/androids-http-clients.html
	 */
	public static void disableConnectionReuseIfNecessary() {
		// HTTP connection reuse which was buggy pre-froyo
		if (hasHttpConnectionBug()) {
			System.setProperty("http.keepAlive", "false");
		}
	}

	/**
	 * Get the size in bytes of a bitmap.
	 * 
	 * @param bitmap
	 * @return size in bytes
	 */
	public static int getBitmapSize(Bitmap bitmap) {
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
			return bitmap.getByteCount();
		}
		// Pre HC-MR1
		return bitmap.getRowBytes() * bitmap.getHeight();
	}

	/**
	 * Check if external storage is built-in or removable.
	 * 
	 * @return True if external storage is removable (like an SD card), false
	 *         otherwise.
	 */
	public static boolean isExternalStorageRemovable() {
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
			return Environment.isExternalStorageRemovable();
		}
		return true;
	}

	/**
	 * Get the external app cache directory.
	 * 
	 * @param context
	 *            The context to use
	 * @return The external cache dir
	 */
	public static File getExternalCacheDir(Context context) {
		if (hasExternalCacheDir()) {
			return context.getExternalCacheDir();
		}

		// Before Froyo we need to construct the external cache dir ourselves
		final String cacheDir = "/Android/data/" + context.getPackageName()
				+ "/cache/";
		return new File(Environment.getExternalStorageDirectory().getPath()
				+ cacheDir);
	}

	/**
	 * Check how much usable space is available at a given path.
	 * 
	 * @param path
	 *            The path to check
	 * @return The space available in bytes
	 */
	public static long getUsableSpace(File path) {
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
			return path.getUsableSpace();
		}
		final StatFs stats = new StatFs(path.getPath());
		return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks();
	}

	/**
	 * Get the memory class of this device (approx. per-app memory limit)
	 * 
	 * @param context
	 * @return
	 */
	public static int getMemoryClass(Context context) {
		return ((ActivityManager) context
				.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
	}

	/**
	 * Check if OS version has a http URLConnection bug. See here for more
	 * information:
	 * http://android-developers.blogspot.com/2011/09/androids-http-clients.html
	 * 
	 * @return
	 */
	public static boolean hasHttpConnectionBug() {
		return Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO;
	}

	/**
	 * Check if OS version has built-in external cache dir method.
	 * 
	 * @return
	 */
	public static boolean hasExternalCacheDir() {
		return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO;
	}

	/**
	 * Check if ActionBar is available.
	 * 
	 * @return
	 */
	public static boolean hasActionBar() {
		return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
	}

	/**
	 * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
	 */
	public static int dip2px(Context context, float dpValue) {
		final float scale = context.getResources().getDisplayMetrics().density;
		return (int) (dpValue * scale + 0.5f);
	}

	public static int[] getScreenWidthAndHeight(Context ctx) {
		WindowManager mWm = (WindowManager) ctx
				.getSystemService(Context.WINDOW_SERVICE);

		DisplayMetrics dm = new DisplayMetrics();
		// 获取屏幕信息
		mWm.getDefaultDisplay().getMetrics(dm);

		int screenWidth = dm.widthPixels;

		int screenHeigh = dm.heightPixels;

		return new int[] { screenWidth, screenHeigh };
	}
	
	public static int[] getMidPicWidthAndHeight(Context ctx){
		int[] wh = getScreenWidthAndHeight(ctx);
		//return new int[]{wh[0], wh[0]};
		return new int[]{300,300};
	}
	
	public static int[] getBigPicWidthAndHeight(Context ctx){
		int[] wh = getScreenWidthAndHeight(ctx);
		
		Logger.d("Test", "width-->" + wh[0]);
		Logger.d("Test", "height-->" + wh[1]);
		
		return wh;
	}
}
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.PopupWindow;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.emilsjolander.components.stickylistheaders.StickyListHeadersAdapter;
import com.emilsjolander.components.stickylistheaders.StickyListHeadersListView;
import com.ustc.dystu.dandelion.bean.FootInfo;
import com.ustc.dystu.dandelion.bean.NoteInfo;
import com.ustc.dystu.dandelion.bean.UserInfo;
import com.ustc.dystu.dandelion.constant.Constants;
import com.ustc.dystu.dandelion.fragment.BaseFragment;
import com.ustc.dystu.dandelion.net.DandelionAPI;
import com.ustc.dystu.dandelion.utils.LocationTask;
import com.ustc.dystu.dandelion.utils.Logger;
import com.ustc.dystu.dandelion.utils.image.ImageCache;
import com.ustc.dystu.dandelion.utils.image.ImageFetcher;
import com.ustc.dystu.dandelion.utils.image.RoundedImageView;
import com.ustc.dystu.dandelion.utils.image.Utils;
package com.ustc.dystu.dandelion;




public class NoteInfoActivity extends Activity implements OnClickListener{

	private static final String TAG = "NoteInfoActivity";
	private static final int REQUEST_GET_NOTE_INFO = 0x1;

	ImageView ivBack;
	ImageView ivShareOrEdit;
	ProgressBar pbProgress;

	NoteInfo mNoteInfo;

	ArrayList<FootInfo> mFootList = new ArrayList<FootInfo>();