Add percentage progress bar for attachment downloads

This commit is contained in:
harjot-oberai 2017-03-02 04:48:28 +05:30 committed by cketti
parent 24c8c25eef
commit 2accaae901
13 changed files with 170 additions and 17 deletions

View file

@ -0,0 +1,5 @@
package com.fsck.k9.mail;
public interface AttachmentProgressCallback {
void onUpdate(int progress);
}

View file

@ -128,7 +128,8 @@ public abstract class Folder<T extends Message> {
MessageRetrievalListener<T> listener) throws MessagingException; MessageRetrievalListener<T> listener) throws MessagingException;
public void fetchPart(Message message, Part part, public void fetchPart(Message message, Part part,
MessageRetrievalListener<Message> listener) throws MessagingException { MessageRetrievalListener<Message> listener,
AttachmentProgressCallback progressCallback) throws MessagingException {
// This is causing trouble. Disabled for now. See issue 1733 // This is causing trouble. Disabled for now. See issue 1733
//throw new RuntimeException("fetchPart() not implemented."); //throw new RuntimeException("fetchPart() not implemented.");

View file

@ -576,7 +576,7 @@ public class MimeMessage extends Message {
@Override @Override
public void body(BodyDescriptor bd, InputStream in) throws IOException, MimeException { public void body(BodyDescriptor bd, InputStream in) throws IOException, MimeException {
expect(Part.class); expect(Part.class);
Body body = MimeUtility.createBody(in, bd.getTransferEncoding(), bd.getMimeType()); Body body = MimeUtility.createBody(in, bd.getTransferEncoding(), bd.getMimeType(), null);
((Part)stack.peek()).setBody(body); ((Part)stack.peek()).setBody(body);
} }

View file

@ -6,17 +6,22 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Locale; import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import com.fsck.k9.mail.AttachmentProgressCallback;
import com.fsck.k9.mail.Body; import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.BodyPart; import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.Message; import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Multipart; import com.fsck.k9.mail.Multipart;
import com.fsck.k9.mail.Part; import com.fsck.k9.mail.Part;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.CountingOutputStream;
import org.apache.james.mime4j.codec.Base64InputStream; import org.apache.james.mime4j.codec.Base64InputStream;
import org.apache.james.mime4j.codec.QuotedPrintableInputStream; import org.apache.james.mime4j.codec.QuotedPrintableInputStream;
import org.apache.james.mime4j.util.MimeUtil; import org.apache.james.mime4j.util.MimeUtil;
@ -985,14 +990,14 @@ public class MimeUtility {
return isSameMimeType(mimeType, DEFAULT_ATTACHMENT_MIME_TYPE); return isSameMimeType(mimeType, DEFAULT_ATTACHMENT_MIME_TYPE);
} }
public static Body createBody(InputStream in, String contentTransferEncoding, String contentType) public static Body createBody(InputStream in, String contentTransferEncoding, String contentType, final AttachmentProgressCallback progressCallback)
throws IOException { throws IOException {
if (contentTransferEncoding != null) { if (contentTransferEncoding != null) {
contentTransferEncoding = MimeUtility.getHeaderParameter(contentTransferEncoding, null); contentTransferEncoding = MimeUtility.getHeaderParameter(contentTransferEncoding, null);
} }
BinaryTempFileBody tempBody; final BinaryTempFileBody tempBody;
if (MimeUtil.isMessage(contentType)) { if (MimeUtil.isMessage(contentType)) {
tempBody = new BinaryTempFileMessageBody(contentTransferEncoding); tempBody = new BinaryTempFileMessageBody(contentTransferEncoding);
} else { } else {
@ -1000,9 +1005,23 @@ public class MimeUtility {
} }
OutputStream out = tempBody.getOutputStream(); OutputStream out = tempBody.getOutputStream();
final CountingOutputStream countingOutputStream = new CountingOutputStream(out);
Timer timer = null;
try { try {
IOUtils.copy(in, out); if (progressCallback != null) {
timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
progressCallback.onUpdate((int) countingOutputStream.getCount());
}
}, 0, 50);
}
IOUtils.copy(in, countingOutputStream);
} finally { } finally {
if (timer != null) {
timer.cancel();
}
out.close(); out.close();
} }

View file

@ -3,6 +3,7 @@ package com.fsck.k9.mail.store.imap;
import java.io.IOException; import java.io.IOException;
import com.fsck.k9.mail.AttachmentProgressCallback;
import com.fsck.k9.mail.Part; import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.filter.FixedLengthInputStream; import com.fsck.k9.mail.filter.FixedLengthInputStream;
import com.fsck.k9.mail.internet.MimeHeader; import com.fsck.k9.mail.internet.MimeHeader;
@ -11,11 +12,17 @@ import com.fsck.k9.mail.internet.MimeUtility;
class FetchPartCallback implements ImapResponseCallback { class FetchPartCallback implements ImapResponseCallback {
private Part mPart; private Part mPart;
private AttachmentProgressCallback attachmentProgressCallback;
FetchPartCallback(Part part) { FetchPartCallback(Part part) {
mPart = part; mPart = part;
} }
FetchPartCallback(Part part, AttachmentProgressCallback attachmentProgressCallback) {
mPart = part;
this.attachmentProgressCallback = attachmentProgressCallback;
}
@Override @Override
public Object foundLiteral(ImapResponse response, FixedLengthInputStream literal) throws IOException { public Object foundLiteral(ImapResponse response, FixedLengthInputStream literal) throws IOException {
if (response.getTag() == null && if (response.getTag() == null &&
@ -27,7 +34,11 @@ class FetchPartCallback implements ImapResponseCallback {
String contentType = mPart String contentType = mPart
.getHeader(MimeHeader.HEADER_CONTENT_TYPE)[0]; .getHeader(MimeHeader.HEADER_CONTENT_TYPE)[0];
return MimeUtility.createBody(literal, contentTransferEncoding, contentType); if (attachmentProgressCallback != null) {
return MimeUtility.createBody(literal, contentTransferEncoding, contentType, attachmentProgressCallback);
}
return MimeUtility.createBody(literal, contentTransferEncoding, contentType, null);
} }
return null; return null;
} }

View file

@ -18,6 +18,7 @@ import java.util.concurrent.ConcurrentHashMap;
import android.text.TextUtils; import android.text.TextUtils;
import com.fsck.k9.mail.AttachmentProgressCallback;
import com.fsck.k9.mail.Body; import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.FetchProfile; import com.fsck.k9.mail.FetchProfile;
import com.fsck.k9.mail.Flag; import com.fsck.k9.mail.Flag;
@ -779,7 +780,7 @@ class ImapFolder extends Folder<ImapMessage> {
} }
@Override @Override
public void fetchPart(Message message, Part part, MessageRetrievalListener<Message> listener) public void fetchPart(Message message, Part part, MessageRetrievalListener<Message> listener, AttachmentProgressCallback progressCallback)
throws MessagingException { throws MessagingException {
checkOpen(); checkOpen();
@ -800,7 +801,7 @@ class ImapFolder extends Folder<ImapMessage> {
ImapResponse response; ImapResponse response;
int messageNumber = 0; int messageNumber = 0;
ImapResponseCallback callback = new FetchPartCallback(part); ImapResponseCallback callback = new FetchPartCallback(part, progressCallback);
do { do {
response = connection.readResponse(callback); response = connection.readResponse(callback);
@ -838,7 +839,7 @@ class ImapFolder extends Folder<ImapMessage> {
part.getHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING)[0]; part.getHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING)[0];
String contentType = part.getHeader(MimeHeader.HEADER_CONTENT_TYPE)[0]; String contentType = part.getHeader(MimeHeader.HEADER_CONTENT_TYPE)[0];
MimeMessageHelper.setBody(part, MimeUtility.createBody(bodyStream, contentTransferEncoding, MimeMessageHelper.setBody(part, MimeUtility.createBody(bodyStream, contentTransferEncoding,
contentType)); contentType, progressCallback));
} else { } else {
// This shouldn't happen // This shouldn't happen
throw new MessagingException("Got FETCH response with bogus parameters"); throw new MessagingException("Got FETCH response with bogus parameters");

View file

@ -934,7 +934,7 @@ public class ImapFolderTest {
Part part = createPart("TEXT"); Part part = createPart("TEXT");
when(imapConnection.readResponse(any(ImapResponseCallback.class))).thenReturn(createImapResponse("x OK")); when(imapConnection.readResponse(any(ImapResponseCallback.class))).thenReturn(createImapResponse("x OK"));
folder.fetchPart(message, part, null); folder.fetchPart(message, part, null, null);
verify(imapConnection).sendCommand("UID FETCH 1 (UID BODY.PEEK[TEXT]<0.4096>)", false); verify(imapConnection).sendCommand("UID FETCH 1 (UID BODY.PEEK[TEXT]<0.4096>)", false);
} }
@ -948,7 +948,7 @@ public class ImapFolderTest {
Part part = createPart("1.1"); Part part = createPart("1.1");
when(imapConnection.readResponse(any(ImapResponseCallback.class))).thenReturn(createImapResponse("x OK")); when(imapConnection.readResponse(any(ImapResponseCallback.class))).thenReturn(createImapResponse("x OK"));
folder.fetchPart(message, part, null); folder.fetchPart(message, part, null, null);
verify(imapConnection).sendCommand("UID FETCH 1 (UID BODY.PEEK[1.1])", false); verify(imapConnection).sendCommand("UID FETCH 1 (UID BODY.PEEK[1.1])", false);
} }
@ -962,7 +962,7 @@ public class ImapFolderTest {
Part part = createPlainTextPart("1.1"); Part part = createPlainTextPart("1.1");
setupSingleFetchResponseToCallback(); setupSingleFetchResponseToCallback();
folder.fetchPart(message, part, null); folder.fetchPart(message, part, null, null);
ArgumentCaptor<Body> bodyArgumentCaptor = ArgumentCaptor.forClass(Body.class); ArgumentCaptor<Body> bodyArgumentCaptor = ArgumentCaptor.forClass(Body.class);
verify(part).setBody(bodyArgumentCaptor.capture()); verify(part).setBody(bodyArgumentCaptor.capture());

View file

@ -69,8 +69,11 @@ import com.fsck.k9.activity.misc.Attachment;
import com.fsck.k9.controller.MessagingController; import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.controller.MessagingListener; import com.fsck.k9.controller.MessagingListener;
import com.fsck.k9.controller.SimpleMessagingListener; import com.fsck.k9.controller.SimpleMessagingListener;
import com.fsck.k9.fragment.AttachmentDownloadDialogFragment;
import com.fsck.k9.fragment.ProgressDialogFragment; import com.fsck.k9.fragment.ProgressDialogFragment;
import com.fsck.k9.fragment.ProgressDialogFragment.CancelListener; import com.fsck.k9.fragment.ProgressDialogFragment.CancelListener;
import com.fsck.k9.fragment.AttachmentDownloadDialogFragment;
import com.fsck.k9.fragment.AttachmentDownloadDialogFragment.AttachmentDownloadCancelListener;
import com.fsck.k9.helper.Contacts; import com.fsck.k9.helper.Contacts;
import com.fsck.k9.helper.IdentityHelper; import com.fsck.k9.helper.IdentityHelper;
import com.fsck.k9.helper.MailTo; import com.fsck.k9.helper.MailTo;
@ -101,7 +104,7 @@ import com.fsck.k9.ui.compose.QuotedMessagePresenter;
@SuppressWarnings("deprecation") // TODO get rid of activity dialogs and indeterminate progress bars @SuppressWarnings("deprecation") // TODO get rid of activity dialogs and indeterminate progress bars
public class MessageCompose extends K9Activity implements OnClickListener, public class MessageCompose extends K9Activity implements OnClickListener,
CancelListener, OnFocusChangeListener, OnCryptoModeChangedListener, CancelListener, AttachmentDownloadCancelListener, OnFocusChangeListener, OnCryptoModeChangedListener,
OnOpenPgpInlineChangeListener, OnOpenPgpSignOnlyChangeListener, MessageBuilder.Callback, OnOpenPgpInlineChangeListener, OnOpenPgpSignOnlyChangeListener, MessageBuilder.Callback,
AttachmentPresenter.AttachmentsChangedListener, RecipientPresenter.RecipientsChangedListener { AttachmentPresenter.AttachmentsChangedListener, RecipientPresenter.RecipientsChangedListener {
@ -1053,6 +1056,10 @@ public class MessageCompose extends K9Activity implements OnClickListener,
return false; return false;
} }
@Override
public void onProgressCancel(AttachmentDownloadDialogFragment fragment) {
attachmentPresenter.attachmentProgressDialogCancelled();
}
public void onProgressCancel(ProgressDialogFragment fragment) { public void onProgressCancel(ProgressDialogFragment fragment) {
attachmentPresenter.attachmentProgressDialogCancelled(); attachmentPresenter.attachmentProgressDialogCancelled();

View file

@ -18,6 +18,8 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
@ -63,8 +65,10 @@ import com.fsck.k9.controller.MessagingControllerCommands.PendingExpunge;
import com.fsck.k9.controller.MessagingControllerCommands.PendingMarkAllAsRead; import com.fsck.k9.controller.MessagingControllerCommands.PendingMarkAllAsRead;
import com.fsck.k9.controller.MessagingControllerCommands.PendingMoveOrCopy; import com.fsck.k9.controller.MessagingControllerCommands.PendingMoveOrCopy;
import com.fsck.k9.controller.MessagingControllerCommands.PendingSetFlag; import com.fsck.k9.controller.MessagingControllerCommands.PendingSetFlag;
import com.fsck.k9.fragment.AttachmentDownloadDialogFragment;
import com.fsck.k9.helper.Contacts; import com.fsck.k9.helper.Contacts;
import com.fsck.k9.mail.Address; import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.AttachmentProgressCallback;
import com.fsck.k9.mail.AuthenticationFailedException; import com.fsck.k9.mail.AuthenticationFailedException;
import com.fsck.k9.mail.CertificateValidationException; import com.fsck.k9.mail.CertificateValidationException;
import com.fsck.k9.mail.FetchProfile; import com.fsck.k9.mail.FetchProfile;
@ -150,6 +154,8 @@ public class MessagingController {
private MessagingListener checkMailListener = null; private MessagingListener checkMailListener = null;
private volatile boolean stopped = false; private volatile boolean stopped = false;
private MessagingListener attachmentDownloadProgressListener = null;
public static synchronized MessagingController getInstance(Context context) { public static synchronized MessagingController getInstance(Context context) {
if (inst == null) { if (inst == null) {
@ -259,6 +265,10 @@ public class MessagingController {
throw new Error(e); throw new Error(e);
} }
public void addDownloadProgressListener(MessagingListener messagingListener){
attachmentDownloadProgressListener = messagingListener;
}
public void addListener(MessagingListener listener) { public void addListener(MessagingListener listener) {
listeners.add(listener); listeners.add(listener);
refreshListener(listener); refreshListener(listener);
@ -1483,7 +1493,7 @@ public class MessagingController {
* Now download the parts we're interested in storing. * Now download the parts we're interested in storing.
*/ */
for (Part part : viewables) { for (Part part : viewables) {
remoteFolder.fetchPart(message, part, null); remoteFolder.fetchPart(message, part, null, null);
} }
// Store the updated message locally // Store the updated message locally
localFolder.appendMessages(Collections.singletonList(message)); localFolder.appendMessages(Collections.singletonList(message));
@ -2551,8 +2561,15 @@ public class MessagingController {
remoteFolder = remoteStore.getFolder(folderName); remoteFolder = remoteStore.getFolder(folderName);
remoteFolder.open(Folder.OPEN_MODE_RW); remoteFolder.open(Folder.OPEN_MODE_RW);
AttachmentProgressCallback attachmentProgressCallback = new AttachmentProgressCallback() {
@Override
public void onUpdate(int progress) {
attachmentDownloadProgressListener.updateProgress(progress);
}
};
Message remoteMessage = remoteFolder.getMessage(message.getUid()); Message remoteMessage = remoteFolder.getMessage(message.getUid());
remoteFolder.fetchPart(remoteMessage, part, null); remoteFolder.fetchPart(remoteMessage, part, null, attachmentProgressCallback);
localFolder.addPartToMessage(message, part); localFolder.addPartToMessage(message, part);

View file

@ -73,4 +73,6 @@ public interface MessagingListener {
void remoteSearchFailed(String folder, String err); void remoteSearchFailed(String folder, String err);
void enableProgressIndicator(boolean enable); void enableProgressIndicator(boolean enable);
void updateProgress(int progress);
} }

View file

@ -181,4 +181,9 @@ public abstract class SimpleMessagingListener implements MessagingListener {
@Override @Override
public void enableProgressIndicator(boolean enable) { public void enableProgressIndicator(boolean enable) {
} }
@Override
public void updateProgress(int progress) {
}
} }

View file

@ -0,0 +1,83 @@
package com.fsck.k9.fragment;
import android.app.Activity;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.controller.MessagingListener;
import com.fsck.k9.controller.SimpleMessagingListener;
public class AttachmentDownloadDialogFragment extends DialogFragment {
ProgressDialog dialog;
int progress = 0;
MessagingListener messagingListener;
protected static final String ARG_SIZE = "size";
protected static final String ARG_MESSAGE = "message";
public static AttachmentDownloadDialogFragment newInstance(int size, String message) {
AttachmentDownloadDialogFragment attachmentDownloadDialogFragment = new AttachmentDownloadDialogFragment();
Bundle args = new Bundle();
args.putInt(ARG_SIZE, size);
args.putString(ARG_MESSAGE, message);
attachmentDownloadDialogFragment.setArguments(args);
return attachmentDownloadDialogFragment;
}
public void updateDownloadProgress(int newProgress) {
progress = newProgress;
dialog.setProgress(newProgress);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Bundle args = getArguments();
int size = args.getInt(ARG_SIZE);
String message = args.getString(ARG_MESSAGE);
progress = 0;
messagingListener = new SimpleMessagingListener() {
@Override
public void updateProgress(int progress) {
updateDownloadProgress(progress);
}
};
MessagingController.getInstance(getActivity()).addDownloadProgressListener(messagingListener);
dialog = new ProgressDialog(getActivity());
dialog.setMessage(message);
dialog.setMax(size);
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setProgress(0);
dialog.show();
return dialog;
}
@Override
public void onCancel(DialogInterface dialog) {
Activity activity = getActivity();
if (activity != null && activity instanceof AttachmentDownloadCancelListener) {
AttachmentDownloadCancelListener listener = (AttachmentDownloadCancelListener) activity;
listener.onProgressCancel(this);
}
super.onCancel(dialog);
}
public interface AttachmentDownloadCancelListener {
void onProgressCancel(AttachmentDownloadDialogFragment fragment);
}
}

View file

@ -37,6 +37,7 @@ import com.fsck.k9.activity.MessageLoaderHelper;
import com.fsck.k9.activity.MessageLoaderHelper.MessageLoaderCallbacks; import com.fsck.k9.activity.MessageLoaderHelper.MessageLoaderCallbacks;
import com.fsck.k9.activity.MessageReference; import com.fsck.k9.activity.MessageReference;
import com.fsck.k9.controller.MessagingController; import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.fragment.AttachmentDownloadDialogFragment;
import com.fsck.k9.fragment.ConfirmationDialogFragment; import com.fsck.k9.fragment.ConfirmationDialogFragment;
import com.fsck.k9.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener; import com.fsck.k9.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener;
import com.fsck.k9.fragment.ProgressDialogFragment; import com.fsck.k9.fragment.ProgressDialogFragment;
@ -534,7 +535,8 @@ public class MessageViewFragment extends Fragment implements ConfirmationDialogF
} }
case R.id.dialog_attachment_progress: { case R.id.dialog_attachment_progress: {
String message = getString(R.string.dialog_attachment_progress_title); String message = getString(R.string.dialog_attachment_progress_title);
fragment = ProgressDialogFragment.newInstance(null, message); int size = (int) currentAttachmentViewInfo.size;
fragment = AttachmentDownloadDialogFragment.newInstance(size, message);
break; break;
} }
default: { default: {
@ -796,7 +798,7 @@ public class MessageViewFragment extends Fragment implements ConfirmationDialogF
@Override @Override
public void onViewAttachment(AttachmentViewInfo attachment) { public void onViewAttachment(AttachmentViewInfo attachment) {
//TODO: check if we have to download the attachment first //TODO: check if we have to download the attachment first
currentAttachmentViewInfo = attachment;
getAttachmentController(attachment).viewAttachment(); getAttachmentController(attachment).viewAttachment();
} }