public class ConversationAdapter extends CursorAdapter {

	private LayoutInflater mInflater;
	private TweetImageSpan mTweetImageSpan;

	public ConversationAdapter(Context context) {
		super(context, null, 0);
		mInflater = LayoutInflater.from(context);
		mTweetImageSpan = new TweetImageSpan(context);
	}

	@Override
	public View newView(Context context, Cursor cursor, ViewGroup parent) {
		View view = mInflater.inflate(R.layout.comments_2_me, null);
		ViewHolder holder = new ViewHolder();

		holder.myText = (TweetTextView) view.findViewById(R.id.my_text);
		holder.myTextCreateAt = (TextView) view.findViewById(R.id.my_create_at);
		holder.myTextCreateAtIndex = cursor.getColumnIndex(Comment.created_at);

		holder.avatar = (ImageView) view.findViewById(R.id.avatar);
		holder.screenName = (TextView) view.findViewById(R.id.screen_name);
		holder.screenNameIndex = cursor.getColumnIndex(User.screen_name);
		holder.remarkIndex = cursor.getColumnIndex(User.remark);
		holder.hisText = (TweetTextView) view.findViewById(R.id.his_text);
		holder.uidIndex = cursor.getColumnIndex(Comment.uid);
		holder.hisCreateAt = (TextView) view.findViewById(R.id.his_create_at);
		holder.hisCreateAtIndex = cursor.getColumnIndex(Comment.created_at);

		holder.avatarIndex = cursor.getColumnIndex(User.avatar_large);
		holder.hisTextIndex = cursor.getColumnIndex(Comment.columnText);
		holder.statusIndex = cursor.getColumnIndex(Comment.status);
		holder.replyCommentIndex = cursor.getColumnIndex(Comment.reply_comment);

		view.setTag(holder);
		return view;
	}

