package de.bsd.zwitscher; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.ObjectOutputStream; import android.app.IntentService; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.Parcelable; import android.util.Log; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import com.bugsense.trace.BugSenseHandler; import de.bsd.zwitscher.account.Account; import de.bsd.zwitscher.helper.NetworkHelper; import de.bsd.zwitscher.other.ReadItLaterStore; import twitter4j.StatusUpdate; import twitter4j.TwitterException; import twitter4j.media.MediaProvider; /** * Intent service that does async updates to the server * * @author Heiko W. Rupp */ public class UpdateStatusService extends IntentService { public UpdateStatusService() { super("UpdateStatusService"); } UpdateStatusService(String name) { super(name); } public static void sendUpdate(Context caller, Account account, UpdateRequest request) { Intent intent = new Intent(caller,UpdateStatusService.class); intent.putExtra("account",account); intent.putExtra("request",(Parcelable)request); caller.startService(intent); } @Override protected void onHandleIntent(Intent intent) { Bundle bundle = intent.getExtras(); UpdateRequest request = bundle.getParcelable("request"); Account account = bundle.getParcelable("account"); Context context = getApplicationContext(); TwitterHelper th = new TwitterHelper(context.getApplicationContext(), account); UpdateResponse ret; MediaProvider mediaProvider = th.getMediaProvider(); if (request.updateType==UpdateType.UPDATE && request.picturePath!=null) { if (mediaProvider.equals(MediaProvider.TWITTER)) { request.statusUpdate.setMedia(new File(request.picturePath)); } } NetworkHelper nh = new NetworkHelper(context); if (!nh.isOnline()) { // We are not online, queue the request ret = queueUpUpdate(request, context.getString(R.string.queueing), account); createNotification(ret); stopSelf(); // TODO correct? } try { switch (request.updateType) { case UPDATE: if (request.picturePath!=null) { if (mediaProvider.equals(MediaProvider.TWITTER)) { request.statusUpdate.setMedia(new File(request.picturePath)); } else { // External picture provider like yfrog StatusUpdate statusUpdate = request.statusUpdate; String tmp = th.postPicture(request.picturePath, statusUpdate.getStatus()); // if tmp == null picture upload failed - it is probably a good idea to inform the user. if (tmp==null) { UpdateResponse tmpResponse = new UpdateResponse(request.updateType,"Picture upload failed - was the image too big?"); tmpResponse.setFailure(); createNotification(tmpResponse); return; } String res = statusUpdate.getStatus() + " " + tmp; StatusUpdate up = new StatusUpdate(res); up.setInReplyToStatusId(statusUpdate.getInReplyToStatusId()); up.setLocation(statusUpdate.getLocation()); up.setPlaceId(statusUpdate.getPlaceId()); up.setPossiblySensitive(statusUpdate.isPossiblySensitive()); up.setDisplayCoordinates(statusUpdate.isDisplayCoordinates()); request.statusUpdate = up; } } ret = th.updateStatus(request); ret.someBool = request.someBool; break; case FAVORITE: ret = th.favorite(request); break; case DIRECT: ret = th.direct(request); break; case RETWEET: ret = th.retweet(request); break; case UPLOAD_PIC: if (request.picturePath!=null) { String url = th.postPicture(request.picturePath, request.message); if (url!=null) { ret = new UpdateResponse(request.updateType,request.view,url); ret.setSuccess(); } else { ret = new UpdateResponse(request.updateType,request.view,""); ret.setFailure(); ret.setMessage("Picture upload failed"); } ret.someBool = request.someBool; } else { ret = new UpdateResponse(request.updateType,request.view,""); ret.setFailure(); ret.setMessage("No picture passed"); } break; case LATER_READING: ReadItLaterStore store = new ReadItLaterStore(request.extUser,request.extPassword); String result = store.store(request.status,!account.isStatusNet(),request.url); ret = new UpdateResponse(request.updateType, result); break; case REPORT_AS_SPAMMER: th.reportAsSpammer(request.id); ret = new UpdateResponse(request.updateType, context.getString(R.string.ok)); break; case ADD_TO_LIST: th.addUserToLists(request.userId,(int)request.id); ret = new UpdateResponse(request.updateType, context.getString(R.string.added)); break; case REMOVE_FROM_LIST: th.removeUserFromList(request.userId,(int)request.id); ret = new UpdateResponse(request.updateType, context.getString(R.string.removed)); break; case FOLLOW_UNFOLLOW: th.followUnfollowUser(request.userId,request.someBool); ret = new UpdateResponse(request.updateType, context.getString(R.string.follow_unfollow_set)); break; case DELETE_STATUS: th.deleteStatus(request.id); ret = new UpdateResponse(request.updateType, context.getString(R.string.status_deleted)); break; default: throw new IllegalArgumentException(context.getString(R.string.update_not_yet_supported, request.updateType)); } if (ret!=null && (ret.getStatusCode()==502||ret.getStatusCode()==503||ret.getStatusCode()==420)) { ret = queueUpUpdate(request,context.getString(R.string.queueing_code, ret.getMessage()), account); } } catch (TwitterException e) { BugSenseHandler.sendExceptionMessage("UpdateStatus",request.getUpdateType().toString(),e); ret = queueUpUpdate(request,context.getString(R.string.queueing), account); } onPostExecute(ret); stopSelf(); } /** * Queue up the Update request for later sending. * * @param request Request to queue up * @param message Reason why this was queued * @param account * @return a new surrogate request to continue processing with * @see de.bsd.zwitscher.helper.FlushQueueTask */ private UpdateResponse queueUpUpdate(UpdateRequest request, String message, Account account) { TweetDB tdb = TweetDB.getInstance(getApplicationContext()); UpdateResponse response; try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bos); out.writeObject(request); out.close(); tdb.persistUpdate(account.getId(), bos.toByteArray()); response = new UpdateResponse(UpdateType.QUEUED, message); response.setSuccess(); // This is the success of the queueing, not the inner job. } catch (IOException e) { BugSenseHandler.sendException(e); response = new UpdateResponse(UpdateType.QUEUED, e.getMessage()); } createNotification(response); return response; } protected void onPostExecute(UpdateResponse result) { if (result==null) { Toast.makeText(getApplicationContext(),"No result - should not happen",Toast.LENGTH_SHORT).show(); return; } if (result.getUpdateType()==UpdateType.UPLOAD_PIC) { TextView textView = (TextView) result.view; if (textView==null) return; if (textView.getText().length()==0) textView.setText(result.getMessage()); else textView.append(" " + result.getMessage()); } else if (result.getUpdateType() == UpdateType.FAVORITE) { ImageView favoriteButton = (ImageView) result.view; if (favoriteButton==null || result.status == null) return; try { if (result.status.isFavorited()) favoriteButton.setImageResource(R.drawable.favorite_on); else favoriteButton.setImageResource(R.drawable.favorite_off); } catch (Exception e) { Log.i("UpdateStatusTask", "Favorite button seems to be gone"); } } else if (result.getUpdateType()==UpdateType.UPDATE) { if (result.isSuccess() && result.getPicturePath()!=null) { if (result.someBool) { // only delete if some bool is set, which means that the we allow to remove the picture File file = new File(result.getPicturePath()); if (file.exists()) { // file.delete(); // TODO only for camera shots, not for Gallery images } } } } createNotification(result); } /** * Create a notification for the (Android) system wide message center and put a message there * @param result Result of a status update */ private void createNotification(UpdateResponse result) { String ns = Context.NOTIFICATION_SERVICE; final NotificationManager mNotificationManager = (NotificationManager) getApplicationContext().getSystemService(ns); mNotificationManager.cancelAll(); int icon = R.drawable.zwitscher_notif; Notification notification; if (Build.VERSION.SDK_INT<16) { if (!result.isSuccess()) { notification = new Notification(icon,result.getUpdateType().toString() + " failed",System.currentTimeMillis()); } else { notification = new Notification(icon,result.getUpdateType().toString(), System.currentTimeMillis()); } } else { Notification.Builder builder = new Notification.Builder(getApplicationContext()); if (result.isSuccess()) { builder.setAutoCancel(true); } builder.setContentTitle("Zwitscher update"); String text = "Result of " + result.getUpdateType().toString() + " is success: " + result.isSuccess(); if (Build.VERSION.SDK_INT<16) { builder.setContentText(text); } else { builder.setSubText(text); } if (result.getUpdateType()==UpdateType.QUEUED) { builder.setTicker("Queued for later sending"); } else { builder.setTicker(getString(R.string.sending_succeeded)); } builder.setSmallIcon(R.drawable.zwitscher_notif); if (Build.VERSION.SDK_INT<16) { notification = builder.getNotification(); } else { notification = builder.build(); } } PendingIntent pintent; if (result.isSuccess()) { pintent = PendingIntent.getActivity(getApplicationContext(), 0, new Intent(), PendingIntent.FLAG_CANCEL_CURRENT); } else { // Only set the Error display intent when this is no success String head = result.getUpdateType() + " failed:"; String text = result.getMessage(); String message =""; if (result.getUpdateType()==UpdateType.QUEUED) message = "Queueing failed : "+ result.getMessage(); if (result.getUpdateType()==UpdateType.UPDATE) { if (result.getUpdate()!=null && result.getUpdate().getStatus()!=null) { message= result.getUpdate().getStatus(); } } if (result.getUpdateType()==UpdateType.DIRECT) message= result.getOrigMessage(); Intent intent = new Intent(getApplicationContext(),ErrorDisplayActivity.class); Bundle bundle=new Bundle(3); bundle.putString("e_head", head); bundle.putString("e_body", text); bundle.putString("e_text", message); intent.putExtras(bundle); pintent = PendingIntent.getActivity(getApplicationContext(), 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); notification.setLatestEventInfo(getApplicationContext(), head, text, pintent); } mNotificationManager.notify(3,notification); if (result.isSuccess()) { // remove notifcation after some time secs long delayInMilliseconds = 5000; final HandlerThread t = new HandlerThread("bla"); t.start(); Runnable r = new Runnable() { public void run() { mNotificationManager.cancel(3); t.quit(); } }; Handler h = new Handler(t.getLooper()); boolean success = h.postDelayed(r, delayInMilliseconds); } } }