785 lines
36 KiB
Java
785 lines
36 KiB
Java
/*
|
|
* Twidere - Twitter client for Android
|
|
*
|
|
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package org.mariotaku.twidere.service;
|
|
|
|
import android.app.IntentService;
|
|
import android.app.Notification;
|
|
import android.content.ComponentName;
|
|
import android.content.ContentResolver;
|
|
import android.content.ContentValues;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.SharedPreferences;
|
|
import android.content.pm.PackageManager;
|
|
import android.database.Cursor;
|
|
import android.graphics.BitmapFactory;
|
|
import android.net.Uri;
|
|
import android.os.Handler;
|
|
import android.os.Parcelable;
|
|
import android.provider.BaseColumns;
|
|
import android.support.v4.app.NotificationCompat;
|
|
import android.support.v4.app.NotificationCompat.Builder;
|
|
import android.text.TextUtils;
|
|
import android.util.Log;
|
|
import android.widget.Toast;
|
|
|
|
import com.nostra13.universalimageloader.utils.IoUtils;
|
|
import com.twitter.Extractor;
|
|
|
|
import org.apache.commons.lang3.math.NumberUtils;
|
|
import org.mariotaku.restfu.http.ContentType;
|
|
import org.mariotaku.restfu.http.mime.FileTypedData;
|
|
import org.mariotaku.sqliteqb.library.Expression;
|
|
import org.mariotaku.twidere.Constants;
|
|
import org.mariotaku.twidere.R;
|
|
import org.mariotaku.twidere.activity.MainActivity;
|
|
import org.mariotaku.twidere.activity.MainHondaJOJOActivity;
|
|
import org.mariotaku.twidere.api.twitter.Twitter;
|
|
import org.mariotaku.twidere.api.twitter.TwitterException;
|
|
import org.mariotaku.twidere.api.twitter.TwitterUpload;
|
|
import org.mariotaku.twidere.api.twitter.model.MediaUploadResponse;
|
|
import org.mariotaku.twidere.api.twitter.model.Status;
|
|
import org.mariotaku.twidere.api.twitter.model.StatusUpdate;
|
|
import org.mariotaku.twidere.api.twitter.model.UserMentionEntity;
|
|
import org.mariotaku.twidere.app.TwidereApplication;
|
|
import org.mariotaku.twidere.model.DraftItem;
|
|
import org.mariotaku.twidere.model.DraftItemCursorIndices;
|
|
import org.mariotaku.twidere.model.MediaUploadResult;
|
|
import org.mariotaku.twidere.model.ParcelableAccount;
|
|
import org.mariotaku.twidere.model.ParcelableDirectMessage;
|
|
import org.mariotaku.twidere.model.ParcelableLocation;
|
|
import org.mariotaku.twidere.model.ParcelableMediaUpdate;
|
|
import org.mariotaku.twidere.model.ParcelableStatus;
|
|
import org.mariotaku.twidere.model.ParcelableStatusUpdate;
|
|
import org.mariotaku.twidere.model.SingleResponse;
|
|
import org.mariotaku.twidere.model.StatusShortenResult;
|
|
import org.mariotaku.twidere.model.UploaderMediaItem;
|
|
import org.mariotaku.twidere.preference.ServicePickerPreference;
|
|
import org.mariotaku.twidere.provider.TwidereDataStore.CachedHashtags;
|
|
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages;
|
|
import org.mariotaku.twidere.provider.TwidereDataStore.Drafts;
|
|
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
|
|
import org.mariotaku.twidere.util.BitmapUtils;
|
|
import org.mariotaku.twidere.util.ContentValuesCreator;
|
|
import org.mariotaku.twidere.util.MediaUploaderInterface;
|
|
import org.mariotaku.twidere.util.NotificationManagerWrapper;
|
|
import org.mariotaku.twidere.util.StatusCodeMessageUtils;
|
|
import org.mariotaku.twidere.util.StatusShortenerInterface;
|
|
import org.mariotaku.twidere.util.TwidereListUtils;
|
|
import org.mariotaku.twidere.util.TwidereValidator;
|
|
import org.mariotaku.twidere.util.TwitterAPIFactory;
|
|
import org.mariotaku.twidere.util.Utils;
|
|
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper;
|
|
import org.mariotaku.twidere.util.io.ContentLengthInputStream;
|
|
import org.mariotaku.twidere.util.io.ContentLengthInputStream.ReadListener;
|
|
|
|
import java.io.File;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
|
|
import javax.inject.Inject;
|
|
|
|
import edu.tsinghua.hotmobi.HotMobiLogger;
|
|
import edu.tsinghua.hotmobi.model.TimelineType;
|
|
import edu.tsinghua.hotmobi.model.TweetEvent;
|
|
|
|
import static android.text.TextUtils.isEmpty;
|
|
import static org.mariotaku.twidere.util.ContentValuesCreator.createMessageDraft;
|
|
import static org.mariotaku.twidere.util.Utils.getImagePathFromUri;
|
|
import static org.mariotaku.twidere.util.Utils.getImageUploadStatus;
|
|
|
|
public class BackgroundOperationService extends IntentService implements Constants {
|
|
|
|
private TwidereValidator mValidator;
|
|
private final Extractor extractor = new Extractor();
|
|
|
|
private Handler mHandler;
|
|
private SharedPreferences mPreferences;
|
|
private ContentResolver mResolver;
|
|
@Inject
|
|
AsyncTwitterWrapper mTwitter;
|
|
@Inject
|
|
NotificationManagerWrapper mNotificationManager;
|
|
|
|
private MediaUploaderInterface mUploader;
|
|
private StatusShortenerInterface mShortener;
|
|
|
|
private boolean mUseUploader, mUseShortener;
|
|
|
|
public BackgroundOperationService() {
|
|
super("background_operation");
|
|
}
|
|
|
|
@Override
|
|
public void onCreate() {
|
|
super.onCreate();
|
|
GeneralComponentHelper.build(this).inject(this);
|
|
final TwidereApplication app = TwidereApplication.getInstance(this);
|
|
mHandler = new Handler();
|
|
mPreferences = getSharedPreferences(SHARED_PREFERENCES_NAME, MODE_PRIVATE);
|
|
mValidator = new TwidereValidator(this);
|
|
mResolver = getContentResolver();
|
|
final String uploaderComponent = mPreferences.getString(KEY_MEDIA_UPLOADER, null);
|
|
final String shortenerComponent = mPreferences.getString(KEY_STATUS_SHORTENER, null);
|
|
mUseUploader = !ServicePickerPreference.isNoneValue(uploaderComponent);
|
|
mUseShortener = !ServicePickerPreference.isNoneValue(shortenerComponent);
|
|
mUploader = mUseUploader ? MediaUploaderInterface.getInstance(app, uploaderComponent) : null;
|
|
mShortener = mUseShortener ? StatusShortenerInterface.getInstance(app, shortenerComponent) : null;
|
|
}
|
|
|
|
@Override
|
|
public int onStartCommand(final Intent intent, final int flags, final int startId) {
|
|
super.onStartCommand(intent, flags, startId);
|
|
return START_STICKY;
|
|
}
|
|
|
|
public void showErrorMessage(final CharSequence message, final boolean longMessage) {
|
|
mHandler.post(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
Utils.showErrorMessage(BackgroundOperationService.this, message, longMessage);
|
|
}
|
|
});
|
|
}
|
|
|
|
public void showErrorMessage(final int actionRes, final Exception e, final boolean longMessage) {
|
|
mHandler.post(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
Utils.showErrorMessage(BackgroundOperationService.this, actionRes, e, longMessage);
|
|
}
|
|
});
|
|
}
|
|
|
|
public void showErrorMessage(final int actionRes, final String message, final boolean longMessage) {
|
|
mHandler.post(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
Utils.showErrorMessage(BackgroundOperationService.this, actionRes, message, longMessage);
|
|
}
|
|
});
|
|
}
|
|
|
|
public void showOkMessage(final int messageRes, final boolean longMessage) {
|
|
showToast(getString(messageRes), longMessage);
|
|
}
|
|
|
|
private void showToast(final CharSequence message, final boolean longMessage) {
|
|
mHandler.post(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
Toast.makeText(BackgroundOperationService.this, message, longMessage ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT).show();
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
protected void onHandleIntent(final Intent intent) {
|
|
if (intent == null) return;
|
|
final String action = intent.getAction();
|
|
if (action == null) return;
|
|
switch (action) {
|
|
case INTENT_ACTION_UPDATE_STATUS:
|
|
handleUpdateStatusIntent(intent);
|
|
break;
|
|
case INTENT_ACTION_SEND_DIRECT_MESSAGE:
|
|
handleSendDirectMessageIntent(intent);
|
|
break;
|
|
case INTENT_ACTION_DISCARD_DRAFT:
|
|
handleDiscardDraftIntent(intent);
|
|
break;
|
|
case INTENT_ACTION_SEND_DRAFT: {
|
|
handleSendDraftIntent(intent);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void handleSendDraftIntent(Intent intent) {
|
|
final Uri uri = intent.getData();
|
|
if (uri == null) return;
|
|
mNotificationManager.cancel(uri.toString(), NOTIFICATION_ID_DRAFTS);
|
|
final long def = -1;
|
|
final long draftId = NumberUtils.toLong(uri.getLastPathSegment(), def);
|
|
if (draftId == -1) return;
|
|
final Expression where = Expression.equals(Drafts._ID, draftId);
|
|
final ContentResolver cr = getContentResolver();
|
|
final Cursor c = cr.query(Drafts.CONTENT_URI, Drafts.COLUMNS, where.getSQL(), null, null);
|
|
if (c == null) return;
|
|
final DraftItemCursorIndices i = new DraftItemCursorIndices(c);
|
|
final DraftItem item;
|
|
try {
|
|
if (!c.moveToFirst()) return;
|
|
item = i.newObject(c);
|
|
} finally {
|
|
c.close();
|
|
}
|
|
cr.delete(Drafts.CONTENT_URI, where.getSQL(), null);
|
|
if (item.action_type == Drafts.ACTION_UPDATE_STATUS || item.action_type <= 0) {
|
|
updateStatuses(new ParcelableStatusUpdate(this, item));
|
|
} else if (item.action_type == Drafts.ACTION_SEND_DIRECT_MESSAGE) {
|
|
final long recipientId = item.action_extras != null ? item.action_extras.optLong(EXTRA_RECIPIENT_ID) : -1;
|
|
if (item.account_ids == null || item.account_ids.length <= 0 || recipientId <= 0) {
|
|
return;
|
|
}
|
|
final long accountId = item.account_ids[0];
|
|
final String imageUri = item.media != null && item.media.length > 0 ? item.media[0].uri : null;
|
|
sendMessage(accountId, recipientId, item.text, imageUri);
|
|
}
|
|
}
|
|
|
|
private void handleDiscardDraftIntent(Intent intent) {
|
|
final Uri data = intent.getData();
|
|
if (data == null) return;
|
|
mNotificationManager.cancel(data.toString(), NOTIFICATION_ID_DRAFTS);
|
|
final ContentResolver cr = getContentResolver();
|
|
final long def = -1;
|
|
final long id = NumberUtils.toLong(data.getLastPathSegment(), def);
|
|
final Expression where = Expression.equals(Drafts._ID, id);
|
|
cr.delete(Drafts.CONTENT_URI, where.getSQL(), null);
|
|
}
|
|
|
|
private void handleSendDirectMessageIntent(final Intent intent) {
|
|
final long accountId = intent.getLongExtra(EXTRA_ACCOUNT_ID, -1);
|
|
final long recipientId = intent.getLongExtra(EXTRA_RECIPIENT_ID, -1);
|
|
final String imageUri = intent.getStringExtra(EXTRA_IMAGE_URI);
|
|
final String text = intent.getStringExtra(EXTRA_TEXT);
|
|
sendMessage(accountId, recipientId, text, imageUri);
|
|
}
|
|
|
|
private void sendMessage(long accountId, long recipientId, String text, String imageUri) {
|
|
if (accountId <= 0 || recipientId <= 0 || isEmpty(text)) return;
|
|
final String title = getString(R.string.sending_direct_message);
|
|
final Builder builder = new Builder(this);
|
|
builder.setSmallIcon(R.drawable.ic_stat_send);
|
|
builder.setProgress(100, 0, true);
|
|
builder.setTicker(title);
|
|
builder.setContentTitle(title);
|
|
builder.setContentText(text);
|
|
builder.setOngoing(true);
|
|
final Notification notification = builder.build();
|
|
startForeground(NOTIFICATION_ID_SEND_DIRECT_MESSAGE, notification);
|
|
final SingleResponse<ParcelableDirectMessage> result = sendDirectMessage(builder, accountId, recipientId, text,
|
|
imageUri);
|
|
|
|
if (result.getData() != null && result.getData().id > 0) {
|
|
final ContentValues values = ContentValuesCreator.createDirectMessage(result.getData());
|
|
final String delete_where = DirectMessages.ACCOUNT_ID + " = " + accountId + " AND "
|
|
+ DirectMessages.MESSAGE_ID + " = " + result.getData().id;
|
|
mResolver.delete(DirectMessages.Outbox.CONTENT_URI, delete_where, null);
|
|
mResolver.insert(DirectMessages.Outbox.CONTENT_URI, values);
|
|
showOkMessage(R.string.direct_message_sent, false);
|
|
} else {
|
|
final ContentValues values = createMessageDraft(accountId, recipientId, text, imageUri);
|
|
mResolver.insert(Drafts.CONTENT_URI, values);
|
|
showErrorMessage(R.string.action_sending_direct_message, result.getException(), true);
|
|
}
|
|
stopForeground(false);
|
|
mNotificationManager.cancel(NOTIFICATION_ID_SEND_DIRECT_MESSAGE);
|
|
}
|
|
|
|
private void handleUpdateStatusIntent(final Intent intent) {
|
|
final ParcelableStatusUpdate status = intent.getParcelableExtra(EXTRA_STATUS);
|
|
final Parcelable[] status_parcelables = intent.getParcelableArrayExtra(EXTRA_STATUSES);
|
|
final ParcelableStatusUpdate[] statuses;
|
|
if (status_parcelables != null) {
|
|
statuses = new ParcelableStatusUpdate[status_parcelables.length];
|
|
for (int i = 0, j = status_parcelables.length; i < j; i++) {
|
|
statuses[i] = (ParcelableStatusUpdate) status_parcelables[i];
|
|
}
|
|
} else if (status != null) {
|
|
statuses = new ParcelableStatusUpdate[1];
|
|
statuses[0] = status;
|
|
} else
|
|
return;
|
|
updateStatuses(statuses);
|
|
}
|
|
|
|
private void updateStatuses(ParcelableStatusUpdate... statuses) {
|
|
final Builder builder = new Builder(this);
|
|
startForeground(NOTIFICATION_ID_UPDATE_STATUS, updateUpdateStatusNotification(this, builder, 0, null));
|
|
for (final ParcelableStatusUpdate item : statuses) {
|
|
mNotificationManager.notify(NOTIFICATION_ID_UPDATE_STATUS,
|
|
updateUpdateStatusNotification(this, builder, 0, item));
|
|
final ContentValues draftValues = ContentValuesCreator.createStatusDraft(item,
|
|
ParcelableAccount.getAccountIds(item.accounts));
|
|
final Uri draftUri = mResolver.insert(Drafts.CONTENT_URI, draftValues);
|
|
final long def = -1;
|
|
final long draftId = draftUri != null ? NumberUtils.toLong(draftUri.getLastPathSegment(), def) : -1;
|
|
mTwitter.addSendingDraftId(draftId);
|
|
final List<SingleResponse<ParcelableStatus>> result = updateStatus(builder, item);
|
|
boolean failed = false;
|
|
Exception exception = null;
|
|
final Expression where = Expression.equals(Drafts._ID, draftId);
|
|
final List<Long> failedAccountIds = TwidereListUtils.fromArray(ParcelableAccount.getAccountIds(item.accounts));
|
|
|
|
for (final SingleResponse<ParcelableStatus> response : result) {
|
|
final ParcelableStatus data = response.getData();
|
|
if (data == null) {
|
|
failed = true;
|
|
if (exception == null) {
|
|
exception = response.getException();
|
|
}
|
|
} else if (data.account_id > 0) {
|
|
failedAccountIds.remove(data.account_id);
|
|
// BEGIN HotMobi
|
|
final TweetEvent event = TweetEvent.create(this, data, TimelineType.OTHER);
|
|
event.setAction(TweetEvent.Action.TWEET);
|
|
HotMobiLogger.getInstance(this).log(data.account_id, event);
|
|
// END HotMobi
|
|
}
|
|
}
|
|
|
|
if (result.isEmpty()) {
|
|
showErrorMessage(R.string.action_updating_status, getString(R.string.no_account_selected), false);
|
|
} else if (failed) {
|
|
// If the status is a duplicate, there's no need to save it to
|
|
// drafts.
|
|
if (exception instanceof TwitterException
|
|
&& ((TwitterException) exception).getErrorCode() == StatusCodeMessageUtils.STATUS_IS_DUPLICATE) {
|
|
showErrorMessage(getString(R.string.status_is_duplicate), false);
|
|
} else {
|
|
final ContentValues accountIdsValues = new ContentValues();
|
|
accountIdsValues.put(Drafts.ACCOUNT_IDS, TwidereListUtils.toString(failedAccountIds, ',', false));
|
|
mResolver.update(Drafts.CONTENT_URI, accountIdsValues, where.getSQL(), null);
|
|
showErrorMessage(R.string.action_updating_status, exception, true);
|
|
final ContentValues notifValues = new ContentValues();
|
|
notifValues.put(BaseColumns._ID, draftId);
|
|
mResolver.insert(Drafts.CONTENT_URI_NOTIFICATIONS, notifValues);
|
|
}
|
|
} else {
|
|
showOkMessage(R.string.status_updated, false);
|
|
mResolver.delete(Drafts.CONTENT_URI, where.getSQL(), null);
|
|
if (item.media != null) {
|
|
for (final ParcelableMediaUpdate media : item.media) {
|
|
final String path = getImagePathFromUri(this, Uri.parse(media.uri));
|
|
if (path != null) {
|
|
if (!new File(path).delete()) {
|
|
Log.d(LOGTAG, String.format("unable to delete %s", path));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
mTwitter.removeSendingDraftId(draftId);
|
|
if (mPreferences.getBoolean(KEY_REFRESH_AFTER_TWEET, false)) {
|
|
mHandler.post(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
mTwitter.refreshAll();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
stopForeground(false);
|
|
mNotificationManager.cancel(NOTIFICATION_ID_UPDATE_STATUS);
|
|
}
|
|
|
|
|
|
private SingleResponse<ParcelableDirectMessage> sendDirectMessage(final NotificationCompat.Builder builder,
|
|
final long accountId, final long recipientId,
|
|
final String text, final String imageUri) {
|
|
final Twitter twitter = TwitterAPIFactory.getTwitterInstance(this, accountId, true, true);
|
|
final TwitterUpload twitterUpload = TwitterAPIFactory.getTwitterInstance(this, accountId, true, true, TwitterUpload.class);
|
|
if (twitter == null || twitterUpload == null) return SingleResponse.getInstance();
|
|
try {
|
|
final ParcelableDirectMessage directMessage;
|
|
if (imageUri != null) {
|
|
final String path = getImagePathFromUri(this, Uri.parse(imageUri));
|
|
if (path == null) throw new FileNotFoundException();
|
|
final BitmapFactory.Options o = new BitmapFactory.Options();
|
|
o.inJustDecodeBounds = true;
|
|
BitmapFactory.decodeFile(path, o);
|
|
final File file = new File(path);
|
|
BitmapUtils.downscaleImageIfNeeded(file, 100);
|
|
final ContentLengthInputStream is = new ContentLengthInputStream(file);
|
|
is.setReadListener(new MessageMediaUploadListener(this, mNotificationManager, builder, text));
|
|
// final MediaUploadResponse uploadResp = twitter.uploadMedia(file.getName(), is, o.outMimeType);
|
|
final MediaUploadResponse uploadResp = twitterUpload.uploadMedia(file);
|
|
directMessage = new ParcelableDirectMessage(twitter.sendDirectMessage(recipientId, text,
|
|
uploadResp.getId()), accountId, true);
|
|
if (!file.delete()) {
|
|
Log.d(LOGTAG, String.format("unable to delete %s", path));
|
|
}
|
|
} else {
|
|
directMessage = new ParcelableDirectMessage(twitter.sendDirectMessage(recipientId, text), accountId,
|
|
true);
|
|
}
|
|
Utils.setLastSeen(this, recipientId, System.currentTimeMillis());
|
|
|
|
|
|
return SingleResponse.getInstance(directMessage);
|
|
} catch (final IOException e) {
|
|
return SingleResponse.getInstance(e);
|
|
} catch (final TwitterException e) {
|
|
return SingleResponse.getInstance(e);
|
|
}
|
|
}
|
|
|
|
private void showToast(final int resId, final int duration) {
|
|
mHandler.post(new ToastRunnable(this, resId, duration));
|
|
}
|
|
|
|
private List<SingleResponse<ParcelableStatus>> updateStatus(final Builder builder,
|
|
final ParcelableStatusUpdate statusUpdate) {
|
|
final ArrayList<ContentValues> hashTagValues = new ArrayList<>();
|
|
final Collection<String> hashTags = extractor.extractHashtags(statusUpdate.text);
|
|
for (final String hashTag : hashTags) {
|
|
final ContentValues values = new ContentValues();
|
|
values.put(CachedHashtags.NAME, hashTag);
|
|
hashTagValues.add(values);
|
|
}
|
|
final boolean hasEasterEggTriggerText = statusUpdate.text.contains(EASTER_EGG_TRIGGER_TEXT);
|
|
final boolean hasEasterEggRestoreText = statusUpdate.text.contains(EASTER_EGG_RESTORE_TEXT_PART1)
|
|
&& statusUpdate.text.contains(EASTER_EGG_RESTORE_TEXT_PART2)
|
|
&& statusUpdate.text.contains(EASTER_EGG_RESTORE_TEXT_PART3);
|
|
boolean mentionedHondaJOJO = false, notReplyToOther = false;
|
|
mResolver.bulkInsert(CachedHashtags.CONTENT_URI,
|
|
hashTagValues.toArray(new ContentValues[hashTagValues.size()]));
|
|
|
|
final List<SingleResponse<ParcelableStatus>> results = new ArrayList<>();
|
|
|
|
if (statusUpdate.accounts.length == 0) return Collections.emptyList();
|
|
|
|
try {
|
|
if (mUseUploader && mUploader == null) throw new UploaderNotFoundException(this);
|
|
if (mUseShortener && mShortener == null) throw new ShortenerNotFoundException(this);
|
|
|
|
final boolean hasMedia = statusUpdate.media != null && statusUpdate.media.length > 0;
|
|
|
|
final String overrideStatusText;
|
|
if (mUseUploader && mUploader != null && hasMedia) {
|
|
final MediaUploadResult uploadResult;
|
|
try {
|
|
mUploader.waitForService();
|
|
uploadResult = mUploader.upload(statusUpdate,
|
|
UploaderMediaItem.getFromStatusUpdate(this, statusUpdate));
|
|
} catch (final Exception e) {
|
|
throw new UploadException(this);
|
|
} finally {
|
|
mUploader.unbindService();
|
|
}
|
|
if (mUseUploader && hasMedia && uploadResult == null)
|
|
throw new UploadException(this);
|
|
if (uploadResult.error_code != 0)
|
|
throw new UploadException(uploadResult.error_message);
|
|
overrideStatusText = getImageUploadStatus(this, uploadResult.media_uris, statusUpdate.text);
|
|
} else {
|
|
overrideStatusText = null;
|
|
}
|
|
|
|
final String unShortenedText = isEmpty(overrideStatusText) ? statusUpdate.text : overrideStatusText;
|
|
|
|
final boolean shouldShorten = mValidator.getTweetLength(unShortenedText) > mValidator.getMaxTweetLength();
|
|
final String shortenedText;
|
|
if (shouldShorten) {
|
|
if (mUseShortener) {
|
|
final StatusShortenResult shortenedResult;
|
|
mShortener.waitForService();
|
|
try {
|
|
shortenedResult = mShortener.shorten(statusUpdate, unShortenedText);
|
|
} catch (final Exception e) {
|
|
throw new ShortenException(this);
|
|
} finally {
|
|
mShortener.unbindService();
|
|
}
|
|
if (shortenedResult == null || shortenedResult.shortened == null)
|
|
throw new ShortenException(this);
|
|
shortenedText = shortenedResult.shortened;
|
|
} else
|
|
throw new StatusTooLongException(this);
|
|
} else {
|
|
shortenedText = unShortenedText;
|
|
}
|
|
if (statusUpdate.media != null) {
|
|
for (final ParcelableMediaUpdate media : statusUpdate.media) {
|
|
final String path = getImagePathFromUri(this, Uri.parse(media.uri));
|
|
final File file = path != null ? new File(path) : null;
|
|
if (!mUseUploader && file != null && file.exists()) {
|
|
BitmapUtils.downscaleImageIfNeeded(file, 95);
|
|
}
|
|
}
|
|
}
|
|
for (final ParcelableAccount account : statusUpdate.accounts) {
|
|
final Twitter twitter = TwitterAPIFactory.getTwitterInstance(this, account.account_id, true, true);
|
|
final TwitterUpload upload = TwitterAPIFactory.getTwitterInstance(this, account.account_id, true, true, TwitterUpload.class);
|
|
final StatusUpdate status = new StatusUpdate(shortenedText);
|
|
if (statusUpdate.in_reply_to_status_id > 0) {
|
|
status.inReplyToStatusId(statusUpdate.in_reply_to_status_id);
|
|
}
|
|
if (statusUpdate.location != null) {
|
|
status.location(ParcelableLocation.toGeoLocation(statusUpdate.location));
|
|
}
|
|
if (!mUseUploader && hasMedia) {
|
|
final BitmapFactory.Options o = new BitmapFactory.Options();
|
|
o.inJustDecodeBounds = true;
|
|
final long[] mediaIds = new long[statusUpdate.media.length];
|
|
ContentLengthInputStream is = null;
|
|
try {
|
|
for (int i = 0, j = mediaIds.length; i < j; i++) {
|
|
final ParcelableMediaUpdate media = statusUpdate.media[i];
|
|
final String path = getImagePathFromUri(this, Uri.parse(media.uri));
|
|
if (path == null) throw new FileNotFoundException();
|
|
BitmapFactory.decodeFile(path, o);
|
|
final File file = new File(path);
|
|
is = new ContentLengthInputStream(file);
|
|
is.setReadListener(new StatusMediaUploadListener(this, mNotificationManager, builder,
|
|
statusUpdate));
|
|
final ContentType contentType;
|
|
if (TextUtils.isEmpty(o.outMimeType)) {
|
|
contentType = ContentType.parse("image/*");
|
|
} else {
|
|
contentType = ContentType.parse(o.outMimeType);
|
|
}
|
|
final MediaUploadResponse uploadResp = upload.uploadMedia(new FileTypedData(is,
|
|
file.getName(), file.length(), contentType));
|
|
mediaIds[i] = uploadResp.getId();
|
|
}
|
|
} catch (final FileNotFoundException e) {
|
|
Log.w(LOGTAG, e);
|
|
} catch (final TwitterException e) {
|
|
Log.w(LOGTAG, e);
|
|
final SingleResponse<ParcelableStatus> response = SingleResponse.getInstance(e);
|
|
results.add(response);
|
|
continue;
|
|
} finally {
|
|
IoUtils.closeSilently(is);
|
|
}
|
|
status.mediaIds(mediaIds);
|
|
}
|
|
status.possiblySensitive(statusUpdate.is_possibly_sensitive);
|
|
|
|
if (twitter == null) {
|
|
results.add(SingleResponse.<ParcelableStatus>getInstance(new NullPointerException()));
|
|
continue;
|
|
}
|
|
try {
|
|
final Status resultStatus = twitter.updateStatus(status);
|
|
if (!mentionedHondaJOJO) {
|
|
final UserMentionEntity[] entities = resultStatus.getUserMentionEntities();
|
|
if (entities == null || entities.length == 0) {
|
|
mentionedHondaJOJO = statusUpdate.text.contains("@" + HONDAJOJO_SCREEN_NAME);
|
|
} else if (entities.length == 1 && entities[0].getId() == HONDAJOJO_ID) {
|
|
mentionedHondaJOJO = true;
|
|
}
|
|
Utils.setLastSeen(this, entities, System.currentTimeMillis());
|
|
}
|
|
if (!notReplyToOther) {
|
|
final long inReplyToUserId = resultStatus.getInReplyToUserId();
|
|
if (inReplyToUserId <= 0 || inReplyToUserId == HONDAJOJO_ID) {
|
|
notReplyToOther = true;
|
|
}
|
|
}
|
|
final ParcelableStatus result = new ParcelableStatus(resultStatus, account.account_id, false);
|
|
results.add(SingleResponse.getInstance(result));
|
|
} catch (final TwitterException e) {
|
|
Log.w(LOGTAG, e);
|
|
final SingleResponse<ParcelableStatus> response = SingleResponse.getInstance(e);
|
|
results.add(response);
|
|
}
|
|
}
|
|
} catch (final UpdateStatusException e) {
|
|
Log.w(LOGTAG, e);
|
|
final SingleResponse<ParcelableStatus> response = SingleResponse.getInstance(e);
|
|
results.add(response);
|
|
}
|
|
if (mentionedHondaJOJO) {
|
|
triggerEasterEgg(notReplyToOther, hasEasterEggTriggerText, hasEasterEggRestoreText);
|
|
}
|
|
return results;
|
|
}
|
|
|
|
private void triggerEasterEgg(boolean notReplyToOther, boolean hasEasterEggTriggerText, boolean hasEasterEggRestoreText) {
|
|
final PackageManager pm = getPackageManager();
|
|
final ComponentName main = new ComponentName(this, MainActivity.class);
|
|
final ComponentName main2 = new ComponentName(this, MainHondaJOJOActivity.class);
|
|
if (hasEasterEggTriggerText && notReplyToOther) {
|
|
pm.setComponentEnabledSetting(main, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
|
PackageManager.DONT_KILL_APP);
|
|
pm.setComponentEnabledSetting(main2, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
|
PackageManager.DONT_KILL_APP);
|
|
showToast(R.string.easter_egg_triggered_message, Toast.LENGTH_SHORT);
|
|
} else if (hasEasterEggRestoreText) {
|
|
pm.setComponentEnabledSetting(main, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
|
PackageManager.DONT_KILL_APP);
|
|
pm.setComponentEnabledSetting(main2, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
|
PackageManager.DONT_KILL_APP);
|
|
showToast(R.string.icon_restored_message, Toast.LENGTH_SHORT);
|
|
}
|
|
}
|
|
|
|
private static Notification updateSendDirectMessageNotification(final Context context,
|
|
final NotificationCompat.Builder builder, final int progress, final String message) {
|
|
builder.setContentTitle(context.getString(R.string.sending_direct_message));
|
|
if (message != null) {
|
|
builder.setContentText(message);
|
|
}
|
|
builder.setSmallIcon(R.drawable.ic_stat_send);
|
|
builder.setProgress(100, progress, progress >= 100 || progress <= 0);
|
|
builder.setOngoing(true);
|
|
return builder.build();
|
|
}
|
|
|
|
private static Notification updateUpdateStatusNotification(final Context context,
|
|
final NotificationCompat.Builder builder, final int progress, final ParcelableStatusUpdate status) {
|
|
builder.setContentTitle(context.getString(R.string.updating_status_notification));
|
|
if (status != null) {
|
|
builder.setContentText(status.text);
|
|
}
|
|
builder.setSmallIcon(R.drawable.ic_stat_send);
|
|
builder.setProgress(100, progress, progress >= 100 || progress <= 0);
|
|
builder.setOngoing(true);
|
|
return builder.build();
|
|
}
|
|
|
|
private static class ToastRunnable implements Runnable {
|
|
private final Context context;
|
|
private final int resId;
|
|
private final int duration;
|
|
|
|
public ToastRunnable(final Context context, final int resId, final int duration) {
|
|
this.context = context;
|
|
this.resId = resId;
|
|
this.duration = duration;
|
|
}
|
|
|
|
@Override
|
|
public void run() {
|
|
Toast.makeText(context, resId, duration).show();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
static class MessageMediaUploadListener implements ReadListener {
|
|
private final Context context;
|
|
private final NotificationManagerWrapper manager;
|
|
|
|
int percent;
|
|
|
|
private final Builder builder;
|
|
private final String message;
|
|
|
|
MessageMediaUploadListener(final Context context, final NotificationManagerWrapper manager,
|
|
final NotificationCompat.Builder builder, final String message) {
|
|
this.context = context;
|
|
this.manager = manager;
|
|
this.builder = builder;
|
|
this.message = message;
|
|
}
|
|
|
|
@Override
|
|
public void onRead(final long length, final long position) {
|
|
final int percent = length > 0 ? (int) (position * 100 / length) : 0;
|
|
if (this.percent != percent) {
|
|
manager.notify(NOTIFICATION_ID_SEND_DIRECT_MESSAGE,
|
|
updateSendDirectMessageNotification(context, builder, percent, message));
|
|
}
|
|
this.percent = percent;
|
|
}
|
|
}
|
|
|
|
static class ShortenerNotFoundException extends UpdateStatusException {
|
|
private static final long serialVersionUID = -7262474256595304566L;
|
|
|
|
public ShortenerNotFoundException(final Context context) {
|
|
super(context.getString(R.string.error_message_tweet_shortener_not_found));
|
|
}
|
|
}
|
|
|
|
static class ShortenException extends UpdateStatusException {
|
|
private static final long serialVersionUID = 3075877185536740034L;
|
|
|
|
public ShortenException(final Context context) {
|
|
super(context.getString(R.string.error_message_tweet_shorten_failed));
|
|
}
|
|
}
|
|
|
|
static class StatusMediaUploadListener implements ReadListener {
|
|
private final Context context;
|
|
private final NotificationManagerWrapper manager;
|
|
|
|
int percent;
|
|
|
|
private final Builder builder;
|
|
private final ParcelableStatusUpdate statusUpdate;
|
|
|
|
StatusMediaUploadListener(final Context context, final NotificationManagerWrapper manager,
|
|
final NotificationCompat.Builder builder, final ParcelableStatusUpdate statusUpdate) {
|
|
this.context = context;
|
|
this.manager = manager;
|
|
this.builder = builder;
|
|
this.statusUpdate = statusUpdate;
|
|
}
|
|
|
|
@Override
|
|
public void onRead(final long length, final long position) {
|
|
final int percent = length > 0 ? (int) (position * 100 / length) : 0;
|
|
if (this.percent != percent) {
|
|
manager.notify(NOTIFICATION_ID_UPDATE_STATUS,
|
|
updateUpdateStatusNotification(context, builder, percent, statusUpdate));
|
|
}
|
|
this.percent = percent;
|
|
}
|
|
}
|
|
|
|
static class StatusTooLongException extends UpdateStatusException {
|
|
private static final long serialVersionUID = -6469920130856384219L;
|
|
|
|
public StatusTooLongException(final Context context) {
|
|
super(context.getString(R.string.error_message_status_too_long));
|
|
}
|
|
}
|
|
|
|
static class UpdateStatusException extends Exception {
|
|
private static final long serialVersionUID = -1267218921727097910L;
|
|
|
|
public UpdateStatusException(final String message) {
|
|
super(message);
|
|
}
|
|
}
|
|
|
|
static class UploaderNotFoundException extends UpdateStatusException {
|
|
private static final long serialVersionUID = 1041685850011544106L;
|
|
|
|
public UploaderNotFoundException(final Context context) {
|
|
super(context.getString(R.string.error_message_image_uploader_not_found));
|
|
}
|
|
}
|
|
|
|
static class UploadException extends UpdateStatusException {
|
|
private static final long serialVersionUID = 8596614696393917525L;
|
|
|
|
public UploadException(final Context context) {
|
|
super(context.getString(R.string.error_message_image_upload_failed));
|
|
}
|
|
|
|
public UploadException(final String message) {
|
|
super(message);
|
|
}
|
|
}
|
|
}
|