	@Override
	public void bindView(View view, final Context context, Cursor cursor) {
		final ViewHolder holder = (ViewHolder) view.getTag();
		Picasso.with(context)
				.load(cursor.getString(holder.avatarIndex))
				.error(R.drawable.error)
				.placeholder(R.drawable.error)
				.into(holder.avatar);

		String replyString = cursor.getString(holder.replyCommentIndex);
		String statusString = cursor.getString(holder.statusIndex);
		if (TextUtils.isEmpty(replyString)) {
			// 表示ta回复我...
			JSONObject status = null;
			try {
				status = new JSONObject(statusString);
			} catch (JSONException e) {
				Toast.makeText(context, R.string.malformed_json, Toast.LENGTH_LONG).show();
			}
			if (status != null) {
				holder.myText.setText(status.optString(Status.text));
				CatnutUtils.vividTweet(holder.myText, mTweetImageSpan);
			}
		} else {
			// 表示ta回复我的评论
			JSONObject reply = null;
			try {
				reply = new JSONObject(replyString);
			} catch (JSONException e) {
				Toast.makeText(context, R.string.malformed_json, Toast.LENGTH_LONG).show();
			}
			if (reply != null) {
				holder.myText.setText(reply.optString(Comment.text));
				CatnutUtils.vividTweet(holder.myText, mTweetImageSpan);
			}
		}

		holder.hisText.setText(cursor.getString(holder.hisTextIndex));
		CatnutUtils.vividTweet(holder.hisText, mTweetImageSpan);
		final String screenName = cursor.getString(holder.screenNameIndex);
		String remark = cursor.getString(holder.remarkIndex);
		holder.screenName.setText(TextUtils.isEmpty(remark) ? screenName : remark);
		holder.hisCreateAt.setText(DateUtils.getRelativeTimeSpanString(
				DateTime.getTimeMills(cursor.getString(holder.hisCreateAtIndex))
		));
		holder.myTextCreateAt.setText(DateUtils.getRelativeTimeSpanString(
				DateTime.getTimeMills(cursor.getString(holder.myTextCreateAtIndex))
		));
		// bind tag for click listener
		final long uid = cursor.getLong(holder.uidIndex);
		holder.avatar.setOnTouchListener(new View.OnTouchListener() {
			@Override
			public boolean onTouch(View v, MotionEvent event) {
				return CatnutUtils.imageOverlay(v, event);
			}
		});
		holder.avatar.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				holder.avatar.getDrawable().clearColorFilter();
				holder.avatar.invalidate();
				Intent intent = new Intent(context, ProfileActivity.class);
				intent.putExtra(Constants.ID, uid);
				intent.putExtra(User.screen_name, screenName);
				context.startActivity(intent);
			}
		});
	}

	private static class ViewHolder {
		TweetTextView myText;
		TextView myTextCreateAt;
		int myTextCreateAtIndex;

		ImageView avatar;
		int avatarIndex;
		TextView screenName;
		int screenNameIndex;
		int remarkIndex;
		int uidIndex;
		TweetTextView hisText;
		TextView hisCreateAt;
		int hisCreateAtIndex;

		int hisTextIndex;
		int statusIndex;
		int replyCommentIndex;
	}
} public class CommentsAPI {

	private static final String BASE_URI = CatnutAPI.API_DOMAIN + "/2/comments/";

	/**
	 * 根据微博ID返回某条微博的评论列表
	 *
	 * @param id               需要查询的微博ID
	 * @param since_id         若指定此参数，则返回ID比since_id大的评论（即比since_id时间晚的评论），默认为0
	 * @param max_id           若指定此参数，则返回ID小于或等于max_id的评论，默认为0
	 * @param count            单页返回的记录条数，默认为50
	 * @param page             返回结果的页码，默认为1
	 * @param filter_by_author 作者筛选类型，0：全部、1：我关注的人、2：陌生人，默认为0
	 * @return api
	 */
	public static CatnutAPI show(long id, long since_id, long max_id, int count, int page, int filter_by_author) {
		StringBuilder uri = new StringBuilder(BASE_URI);
		uri.append("show.json")
				.append("?id=").append(id)
				.append("&since_id=").append(CatnutUtils.optValue(since_id, 0))
				.append("&max_id=").append(CatnutUtils.optValue(max_id, 0))
				.append("&count=").append(CatnutUtils.optValue(count, 50))
				.append("&page=").append(CatnutUtils.optValue(page, 1))
				.append("&filter_by_author=").append(CatnutUtils.optValue(filter_by_author, 0));
		return new CatnutAPI(Request.Method.GET, uri, true, null);
	}

	/**
	 * 对一条微博进行评论
	 *
	 * @param comment     评论内容，必须做URLencode，内容不超过140个汉字
	 * @param id          需要评论的微博ID
	 * @param comment_ori 当评论转发微博时，是否评论给原微博，0：否、1：是，默认为0
	 * @param rip         开发者上报的操作用户真实IP，形如：211.156.0.1
	 * @return api
	 */
	public static CatnutAPI create(String comment, long id, int comment_ori, String rip) {
		StringBuilder uri = new StringBuilder(BASE_URI).append("create.json");
		Map<String, String> params = new HashMap<String, String>();
		params.put("comment", comment.trim());
		params.put("id", String.valueOf(id));
		params.put("comment_ori", String.valueOf(CatnutUtils.optValue(comment_ori, 0)));
		params.put("rip", String.valueOf(rip));
		return new CatnutAPI(Request.Method.POST, uri, true, params);
	}

	/**
	 * 回复一条评论
	 *
	 * @param cid             需要回复的评论ID
	 * @param id              需要评论的微博ID
	 * @param comment         回复评论内容，必须做URLencode，内容不超过140个汉字
	 * @param without_mention 回复中是否自动加入“回复@用户名”，0：是、1：否，默认为0
	 * @param comment_ori     当评论转发微博时，是否评论给原微博，0：否、1：是，默认为0
	 * @param rip             开发者上报的操作用户真实IP，形如：211.156.0.1
	 * @return api
	 */
	public static CatnutAPI reply(long cid, long id, String comment, int without_mention, int comment_ori, String rip) {
		StringBuilder uri = new StringBuilder(BASE_URI).append("reply.json");
		Map<String, String> params = new HashMap<String, String>();
		params.put("cid", String.valueOf(cid));
		params.put("id", String.valueOf(id));
		params.put("comment", comment.trim());
		params.put("without_mention", String.valueOf(CatnutUtils.optValue(without_mention, 0)));
		params.put("comment_ori", String.valueOf(CatnutUtils.optValue(comment_ori, 0)));
		params.put("rip", String.valueOf(rip));
		return new CatnutAPI(Request.Method.POST, uri, true, params);
	}

	/**
	 * 获取当前登录用户所接收到的评论列表
	 *
	 * @param since_id         若指定此参数，则返回ID比since_id大的评论（即比since_id时间晚的评论），默认为0
	 * @param max_id           若指定此参数，则返回ID小于或等于max_id的评论，默认为0
	 * @param count            单页返回的记录条数，默认为50
	 * @param page             返回结果的页码，默认为1
	 * @param filter_by_author 作者筛选类型，0：全部、1：我关注的人、2：陌生人，默认为0
	 * @param filter_by_source 来源筛选类型，0：全部、1：来自微博的评论、2：来自微群的评论，默认为0
	 * @return api
	 */
	public static CatnutAPI to_me(long since_id, long max_id, int count, int page, int filter_by_author, int filter_by_source) {
		StringBuilder uri = new StringBuilder(BASE_URI);
		uri.append("to_me.json")
				.append("?since_id=").append(CatnutUtils.optValue(since_id, 0))
				.append("&max_id=").append(CatnutUtils.optValue(max_id, 0))
				.append("&count=").append(CatnutUtils.optValue(count, 50))
				.append("&page=").append(CatnutUtils.optValue(page, 1))
				.append("&filter_by_author=").append(CatnutUtils.optValue(filter_by_author, 0))
				.append("&filter_by_source=").append(CatnutUtils.optValue(filter_by_source, 0));
		return new CatnutAPI(Request.Method.GET, uri, true, null);
	}
} public class CatnutAPI {

	/** 新浪微博api域名（注意貌似有些api不是用这个的=.=） */
	public static final String API_DOMAIN = "https://api.weibo.com";

	/** {@link com.android.volley.Request.Method} */
	public final int method;
	/** http uri */
	public final String uri;
	/** 是否需要传递access token */
	public final boolean authRequired;
	/** 是否需要在map里设置请求参数，通常用于post和put方法，没有赋null即可 */
	public final Map<String, String> params;

	public CatnutAPI(int method, CharSequence uri, boolean authRequired, Map<String, String> params) {
		this.method = method;
		this.uri = uri.toString();
		this.authRequired = authRequired;
		this.params = params;
	}

	public static String encode(String string) {
		String encode = null;
		try {
			encode = URLEncoder.encode(string, "utf-8");
		} catch (UnsupportedEncodingException e) {
		}
		return encode;
	}
} public class CatnutProvider extends ContentProvider {

	public static final String TAG = "CatnutProvider";
	private static final String CLEAR_DATA = "clear";

	public static final String AUTHORITY = "org.catnut";
	public static final String BASE_URI = "content://" + AUTHORITY;

	public static final String MULTIPLE_RECORDS_MIME_TYPE = "vnd.android.cursor.dir/vnd." + AUTHORITY + ".";
	public static final String SINGLE_RECORD_MIME_TYPE = "vnd.android.cursor.item/vnd." + AUTHORITY + ".";

	public static final int USER = 0;
	public static final int USERS = 1;
	public static final int STATUS = 2;
	public static final int STATUSES = 3;
	public static final int DRAFT = 4;
	public static final int DRAFTS = 5;
	public static final int PHOTO = 6;
	public static final int PHOTOS = 7;
	public static final int COMMENT = 8;
	public static final int COMMENTS = 9;

	// plugins...starting
	public static final int ZHIHU = 20;
	public static final int ZHIHUS = 21;
	// plugins...ending

	public static final int CLEAR = 100;

	private static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);

	static {
		matcher.addURI(AUTHORITY, User.MULTIPLE + "/#", USER);
		matcher.addURI(AUTHORITY, User.MULTIPLE, USERS);
		matcher.addURI(AUTHORITY, Status.MULTIPLE + "/#", STATUS);
		matcher.addURI(AUTHORITY, Status.MULTIPLE, STATUSES);
		matcher.addURI(AUTHORITY, Draft.MULTIPLE + "/#", DRAFT);
		matcher.addURI(AUTHORITY, Draft.MULTIPLE, DRAFTS);
		matcher.addURI(AUTHORITY, Photo.MULTIPLE + "/#", PHOTO);
		matcher.addURI(AUTHORITY, Photo.MULTIPLE, PHOTOS);
		matcher.addURI(AUTHORITY, Comment.MULTIPLE + "/#", COMMENT);
		matcher.addURI(AUTHORITY, Comment.MULTIPLE, COMMENTS);

		// plugins...starting
		matcher.addURI(AUTHORITY, Zhihu.MULTIPLE + "/#", ZHIHU);
		matcher.addURI(AUTHORITY, Zhihu.MULTIPLE, ZHIHUS);
		// plugins...ending

		matcher.addURI(AUTHORITY, CLEAR_DATA, CLEAR);
	}

	/**
	 * 列表uri，如http://example.org/resources
	 *
	 * @param path 如 resources
	 * @return Uri
	 */
	public static Uri parse(String path) {
		return Uri.parse(BASE_URI + "/" + path);
	}

	/**
	 * 单个对象uri，如http://example.org/resources/1
	 *
	 * @param path 如 resources
	 * @param id
	 * @return Uri
	 */
	public static Uri parse(String path, long id) {
		return Uri.parse(BASE_URI + "/" + path + "/" + id);
	}

	/**
	 * 清除缓存数据
	 *
	 * @return uri
	 */
	public static Uri clear() {
		return Uri.parse(BASE_URI + File.separator + CLEAR_DATA);
	}

	/**
	 * 数据源实际持有的数据库连接
	 */
	private TingtingSource mDb;

	@Override
	public boolean onCreate() {
		mDb = new TingtingSource(getContext(), "catnut.db", 2);
		return true;
	}

	@Override
	public String getType(Uri uri) {
		String type = null;
		switch (matcher.match(uri)) {
			case USER:
				type = SINGLE_RECORD_MIME_TYPE + User.SINGLE;
				break;
			case USERS:
				type = MULTIPLE_RECORDS_MIME_TYPE + User.MULTIPLE;
				break;
			case STATUS:
				type = SINGLE_RECORD_MIME_TYPE + Status.SINGLE;
				break;
			case STATUSES:
				type = MULTIPLE_RECORDS_MIME_TYPE + Status.MULTIPLE;
				break;
			case DRAFT:
				type = SINGLE_RECORD_MIME_TYPE + Draft.SINGLE;
				break;
			case DRAFTS:
				type = MULTIPLE_RECORDS_MIME_TYPE + Draft.MULTIPLE;
				break;
			case PHOTO:
				type = SINGLE_RECORD_MIME_TYPE + Photo.SINGLE;
				break;
			case PHOTOS:
				type = MULTIPLE_RECORDS_MIME_TYPE + Photo.MULTIPLE;
				break;
			case COMMENT:
				type = SINGLE_RECORD_MIME_TYPE + Comment.SINGLE;
				break;
			case COMMENTS:
				type = MULTIPLE_RECORDS_MIME_TYPE + Comment.MULTIPLE;
				break;
			// plugins...starting
			case ZHIHU:
				type = SINGLE_RECORD_MIME_TYPE + Zhihu.SINGLE;
				break;
			case ZHIHUS:
				type = MULTIPLE_RECORDS_MIME_TYPE + Zhihu.MULTIPLE;
				break;
			// plugins...ending
			default:
				Log.wtf(TAG, "unknown uri: " + uri);
		}
		return type;
	}

	@Override
	public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
		SQLiteDatabase db = mDb.getReadableDatabase();
		Cursor cursor;
		switch (matcher.match(uri)) {
			case USER:
				cursor = db.query(User.TABLE, projection, queryWithId(uri), selectionArgs, null, null, null);
				break;
			case USERS:
				cursor = db.rawQuery(selection, selectionArgs);
//				cursor = db.query(User.TABLE, projection, selection, selectionArgs, null, null, sortOrder);
				break;
			case STATUS:
				cursor = db.query(Status.TABLE, projection, queryWithId(uri), selectionArgs, null, null, null);
				break;
			case STATUSES:
				// 这里，比较特殊，直接在selection中写sql，可以包含占位符
				cursor = db.rawQuery(selection, selectionArgs);
				break;
			case DRAFT:
			case DRAFTS:
				cursor = db.query(Draft.TABLE, projection, selection, selectionArgs, selection, null, sortOrder);
				break;
			case PHOTO:
			case PHOTOS:
				cursor = db.rawQuery(selection, selectionArgs);
				break;
			case COMMENT:
				// fall through currently
			case COMMENTS:
				cursor = db.rawQuery(selection, selectionArgs);
				break;
			// plugins...starting
			case ZHIHU:
				cursor = db.query(Zhihu.TABLE, projection, queryWithId(uri), selectionArgs, null, null, sortOrder);
				break;
			case ZHIHUS:
				cursor = db.query(Zhihu.TABLE, projection, selection, selectionArgs, null, null, sortOrder);
				break;
			// plugins...ending
			default:
				Log.wtf(TAG, "unknown uri: " + uri);
				return null;
		}
		cursor.setNotificationUri(getContext().getContentResolver(), uri);
		return cursor;
	}

	@Override
	public Uri insert(Uri uri, ContentValues values) {
		String table;
		switch (matcher.match(uri)) {
			case USERS:
				table = User.TABLE;
				break;
			case STATUSES:
				table = Status.TABLE;
				break;
			case DRAFTS:
				table = Draft.TABLE;
				break;
			case PHOTOS:
				table = Photo.TABLE;
				break;
			case COMMENTS:
				table = Comment.TABLE;
				break;
			// plugins...starting
			case ZHIHUS:
				table = Zhihu.TABLE;
				break;
			// plugins...ending
			default:
				throw new RuntimeException("unknown uri: " + uri);
		}
		SQLiteDatabase db = mDb.getWritableDatabase();
		long id = db.insertWithOnConflict(table, null, values, SQLiteDatabase.CONFLICT_REPLACE);
		getContext().getContentResolver().notifyChange(uri, null, false); // no syncAdapter
		return ContentUris.withAppendedId(uri, id);
	}

	@Override
	public int bulkInsert(Uri uri, ContentValues[] values) {
		int conflicOption = SQLiteDatabase.CONFLICT_REPLACE;
		String table;
		switch (matcher.match(uri)) {
			case USERS:
				table = User.TABLE;
				break;
			case STATUSES:
				table = Status.TABLE;
				break;
			case PHOTOS:
				table = Photo.TABLE;
				break;
			case COMMENTS:
				table = Comment.TABLE;
				break;
			// plugins...starting
			case ZHIHUS:
				table = Zhihu.TABLE;
				conflicOption = SQLiteDatabase.CONFLICT_IGNORE;
				break;
			// plugins...ending
			default:
				throw new RuntimeException("unknown uri: " + uri);
		}
		SQLiteDatabase db = mDb.getWritableDatabase();
		db.beginTransaction();
		for (int i = 0; i < values.length; i++) {
			if (values[i] != null) {
				db.insertWithOnConflict(table, null, values[i], conflicOption);
			}
		}
		db.setTransactionSuccessful();
		db.endTransaction();
		getContext().getContentResolver().notifyChange(uri, null, false);
		return values.length;
	}

	@Override
	public int delete(Uri uri, String selection, String[] selectionArgs) {
		int count;
		switch (matcher.match(uri)) {
			case STATUSES:
				count = mDb.getWritableDatabase().delete(Status.TABLE, selection, selectionArgs);
				break;
			case DRAFTS:
				count = mDb.getWritableDatabase().delete(Draft.TABLE, selection, selectionArgs);
				break;
			case PHOTOS:
				count = mDb.getWritableDatabase().delete(Photo.TABLE, selection, selectionArgs);
				break;
			case COMMENTS:
				count = mDb.getWritableDatabase().delete(Comment.TABLE, selection, selectionArgs);
				break;
			// plugins...starting
			case ZHIHUS:
				// 直接清掉全部
				count = mDb.getWritableDatabase().delete(Zhihu.TABLE, null, null);
				break;
			// plugins...ending
			case CLEAR: // 插件的缓存清除分开搞
				SQLiteDatabase db = mDb.getWritableDatabase();
				db.beginTransaction();
				db.execSQL("delete from " + Photo.TABLE);
				db.execSQL("delete from " + User.TABLE + " where "
						+ BaseColumns._ID + " != " + CatnutApp.getTingtingApp().getAccessToken().uid);
				db.execSQL("delete from " + Status.TABLE);
				db.execSQL("delete from " + Comment.TABLE);
				db.setTransactionSuccessful();
				db.endTransaction();
				count = Integer.MAX_VALUE;
				break;
			default:
				throw new UnsupportedOperationException("not supported for now!");
		}
		getContext().getContentResolver().notifyChange(uri, null, false);
		return count;
	}

	@Override
	public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
		int count = 1; // 随便填的
		SQLiteDatabase db;
		switch (matcher.match(uri)) {
			case USERS:
			case STATUSES:
				db = mDb.getWritableDatabase();
				db.execSQL(selection);
				break;
			// plugins...starting
			case ZHIHU:
				selection = Zhihu.ANSWER_ID + "=" + uri.getLastPathSegment();
				count = mDb.getWritableDatabase().update(Zhihu.TABLE, values, selection, selectionArgs);
				break;
			// plugins...ending
			default:
				throw new UnsupportedOperationException("not supported for now!");
		}
		getContext().getContentResolver().notifyChange(uri, null, false);
		return count;
	}

	/**
	 * 直接以id查询记录
	 *
	 * @param uri
	 * @return "_id=?"
	 */
	private static String queryWithId(Uri uri) {
		return "_id=" + uri.getLastPathSegment();
	}

	/**
	 * sqlite数据库
	 *
	 * @author longkai
	 */
	private static class TingtingSource extends SQLiteOpenHelper {

		private static final String TAG = "TingtingSource";

		private Context mContext;

		public TingtingSource(Context context, String name, int version) {
			super(context, name, null, version);
			mContext = context;
		}

		@Override
		public void onCreate(SQLiteDatabase db) {
			db.execSQL(User.METADATA.ddl());
			db.execSQL(Status.METADATA.ddl());
			db.execSQL(Draft.METADATA.ddl());
			db.execSQL(Photo.METADATA.ddl());
			db.execSQL(Comment.METADATA.ddl());
			// plugins...starting
			db.execSQL(Zhihu.METADATA.ddl());
			// plugins...ending

			// 保存分享的图片
			(new Thread(new Runnable() {
				@Override
				public void run() {
					Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_launcher);
					FileOutputStream shareImage = null;
					try {
						shareImage = new FileOutputStream(mContext.getExternalCacheDir() + File.separator + Constants.SHARE_IMAGE);
						bitmap.compress(Bitmap.CompressFormat.PNG, 100, shareImage);
					} catch (FileNotFoundException e) {
					} finally {
						if (shareImage != null) {
							try {
								shareImage.close();
							} catch (IOException e) {
							}
						}
					}
				}
			})).start();
			Log.i(TAG, "finish create tables...");
		}

		@Override
		public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
			Log.i(TAG, "drop tables...");
			db.execSQL("DROP TABLE IF EXISTS " + User.TABLE);
			db.execSQL("DROP TABLE IF EXISTS " + Status.TABLE);
			db.execSQL("DROP TABLE IF EXISTS " + Draft.TABLE);
			db.execSQL("DROP TABLE IF EXISTS " + Photo.TABLE);
			db.execSQL("DROP TABLE IF EXISTS " + Comment.TABLE);

			// plugins...starting
			db.execSQL("DROP TABLE IF EXISTS " + Zhihu.TABLE);
			// plugins...ending

			// recreate...
			onCreate(db);
			Log.i(TAG, "finish upgrade table...");
		}
	}
} public class CatnutRequest extends Request<JSONObject> {

	public static final String TAG = "CatnutRequest";

	protected Context mContext;
	protected CatnutAPI mApi;
	protected CatnutProcessor<JSONObject> mProcessor;
	protected Response.Listener<JSONObject> mListener;

	public CatnutRequest(
		Context context,
		CatnutAPI api,
		CatnutProcessor<JSONObject> processor,
		Response.Listener<JSONObject> listener,
		Response.ErrorListener errorListener) {
		super(api.method, api.uri, errorListener);
		mContext = context;
		mApi = api;
		mProcessor = processor;
		mListener = listener;
	}

	@Override
	protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) {
		try {
			String jsonString =
					new String(response.data, HttpHeaderParser.parseCharset(response.headers));
			Response<JSONObject> success = Response.success(new JSONObject(jsonString),
					HttpHeaderParser.parseCacheHeaders(response));
			if (mProcessor != null) {
				// do in background...
				mProcessor.asyncProcess(mContext, success.result);
			}
			return success;
		} catch (UnsupportedEncodingException e) {
			return Response.error(new ParseError(e));
		} catch (JSONException je) {
			return Response.error(new ParseError(je));
		} catch (Exception ex) {
			Log.e(TAG, "process error!", ex);
			return Response.error(new VolleyError(response));
		}
	}

	@Override
	protected void deliverResponse(JSONObject response) {
		if (mListener == null) {
			Log.d(TAG, "finish http request without response on main-thread!");
		} else {
			mListener.onResponse(response);
		}
	}

	@Override
	public Map<String, String> getHeaders() throws AuthFailureError {
		return mApi.authRequired ? CatnutApp.getAuthHeaders() : super.getHeaders();
	}

	@Override
	protected Map<String, String> getParams() throws AuthFailureError {
		return mApi.params == null ? super.getParams() : mApi.params;
	}
} public class Comment implements CatnutMetadata<JSONObject, ContentValues> {

	public static final String TABLE = "comments";
	public static final String SINGLE = "comment";
	public static final String MULTIPLE = "comments";

	public static final String created_at = "created_at";
	public static final String text = "text";
	public static final String columnText = "_text";
	public static final String source = "source";
	public static final String uid = "uid"; // 评论作者的用户信息字段
	public static final String status = "status";
	public static final String reply_comment = "reply_comment"; //评论来源评论，当本评论属于对另一评论的回复时返回此字段

	public static final Comment METADATA = new Comment();

	private Comment() {
	}

	@Override
	public String ddl() {
		Log.d(TABLE, "create table [" + TABLE + "]");

		StringBuilder ddl = new StringBuilder("CREATE TABLE ");
		ddl.append(TABLE).append("(")
				.append(BaseColumns._ID).append(" int primary key,")
				.append(created_at).append(" text,")
				.append(columnText).append(" text,")
				.append(source).append(" text,")
				.append(uid).append(" int,")
				.append(status).append(" text,")
				.append(reply_comment).append(" text")
				.append(")");
		return ddl.toString();
	}

	@Override
	public ContentValues convert(JSONObject data) {
		ContentValues values = new ContentValues();
		values.put(BaseColumns._ID, data.optLong(Constants.ID));
		values.put(created_at, data.optString(created_at));
		values.put(columnText, data.optString(text));
		values.put(source, data.optString(source));
		JSONObject user = data.optJSONObject(User.SINGLE);
		if (user != null) {
			values.put(uid, user.optLong(Constants.ID));
		}
		values.put(status, data.optString(status));
		values.put(reply_comment, data.optString(reply_comment));
		return values;
	}
} public final class Status implements CatnutMetadata<JSONObject, ContentValues> {

	private static final String TAG = "Status";

	/** 标记微博的类型，本地使用 */ // todo: 可能一条微博有多个type...
	public static final String TYPE = "_type";
	/** 评论指向的微博id，本地使用 */
	public static final String TO_WHICH_TWEET = "_to";

	/** 本地使用，标记为主页微博 */
	public static final int HOME = 2;
	/** 本地使用，标记为转发微博 */
	public static final int RETWEET = 3;
	/** 本地使用，标记为公共微博 */
	public static final int PUBLIC = 4;
	/** 本地使用，标记为评论 */
	public static final int COMMENT = 5;
	/** 本地使用，标记为@我的微博 */
	public static final int MENTION = 6;
	/** 本地使用，标记为其它类型的微博 */
	public static final int OTHERS = 1;


	public static final String TABLE = "statuses";
	public static final String SINGLE = "status";
	public static final String MULTIPLE = "statuses";

	public static final String COMMENTS = "comments";
	public static final String FAVORITES = "favorites";
	public static final String total_number = "total_number";

	public static final String MEDIUM_THUMBNAIL = "bmiddle";
	public static final String LARGE_THUMBNAIL = "large";

	// singleton
	public static final Status METADATA = new Status();

	private Status() {
	}

	// 自关联，直接存储json字符串
	public static final String retweeted_status = "retweeted_status";

	/** 创建时间 */
	public static final String created_at = "created_at";
	/** 内容 */
	public static final String text = "text";
	public static final String columnText = "_text"; // keywords...
	/** 来源 */
	public static final String source = "source";
	/** 是否已收藏 boolean */
	public static final String favorited = "favorited";
	/** 是否被截断 boolean */
	public static final String truncated = "truncated";
	/** （暂未支持）回复ID */
//	public static final String in_reply_to_status_id = "in_reply_to_status_id";
	/** （暂未支持）回复人UID */
//	public static final String in_reply_to_user_id = "in_reply_to_user_id";
	/** （暂未支持）回复人昵称 */
//	public static final String in_reply_to_screen_name = "in_reply_to_screen_name";
	/** 微博配图地址，key=thumbnail_pic，多图时返回多图链接，无配图返回"[]" array */
	public static final String pic_urls = "pic_urls";
	/** 缩略图片地址，没有时不返回此字段 */
	public static final String thumbnail_pic = "thumbnail_pic";
	/** 中等尺寸图片地址，没有时不返回此字段 */
	public static final String bmiddle_pic = "bmiddle_pic";
	/** 原始图片地址，没有时不返回此字段 */
	public static final String original_pic = "original_pic";
	/** 被转发的原微博信息字段，当该微博为转发微博时返回 long */
	public static final String retweeted_status_id = "retweeted_status_id";
	// todo: geo
	/** 作者 long */
	public static final String uid = "uid";
	/** 转发数 int */
	public static final String reposts_count = "reposts_count";
	/** 评论数 int */
	public static final String comments_count = "comments_count";
	/** 表态数 int */
	public static final String attitudes_count = "attitudes_count";
	/** 暂未支持 */
	public static final String mlevel = "mlevel";
	// todo: visible
	// 微博的可见性及指定可见分组信息。该object中type取值，0：普通微博，1：私密微博，3：指定分组微博，4：密友微博；list_id为分组的组号
	// "visible": {
	// 	"type": 0,
	//	"list_id": 0
	// }

	@Override
	public String ddl() {
		Log.i(TAG, "create table [statuses]...");

		StringBuilder ddl = new StringBuilder("CREATE TABLE ");
		ddl.append(TABLE).append("(")
				.append(BaseColumns._ID).append(" int primary key,")
				.append(TYPE).append(" int,") // 标记微博的类型
				.append(TO_WHICH_TWEET).append(" int,") // 评论指向的微博id
				.append(created_at).append(" text,")
				.append(columnText).append(" text,")
				.append(source).append(" text,")
				.append(favorited).append(" int,")
				.append(truncated).append(" int,")
				.append(pic_urls).append(" text,")
				.append(thumbnail_pic).append(" text,")
				.append(bmiddle_pic).append(" text,")
				.append(original_pic).append(" text,")
				.append(retweeted_status).append(" text,")
				.append(uid).append(" int,")
				.append(reposts_count).append(" int,")
				.append(comments_count).append(" int,")
				.append(attitudes_count).append(" int");
		return ddl.append(")").toString();
	}

	@Override
	public ContentValues convert(JSONObject json) {
		ContentValues tweet = new ContentValues();
		tweet.put(BaseColumns._ID, json.optLong(Constants.ID));
		tweet.put(created_at, json.optString(created_at));
		// 注意json字段和sql字段的不同！
		tweet.put(columnText, json.optString(text));
		tweet.put(source, json.optString(source));
		tweet.put(favorited, json.optBoolean(favorited));
		tweet.put(truncated, json.optBoolean(truncated));
		// 有些时候，数据来源是不可靠的
		JSONArray thumbs = json.optJSONArray(pic_urls);
		if (thumbs != null) {
			// 直接存储为json字符串
			tweet.put(pic_urls, thumbs.toString());
		}
		tweet.put(thumbnail_pic, json.optString(thumbnail_pic));
		tweet.put(bmiddle_pic, json.optString(bmiddle_pic));
		tweet.put(original_pic, json.optString(original_pic));
		// 检查是否是转发微博
		if (json.has(retweeted_status)) {
			tweet.put(retweeted_status, json.optJSONObject(retweeted_status).toString());
		}
		// 加入作者的外键
		if (json.has(User.SINGLE)) {
			tweet.put(uid, json.optJSONObject(User.SINGLE).optLong(Constants.ID));
		} else if (json.has(uid)) {
			tweet.put(uid, json.optLong(uid));
		}
		tweet.put(reposts_count, json.optInt(reposts_count));
		tweet.put(comments_count, json.optInt(comments_count));
		tweet.put(attitudes_count, json.optInt(attitudes_count));
		return tweet;
	}
} public final class User implements CatnutMetadata<JSONObject, ContentValues> {

	private static final String TAG = "User";

	public static final String TABLE = "users";
	public static final String SINGLE = "user";
	public static final String MULTIPLE = "users";

	public static final String next_cursor = "next_cursor";
	public static final String total_number = "total_number";

	// singleton
	public static final User METADATA = new User();

	private User() {
	}

	/** 昵称 */
	public static final String screen_name = "screen_name";
	/** 友好显示名称 */
	public static final String name = "name";
	/** 所在省级ID int */
	public static final String province = "province";
	/** 所在省级ID int */
	public static final String city = "city";
	/** 所在地 */
	public static final String location = "location";
	/** 个人描述 */
	public static final String description = "description";
	/** 博客地址 */
	public static final String url = "url";
	/** 头像地址（中图），50×50像素 */
	public static final String profile_image_url = "profile_image_url";
	/** 主页封面图片（桌面） */
	public static final String cover_image = "cover_image";
	/** 主页封面图片（手机） */
	public static final String cover_image_phone = "cover_image_phone";
	/** 微博统一URL地址 */
	public static final String profile_url = "profile_url";
	/** 个性化域名 */
	public static final String domain = "domain";
	/** 微号 */
	public static final String weihao = "weihao";
	/** 性别，m：男、f：女、n：未知 */
	public static final String gender = "gender";
	/** 粉丝数 int */
	public static final String followers_count = "followers_count";
	/** 关注数 int */
	public static final String friends_count = "friends_count";
	/** 微博数 int */
	public static final String statuses_count = "statuses_count";
	/** 收藏数 int */
	public static final String favourites_count = "favourites_count";
	/** 用户创建（注册）时间 */
	public static final String created_at = "created_at";
	/** 暂未支持 boolean */
	public static final String following = "following";
	/** 是否允许所有人给我发私信 boolean */
	public static final String allow_all_act_msg = "allow_all_act_msg";
	/** 是否允许标识用户的地理位置 boolean */
	public static final String geo_enabled = "geo_enabled";
	/** 是否是微博认证用户，即加V用户 boolean */
	public static final String verified = "verified";
	/** 暂未支持 int */
	public static final String verified_type = "verified_type";
	/** 用户备注信息，只有在查询用户关系时才返回此字段 */
	public static final String remark = "remark";
	/** int */
//	public static final String ptype = "ptype";
	/** 是否允许所有人对我的微博进行评论 boolean */
	public static final String allow_all_comment = "allow_all_comment";
	/** 头像地址（大图），180×180像素 */
	public static final String avatar_large = "avatar_large";
	/** 头像地址（高清），高清头像原图 */
	public static final String avatar_hd = "avatar_hd";
	/** 认证原因 */
	public static final String verified_reason = "verified_reason";
	/** 是否关注当前登录用户 boolean */
	public static final String follow_me = "follow_me";
	/** 在线状态，0：不在线、1：在线 int */
	public static final String online_status = "online_status";
	/** 互粉数 int */
	public static final String bi_followers_count = "bi_followers_count";
	/** 当前的语言版本，zh-cn：简体中文，zh-tw：繁体中文，en：英语 */
	public static final String lang = "lang";
	/** int */
//	public static final String star = "star";
	/** int */
//	public static final String mbtype = "mbtype";
	/** int */
//	public static final String mbrank = "mbrank";
	/** int */
//	public static final String block_word = "block_word";
	/** 关联最新的一条微博 long */
	public static final String status_id = "status_id";

	@Override
	public String ddl() {
		Log.i(TAG, "create table [users]...");

		StringBuilder ddl = new StringBuilder("CREATE TABLE ");
		ddl.append(TABLE).append("(")
				.append(BaseColumns._ID).append(" int primary key,")
				.append(screen_name).append(" text,")
				.append(name).append(" text,")
				.append(province).append(" int,")
				.append(city).append(" int,")
				.append(location).append(" text,")
				.append(description).append(" text,")
				.append(url).append(" text,")
				.append(profile_image_url).append(" text,")
				.append(cover_image).append(" text,")
				.append(cover_image_phone).append(" text,")
				.append(profile_url).append(" text,")
				.append(domain).append(" text,")
				.append(weihao).append(" text,")
				.append(gender).append(" text,")
				.append(followers_count).append(" int,")
				.append(friends_count).append(" int,")
				.append(statuses_count).append(" int,")
				.append(favourites_count).append(" int,")
				.append(created_at).append(" text,")
				.append(following).append(" int,")
				.append(allow_all_act_msg).append(" int,")
				.append(geo_enabled).append(" int,")
				.append(verified).append(" int,")
				.append(verified_type).append(" int,")
				.append(remark).append(" int,")
//				.append(ptype).append(" int,")
				.append(allow_all_comment).append(" int,")
				.append(avatar_large).append(" text,")
				.append(avatar_hd).append(" text,")
				.append(verified_reason).append(" text,")
				.append(follow_me).append(" int,")
				.append(online_status).append(" int,")
				.append(bi_followers_count).append(" int,")
				.append(lang).append(" text"); // 注意最后一个字段！
//				.append(star).append(" int,")
//				.append(mbtype).append(" int,")
//				.append(mbrank).append(" int,")
//				.append(block_word).append(" int");
		return ddl.append(")").toString();
	}

	@Override
	public ContentValues convert(JSONObject json) {
		ContentValues user = new ContentValues();
		user.put(BaseColumns._ID, json.optLong(Constants.ID));
		user.put(screen_name, json.optString(screen_name));
		user.put(name, json.optString(name));
		user.put(province, json.optInt(province));
		user.put(city, json.optInt(city));
		user.put(location, json.optString(location));
		user.put(description, json.optString(description));
		user.put(url, json.optString(url));
		user.put(profile_image_url, json.optString(profile_image_url));
		user.put(cover_image, json.optString(cover_image));
		user.put(cover_image_phone, json.optString(cover_image_phone));
		user.put(profile_url, json.optString(profile_url));
		user.put(domain, json.optString(domain));
		user.put(weihao, json.optString(weihao));
		user.put(gender, json.optString(gender));
		user.put(followers_count, json.optInt(followers_count));
		user.put(friends_count, json.optInt(friends_count));
		user.put(statuses_count, json.optInt(statuses_count));
		user.put(favourites_count, json.optInt(favourites_count));
		user.put(created_at, json.optString(created_at));
		user.put(following, json.optBoolean(following));
		user.put(allow_all_act_msg, json.optBoolean(allow_all_act_msg));
		user.put(geo_enabled, json.optBoolean(geo_enabled));
		user.put(verified, json.optBoolean(verified));
		user.put(verified_type, json.optInt(verified_type));
		user.put(remark, json.optString(remark));
//		user.put(ptype, json.optInt(ptype));
		user.put(allow_all_comment, json.optBoolean(allow_all_comment));
		user.put(avatar_large, json.optString(avatar_large));
		user.put(avatar_hd, json.optString(avatar_hd));
		user.put(verified_reason, json.optString(verified_reason));
		user.put(follow_me, json.optBoolean(follow_me));
		user.put(online_status, json.optInt(online_status));
		user.put(bi_followers_count, json.optInt(bi_followers_count));
		user.put(lang, json.optString(lang));
//		user.put(star, json.optString(star));
//		user.put(mbtype, json.optInt(mbtype));
//		user.put(mbrank, json.optInt(mbrank));
//		user.put(block_word, json.optInt(block_word));
		// 添加最新微博id
		if (json.has(SINGLE)) {
			user.put(status_id, json.optJSONObject(Status.SINGLE).optLong(Constants.ID));
		}
		return user;
	}
} public class StatusProcessor {

	/**
	 * 判断是不是新浪在时间线中强奸的广告
	 *
	 * @param weibo json
	 * @return if it' s ad. or malformed data true or false
	 */
	public static final boolean fuckSina(JSONObject weibo) {
		if (weibo == null) {
			return true;
		}
		if (weibo.has(User.SINGLE)) {
			// 如果不是我关注的用户，那么基本上就是新浪的广告了，fuck
			JSONObject user = weibo.optJSONObject(User.SINGLE);
			if (user == null || !user.optBoolean(User.following)) {
				return true;
			}
		}
		// todo: 如果返回的微博是没有user这个字段，只有uid咋整呢?
		return false;
	}

	/**
	 * 持久化时间线
	 *
	 * @author longkai
	 */
	public static class TimelineProcessor implements CatnutProcessor<JSONObject> {

		private int type = Status.HOME;

		private boolean hasUser = true;

		public TimelineProcessor(boolean hasUser) {
			this.hasUser = hasUser;
		}

		public TimelineProcessor(int type, boolean hasUser) {
			this.type = type;
			this.hasUser = hasUser;
		}

		public void asyncProcess(Context context, JSONObject jsonObject) throws Exception {
			JSONArray jsonArray = jsonObject.optJSONArray(Status.MULTIPLE);
			ContentValues[] statuses = new ContentValues[jsonArray.length()];
			ContentValues[] users = null;
			if (hasUser) {
				users = new ContentValues[statuses.length];
			}
			User userMetadata = User.METADATA;
			Status statusMetadata = Status.METADATA;
			JSONObject json;
			ContentValues status;
			for (int i = 0; i < jsonArray.length(); i++) {
				json = jsonArray.optJSONObject(i);
				// 过滤一下不要的微博
				if (type == Status.HOME && fuckSina(json)) {
					continue;
				}
				// 一次持久化微博和作者信息
				status = statusMetadata.convert(json);
				status.put(Status.TYPE, type); // 标记为啥类型的微博
				statuses[i] = status;
				// 如果这条微博包含了原作者信息
				if (hasUser && json.has(User.SINGLE)) {
					users[i] = userMetadata.convert(json.optJSONObject(User.SINGLE));
				}
			}
			context.getContentResolver().bulkInsert(CatnutProvider.parse(Status.MULTIPLE), statuses);
			if (hasUser) {
				context.getContentResolver().bulkInsert(CatnutProvider.parse(User.MULTIPLE), users);
			}
		}
	}

	/**
	 * 持久化评论时间线
	 *
	 * @author longkai
	 */
	public static class CommentTweetsProcessor implements CatnutProcessor<JSONObject> {

		/** 改评论指向的微博id */
		private long to;

		public CommentTweetsProcessor(long to) {
			this.to = to;
		}

		@Override
		public void asyncProcess(Context context, JSONObject data) throws Exception {
			JSONArray array = data.optJSONArray(Status.COMMENTS);
			ContentValues[] comments = new ContentValues[array.length()];
			ContentValues[] users = new ContentValues[array.length()];
			ContentValues comment;
			JSONObject jsonUser;
			JSONObject jsonStatus;
			for (int i = 0; i < comments.length; i++) {
				// 解析微博
				jsonStatus = array.optJSONObject(i);
				comment = Status.METADATA.convert(jsonStatus);
				comment.put(Status.TYPE, Status.COMMENT); // 标记为评论微博
				comment.put(Status.TO_WHICH_TWEET, to);
				comments[i] = comment;
				// 解析评论作者
				jsonUser = jsonStatus.optJSONObject(User.SINGLE);
				users[i] = User.METADATA.convert(jsonUser);
			}
			// 持久化
			context.getContentResolver().bulkInsert(CatnutProvider.parse(Status.MULTIPLE), comments);
			context.getContentResolver().bulkInsert(CatnutProvider.parse(User.MULTIPLE), users);
		}
	}

	/**
	 * 当前登录用户收到的评论
	 *
	 * @author longkai
	 */
	public static class Comments2MeProcessor implements CatnutProcessor<JSONObject> {
		@Override
		public void asyncProcess(Context context, JSONObject data) throws Exception {
			JSONArray commentsArray = data.optJSONArray(Status.COMMENTS);
			ContentValues[] comments = new ContentValues[commentsArray.length()];
			ContentValues[] users = new ContentValues[comments.length];
			JSONObject json;
			for (int i = 0; i < comments.length; i++) {
				json = commentsArray.optJSONObject(i);
				comments[i] = Comment.METADATA.convert(json);
				users[i] = User.METADATA.convert(json.optJSONObject(User.SINGLE));
			}
			context.getContentResolver().bulkInsert(CatnutProvider.parse(Comment.MULTIPLE), comments);
			context.getContentResolver().bulkInsert(CatnutProvider.parse(User.MULTIPLE), users);
		}
	}

	/**
	 * 处理单条评论
	 *
	 * @author longkai
	 */
	public static class CommentTweetProcessor implements CatnutProcessor<JSONObject> {

		private long id;

		public CommentTweetProcessor(long id) {
			this.id = id;
		}

		@Override
		public void asyncProcess(Context context, JSONObject data) throws Exception {
			ContentValues comment = new ContentValues();
			comment = Status.METADATA.convert(data);
			comment.put(Status.TYPE, Status.COMMENT);
			comment.put(Status.TO_WHICH_TWEET, id);

			context.getContentResolver().insert(CatnutProvider.parse(Status.MULTIPLE), comment);
			String increment = CatnutUtils.increment(true, Status.TABLE, Status.comments_count, id);
			context.getContentResolver().update(CatnutProvider.parse(Status.MULTIPLE), null, increment, null);
		}
	}

	/**
	 * 收藏/取消收藏处理器
	 *
	 * @author longkai
	 */
	public static class FavoriteTweetProcessor implements CatnutProcessor<JSONObject> {

		@Override
		public void asyncProcess(Context context, JSONObject data) throws Exception {
			JSONObject jsonObject = data.optJSONObject(Status.SINGLE);
			StringBuilder update = new StringBuilder("UPDATE ").append(Status.TABLE);
			update.append(" SET ").append(Status.favorited).append("=")
					.append(jsonObject.optBoolean(Status.favorited) ? "1" : "0")
					.append(" WHERE ").append(BaseColumns._ID).append("=").append(jsonObject.getLong(Constants.ID));
			context.getContentResolver().update(CatnutProvider.parse(Status.MULTIPLE), null, update.toString(), null);
			// 更新用户数据
			JSONObject user = jsonObject.optJSONObject(User.SINGLE);
			if (user != null) {
				String increment = CatnutUtils.increment(true, User.TABLE, User.favourites_count, user.getLong(Constants.ID));
				context.getContentResolver().update(CatnutProvider.parse(User.MULTIPLE), null, increment, null);
			}
		}
	}

	/**
	 * 持久化收藏列表
	 *
	 * @author longkai
	 */
	public static class FavoriteTweetsProcessor implements CatnutProcessor<JSONObject> {

		@Override
		public void asyncProcess(Context context, JSONObject data) throws Exception {
			int delete = 0;
			JSONArray array = data.optJSONArray(Status.FAVORITES);
			ContentValues[] favorites = new ContentValues[array.length()];
			ContentValues[] users = new ContentValues[favorites.length];
			JSONObject json;
			for (int i = 0; i < favorites.length; i++) {
				// 解析微博，这里共用了变量名
				json = array.optJSONObject(i).optJSONObject(Status.SINGLE);
				if (json.optInt("deleted") == 1) {
					// 可能微博已经被删除了
					delete++;
					continue;
				}
				ContentValues values = Status.METADATA.convert(json);
				values.put(Status.favorited, 1);
				favorites[i] = values;
				// 解析用户
				users[i] = User.METADATA.convert(json.optJSONObject(User.SINGLE));
			}
			// persist
			context.getContentResolver().bulkInsert(CatnutProvider.parse(User.MULTIPLE), users);
			context.getContentResolver().bulkInsert(CatnutProvider.parse(Status.MULTIPLE), favorites);
			data.put(FavoriteFragment.TAG, delete);
		}
	}

	/**
	 * 普通的一条微博解析，需要提供一个微博类型
	 *
	 * @author longkai
	 */
	public static class SingleTweetProcessor implements CatnutProcessor<JSONObject> {

		// 微博类型
		private int type;

		public SingleTweetProcessor(int type) {
			this.type = type;
		}

		@Override
		public void asyncProcess(Context context, JSONObject data) throws Exception {
			ContentValues status = Status.METADATA.convert(data);
			status.put(Status.TYPE, type); // 添加类型
			ContentValues user = User.METADATA.convert(data.optJSONObject(User.SINGLE));
			context.getContentResolver().insert(CatnutProvider.parse(Status.MULTIPLE), status);
			context.getContentResolver().insert(CatnutProvider.parse(User.MULTIPLE), user);
			// 如果是转发微博的话，自增一下
			if (type == Status.RETWEET) {
				long id = data.optJSONObject(Status.retweeted_status).optLong(Constants.ID);
				String increment = CatnutUtils.increment(true, Status.TABLE, Status.reposts_count, id);
				context.getContentResolver().update(CatnutProvider.parse(Status.MULTIPLE), null, increment, null);
			}
		}
	}
} public class TweetActivity extends Activity {

	private EasyTracker mTracker;

	private OnFragmentBackPressedListener mKeyDownListener;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		if (CatnutApp.getTingtingApp().getPreferences()
				.getBoolean(getString(R.string.pref_enable_analytics), true)) {
			mTracker = EasyTracker.getInstance(this);
		}
		ActionBar bar = getActionBar();
		bar.setIcon(R.drawable.ic_title_view_tweet);
		bar.setDisplayHomeAsUpEnabled(true);

		long id = getIntent().getLongExtra(Constants.ID, 0L);
		String json = getIntent().getStringExtra(Constants.JSON);
		if (savedInstanceState == null) {
			TweetFragment fragment = id != 0L
					? TweetFragment.getFragment(id)
					: TweetFragment.getFragment(json);
			// 添加back回调
			mKeyDownListener = fragment;
			getFragmentManager().beginTransaction()
					.replace(android.R.id.content, fragment)
					.commit();
		}
	}

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		switch (keyCode) {
			case KeyEvent.KEYCODE_BACK:
				if (mKeyDownListener != null) {
					mKeyDownListener.onBackPressed();
				}
				break;
			default:
				break;
		}
		return super.onKeyDown(keyCode, event);
	}

	@Override
	protected void onStart() {
		super.onStart();
		if (mTracker != null) {
			mTracker.activityStart(this);
		}
	}

	@Override
	protected void onStop() {
		super.onStop();
		if (mTracker != null) {
			mTracker.activityStop(this);
		}
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		switch (item.getItemId()) {
			case android.R.id.home:
				if (mKeyDownListener != null) {
					mKeyDownListener.onBackPressed();
				} else {
					this.navigateUpTo(getIntent());
				}
				break;
			default:
				break;
		}
		return super.onOptionsItemSelected(item);
	}
} public class CatnutUtils {

	private static final String TAG = "CatnutUtils";

	/**
	 * cursor默认没有得到boolean类型的值，所以才有了这个方法
	 *
	 * @param cursor
	 * @param columnName
	 * @return x == 1 ? true : false
	 */
	public static boolean getBoolean(Cursor cursor, String columnName) {
		return cursor.getInt(cursor.getColumnIndex(columnName)) == 1 ? true : false;
	}

	/**
	 * 判断实际的int是否为0，否则返回默认的int
	 *
	 * @param real
	 * @param defaultValue
	 * @return int
	 */
	public static int optValue(int real, int defaultValue) {
		return real == 0 ? defaultValue : real;
	}

	/**
	 * 判断实际的long是否为0，否则返回默认的long
	 *
	 * @param real
	 * @param defaultValue
	 * @return long
	 */
	public static long optValue(long real, long defaultValue) {
		return real == 0L ? defaultValue : real;
	}

	/**
	 * 判断实际的String是否为null或者空串，否则返回默认的字符串
	 *
	 * @param real
	 * @param defaultValue
	 * @return String
	 */
	public static String optValue(String real, String defaultValue) {
		return TextUtils.isEmpty(real) ? defaultValue : real;
	}

	/**
	 * 判断实际的float是否为0.0f，否则返回默认的float
	 *
	 * @param real
	 * @param defaultValue
	 * @return float
	 */
	public static float optValue(float real, float defaultValue) {
		return real == 0.0F ? defaultValue : real;
	}

	/**
	 * 判断实际的double是否为0.0D，否则返回默认的double
	 *
	 * @param real
	 * @param defaultValue
	 * @return double
	 */
	public static double optValue(double real, double defaultValue) {
		return real == 0.0D ? defaultValue : real;
	}

	/**
	 * 判断实际的boolean是否为false，否则返回默认的boolean
	 *
	 * @param real
	 * @param defaultValue
	 * @return boolean
	 */
	public static boolean optValue(boolean real, boolean defaultValue) {
		return !real ? defaultValue : real;
	}

	/**
	 * 将数字转换为相对数字，节省版面，比如1234->1.2k，34567->3.5w
	 * <p/>
	 * 注意，0返回null，最大的权值为w
	 *
	 * @param number
	 * @return 数字相对大小字符串
	 */
	public static String approximate(int number) {
		if (number == 0) {
			return null;
		}
		if (number < 1000) {
			return String.valueOf(number);
		} else if (number < 10000) {
			float f = number * 1f / 1000;
			return String.valueOf(Math.round(f * 10) / 10.0) + "k";
		} else {
			float f = number * 1f / 10000;
			return String.valueOf(Math.round(f * 10) / 10.0) + "w";
		}
	}

	/**
	 * 四舍五入float
	 *
	 * @param num 浮点数
	 * @param bit 多少位
	 * @return the  you required
	 */
	public static float scaleNumber(float num, int bit) {
		int range = 10 * bit;
		return (float) Math.round(num * range) / range;
	}

	/**
	 * 设置某个view里面的textview的text
	 *
	 * @param parent     父容器
	 * @param textViewId textview的id
	 * @param text
	 * @return that textview
	 */
	public static TextView setText(View parent, int textViewId, CharSequence text) {
		TextView textView = (TextView) parent.findViewById(textViewId);
		textView.setText(text);
		return textView;
	}

	/**
	 * 自定义raw sql以获取cursor，包含所有的除了having，group外的所有字段
	 *
	 * @param context
	 * @param uri
	 * @param projection
	 * @param selection
	 * @param selectionArgs
	 * @param from
	 * @param joinOn
	 * @param sort
	 * @param limit
	 * @return {@link android.content.CursorLoader}
	 */
	public static CursorLoader getCursorLoader(Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, String from, String joinOn, String sort, String limit) {
		String sql = buildQuery(projection, selection, from, joinOn, sort, limit);
		return new CursorLoader(context, uri, null, sql, selectionArgs, null);
	}

	/**
	 * 模仿android的凑sql的方法，不同在于支持join和on，用在raw-query
	 *
	 * @param projection
	 * @param selection
	 * @param from
	 * @param joinOn     自己写上什么类型的join和on关键字(right, full not support)
	 * @param sort
	 * @param limit
	 * @return query sql
	 */
	public static String buildQuery(String[] projection, String selection, String from, String joinOn, String sort, String limit) {
		StringBuilder query = new StringBuilder("SELECT");
		if (projection == null) {
			query.append(" * ");
		} else {
			query.append(" ").append(projection(projection));
		}

		query.append(" FROM ").append(from);

		if (joinOn != null) {
			// 至于是什么类型的join，多少个join和on，这里得自己写上
			query.append(" ").append(joinOn);
		}

		if (selection != null) {
			query.append(" WHERE ").append(selection);
		}

		if (sort != null) {
			query.append(" ORDER BY ").append(sort);
		}

		if (limit != null) {
			query.append(" LIMIT ").append(limit);
		}

		return query.toString();
	}

	/**
	 * 给字符串首位加上单引号
	 */
	public static String quote(String string) {
		return "'" + string + "'";
	}

	/**
	 * 拼接投影sql
	 *
	 * @param projection
	 * @return turn a string[] into xx,xxx,xxx,...,xx
	 */
	private static String projection(String[] projection) {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < projection.length; i++) {
			sb.append(projection[i]);
			if (i != projection.length - 1) {
				sb.append(",");
			}
		}
		return sb.toString();
	}

	/**
	 * 更新sql
	 */
	public static String update(ContentValues values, String from, String where) {
		StringBuilder sb = new StringBuilder("UPDATE ").append(from).append(" SET");
		for (String key : values.keySet()) {
			sb.append(" ").append(key).append("=").append(reflectString(values.get(key))).append(",");
		}
		sb.deleteCharAt(sb.length() - 1); // remove the last ','
		if (where != null) {
			sb.append(" WHERE ").append(where);
		}
		return sb.toString();
	}

	/**
	 * 对某个字段自增或者自减法，只能通过_id=?来唯一确定哦
	 *
	 * @param increment
	 * @param table
	 * @param column
	 * @param _id
	 * @return update sql
	 */
	public static String increment(boolean increment, String table, String column, long _id) {
		return new StringBuilder("UPDATE ")
				.append(table).append(" ").append(" SET ")
				.append(column).append("=").append(column)
				.append(increment ? "+1" : "-1")
				.append(" WHERE ").append(BaseColumns._ID)
				.append("=").append(_id)
				.toString();
	}

	private static String reflectString(Object object) {
		if (object instanceof CharSequence) {
			return quote(object.toString());
		} else {
			// 其它情况下一律认为是number类型
			return object.toString();
		}
	}

	/**
	 * simple wrap the keywords with '%xx%'
	 *
	 * @param keywords
	 * @return '%keywords%'
	 */
	public static String like(String keywords) {
		return "'%" + keywords + "%'";
	}

	/**
	 * 给微博文字加上样式，表情，##，@，链接等
	 *
	 * @param text      微博
	 * @param imageSpan 不需要表情传入null
	 */
	public static void vividTweet(TweetTextView text, TweetImageSpan imageSpan) {
		if (imageSpan != null) {
			text.setText(imageSpan.getImageSpan(text.getText()));
		}
		Linkify.addLinks(text, MENTION_PATTERN, MENTION_SCHEME, null, MENTION_FILTER);
		Linkify.addLinks(text, TOPIC_PATTERN, TOPIC_SCHEME, null, TOPIC_FILTER);
		Linkify.addLinks(text, WEB_URL, null, null, URL_FILTER);
		removeLinkUnderline(text);
	}

	/**
	 * 清除textview链接的下划线
	 *
	 * @param textView
	 */
	public static void removeLinkUnderline(TextView textView) {
		Spannable s = Spannable.Factory.getInstance().newSpannable(textView.getText());
		URLSpan[] spans = s.getSpans(0, s.length(), URLSpan.class);
		for (URLSpan span : spans) {
			int start = s.getSpanStart(span);
			int end = s.getSpanEnd(span);
			s.removeSpan(span);
			span = new TweetURLSpan(span.getURL());
			s.setSpan(span, start, end, 0);
		}
		textView.setText(s);
	}

	/**
	 * 当使用list pref时只能存储string array，so 这个方法你懂的
	 *
	 * @param pref
	 * @param key
	 * @param defaultValue
	 * @return key exists ? existed value : defaultValue
	 */
	public static int resolveListPrefInt(SharedPreferences pref, String key, int defaultValue) {
		String value = pref.getString(key, null);
		if (value == null) {
			return defaultValue;
		}
		return Integer.parseInt(value);
	}

	/**
	 * 简单的判断一下edit text的是否有输入，裁剪掉两边的空白
	 */
	public static boolean hasLength(EditText editText) {
		// text never null!
		String str = editText.getText().toString();
		if (!TextUtils.isEmpty(str)) {
			return str.trim().length() > 0;
		} else {
			return false;
		}
	}

	/**
	 * 微博文字转表情
	 *
	 * @param boundPx the icon' s rectangle bound, if zero, use the default
	 */
	public static SpannableString text2Emotion(Context context, String key, int boundPx) {
		SpannableString spannable = new SpannableString(key);
		InputStream inputStream = null;
		Drawable drawable = null;
		try {
			inputStream = context.getAssets().open(TweetImageSpan.EMOTIONS_DIR + TweetImageSpan.EMOTIONS.get(key));
			drawable = Drawable.createFromStream(inputStream, null);
		} catch (IOException e) {
			Log.e(TAG, "load emotion error!", e);
		} finally {
			closeIO(inputStream);
		}
		if (drawable != null) {
			if (boundPx == 0) {
				drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
			} else {
				drawable.setBounds(0, 0, boundPx, boundPx);
			}
			ImageSpan span = new ImageSpan(drawable, ImageSpan.ALIGN_BASELINE);
			spannable.setSpan(span, 0, key.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
		}
		return spannable;
	}

	/**
	 * get byte[] from input stream
	 *
	 * @param inputStream
	 * @return byte[]
	 * @throws IOException
	 */
	public static byte[] getBytes(InputStream inputStream) throws IOException {
		ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
		int bufferSize = 2048 * 10; // 2m
		byte[] buffer = new byte[bufferSize];

		int len;
		while ((len = inputStream.read(buffer)) != -1) {
			byteBuffer.write(buffer, 0, len);
		}
		return byteBuffer.toByteArray();
	}

	public static Uri createImageFile() {
		// Create an image file name
		String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
		String imageFileName = "JPEG_" + timeStamp + "_";
		File storageDir = Environment.getExternalStoragePublicDirectory(
				Environment.DIRECTORY_PICTURES);
		File image;
		try {
			image = File.createTempFile(
					imageFileName,  /* prefix */
					".jpg",         /* suffix */
					storageDir      /* directory */
			);
		} catch (IOException e) {
			Log.d(TAG, "create tmp file error!", e);
			return null;
		}
		return Uri.fromFile(image);
	}

	// 是否需要更新
	public static void checkout(boolean required, Context context, SharedPreferences preferences) {
		long now = System.currentTimeMillis();
		String key = context.getString(R.string.pref_check_upgrade);
		if (required) {
			context.startService(new Intent(context, UpgradeService.class));
			preferences.edit().putLong(key, now).commit();
		} else {
			long last = preferences.getLong(key, now);
			if (now - last > (7 * 24 * 60 * 60 * 1000)) { // 每周更新一次
				Log.d(TAG, "need upgrade...");
				preferences.edit().putLong(key, now).commit();
				context.startService(new Intent(context, UpgradeService.class));
			}
		}
	}

	/**
	 * 根据偏好中保存的文件路径获取自定义字体
	 *
	 * @param pref
	 * @param key
	 * @param defaultTypeface
	 * @return 若发生任何异常或者和默认相同则返回null
	 */
	public static Typeface getTypeface(SharedPreferences pref, String key, String defaultTypeface) {
		Typeface typeface = null;
		String fontPath = pref.getString(key, null);
		if (fontPath == null || fontPath.equals(defaultTypeface)) {
			return null;
		} else {
			try {
				typeface = Typeface.createFromFile(new File(fontPath));
			} catch (Exception e) {
			}
		}
		return typeface;
	}

	/**
	 * helper method to check whether the typeface is null or not.
	 *
	 * @param textView
	 * @param typeface
	 */
	public static void setTypeface(TextView textView, Typeface typeface) {
		if (typeface != null) {
			textView.setTypeface(typeface);
		}
	}

	/**
	 * 设置行间距
	 *
	 * @param preferences
	 * @param key
	 * @param defaultValue
	 */
	public static float getLineSpacing(SharedPreferences preferences, String key, String defaultValue) {
		return Float.parseFloat(preferences.getString(key, defaultValue));
	}

	public static String mkdir(Context context, String location) throws Exception {
		// 注意，这里用户有可能吧sk卡连接到电脑上了...
		File dir = new File(context.getExternalCacheDir().getPath()
				+ File.separator + location);
		if (!dir.exists() && !dir.mkdirs()) {
			throw new RuntimeException(context.getString(R.string.cannot_create_dir));
		}
		return dir.getPath();
	}

	// 关掉io
	public static void closeIO(Closeable... closeables) {
		for (Closeable closeable : closeables) {
			if (closeable != null) {
				try {
					closeable.close();
				} catch (IOException e) {
					// no-op
				}
			}
		}
	}

	/**
	 * copy plain text to clip board
	 *
	 * @param context
	 * @param tag
	 * @param text
	 * @param toastText may null, no toast at all
	 */
	public static void copy2ClipBoard(Context context, String tag, String text, String toastText) {
		ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
		ClipData clip = ClipData.newPlainText(tag, text);
		clipboard.setPrimaryClip(clip);
		if (toastText != null) {
			Toast.makeText(context, toastText, Toast.LENGTH_SHORT).show();
		}
	}

	/**
	 * 检查激活了那些插件
	 *
	 * @return plug' s id, or null
	 */
	public static ArrayList<Integer> enabledPlugins() {
		ArrayList<Integer> ids = new ArrayList<Integer>();
		if (CatnutApp.getBoolean(R.string.pref_enable_zhihu,R.bool.default_plugin_status)) {
			ids.add(PluginsPrefFragment.ZHIHU);
		}
		if (CatnutApp.getBoolean(R.string.pref_enable_fantasy, R.bool.pref_enable_fantasy)) {
			ids.add(PluginsPrefFragment.FANTASY);
		}
		return ids.size() == 0 ? null : ids;
	}

	public static int getScreenWidth(Context context) {
		DisplayMetrics metrics = context.getResources().getDisplayMetrics();
		return metrics.widthPixels;
	}

	/**
	 * inject a image touch overley
	 * @param v
	 * @param event
	 */
	public static boolean imageOverlay(View v, MotionEvent event) {
		ImageView view = (ImageView) v;
		switch (event.getAction()) {
			case MotionEvent.ACTION_DOWN:
				// overlay is black with transparency of 0x77 (119)
				view.getDrawable().setColorFilter(0x77000000, PorterDuff.Mode.SRC_ATOP);
				view.invalidate();
				break;
			case MotionEvent.ACTION_UP:
			case MotionEvent.ACTION_CANCEL: {
				// clear the overlay
				view.getDrawable().clearColorFilter();
				view.invalidate();
				break;
			}
		}
		return false;
	}

	/**
	 * if pics.len > 1, return the json, or null
	 * @param json
	 * @return
	 */
	public static JSONArray optPics(String json) {
		if (!TextUtils.isEmpty(json)) {
			try {
				JSONArray jsonArray = new JSONArray(json);
				return optPics(jsonArray);
			} catch (JSONException e) {
				Log.wtf(TAG, "cannot serialize pics json array!", e);
				return null;
			}
		}
		return null;
	}

	/**
	 * if pics.len > 1, return the json, or null
	 * @param jsonArray
	 * @return
	 */
	public static JSONArray optPics(JSONArray jsonArray) {
		return jsonArray == null ? null : jsonArray.length() > 1 ? jsonArray : null;
	}
} public class Constants {

	public static final String WEIBO_DOMAIN = "weibo.com/";

	public static final String ACTION = "action";
	public static final String ID = "id";
	public static final String PIC = "pic";
	public static final String THUMBNAIL = "thumbnail";
	public static final String IMAGE_DIR = "images/"; // 保持在用户外部存储上的，并非cache的
	public static final String FANTASY_DIR = "fantasy";
	public static final String NULL = "null";
	public static final String JPG = ".jpg";
	public static final String JSON = "json";
	public static final String KEYWORDS = "keywords";

	public static final float GOLDEN_RATIO = 0.618f;

	public static final String SHARE_IMAGE = "share.png";

	// default sort order, must have `_id` field!
	public static final String DEFAULT_ORDER = BaseColumns._ID + " desc";
	public static final String RANDOM_ORDER = "RANDOM()";

	public static final String[] COUNT_PROJECTION = new String[]{
			"count(0)"
	};
}
import android.app.ActionBar;
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Handler;
import android.provider.BaseColumns;
import android.util.Log;
import android.view.View;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.Toast;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import org.catnut.R;
import org.catnut.adapter.ConversationAdapter;
import org.catnut.api.CommentsAPI;
import org.catnut.core.CatnutAPI;
import org.catnut.core.CatnutProvider;
import org.catnut.core.CatnutRequest;
import org.catnut.metadata.Comment;
import org.catnut.metadata.Status;
import org.catnut.metadata.User;
import org.catnut.processor.StatusProcessor;
import org.catnut.ui.TweetActivity;
import org.catnut.util.CatnutUtils;
import org.catnut.util.Constants;
import org.json.JSONArray;
import org.json.JSONObject;
/*
 * The MIT License (MIT)
 * Copyright (c) 2014 longkai
 * The software shall be used for good, not evil.
 */
