package com.mcxiaoke.minicat.service; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.Parcelable; import android.os.RemoteException; import android.support.v4.app.NotificationCompat; import android.util.Log; import com.mcxiaoke.bus.Bus; import com.mcxiaoke.minicat.AppContext; import com.mcxiaoke.minicat.R; import com.mcxiaoke.minicat.api.Api; import com.mcxiaoke.minicat.api.ApiException; import com.mcxiaoke.minicat.api.Paging; import com.mcxiaoke.minicat.app.UIRecords; import com.mcxiaoke.minicat.controller.CacheController; import com.mcxiaoke.minicat.controller.DataController; import com.mcxiaoke.minicat.dao.model.BaseModel; import com.mcxiaoke.minicat.dao.model.DirectMessageModel; import com.mcxiaoke.minicat.dao.model.IBaseColumns; import com.mcxiaoke.minicat.dao.model.StatusColumns; import com.mcxiaoke.minicat.dao.model.StatusModel; import com.mcxiaoke.minicat.dao.model.StatusUpdateInfo; import com.mcxiaoke.minicat.dao.model.StatusUpdateInfoColumns; import com.mcxiaoke.minicat.dao.model.UserColumns; import com.mcxiaoke.minicat.dao.model.UserModel; import com.mcxiaoke.minicat.util.Assert; import com.mcxiaoke.minicat.util.IOHelper; import com.mcxiaoke.minicat.util.ImageHelper; import com.mcxiaoke.minicat.util.LogUtil; import com.mcxiaoke.minicat.util.NetworkHelper; import com.mcxiaoke.minicat.util.StringHelper; import com.mcxiaoke.minicat.util.UmengHelper; import com.mcxiaoke.minicat.util.Utils; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; /** * @author mcxiaoke * @version 8.1 2012.03.07 */ public final class SyncService extends Service implements Handler.Callback { public static final int MAX_TIMELINE_COUNT = 60; public static final int DEFAULT_TIMELINE_COUNT = 20; public static final int MAX_USERS_COUNT = 60; public static final int DEFAULT_USERS_COUNT = 20; public static final int MAX_IDS_COUNT = 2000; public static final int RESULT_SUCCESS = 1; public static final int RESULT_ERROR = -1; public static final int STATUS_SHOW = -101; public static final int STATUS_DELETE = -102; public static final int STATUS_FAVORITE = -103; public static final int STATUS_UNFAVORITE = -104; public static final int STATUS_UPDATE = -150; public static final int USER_SHOW = -201; public static final int USER_FOLLOW = -202; public static final int USER_UNFOLLOW = -203; public static final int USER_BLOCK = -204; public static final int USER_UNBLOCK = -205; public static final int DM_DELETE = -302; public static final int FRIENDSHIPS_EXISTS = -401; public static final int FRIENDSHIPS_SHOW = -402; public static final int FRIENDSHIPS_REQUESTS = -403; public static final int FRIENDSHIPS_ACCEPT = -404; public static final int FRIENDSHIPS_DENY = -405; public static final int DRAFTS_SEND = -501; public static final int NOTIFICATION_ID = 1001; private static final String TAG = SyncService.class.getSimpleName(); private static final boolean DEBUG = AppContext.DEBUG; private static final int MSG_SYNC_DATA = 0; private static final int MSG_EXEC_IDOP = 1; private static final int MSG_POST_DATA = 2; private static final int MSG_CMD_OTHERS = 3; private static final int MSG_STOP_CHECK = 4; private static final long STOP_SELF_CHECK_INTERVAL = 1000 * 30; private static final long CANCEL_DEPLAY_TIME = 2000L; private NotificationManager mNotificationManager; private Api mApi; private Handler mUiHandler; private Handler mCommandHandler; private HandlerThread mHandlerThread; private ExecutorService mExecutor; private SyncService mService; private AtomicInteger mCounter = new AtomicInteger(); private volatile boolean mIdle = true; private volatile boolean isSending = false; private static void debug(String message) { LogUtil.v(TAG, message); } public static void deleteDirectMessage(Context context, String id, final Handler handler) { Intent intent = new Intent(context, SyncService.class); intent.putExtra("type", DM_DELETE); intent.putExtra("id", id); if (handler != null) { intent.putExtra("messenger", new Messenger(handler)); } context.startService(intent); } public static void follow(Context context, String userId, final Handler handler) { Intent intent = new Intent(context, SyncService.class); intent.putExtra("type", USER_FOLLOW); intent.putExtra("id", userId); if (handler != null) { intent.putExtra("messenger", new Messenger(handler)); } context.startService(intent); } public static void unFollow(Context context, String userId, final Handler handler) { Intent intent = new Intent(context, SyncService.class); intent.putExtra("type", USER_UNFOLLOW); intent.putExtra("id", userId); intent.putExtra("messenger", new Messenger(handler)); context.startService(intent); } public static void showUser(Context context, String id, final Handler handler) { startService(context, USER_SHOW, id, handler); } private static void startService(Context context, int type, String id, final Handler handler) { Intent intent = new Intent(context, SyncService.class); intent.putExtra("type", type); intent.putExtra("id", id); if (handler != null) { intent.putExtra("messenger", new Messenger(handler)); } context.startService(intent); } public static void favorite(Context context, String id, final Handler handler) { favoriteAction(context, id, handler, true); } public static void unfavorite(Context context, String id, final Handler handler) { favoriteAction(context, id, handler, false); } private static void favoriteAction(Context context, String id, final Handler handler, boolean favorite) { startService(context, favorite ? STATUS_FAVORITE : STATUS_UNFAVORITE, id, handler); } public static void deleteStatus(Context context, String id, final Handler handler) { Intent intent = new Intent(context, SyncService.class); intent.putExtra("type", STATUS_DELETE); intent.putExtra("id", id); if (handler != null) { intent.putExtra("messenger", new Messenger(handler)); } context.startService(intent); } public static void doProfile(Context context, String userId, final Handler handler) { startService(context, USER_SHOW, userId, handler); } public static void showStatus(Context context, String id, final Handler handler) { startService(context, STATUS_SHOW, id, handler); } public static void showRelation(Context context, String userA, String userB, final Handler handler) { if (context == null || handler == null) { return; } Intent intent = new Intent(context, SyncService.class); intent.putExtra("type", FRIENDSHIPS_EXISTS); intent.putExtra("user_a", userA); intent.putExtra("user_b", userB); if (handler != null) { intent.putExtra("messenger", new Messenger(handler)); } context.startService(intent); } public static void getConversationList(Context context) { getDirectMessages(context, null, new Paging(), DirectMessageModel.TYPE_CONVERSATION_LIST); } public static void getConversationList(Context context, final Handler handler, Paging paging) { getDirectMessages(context, handler, paging, DirectMessageModel.TYPE_CONVERSATION_LIST); } public static void getConversation(Context context, final Handler handler, Paging paging, String userId) { Intent intent = new Intent(context, SyncService.class); intent.putExtra("type", DirectMessageModel.TYPE_CONVERSATION); if (handler != null) { intent.putExtra("messenger", new Messenger(handler)); } intent.putExtra("id", userId); intent.putExtra("paging", paging); context.startService(intent); } public static void getInbox(Context context, final Handler handler, Paging paging) { getDirectMessages(context, handler, paging, DirectMessageModel.TYPE_INBOX); } public static void getOutbox(Context context, final Handler handler, Paging paging) { getDirectMessages(context, handler, paging, DirectMessageModel.TYPE_OUTBOX); } private static void getDirectMessages(Context context, final Handler handler, Paging paging, int type) { Intent intent = new Intent(context, SyncService.class); intent.putExtra("type", type); if (handler != null) { intent.putExtra("messenger", new Messenger(handler)); } intent.putExtra("paging", paging); context.startService(intent); } public static void getDirectMessages(Context context, final Messenger messenger, Paging paging, String userId) { Intent intent = new Intent(context, SyncService.class); intent.putExtra("type", DirectMessageModel.TYPE_CONVERSATION); intent.putExtra("id", userId); intent.putExtra("messenger", messenger); intent.putExtra("paging", paging); context.startService(intent); } public static void getTimeline(Context context, int type, final Handler handler, String userId, Paging paging) { Intent intent = new Intent(context, SyncService.class); intent.putExtra("type", type); intent.putExtra("id", userId); if (handler != null) { intent.putExtra("messenger", new Messenger(handler)); } intent.putExtra("paging", paging); if (AppContext.DEBUG) { Log.d(TAG, "getTimeline() type=" + type + " paging=" + paging + " userId=" + userId); } context.startService(intent); } public static void getPublicTimeline(Context context, final Handler handler) { getTimeline(context, StatusModel.TYPE_PUBLIC, handler, null, null); } public static void getTimeline(Context context, int type, final Handler handler, Paging paging) { getTimeline(context, type, handler, null, paging); } public static void getUsers(Context context, String userId, int type, Paging paging, final Handler handler) { Intent intent = new Intent(context, SyncService.class); intent.putExtra("type", type); if (handler != null) { intent.putExtra("messenger", new Messenger(handler)); } intent.putExtra("paging", paging); intent.putExtra("id", userId); context.startService(intent); } @Override public void onCreate() { super.onCreate(); debug("onCreate()"); mService = this; mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); mApi = AppContext.getApi(); mUiHandler = new Handler(Looper.getMainLooper()); mHandlerThread = new HandlerThread(TAG); mHandlerThread.start(); mCommandHandler = new Handler(mHandlerThread.getLooper(), this); mExecutor = Executors.newCachedThreadPool(); sendStopCheckMessage(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent == null) { debug("onStartCommand() intent is null."); return START_NOT_STICKY; } int type = intent.getIntExtra("type", BaseModel.TYPE_NONE); debug("onStartCommand type=" + type); switch (type) { case StatusModel.TYPE_HOME: case StatusModel.TYPE_MENTIONS: case StatusModel.TYPE_USER: case StatusModel.TYPE_CONTEXT: case StatusModel.TYPE_PUBLIC: case StatusModel.TYPE_FAVORITES: case StatusModel.TYPE_PHOTO: case DirectMessageModel.TYPE_INBOX: case DirectMessageModel.TYPE_OUTBOX: case DirectMessageModel.TYPE_CONVERSATION_LIST: case DirectMessageModel.TYPE_CONVERSATION: case UserModel.TYPE_FRIENDS: case UserModel.TYPE_FOLLOWERS: { Message message = Message.obtain(); message.what = MSG_SYNC_DATA; message.arg1 = type; message.obj = intent; mCommandHandler.sendMessage(message); } break; case STATUS_SHOW: case STATUS_DELETE: case STATUS_FAVORITE: case STATUS_UNFAVORITE: case USER_SHOW: case USER_FOLLOW: case USER_UNFOLLOW: case USER_BLOCK: case USER_UNBLOCK: case DM_DELETE: { Message message = Message.obtain(); message.what = MSG_EXEC_IDOP; message.arg1 = type; message.obj = intent; mCommandHandler.sendMessage(message); } break; case FRIENDSHIPS_EXISTS: case FRIENDSHIPS_SHOW: case FRIENDSHIPS_REQUESTS: case FRIENDSHIPS_DENY: case FRIENDSHIPS_ACCEPT: { Message message = Message.obtain(); message.what = MSG_CMD_OTHERS; message.arg1 = type; message.obj = intent; mCommandHandler.sendMessage(message); } break; case STATUS_UPDATE: case DRAFTS_SEND: { Message message = Message.obtain(); message.what = MSG_POST_DATA; message.arg1 = type; message.obj = intent; mCommandHandler.sendMessage(message); } break; case BaseModel.TYPE_NONE: break; default: break; } return START_NOT_STICKY; } @Override public void onDestroy() { super.onDestroy(); debug("onDestroy()"); doClose(); } @Override public IBinder onBind(Intent intent) { return null; } @Override public boolean handleMessage(Message msg) { mIdle = false; int what = msg.what; Intent intent = (Intent) msg.obj; debug("handleMessage what=" + what); switch (what) { case MSG_SYNC_DATA: handleSyncDataCommands(intent); break; case MSG_POST_DATA: handlePostDataCommands(intent); break; case MSG_EXEC_IDOP: handleExecOpCommands(intent); break; case MSG_CMD_OTHERS: handleOthersCommands(intent); break; case MSG_STOP_CHECK: handleStopCheck(); break; default: break; } if (intent == null) { return true; } return true; } private void sendStopCheckMessage() { Message message = Message.obtain(); message.what = MSG_STOP_CHECK; mCommandHandler.sendMessageDelayed(message, STOP_SELF_CHECK_INTERVAL); } private void handleSyncDataCommands(Intent intent) { Command cmd = new Command(); cmd.messenger = intent.getParcelableExtra("messenger"); cmd.id = intent.getStringExtra("id"); cmd.type = intent.getIntExtra("type", BaseModel.TYPE_NONE); cmd.paging = intent.getParcelableExtra("paging"); debug("handleMessage cmd=" + cmd); switch (cmd.type) { case BaseModel.TYPE_NONE: break; case StatusModel.TYPE_HOME: case StatusModel.TYPE_MENTIONS: case StatusModel.TYPE_USER: case StatusModel.TYPE_CONTEXT: case StatusModel.TYPE_PUBLIC: case StatusModel.TYPE_FAVORITES: case StatusModel.TYPE_PHOTO: getTimeline(cmd); break; case DirectMessageModel.TYPE_INBOX: getInBox(cmd); break; case DirectMessageModel.TYPE_OUTBOX: getOutBox(cmd); break; case DirectMessageModel.TYPE_CONVERSATION_LIST: getConversationList(cmd); break; case DirectMessageModel.TYPE_CONVERSATION: getConversation(cmd); break; case UserModel.TYPE_FRIENDS: case UserModel.TYPE_FOLLOWERS: getUsers(cmd); break; default: break; } } private void handlePostDataCommands(Intent intent) { int type = intent.getIntExtra("type", BaseModel.TYPE_NONE); switch (type) { case STATUS_UPDATE: { StatusUpdateInfo info = intent.getParcelableExtra(StatusUpdateInfo.TAG); if (info != null) { debug("handlePostDataCommands info=" + info); statusUpdate(info); } } break; case DRAFTS_SEND: { doSendAllDrafts(); } break; } } private void handleExecOpCommands(Intent intent) { Command cmd = new Command(); cmd.messenger = intent.getParcelableExtra("messenger"); cmd.id = intent.getStringExtra("id"); cmd.type = intent.getIntExtra("type", BaseModel.TYPE_NONE); debug("handleExecOpCommands cmd=" + cmd); switch (cmd.type) { case STATUS_SHOW: showStatus(cmd); break; case STATUS_DELETE: deleteStatus(cmd); break; case STATUS_FAVORITE: favorite(cmd, true); break; case STATUS_UNFAVORITE: favorite(cmd, false); break; case USER_SHOW: showUser(cmd); break; case USER_FOLLOW: follow(cmd, true); break; case USER_UNFOLLOW: follow(cmd, false); break; case USER_BLOCK: block(cmd, true); break; case USER_UNBLOCK: block(cmd, false); break; case DM_DELETE: deleteDirectMessage(cmd); break; default: break; } } private void handleOthersCommands(Intent intent) { Command cmd = new Command(); cmd.messenger = intent.getParcelableExtra("messenger"); cmd.id = intent.getStringExtra("id"); cmd.type = intent.getIntExtra("type", BaseModel.TYPE_NONE); debug("handleOthersCommands cmd=" + cmd); switch (cmd.type) { case FRIENDSHIPS_EXISTS: isFriends(cmd, intent); break; case FRIENDSHIPS_SHOW: // TODO break; case FRIENDSHIPS_REQUESTS: // TODO break; case FRIENDSHIPS_DENY: // TODO break; case FRIENDSHIPS_ACCEPT: // TODO break; default: break; } } private void handleStopCheck() { int taskCount = mCounter.get(); if (DEBUG) { debug("handleStopCheck() taskCount=" + taskCount); } if (taskCount == 0) { stopSelf(); } else { sendStopCheckMessage(); } } private void deleteDirectMessage(final Command cmd) { final String id = cmd.id; final Runnable runnable = new Runnable() { @Override public void run() { DirectMessageModel dm = null; try { // 删除消息 // 404 说明消息不存在 // 403 说明不是你的消息,无权限删除 dm = mApi.deleteDirectMessage(id); if (dm == null) { sendSuccessMessage(cmd); } else { DataController.delete(mService, dm); sendParcelableMessage(cmd, dm); } } catch (ApiException e) { if (DEBUG) { e.printStackTrace(); } if (e.statusCode == 404) { DataController.delete(mService, dm); } sendErrorMessage(cmd, e); } } }; execute(runnable); } private void block(final Command cmd, final boolean block) { final String id = cmd.id; Assert.notEmpty(id); final Runnable runnable = new Runnable() { @Override public void run() { UserModel u = null; try { u = block ? mApi.block(id) : mApi.unblock(id); if (u == null) { sendSuccessMessage(cmd); } else { DataController.delete(mService, u); sendParcelableMessage(cmd, u); } } catch (ApiException e) { if (AppContext.DEBUG) { e.printStackTrace(); } sendErrorMessage(cmd, e); } } }; execute(runnable); } private void follow(final Command cmd, final boolean follow) { final String id = cmd.id; Assert.notEmpty(id); final Runnable runnable = new Runnable() { @Override public void run() { try { UserModel u = follow ? mApi.follow(id) : mApi.unfollow(id); if (u != null) { u.setType(UserModel.TYPE_FRIENDS); DataController.updateUserModel(mService, u); } sendSuccessMessage(cmd); } catch (ApiException e) { if (AppContext.DEBUG) { e.printStackTrace(); } sendErrorMessage(cmd, e); } mIdle = true; } }; execute(runnable); } private void showUser(final Command cmd) { final String id = cmd.id; final Runnable runnable = new Runnable() { @Override public void run() { try { UserModel u = mApi.showUser(id); if (u == null) { sendSuccessMessage(cmd); } else { CacheController.cacheAndStore(mService, u); sendParcelableMessage(cmd, u); } } catch (ApiException e) { if (AppContext.DEBUG) { e.printStackTrace(); } sendErrorMessage(cmd, e); } mIdle = true; } }; execute(runnable); } private void favorite(final Command cmd, final boolean favorite) { if (AppContext.DEBUG) { LogUtil.v(TAG, "favorite() action=" + (favorite ? "favorite" : "unfavorite")); } final String id = cmd.id; Assert.notEmpty(id); final Runnable runnable = new Runnable() { @Override public void run() { StatusModel s = null; try { s = favorite ? mApi.favorite(id) : mApi.unfavorite(id); if (AppContext.DEBUG) { LogUtil.d(TAG, "favorite() result=" + s); } if (s == null) { sendErrorMessage(cmd, new ApiException(ApiException.IO_ERROR)); } else { ContentValues values = new ContentValues(); values.put(StatusColumns.FAVORITED, true); DataController.update(mService, s, values); Bundle bundle = new Bundle(); bundle.putInt("type", cmd.type); bundle.putBoolean("boolean", s.isFavorited()); sendSuccessMessage(cmd, bundle); } } catch (ApiException e) { if (AppContext.DEBUG) { e.printStackTrace(); } if (e.statusCode == 404) { DataController.delete(mService, s); } sendErrorMessage(cmd, e); } mIdle = true; } }; execute(runnable); } private void deleteStatus(final Command cmd) { final String id = cmd.id; final Runnable runnable = new Runnable() { @Override public void run() { StatusModel s = null; try { s = mApi.deleteStatus(id); if (s == null) { sendSuccessMessage(cmd); } else { DataController.delete(mService, s); sendParcelableMessage(cmd, s); } } catch (ApiException e) { if (AppContext.DEBUG) { Log.e(TAG, "deleteStatus() error:" + e); } if (e.statusCode == 404) { DataController.delete(mService, s); sendSuccessMessage(cmd); } else { sendErrorMessage(cmd, e); } } mIdle = true; } }; execute(runnable); } private void showStatus(final Command cmd) { final String id = cmd.id; final Runnable runnable = new Runnable() { @Override public void run() { StatusModel s = null; try { s = mApi.showStatus(id); if (s == null) { sendSuccessMessage(cmd); } else { CacheController.cacheAndStore(mService, s); sendParcelableMessage(cmd, s); } } catch (ApiException e) { if (AppContext.DEBUG) { e.printStackTrace(); } if (e.statusCode == 404) { DataController.delete(mService, s); } sendErrorMessage(cmd, e); } mIdle = true; } }; execute(runnable); } private void statusUpdate(final StatusUpdateInfo info) { debug("statusUpdate() info=" + info); final Runnable runnable = new Runnable() { @Override public void run() { doStatusUpdate(info, false); mIdle = true; mUiHandler.post(new Runnable() { @Override public void run() { mNotificationManager.cancel(NOTIFICATION_ID); } }); } }; execute(runnable); } private boolean doStatusUpdate(final StatusUpdateInfo info, boolean needDeleteDraft) { showSendingNotification(); isSending = true; boolean res = false; debug("doStatusUpdate() info=" + info); boolean photoUpload = false; File photo = null; try { StatusModel result = null; if (StringHelper.isEmpty(info.fileName) || new File(info.fileName).length() == 0) { if (info.type == StatusUpdateInfo.TYPE_REPLY) { result = mApi.updateStatus(info.text, info.reply, null, info.location); } else { result = mApi.updateStatus(info.text, null, info.repost, info.location); } } else { File file = new File(info.fileName); if (file.length() < ImageHelper.IMAGE_MAX_SIZE) { photo = new File(IOHelper.getImageCacheDir(this), System.currentTimeMillis() + "_upload.jpg"); IOHelper.copyFile(file, photo); } else if (file.getName().toLowerCase().endsWith(".gif")) { photo = new File(IOHelper.getImageCacheDir(this), System.currentTimeMillis() + "_upload.gif"); IOHelper.copyFile(file, photo); } else { boolean isWifi = NetworkHelper.isWifi(this); int quality = isWifi ? ImageHelper.IMAGE_QUALITY_HIGH : ImageHelper.IMAGE_QUALITY_MEDIUM; int maxWidth = isWifi ? ImageHelper.IMAGE_MAX_WIDTH : ImageHelper.IMAGE_MAX_WIDTH_2; photo = ImageHelper.prepareUploadFile(this, file, quality, maxWidth); } if (photo != null && photo.length() > 0) { if (DEBUG) { debug("doStatusUpdate() photo file=" + file.getName() + " size=" + photo.length() / 1024 + "k"); } photoUpload = true; result = mApi.uploadPhoto(photo, info.text, info.location); photo.delete(); } } if (needDeleteDraft) { DataController.deleteRecord(this, info.id); } if (result != null) { if (photoUpload) { UmengHelper.onPhotoUploadEvent(this, AppContext.getAccount(), result.getId()); } else { UmengHelper.onStatusUpdateEvent(this, AppContext.getAccount(), result.getId()); } res = true; sendSuccessBroadcast(result); } else { onStatusUpdateFailed(info); } } catch (Exception e) { if (DEBUG) { debug(e.toString()); e.printStackTrace(); } onStatusUpdateFailed(info); UmengHelper.onStatusUpdateError(this, AppContext.getAccount(), 0, e.getMessage(), e.getCause() + ""); } finally { if (photo != null) { photo.delete(); } } isSending = false; return res; } private void onStatusUpdateFailed(final StatusUpdateInfo info) { if (DEBUG) { debug("onStatusUpdateFailed() info=" + info); } mUiHandler.post(new Runnable() { @Override public void run() { cancelNotification(NOTIFICATION_ID); doSaveRecords(info); if (AppContext.homeVisible) { Utils.notify(SyncService.this, "消息未发送成功,已保存到草稿箱"); } showFailedNotification("消息未发送成功,已保存到草稿箱", getString(R.string.msg_server_error)); } }); } private void doSendAllDrafts() { if (isSending) { return; } isSending = true; final Runnable runnable = new Runnable() { @Override public void run() { List<StatusUpdateInfo> infos = new ArrayList<StatusUpdateInfo>(); Cursor cursor = getContentResolver().query(StatusUpdateInfoColumns.CONTENT_URI, null, null, null, null); if (cursor != null) { while (cursor.moveToNext()) { infos.add(StatusUpdateInfo.from(cursor)); } } if (infos != null && infos.size() > 0) { for (StatusUpdateInfo info : infos) { doStatusUpdate(info, true); } } mIdle = true; } }; execute(runnable); } private void isFriends(final Command cmd, Intent intent) { final String userA = intent.getStringExtra("user_a"); final String userB = intent.getStringExtra("user_b"); final Runnable runnable = new Runnable() { @Override public void run() { boolean result = false; try { result = mApi.isFriends(userA, userB); } catch (ApiException e) { if (AppContext.DEBUG) { Log.e(TAG, "doDetectFriendships:" + e.getMessage()); } sendErrorMessage(cmd, e); } Bundle data = new Bundle(); data.putBoolean("boolean", result); sendSuccessMessage(cmd, data); } }; execute(runnable); } private void getUsers(final Command cmd) { final String id = cmd.id; final Paging p = cmd.paging == null ? new Paging() : cmd.paging; if (NetworkHelper.isWifi(this)) { p.count = MAX_USERS_COUNT; } else { p.count = DEFAULT_USERS_COUNT; } final Runnable runnable = new Runnable() { @Override public void run() { try { List<UserModel> users = null; if (cmd.type == UserModel.TYPE_FRIENDS) { users = mApi.getFriends(id, p); } else if (cmd.type == UserModel.TYPE_FOLLOWERS) { users = mApi.getFollowers(id, p); } if (users != null && users.size() > 0) { int size = users.size(); ContentResolver cr = getContentResolver(); if (p.page < 2 && id != AppContext.getAccount()) { String where = IBaseColumns.TYPE + "=? AND " + IBaseColumns.OWNER + "=?"; String[] whereArgs = new String[]{String.valueOf(cmd.type), id}; int deletedNums = cr.delete(UserColumns.CONTENT_URI, where, whereArgs); if (AppContext.DEBUG) { Log.d(TAG, "getUsers delete old rows " + deletedNums + " ownerId=" + id); } } int nums = DataController.store(mService, users); if (AppContext.DEBUG) { Log.d(TAG, "getUsers refresh ,insert rows, num=" + nums + " ownerId=" + id); } sendIntMessage(cmd, nums); } else { sendIntMessage(cmd, 0); } } catch (ApiException e) { if (AppContext.DEBUG) { e.printStackTrace(); } sendErrorMessage(cmd, e); } } }; execute(runnable); } private void getConversation(final Command cmd) { final String id = cmd.id; final Paging p = cmd.paging == null ? new Paging() : cmd.paging; if (NetworkHelper.isWifi(this)) { p.count = MAX_TIMELINE_COUNT; } else { p.count = DEFAULT_TIMELINE_COUNT; } final Runnable runnable = new Runnable() { @Override public void run() { try { List<DirectMessageModel> messages = mApi.getConversation(id, p); if (messages != null && messages.size() > 0) { if (AppContext.DEBUG) { Log.d(TAG, "getConversation() id=" + id + " result=" + messages); } int nums = DataController.store(mService, messages); sendIntMessage(cmd, nums); } sendIntMessage(cmd, 0); } catch (ApiException e) { if (DEBUG) { e.printStackTrace(); } sendErrorMessage(cmd, e); } } }; execute(runnable); } private void getConversationList(final Command cmd) { final Paging p = cmd.paging == null ? new Paging() : cmd.paging; if (NetworkHelper.isWifi(this)) { p.count = MAX_TIMELINE_COUNT; } else { p.count = DEFAULT_TIMELINE_COUNT; } final Runnable runnable = new Runnable() { @Override public void run() { try { List<DirectMessageModel> messages = mApi.getConversationList(p); if (messages != null && messages.size() > 0) { int nums = DataController.store(mService, messages); sendIntMessage(cmd, nums); } sendIntMessage(cmd, 0); } catch (ApiException e) { if (DEBUG) { e.printStackTrace(); } sendErrorMessage(cmd, e); } } }; execute(runnable); } private void getDirectMessages(final Command cmd, final boolean in) { final Runnable runnable = new Runnable() { @Override public void run() { Paging p = cmd.paging; if (p == null) { p = new Paging(); } if (NetworkHelper.isWifi(mService)) { p.count = MAX_TIMELINE_COUNT; } else { p.count = DEFAULT_TIMELINE_COUNT; } try { List<DirectMessageModel> messages = in ? mApi .getDirectMessagesInbox(p) : mApi.getDirectMessagesOutbox(p); if (messages != null && messages.size() > 0) { int nums = DataController.store(mService, messages); sendIntMessage(cmd, nums); } sendIntMessage(cmd, 0); } catch (ApiException e) { if (DEBUG) { e.printStackTrace(); } sendErrorMessage(cmd, e); } } }; execute(runnable); } private void getInBox(Command cmd) { getDirectMessages(cmd, true); } private void getOutBox(Command cmd) { getDirectMessages(cmd, false); } private void getTimeline(final Command cmd) { final int type = cmd.type; final Paging p = cmd.paging == null ? new Paging() : cmd.paging; final Runnable runnable = new Runnable() { @Override public void run() { List<StatusModel> statuses = null; String id = cmd.id; if (NetworkHelper.isWifi(mService)) { p.count = MAX_TIMELINE_COUNT; } else { p.count = DEFAULT_TIMELINE_COUNT; } if (AppContext.DEBUG) { Log.d(TAG, "getTimeline userId=" + id + " paging=" + p + " type=" + type); } try { switch (type) { case StatusModel.TYPE_HOME: statuses = mApi.getHomeTimeline(p); break; case StatusModel.TYPE_MENTIONS: statuses = mApi.getMentions(p); break; case StatusModel.TYPE_PUBLIC: p.count = DEFAULT_TIMELINE_COUNT; statuses = mApi.getPublicTimeline(); break; case StatusModel.TYPE_FAVORITES: statuses = mApi.getFavorites(id, p); break; case StatusModel.TYPE_USER: statuses = mApi.getUserTimeline(id, p); break; case StatusModel.TYPE_CONTEXT: statuses = mApi.getContextTimeline(id); break; case StatusModel.TYPE_PHOTO: statuses = mApi.getPhotosTimeline(id, p); break; default: break; } if (statuses == null || statuses.size() == 0) { sendIntMessage(cmd, 0); if (AppContext.DEBUG) Log.d(TAG, "getTimeline() count=0. userId=" + id + " type=" + type); return; } else { if (type == StatusModel.TYPE_PUBLIC) { } int size = statuses.size(); if (type == StatusModel.TYPE_PUBLIC || (size == p.count && p.maxId == null && p.page <= 1)) { deleteOldStatuses(cmd.id, cmd.type); } int insertedCount = DataController.storeStatusesWithUsers(mService, statuses); if (AppContext.DEBUG) { Log.d(TAG, "getTimeline() size=" + size + " userId=" + id + " count=" + p.count + " page=" + p.page + " type=" + type + " insertedCount=" + insertedCount); } sendIntMessage(cmd, insertedCount); } } catch (ApiException e) { if (AppContext.DEBUG) { Log.e(TAG, "getTimeline() [error]" + e.statusCode + ":" + e.errorMessage + " userId=" + id + " type=" + type); e.printStackTrace(); } sendErrorMessage(cmd, e); } mIdle = true; } }; execute(runnable); } private int deleteOldStatuses(final String id, final int type) { int numDeleted = 0; if (type == StatusModel.TYPE_USER) { numDeleted = DataController.deleteUserTimeline(this, id); } else if (type == StatusModel.TYPE_FAVORITES) { numDeleted = DataController.deleteUserFavorites(this, id); } else { numDeleted = DataController.deleteStatusByType(this, type); } if (AppContext.DEBUG) { Log.d(TAG, "deleteOldStatuses numDeleted=" + numDeleted + " type=" + type + " id=" + id); } return numDeleted; } private void execute(Runnable runnable) { if (mExecutor != null && runnable != null) { mExecutor.submit(new MyRunnable(runnable, mCounter)); } } private void sendErrorMessage(Command cmd, ApiException e) { String message = e.getMessage(); if (e.statusCode == ApiException.IO_ERROR) { message = getString(R.string.msg_connection_error); } else if (e.statusCode >= 500) { message = getString(R.string.msg_server_error); } Bundle bundle = new Bundle(); bundle.putInt("error_code", e.statusCode); bundle.putString("error_message", message); sendMessage(cmd, RESULT_ERROR, bundle); } private void sendIntMessage(Command cmd, int size) { Bundle bundle = new Bundle(); bundle.putInt("count", size); sendMessage(cmd, RESULT_SUCCESS, bundle); } private void sendParcelableMessage(Command cmd, Parcelable parcel) { Bundle bundle = new Bundle(); bundle.putParcelable("data", parcel); sendMessage(cmd, RESULT_SUCCESS, bundle); } private void sendSuccessMessage(Command cmd, Bundle bundle) { sendMessage(cmd, RESULT_SUCCESS, bundle); } private void sendSuccessMessage(Command cmd) { sendMessage(cmd, RESULT_SUCCESS, null); } private void sendMessage(Command cmd, int what, final Bundle bundle) { if (cmd.messenger == null) { return; } Message m = Message.obtain(); m.what = what; m.arg1 = cmd.type; if (bundle != null) { m.getData().putAll(bundle); } try { cmd.messenger.send(m); } catch (RemoteException e) { if (AppContext.DEBUG) { e.printStackTrace(); } } } private int showSendingNotification() { int id = NOTIFICATION_ID; PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(), 0); NotificationCompat.Builder builder = new NotificationCompat.Builder(this); builder.setSmallIcon(R.drawable.ic_stat_app); builder.setTicker("饭否消息正在发送..."); builder.setWhen(System.currentTimeMillis()); builder.setContentTitle("饭否消息"); builder.setContentText("正在发送..."); builder.setOngoing(true); builder.setContentIntent(contentIntent); Notification notification = builder.build(); mNotificationManager.notify(id, notification); return id; } private int showFailedNotification(String title, String message) { int id = NOTIFICATION_ID + 2; Intent intent = new Intent(this, UIRecords.class); PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); NotificationCompat.Builder builder = new NotificationCompat.Builder(this); builder.setSmallIcon(R.drawable.ic_stat_app); builder.setTicker(title); builder.setWhen(System.currentTimeMillis()); builder.setContentTitle(title); builder.setContentText(message); builder.setContentIntent(contentIntent); builder.setAutoCancel(true); builder.setOnlyAlertOnce(true); Notification notification = builder.build(); mNotificationManager.notify(id, notification); return id; } private void cancelNotification(final int notificationId) { mNotificationManager.cancel(notificationId); } private void doSaveRecords(StatusUpdateInfo info) { Uri resultUri = DataController.store(this, info); } private void sendSuccessBroadcast(StatusModel status) { Bus.getDefault().post(new StatusUpdateEvent(status)); } private void doClose() { mExecutor.shutdownNow(); mExecutor = null; mCommandHandler.removeCallbacksAndMessages(null); mCommandHandler.getLooper().quit(); mCommandHandler = null; } static class Command { public Messenger messenger; public String id; public int type; public Paging paging; @Override public String toString() { final StringBuilder sb = new StringBuilder("Commmand{"); sb.append("id='").append(id).append('\''); sb.append(", messenger=").append(messenger); sb.append(", type=").append(type); sb.append(", paging=").append(paging); sb.append('}'); return sb.toString(); } } static class MyRunnable implements Runnable { private Runnable runnable; private AtomicInteger counter; public MyRunnable(Runnable runnable, AtomicInteger counter) { this.runnable = runnable; this.counter = counter; } @Override public void run() { counter.incrementAndGet(); if (runnable != null) { runnable.run(); } counter.decrementAndGet(); } } }