added downscale image

This commit is contained in:
Mariotaku Lee 2016-07-30 08:08:57 +08:00
parent 1a997992ca
commit 830ffb7c7a
8 changed files with 573 additions and 638 deletions

View File

@ -10,17 +10,20 @@ import org.mariotaku.twidere.model.ParcelableStatus
*/
fun ParcelableActivity.getActivityStatus(): ParcelableStatus? {
val status: ParcelableStatus
if (Activity.Action.MENTION == action) {
if (ArrayUtils.isEmpty(target_object_statuses)) return null
status = target_object_statuses[0]
} else if (Activity.Action.REPLY == action) {
if (ArrayUtils.isEmpty(target_statuses)) return null
status = target_statuses[0]
} else if (Activity.Action.QUOTE == action) {
if (ArrayUtils.isEmpty(target_statuses)) return null
status = target_statuses[0]
} else {
return null
when (action) {
Activity.Action.MENTION -> {
if (ArrayUtils.isEmpty(target_object_statuses)) return null
status = target_object_statuses[0]
}
Activity.Action.REPLY -> {
if (ArrayUtils.isEmpty(target_statuses)) return null
status = target_statuses[0]
}
Activity.Action.QUOTE -> {
if (ArrayUtils.isEmpty(target_statuses)) return null
status = target_statuses[0]
}
else -> return null
}
status.account_color = account_color
status.user_color = status_user_color

View File

@ -1,615 +0,0 @@
/*
* 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.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.Builder;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.widget.Toast;
import com.twitter.Extractor;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.mariotaku.abstask.library.ManualTaskStarter;
import org.mariotaku.microblog.library.MicroBlog;
import org.mariotaku.microblog.library.MicroBlogException;
import org.mariotaku.microblog.library.twitter.TwitterUpload;
import org.mariotaku.microblog.library.twitter.model.DirectMessage;
import org.mariotaku.microblog.library.twitter.model.ErrorInfo;
import org.mariotaku.microblog.library.twitter.model.MediaUploadResponse;
import org.mariotaku.microblog.library.twitter.model.MediaUploadResponse.ProcessingInfo;
import org.mariotaku.restfu.http.ContentType;
import org.mariotaku.restfu.http.mime.Body;
import org.mariotaku.restfu.http.mime.FileBody;
import org.mariotaku.restfu.http.mime.SimpleBody;
import org.mariotaku.sqliteqb.library.Expression;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.model.Draft;
import org.mariotaku.twidere.model.DraftCursorIndices;
import org.mariotaku.twidere.model.ParcelableAccount;
import org.mariotaku.twidere.model.ParcelableCredentials;
import org.mariotaku.twidere.model.ParcelableDirectMessage;
import org.mariotaku.twidere.model.ParcelableStatus;
import org.mariotaku.twidere.model.ParcelableStatusUpdate;
import org.mariotaku.twidere.model.SingleResponse;
import org.mariotaku.twidere.model.UserKey;
import org.mariotaku.twidere.model.draft.SendDirectMessageActionExtra;
import org.mariotaku.twidere.model.util.ParcelableAccountUtils;
import org.mariotaku.twidere.model.util.ParcelableCredentialsUtils;
import org.mariotaku.twidere.model.util.ParcelableDirectMessageUtils;
import org.mariotaku.twidere.model.util.ParcelableStatusUpdateUtils;
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages;
import org.mariotaku.twidere.provider.TwidereDataStore.Drafts;
import org.mariotaku.twidere.task.twitter.UpdateStatusTask;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.ContentValuesCreator;
import org.mariotaku.twidere.util.MicroBlogAPIFactory;
import org.mariotaku.twidere.util.NotificationManagerWrapper;
import org.mariotaku.twidere.util.SharedPreferencesWrapper;
import org.mariotaku.twidere.util.TwidereValidator;
import org.mariotaku.twidere.util.Utils;
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper;
import org.mariotaku.twidere.util.io.ContentLengthInputStream.ReadListener;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import edu.tsinghua.hotmobi.HotMobiLogger;
import edu.tsinghua.hotmobi.model.TimelineType;
import edu.tsinghua.hotmobi.model.TweetEvent;
public class BackgroundOperationService extends IntentService implements Constants {
private Handler mHandler;
@Inject
SharedPreferencesWrapper mPreferences;
@Inject
AsyncTwitterWrapper mTwitter;
@Inject
NotificationManagerWrapper mNotificationManager;
@Inject
TwidereValidator mValidator;
@Inject
Extractor mExtractor;
private static final long BULK_SIZE = 128 * 1024; // 128KiB
public BackgroundOperationService() {
super("background_operation");
}
@Override
public void onCreate() {
super.onCreate();
GeneralComponentHelper.build(this).inject(this);
mHandler = new Handler();
}
@Override
public void onDestroy() {
super.onDestroy();
}
@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 DraftCursorIndices i = new DraftCursorIndices(c);
final Draft item;
try {
if (!c.moveToFirst()) return;
item = i.newObject(c);
} finally {
c.close();
}
cr.delete(Drafts.CONTENT_URI, where.getSQL(), null);
if (TextUtils.isEmpty(item.action_type)) {
item.action_type = Draft.Action.UPDATE_STATUS;
}
switch (item.action_type) {
case Draft.Action.UPDATE_STATUS_COMPAT_1:
case Draft.Action.UPDATE_STATUS_COMPAT_2:
case Draft.Action.UPDATE_STATUS:
case Draft.Action.REPLY:
case Draft.Action.QUOTE: {
updateStatuses(item.action_type, ParcelableStatusUpdateUtils.fromDraftItem(this, item));
break;
}
case Draft.Action.SEND_DIRECT_MESSAGE_COMPAT:
case Draft.Action.SEND_DIRECT_MESSAGE: {
String recipientId = null;
if (item.action_extras instanceof SendDirectMessageActionExtra) {
recipientId = ((SendDirectMessageActionExtra) item.action_extras).getRecipientId();
}
if (ArrayUtils.isEmpty(item.account_keys) || recipientId == null) {
return;
}
final UserKey accountKey = item.account_keys[0];
final String imageUri = ArrayUtils.isEmpty(item.media) ? null : item.media[0].uri;
sendMessage(accountKey, recipientId, item.text, imageUri);
break;
}
}
}
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 UserKey accountId = intent.getParcelableExtra(EXTRA_ACCOUNT_KEY);
final String recipientId = intent.getStringExtra(EXTRA_RECIPIENT_ID);
final String text = intent.getStringExtra(EXTRA_TEXT);
final String imageUri = intent.getStringExtra(EXTRA_IMAGE_URI);
if (accountId == null || recipientId == null || text == null) return;
sendMessage(accountId, recipientId, text, imageUri);
}
private void sendMessage(@NonNull UserKey accountId, @NonNull String recipientId,
@NonNull String text, @Nullable String imageUri) {
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.setCategory(NotificationCompat.CATEGORY_PROGRESS);
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);
final ContentResolver resolver = getContentResolver();
if (result.hasData()) {
final ParcelableDirectMessage message = result.getData();
final ContentValues values = ContentValuesCreator.createDirectMessage(message);
final String deleteWhere = Expression.and(Expression.equalsArgs(DirectMessages.ACCOUNT_KEY),
Expression.equalsArgs(DirectMessages.MESSAGE_ID)).getSQL();
String[] deleteWhereArgs = {message.account_key.toString(), message.id};
resolver.delete(DirectMessages.Outbox.CONTENT_URI, deleteWhere, deleteWhereArgs);
resolver.insert(DirectMessages.Outbox.CONTENT_URI, values);
showOkMessage(R.string.direct_message_sent, false);
} else {
final ContentValues values = ContentValuesCreator.createMessageDraft(accountId, recipientId, text, imageUri);
resolver.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;
@Draft.Action
final String actionType = intent.getStringExtra(EXTRA_ACTION);
updateStatuses(actionType, statuses);
}
private void updateStatuses(@Draft.Action final String actionType, final ParcelableStatusUpdate... statuses) {
final BackgroundOperationService context = this;
final Builder builder = new Builder(context);
startForeground(NOTIFICATION_ID_UPDATE_STATUS, updateUpdateStatusNotification(context,
builder, 0, null));
for (final ParcelableStatusUpdate item : statuses) {
final UpdateStatusTask task = new UpdateStatusTask(context, new UpdateStatusTask.StateCallback() {
@WorkerThread
@Override
public void onStartUploadingMedia() {
startForeground(NOTIFICATION_ID_UPDATE_STATUS, updateUpdateStatusNotification(context,
builder, 0, item));
}
@WorkerThread
@Override
public void onUploadingProgressChanged(int index, long current, long total) {
int progress = (int) (current * 100 / total);
startForeground(NOTIFICATION_ID_UPDATE_STATUS, updateUpdateStatusNotification(context,
builder, progress, item));
}
@WorkerThread
@Override
public void onShorteningStatus() {
startForeground(NOTIFICATION_ID_UPDATE_STATUS, updateUpdateStatusNotification(context,
builder, 0, item));
}
@WorkerThread
@Override
public void onUpdatingStatus() {
startForeground(NOTIFICATION_ID_UPDATE_STATUS, updateUpdateStatusNotification(context,
builder, 0, item));
}
@Override
public void afterExecute(Context handler, UpdateStatusTask.UpdateStatusResult result) {
boolean failed = false;
final UpdateStatusTask.UpdateStatusException exception = result.getException();
final MicroBlogException[] exceptions = result.getExceptions();
if (exception != null) {
Toast.makeText(context, exception.getMessage(), Toast.LENGTH_SHORT).show();
failed = true;
Log.w(LOGTAG, exception);
} else for (MicroBlogException e : exceptions) {
if (e != null) {
// Show error
String errorMessage = Utils.getErrorMessage(context, e);
if (TextUtils.isEmpty(errorMessage)) {
errorMessage = context.getString(R.string.status_not_updated);
}
Toast.makeText(context, errorMessage, Toast.LENGTH_SHORT).show();
failed = true;
break;
}
}
if (failed) {
// TODO show draft notification
} else {
Toast.makeText(context, R.string.status_updated, Toast.LENGTH_SHORT).show();
}
}
@Override
public void beforeExecute() {
}
});
task.setCallback(this);
task.setParams(Pair.create(actionType, item));
mHandler.post(new Runnable() {
@Override
public void run() {
ManualTaskStarter.invokeBeforeExecute(task);
}
});
final UpdateStatusTask.UpdateStatusResult result = ManualTaskStarter.invokeExecute(task);
mHandler.post(new Runnable() {
@Override
public void run() {
ManualTaskStarter.invokeAfterExecute(task, result);
}
});
final UpdateStatusTask.UpdateStatusException exception = result.getException();
final ParcelableStatus[] updatedStatuses = result.getStatuses();
if (exception != null) {
Log.w(LOGTAG, exception);
} else for (ParcelableStatus status : updatedStatuses) {
if (status == null) continue;
final TweetEvent event = TweetEvent.create(context, status, TimelineType.OTHER);
event.setAction(TweetEvent.Action.TWEET);
HotMobiLogger.getInstance(context).log(status.account_key, event);
}
}
if (mPreferences.getBoolean(KEY_REFRESH_AFTER_TWEET)) {
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 UserKey accountKey,
final String recipientId,
final String text,
final String imageUri) {
final ParcelableCredentials credentials = ParcelableCredentialsUtils.getCredentials(this,
accountKey);
if (credentials == null) return SingleResponse.Companion.getInstance();
final MicroBlog twitter = MicroBlogAPIFactory.getInstance(this, credentials, true, true);
final TwitterUpload twitterUpload = MicroBlogAPIFactory.getInstance(this, credentials,
true, true, TwitterUpload.class);
if (twitter == null || twitterUpload == null) return SingleResponse.Companion.getInstance();
try {
final ParcelableDirectMessage directMessage;
switch (ParcelableAccountUtils.getAccountType(credentials)) {
case ParcelableAccount.Type.FANFOU: {
if (imageUri != null) {
throw new MicroBlogException("Can't send image DM on Fanfou");
}
final DirectMessage dm = twitter.sendFanfouDirectMessage(recipientId, text);
directMessage = ParcelableDirectMessageUtils.fromDirectMessage(dm, accountKey, true);
break;
}
default: {
if (imageUri != null) {
final Uri mediaUri = Uri.parse(imageUri);
FileBody body = null;
try {
body = UpdateStatusTask.Companion.getBodyFromMedia(getContentResolver(), mediaUri,
new MessageMediaUploadListener(this, mNotificationManager,
builder, text), null);
final MediaUploadResponse uploadResp = uploadMedia(twitterUpload, body);
final DirectMessage response = twitter.sendDirectMessage(recipientId,
text, uploadResp.getId());
directMessage = ParcelableDirectMessageUtils.fromDirectMessage(response,
accountKey, true);
} finally {
Utils.closeSilently(body);
}
final String path = Utils.getImagePathFromUri(this, mediaUri);
if (path != null) {
final File file = new File(path);
if (!file.delete()) {
Log.d(LOGTAG, String.format("unable to delete %s", path));
}
}
} else {
final DirectMessage response = twitter.sendDirectMessage(recipientId, text);
directMessage = ParcelableDirectMessageUtils.fromDirectMessage(response,
accountKey, true);
}
break;
}
}
Utils.setLastSeen(this, new UserKey(recipientId, accountKey.getHost()),
System.currentTimeMillis());
return SingleResponse.Companion.getInstance(directMessage);
} catch (final IOException e) {
return SingleResponse.Companion.getInstance(e);
} catch (final MicroBlogException e) {
return SingleResponse.Companion.getInstance(e);
}
}
private MediaUploadResponse uploadMedia(final TwitterUpload upload, Body body) throws IOException, MicroBlogException {
final String mediaType = body.contentType().getContentType();
final long length = body.length();
final InputStream stream = body.stream();
MediaUploadResponse response = upload.initUploadMedia(mediaType, length, null);
final int segments = length == 0 ? 0 : (int) (length / BULK_SIZE + 1);
for (int segmentIndex = 0; segmentIndex < segments; segmentIndex++) {
final int currentBulkSize = (int) Math.min(BULK_SIZE, length - segmentIndex * BULK_SIZE);
final SimpleBody bulk = new SimpleBody(ContentType.OCTET_STREAM, null, currentBulkSize,
stream);
upload.appendUploadMedia(response.getId(), segmentIndex, bulk);
}
response = upload.finalizeUploadMedia(response.getId());
for (ProcessingInfo info = response.getProcessingInfo(); shouldWaitForProcess(info); info = response.getProcessingInfo()) {
final long checkAfterSecs = info.getCheckAfterSecs();
if (checkAfterSecs <= 0) {
break;
}
try {
Thread.sleep(TimeUnit.SECONDS.toMillis(checkAfterSecs));
} catch (InterruptedException e) {
break;
}
response = upload.getUploadMediaStatus(response.getId());
}
ProcessingInfo info = response.getProcessingInfo();
if (info != null && ProcessingInfo.State.FAILED.equals(info.getState())) {
final MicroBlogException exception = new MicroBlogException();
ErrorInfo errorInfo = info.getError();
if (errorInfo != null) {
exception.setErrors(new ErrorInfo[]{errorInfo});
}
throw exception;
}
return response;
}
private boolean shouldWaitForProcess(ProcessingInfo info) {
if (info == null) return false;
switch (info.getState()) {
case ProcessingInfo.State.PENDING:
case ProcessingInfo.State.IN_PROGRESS:
return true;
default:
return false;
}
}
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();
}
public static void updateStatusesAsync(Context context, @Draft.Action final String action,
final ParcelableStatusUpdate... statuses) {
final Intent intent = new Intent(context, BackgroundOperationService.class);
intent.setAction(INTENT_ACTION_UPDATE_STATUS);
intent.putExtra(EXTRA_STATUSES, statuses);
intent.putExtra(EXTRA_ACTION, action);
context.startService(intent);
}
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;
}
}
}

View File

@ -5,12 +5,12 @@ import android.content.ContentValues
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Point
import android.net.Uri
import android.support.annotation.UiThread
import android.support.annotation.WorkerThread
import android.text.TextUtils
import android.util.Pair
import android.util.Size
import org.apache.commons.lang3.ArrayUtils
import org.apache.commons.lang3.math.NumberUtils
import org.mariotaku.abstask.library.AbstractTask
@ -228,8 +228,13 @@ class UpdateStatusTask(internal val context: Context, internal val stateCallback
result.exceptions[i] = MicroBlogException(
context.getString(R.string.error_too_many_photos_fanfou))
} else {
val sizeLimit = Point(2048, 1536)
body = getBodyFromMedia(context.contentResolver,
Uri.parse(statusUpdate.media[0].uri), ContentLengthInputStream.ReadListener { length, position -> stateCallback.onUploadingProgressChanged(-1, position, length) })
Uri.parse(statusUpdate.media[0].uri),
sizeLimit,
ContentLengthInputStream.ReadListener { length, position ->
stateCallback.onUploadingProgressChanged(-1, position, length)
})
val photoUpdate = PhotoStatusUpdate(body,
pendingUpdate.overrideTexts[i])
val requestResult = microBlog.uploadPhoto(photoUpdate)
@ -413,9 +418,11 @@ class UpdateStatusTask(internal val context: Context, internal val stateCallback
//noinspection TryWithIdenticalCatches
var body: Body? = null
try {
body = getBodyFromMedia(context.contentResolver, Uri.parse(media.uri), ContentLengthInputStream.ReadListener { length, position ->
stateCallback.onUploadingProgressChanged(index, position, length)
})
val sizeLimit = Point(2048, 1536)
body = getBodyFromMedia(context.contentResolver, Uri.parse(media.uri), sizeLimit,
ContentLengthInputStream.ReadListener { length, position ->
stateCallback.onUploadingProgressChanged(index, position, length)
})
if (chucked) {
resp = uploadMediaChucked(upload, body, ownerIds)
} else {
@ -652,8 +659,8 @@ class UpdateStatusTask(internal val context: Context, internal val stateCallback
@Throws(IOException::class)
fun getBodyFromMedia(resolver: ContentResolver,
mediaUri: Uri,
readListener: ContentLengthInputStream.ReadListener,
sizeLimit: Size? = null): FileBody {
sizeLimit: Point? = null,
readListener: ContentLengthInputStream.ReadListener): FileBody {
val mediaType = resolver.getType(mediaUri)
val st = resolver.openInputStream(mediaUri) ?: throw FileNotFoundException(mediaUri.toString())
val cis: ContentLengthInputStream
@ -662,6 +669,8 @@ class UpdateStatusTask(internal val context: Context, internal val stateCallback
val o = BitmapFactory.Options()
o.inJustDecodeBounds = true
BitmapFactory.decodeStream(st, null, o)
o.inSampleSize = Utils.calculateInSampleSize(o.outWidth, o.outHeight,
sizeLimit.x, sizeLimit.y)
o.inJustDecodeBounds = false
val bitmap = BitmapFactory.decodeStream(st, null, o)
val os = DirectByteArrayOutputStream()

View File

@ -164,7 +164,8 @@ public final class ContentValuesCreator implements TwidereConstants {
@NonNull
public static ContentValues createActivity(final ParcelableActivity activity,
ParcelableCredentials credentials, UserColorNameManager manager) {
final ParcelableCredentials credentials,
final UserColorNameManager manager) {
final ContentValues values = new ContentValues();
final ParcelableStatus status = ParcelableActivityExtensionsKt.getActivityStatus(activity);

View File

@ -1155,7 +1155,7 @@ public final class Utils implements Constants {
return context.getString(R.string.error_message_with_action, action, message);
}
public static String getErrorMessage(final Context context, final CharSequence action, final Throwable t) {
public static String getErrorMessage(final Context context, final CharSequence action, @Nullable final Throwable t) {
if (context == null) return null;
if (t instanceof MicroBlogException)
return getTwitterErrorMessage(context, action, (MicroBlogException) t);
@ -1786,7 +1786,7 @@ public final class Utils implements Constants {
}
public static void showErrorMessage(final Context context, final CharSequence action,
final Throwable t, final boolean longMessage) {
@Nullable final Throwable t, final boolean longMessage) {
if (context == null) return;
if (t instanceof MicroBlogException) {
showTwitterErrorMessage(context, action, (MicroBlogException) t, longMessage);
@ -1801,7 +1801,8 @@ public final class Utils implements Constants {
showErrorMessage(context, context.getString(actionRes), desc, longMessage);
}
public static void showErrorMessage(final Context context, final int action, final Throwable t,
public static void showErrorMessage(final Context context, final int action,
@Nullable final Throwable t,
final boolean long_message) {
if (context == null) return;
showErrorMessage(context, context.getString(action), t, long_message);

View File

@ -0,0 +1,13 @@
package org.mariotaku.ktextension
/**
* Created by mariotaku on 16/7/30.
*/
fun String.toLong(def: Long): Long {
try {
return toLong()
} catch (e: NumberFormatException) {
return def
}
}

View File

@ -283,7 +283,12 @@ class ParcelableActivitiesAdapter(
if (followingOnly && !activity.status_user_following) return ITEM_VIEW_TYPE_EMPTY
return ITEM_VIEW_TYPE_STATUS
}
Activity.Action.FOLLOW, Activity.Action.FAVORITE, Activity.Action.RETWEET, Activity.Action.FAVORITED_RETWEET, Activity.Action.RETWEETED_RETWEET, Activity.Action.RETWEETED_MENTION, Activity.Action.FAVORITED_MENTION, Activity.Action.LIST_CREATED, Activity.Action.LIST_MEMBER_ADDED, Activity.Action.MEDIA_TAGGED, Activity.Action.RETWEETED_MEDIA_TAGGED, Activity.Action.FAVORITED_MEDIA_TAGGED, Activity.Action.JOINED_TWITTER -> {
Activity.Action.FOLLOW, Activity.Action.FAVORITE, Activity.Action.RETWEET,
Activity.Action.FAVORITED_RETWEET, Activity.Action.RETWEETED_RETWEET,
Activity.Action.RETWEETED_MENTION, Activity.Action.FAVORITED_MENTION,
Activity.Action.LIST_CREATED, Activity.Action.LIST_MEMBER_ADDED,
Activity.Action.MEDIA_TAGGED, Activity.Action.RETWEETED_MEDIA_TAGGED,
Activity.Action.FAVORITED_MEDIA_TAGGED, Activity.Action.JOINED_TWITTER -> {
if (mentionsOnly) return ITEM_VIEW_TYPE_EMPTY
ParcelableActivityUtils.initAfterFilteredSourceIds(activity, filteredUserIds, followingOnly)
if (ArrayUtils.isEmpty(activity.after_filtered_source_ids)) {

View File

@ -0,0 +1,518 @@
/*
* 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.app.Service
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Handler
import android.support.annotation.WorkerThread
import android.support.v4.app.NotificationCompat
import android.support.v4.app.NotificationCompat.Builder
import android.text.TextUtils
import android.util.Log
import android.util.Pair
import android.widget.Toast
import com.twitter.Extractor
import edu.tsinghua.hotmobi.HotMobiLogger
import edu.tsinghua.hotmobi.model.TimelineType
import edu.tsinghua.hotmobi.model.TweetEvent
import org.apache.commons.lang3.ArrayUtils
import org.apache.commons.lang3.math.NumberUtils
import org.mariotaku.abstask.library.ManualTaskStarter
import org.mariotaku.ktextension.asTypedArray
import org.mariotaku.ktextension.toLong
import org.mariotaku.microblog.library.MicroBlogException
import org.mariotaku.microblog.library.twitter.TwitterUpload
import org.mariotaku.microblog.library.twitter.model.MediaUploadResponse
import org.mariotaku.microblog.library.twitter.model.MediaUploadResponse.ProcessingInfo
import org.mariotaku.restfu.http.ContentType
import org.mariotaku.restfu.http.mime.Body
import org.mariotaku.restfu.http.mime.FileBody
import org.mariotaku.restfu.http.mime.SimpleBody
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.Constants
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.*
import org.mariotaku.twidere.model.*
import org.mariotaku.twidere.model.draft.SendDirectMessageActionExtra
import org.mariotaku.twidere.model.util.ParcelableAccountUtils
import org.mariotaku.twidere.model.util.ParcelableCredentialsUtils
import org.mariotaku.twidere.model.util.ParcelableDirectMessageUtils
import org.mariotaku.twidere.model.util.ParcelableStatusUpdateUtils
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages
import org.mariotaku.twidere.provider.TwidereDataStore.Drafts
import org.mariotaku.twidere.task.twitter.UpdateStatusTask
import org.mariotaku.twidere.util.*
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
import org.mariotaku.twidere.util.io.ContentLengthInputStream.ReadListener
import java.io.File
import java.io.IOException
import java.util.concurrent.TimeUnit
import javax.inject.Inject
class BackgroundOperationService : IntentService("background_operation"), Constants {
private var handler: Handler? = null
@Inject
lateinit var preferences: SharedPreferencesWrapper
@Inject
lateinit var twitterWrapper: AsyncTwitterWrapper
@Inject
lateinit var notificationManager: NotificationManagerWrapper
@Inject
lateinit var validator: TwidereValidator
@Inject
lateinit var extractor: Extractor
override fun onCreate() {
super.onCreate()
GeneralComponentHelper.build(this).inject(this)
handler = Handler()
}
override fun onDestroy() {
super.onDestroy()
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
return Service.START_STICKY
}
fun showErrorMessage(message: CharSequence, longMessage: Boolean) {
handler!!.post { Utils.showErrorMessage(this@BackgroundOperationService, message, longMessage) }
}
fun showErrorMessage(actionRes: Int, e: Exception?, longMessage: Boolean) {
handler!!.post { Utils.showErrorMessage(this@BackgroundOperationService, actionRes, e, longMessage) }
}
fun showErrorMessage(actionRes: Int, message: String, longMessage: Boolean) {
handler!!.post { Utils.showErrorMessage(this@BackgroundOperationService, actionRes, message, longMessage) }
}
fun showOkMessage(messageRes: Int, longMessage: Boolean) {
showToast(getString(messageRes), longMessage)
}
private fun showToast(message: CharSequence, longMessage: Boolean) {
handler!!.post { Toast.makeText(this@BackgroundOperationService, message, if (longMessage) Toast.LENGTH_LONG else Toast.LENGTH_SHORT).show() }
}
override fun onHandleIntent(intent: Intent?) {
if (intent == null) return
val action = intent.action ?: return
when (action) {
INTENT_ACTION_UPDATE_STATUS -> {
handleUpdateStatusIntent(intent)
}
INTENT_ACTION_SEND_DIRECT_MESSAGE -> {
handleSendDirectMessageIntent(intent)
}
INTENT_ACTION_DISCARD_DRAFT -> {
handleDiscardDraftIntent(intent)
}
INTENT_ACTION_SEND_DRAFT -> {
handleSendDraftIntent(intent)
}
}
}
private fun handleSendDraftIntent(intent: Intent) {
val uri = intent.data ?: return
notificationManager.cancel(uri.toString(), NOTIFICATION_ID_DRAFTS)
val draftId = uri.lastPathSegment.toLong(-1)
if (draftId == -1L) return
val where = Expression.equals(Drafts._ID, draftId)
val cr = contentResolver
val c = cr.query(Drafts.CONTENT_URI, Drafts.COLUMNS, where.sql, null, null) ?: return
val i = DraftCursorIndices(c)
val item: Draft
try {
if (!c.moveToFirst()) return
item = i.newObject(c)
} finally {
c.close()
}
cr.delete(Drafts.CONTENT_URI, where.sql, null)
if (TextUtils.isEmpty(item.action_type)) {
item.action_type = Draft.Action.UPDATE_STATUS
}
when (item.action_type) {
Draft.Action.UPDATE_STATUS_COMPAT_1, Draft.Action.UPDATE_STATUS_COMPAT_2, Draft.Action.UPDATE_STATUS, Draft.Action.REPLY, Draft.Action.QUOTE -> {
updateStatuses(item.action_type, ParcelableStatusUpdateUtils.fromDraftItem(this, item))
}
Draft.Action.SEND_DIRECT_MESSAGE_COMPAT, Draft.Action.SEND_DIRECT_MESSAGE -> {
var recipientId: String? = null
if (item.action_extras is SendDirectMessageActionExtra) {
recipientId = (item.action_extras as SendDirectMessageActionExtra).recipientId
}
if (ArrayUtils.isEmpty(item.account_keys) || recipientId == null) {
return
}
val accountKey = item.account_keys!![0]
val imageUri = if (ArrayUtils.isEmpty(item.media)) null else item.media[0].uri
sendMessage(accountKey, recipientId, item.text, imageUri)
}
}
}
private fun handleDiscardDraftIntent(intent: Intent) {
val data = intent.data ?: return
notificationManager.cancel(data.toString(), NOTIFICATION_ID_DRAFTS)
val cr = contentResolver
val def: Long = -1
val id = NumberUtils.toLong(data.lastPathSegment, def)
val where = Expression.equals(Drafts._ID, id)
cr.delete(Drafts.CONTENT_URI, where.sql, null)
}
private fun handleSendDirectMessageIntent(intent: Intent) {
val accountId = intent.getParcelableExtra<UserKey>(EXTRA_ACCOUNT_KEY)
val recipientId = intent.getStringExtra(EXTRA_RECIPIENT_ID)
val text = intent.getStringExtra(EXTRA_TEXT)
val imageUri = intent.getStringExtra(EXTRA_IMAGE_URI)
if (accountId == null || recipientId == null || text == null) return
sendMessage(accountId, recipientId, text, imageUri)
}
private fun sendMessage(accountId: UserKey, recipientId: String,
text: String, imageUri: String?) {
val title = getString(R.string.sending_direct_message)
val builder = Builder(this)
builder.setSmallIcon(R.drawable.ic_stat_send)
builder.setProgress(100, 0, true)
builder.setTicker(title)
builder.setContentTitle(title)
builder.setContentText(text)
builder.setCategory(NotificationCompat.CATEGORY_PROGRESS)
builder.setOngoing(true)
val notification = builder.build()
startForeground(NOTIFICATION_ID_SEND_DIRECT_MESSAGE, notification)
val result = sendDirectMessage(builder, accountId,
recipientId, text, imageUri)
val resolver = contentResolver
if (result.hasData()) {
val message = result.data
val values = ContentValuesCreator.createDirectMessage(message)
val deleteWhere = Expression.and(Expression.equalsArgs(DirectMessages.ACCOUNT_KEY),
Expression.equalsArgs(DirectMessages.MESSAGE_ID)).sql
val deleteWhereArgs = arrayOf(message!!.account_key.toString(), message.id)
resolver.delete(DirectMessages.Outbox.CONTENT_URI, deleteWhere, deleteWhereArgs)
resolver.insert(DirectMessages.Outbox.CONTENT_URI, values)
showOkMessage(R.string.direct_message_sent, false)
} else {
val values = ContentValuesCreator.createMessageDraft(accountId, recipientId, text, imageUri)
resolver.insert(Drafts.CONTENT_URI, values)
showErrorMessage(R.string.action_sending_direct_message, result.exception, true)
}
stopForeground(false)
notificationManager.cancel(NOTIFICATION_ID_SEND_DIRECT_MESSAGE)
}
private fun handleUpdateStatusIntent(intent: Intent) {
val status = intent.getParcelableExtra<ParcelableStatusUpdate>(EXTRA_STATUS)
val statusParcelables = intent.getParcelableArrayExtra(EXTRA_STATUSES)
val statuses: Array<ParcelableStatusUpdate>
if (statusParcelables != null) {
statuses = statusParcelables.asTypedArray(ParcelableStatusUpdate.CREATOR)
} else if (status != null) {
statuses = arrayOf(status)
} else
return
@Draft.Action
val actionType = intent.getStringExtra(EXTRA_ACTION)
updateStatuses(actionType, *statuses)
}
private fun updateStatuses(@Draft.Action actionType: String, vararg statuses: ParcelableStatusUpdate) {
val context = this
val builder = Builder(context)
startForeground(NOTIFICATION_ID_UPDATE_STATUS, updateUpdateStatusNotification(context,
builder, 0, null))
for (item in statuses) {
val task = UpdateStatusTask(context, object : UpdateStatusTask.StateCallback {
@WorkerThread
override fun onStartUploadingMedia() {
startForeground(NOTIFICATION_ID_UPDATE_STATUS, updateUpdateStatusNotification(context,
builder, 0, item))
}
@WorkerThread
override fun onUploadingProgressChanged(index: Int, current: Long, total: Long) {
val progress = (current * 100 / total).toInt()
startForeground(NOTIFICATION_ID_UPDATE_STATUS, updateUpdateStatusNotification(context,
builder, progress, item))
}
@WorkerThread
override fun onShorteningStatus() {
startForeground(NOTIFICATION_ID_UPDATE_STATUS, updateUpdateStatusNotification(context,
builder, 0, item))
}
@WorkerThread
override fun onUpdatingStatus() {
startForeground(NOTIFICATION_ID_UPDATE_STATUS, updateUpdateStatusNotification(context,
builder, 0, item))
}
override fun afterExecute(handler: Context?, result: UpdateStatusTask.UpdateStatusResult?) {
var failed = false
val exception = result!!.exception
val exceptions = result.exceptions
if (exception != null) {
Toast.makeText(context, exception.message, Toast.LENGTH_SHORT).show()
failed = true
Log.w(LOGTAG, exception)
} else for (e in exceptions) {
if (e != null) {
// Show error
var errorMessage = Utils.getErrorMessage(context, e)
if (TextUtils.isEmpty(errorMessage)) {
errorMessage = context.getString(R.string.status_not_updated)
}
Toast.makeText(context, errorMessage, Toast.LENGTH_SHORT).show()
failed = true
break
}
}
if (failed) {
// TODO show draft notification
} else {
Toast.makeText(context, R.string.status_updated, Toast.LENGTH_SHORT).show()
}
}
override fun beforeExecute() {
}
})
task.setCallback(this)
task.params = Pair.create(actionType, item)
handler!!.post { ManualTaskStarter.invokeBeforeExecute(task) }
val result = ManualTaskStarter.invokeExecute(task)
handler!!.post { ManualTaskStarter.invokeAfterExecute(task, result) }
val exception = result.exception
val updatedStatuses = result.statuses
if (exception != null) {
Log.w(LOGTAG, exception)
} else
for (status in updatedStatuses) {
if (status == null) continue
val event = TweetEvent.create(context, status, TimelineType.OTHER)
event.setAction(TweetEvent.Action.TWEET)
HotMobiLogger.getInstance(context).log(status.account_key, event)
}
}
if (preferences.getBoolean(KEY_REFRESH_AFTER_TWEET)) {
handler!!.post { twitterWrapper.refreshAll() }
}
stopForeground(false)
notificationManager.cancel(NOTIFICATION_ID_UPDATE_STATUS)
}
private fun sendDirectMessage(builder: NotificationCompat.Builder,
accountKey: UserKey,
recipientId: String,
text: String,
imageUri: String?): SingleResponse<ParcelableDirectMessage> {
val credentials = ParcelableCredentialsUtils.getCredentials(this,
accountKey) ?: return SingleResponse.getInstance<ParcelableDirectMessage>()
val twitter = MicroBlogAPIFactory.getInstance(this, credentials, true, true)
val twitterUpload = MicroBlogAPIFactory.getInstance(this, credentials,
true, true, TwitterUpload::class.java)
if (twitter == null || twitterUpload == null) return SingleResponse.getInstance<ParcelableDirectMessage>()
try {
val directMessage: ParcelableDirectMessage
when (ParcelableAccountUtils.getAccountType(credentials)) {
ParcelableAccount.Type.FANFOU -> {
if (imageUri != null) {
throw MicroBlogException("Can't send image DM on Fanfou")
}
val dm = twitter.sendFanfouDirectMessage(recipientId, text)
directMessage = ParcelableDirectMessageUtils.fromDirectMessage(dm, accountKey, true)
}
else -> {
if (imageUri != null) {
val mediaUri = Uri.parse(imageUri)
var body: FileBody? = null
try {
body = UpdateStatusTask.getBodyFromMedia(contentResolver,
mediaUri, null, MessageMediaUploadListener(this,
notificationManager, builder, text))
val uploadResp = uploadMedia(twitterUpload, body)
val response = twitter.sendDirectMessage(recipientId,
text, uploadResp.id)
directMessage = ParcelableDirectMessageUtils.fromDirectMessage(response,
accountKey, true)
} finally {
Utils.closeSilently(body)
}
val path = Utils.getImagePathFromUri(this, mediaUri)
if (path != null) {
val file = File(path)
if (!file.delete()) {
Log.d(LOGTAG, String.format("unable to delete %s", path))
}
}
} else {
val response = twitter.sendDirectMessage(recipientId, text)
directMessage = ParcelableDirectMessageUtils.fromDirectMessage(response,
accountKey, true)
}
}
}
Utils.setLastSeen(this, UserKey(recipientId, accountKey.host),
System.currentTimeMillis())
return SingleResponse.getInstance(directMessage)
} catch (e: IOException) {
return SingleResponse.getInstance<ParcelableDirectMessage>(e)
} catch (e: MicroBlogException) {
return SingleResponse.getInstance<ParcelableDirectMessage>(e)
}
}
@Throws(IOException::class, MicroBlogException::class)
private fun uploadMedia(upload: TwitterUpload, body: Body): MediaUploadResponse {
val mediaType = body.contentType().contentType
val length = body.length()
val stream = body.stream()
var response = upload.initUploadMedia(mediaType, length, null)
val segments = if (length == 0L) 0 else (length / BULK_SIZE + 1).toInt()
for (segmentIndex in 0..segments - 1) {
val currentBulkSize = Math.min(BULK_SIZE, length - segmentIndex * BULK_SIZE).toInt()
val bulk = SimpleBody(ContentType.OCTET_STREAM, null, currentBulkSize.toLong(),
stream)
upload.appendUploadMedia(response.id, segmentIndex, bulk)
}
response = upload.finalizeUploadMedia(response.id)
run {
var info: ProcessingInfo? = response.processingInfo
while (info != null && shouldWaitForProcess(info)) {
val checkAfterSecs = info.checkAfterSecs
if (checkAfterSecs <= 0) {
break
}
try {
Thread.sleep(TimeUnit.SECONDS.toMillis(checkAfterSecs))
} catch (e: InterruptedException) {
break
}
response = upload.getUploadMediaStatus(response.id)
info = response.processingInfo
}
}
val info = response.processingInfo
if (info != null && ProcessingInfo.State.FAILED == info.state) {
val exception = MicroBlogException()
val errorInfo = info.error
if (errorInfo != null) {
exception.errors = arrayOf(errorInfo)
}
throw exception
}
return response
}
private fun shouldWaitForProcess(info: ProcessingInfo): Boolean {
when (info.state) {
ProcessingInfo.State.PENDING, ProcessingInfo.State.IN_PROGRESS -> return true
else -> return false
}
}
internal class MessageMediaUploadListener(private val context: Context, private val manager: NotificationManagerWrapper,
builder: NotificationCompat.Builder, private val message: String) : ReadListener {
var percent: Int = 0
private val builder: Builder
init {
this.builder = builder
}
override fun onRead(length: Long, position: Long) {
val percent = if (length > 0) (position * 100 / length).toInt() else 0
if (this.percent != percent) {
manager.notify(NOTIFICATION_ID_SEND_DIRECT_MESSAGE,
updateSendDirectMessageNotification(context, builder, percent, message))
}
this.percent = percent
}
}
companion object {
private val BULK_SIZE = (128 * 1024).toLong() // 128KiB
private fun updateSendDirectMessageNotification(context: Context,
builder: NotificationCompat.Builder,
progress: Int, message: String?): Notification {
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 fun updateUpdateStatusNotification(context: Context,
builder: NotificationCompat.Builder,
progress: Int,
status: ParcelableStatusUpdate?): Notification {
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()
}
fun updateStatusesAsync(context: Context, @Draft.Action action: String,
vararg statuses: ParcelableStatusUpdate) {
val intent = Intent(context, BackgroundOperationService::class.java)
intent.action = INTENT_ACTION_UPDATE_STATUS
intent.putExtra(EXTRA_STATUSES, statuses)
intent.putExtra(EXTRA_ACTION, action)
context.startService(intent)
}
}
}