package org.catnut.fragment;


/**
 * 会话列表视图
 *
 * @author longkai
 */
public class ConversationFragment extends TimelineFragment {


	public static final String TAG = ConversationFragment.class.getSimpleName();

	private static final String[] PROJECTION = new String[]{
			"c." + BaseColumns._ID,
			"c." + Comment.created_at,
			Comment.columnText,
			Comment.status,
			Comment.reply_comment,
			Comment.uid,
			User.screen_name,
			User.avatar_large,
			User.remark,
	};

	private Handler mHandler = new Handler();

	private ConversationAdapter mAdapter;
	private RequestQueue mRequestQueue;

	private int mTotal;

	public static ConversationFragment getFragment() {
		return new ConversationFragment();
	}

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setHasOptionsMenu(false);
		mRequestQueue = mApp.getRequestQueue();
		mAdapter = new ConversationAdapter(getActivity());
	}

	@Override
	public void onViewCreated(View view, Bundle savedInstanceState) {
		super.onViewCreated(view, savedInstanceState);
		// load it!
		mSwipeRefreshLayout.setRefreshing(true);
		if (savedInstanceState == null) {
			String key = getString(R.string.pref_first_run);
			boolean firstRun = mPreferences.getBoolean(key, true);
			if (firstRun) {
				refresh();
				mPreferences.edit().putBoolean(key, false).commit();
			} else if (mPreferences.getBoolean(getString(R.string.pref_keep_latest), true)) {
				refresh();
			} else {
				initFromLocal();
			}
		} else {
			initFromLocal();
		}
	}

	@Override
	public void onActivityCreated(Bundle savedInstanceState) {
		super.onActivityCreated(savedInstanceState);
		mListView.setAdapter(mAdapter);
	}

	@Override
	public void onStart() {
		super.onStart();
		ActionBar bar = getActivity().getActionBar();
		bar.setIcon(R.drawable.ic_action_conversation);
		bar.setTitle(getString(R.string.received_reply));
	}

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

	@Override
	public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
		Cursor c = (Cursor) mAdapter.getItem(position);
		String status = c.getString(c.getColumnIndex(Comment.status));
		Intent intent = new Intent(getActivity(), TweetActivity.class);
		intent.putExtra(Constants.JSON, status);
		startActivity(intent);
	}

	@Override
	protected void refresh() {
		// 检测一下是否网络已经连接，否则从本地加载
		if (!isNetworkAvailable()) {
			Toast.makeText(getActivity(), getString(R.string.network_unavailable), Toast.LENGTH_SHORT).show();
			initFromLocal();
			return;
		}
		// refresh!
		final int size = getFetchSize();
		(new Thread(new Runnable() {
			@Override
			public void run() {
				// 这里需要注意一点，我们不需要最新的那条，而是需要(最新那条-数目)，否则你拿最新那条去刷新，球都没有返回Orz...
				String query = CatnutUtils.buildQuery(
						new String[]{BaseColumns._ID},
						null,
						Comment.TABLE,
						null,
						BaseColumns._ID + " desc",
						size + ", 1" // limit x, y
				);
				Cursor cursor = getActivity().getContentResolver().query(
						CatnutProvider.parse(Status.MULTIPLE),
						null, query, null, null
				);
				// the cursor never null?
				final long since_id;
				if (cursor.moveToNext()) {
					since_id = cursor.getLong(0);
				} else {
					since_id = 0;
				}
				cursor.close();
				final CatnutAPI api = CommentsAPI.to_me(since_id, 0, getFetchSize(), 0, 0, 0);