From 7894ee82b3a9f22d460a0c6f79e87be27686a649 Mon Sep 17 00:00:00 2001 From: Marc Blank Date: Wed, 18 Aug 2010 08:50:45 -0700 Subject: [PATCH] New attachment download support for Controller (IMAP/POP3) * Supports download via AttachmentDownloadService Change-Id: I66143a79b99dcdbd307524ba0b81227f09a00e4a --- AndroidManifest.xml | 6 + src/com/android/email/Controller.java | 353 +++++++++++++----- .../android/email/GroupMessagingListener.java | 14 +- .../android/email/MessagingController.java | 104 +++--- src/com/android/email/MessagingListener.java | 17 +- src/com/android/email/mail/Folder.java | 1 + .../android/email/mail/store/ImapStore.java | 34 +- .../service/AttachmentDownloadService.java | 3 +- 8 files changed, 376 insertions(+), 156 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index a32276fc8..e7bf9e346 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -276,6 +276,12 @@ > + + + sCallbackList = + new RemoteCallbackList(); protected Controller(Context _context) { mContext = _context.getApplicationContext(); mProviderContext = _context; - mLegacyController = MessagingController.getInstance(mProviderContext); + mLegacyController = MessagingController.getInstance(mProviderContext, this); mLegacyController.addListener(mLegacyListener); } @@ -106,6 +122,29 @@ public class Controller { mLegacyController.removeListener(mLegacyListener); } + /** + * As a Service, Controller needs this no-argument constructor, but only because there is + * another constructor defined for the class. In the typical case for a Service, there are + * no defined constructors, so the default constructor is used when the Service is instantiated + * by ServiceManager (initialization would be performed in onCreate or onStartCommand, etc.) + * + * Because of this, the Controller Service, when bound, creates a second instance of the class, + * i.e. in addition to the "singleton" created/returned by getInstance. This is unfortunate, + * but not disruptive, as the Service Controller instance references members of sInstance (the + * previously-singleton Controller); it's perhaps best to think of the Service instance as a + * delegate. + * + * TODO: Have Controller behave more like a real Service. This means that the lifecycle of + * the Service (and thus the singleton instance) should be managed by ServiceManager (as happens + * with AttachmentDownloadService and MailService), its initialization should be handled in + * onCreate(), etc. When this is done (and it should be relatively simple), we will be back + * to a true singleton + */ + public Controller() { + mContext = mProviderContext = this; + mLegacyController = null; + } + /** * Gets or creates the singleton instance of Controller. */ @@ -159,12 +198,6 @@ public class Controller { } } - private boolean isActiveResultCallback(Result listener) { - synchronized (mListeners) { - return mListeners.contains(listener); - } - } - /** * Delete all Messages that live in the attachment mailbox */ @@ -504,37 +537,28 @@ public class Controller { Uri uri = ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI, messageId); resolver.update(uri, cv, null, null); - // Split here for target type (Service or MessagingController) - IEmailService service = getServiceForMessage(messageId); - if (service != null) { - // We just need to be sure the callback is installed, if this is the first call - // to the service. - try { - service.setCallback(mServiceCallback); - } catch (RemoteException re) { - // OK - not a critical callback here - } - } else { - // for IMAP & POP only, (attempt to) send the message now - final EmailContent.Account account = - EmailContent.Account.restoreAccountWithId(mProviderContext, accountId); - if (account == null) { - return; - } - final long sentboxId = findOrCreateMailboxOfType(accountId, Mailbox.TYPE_SENT); - Utility.runAsync(new Runnable() { - public void run() { - mLegacyController.sendPendingMessages(account, sentboxId, mLegacyListener); - } - }); + sendPendingMessages(accountId); + } + + private void sendPendingMessagesSmtp(long accountId) { + // for IMAP & POP only, (attempt to) send the message now + final EmailContent.Account account = + EmailContent.Account.restoreAccountWithId(mProviderContext, accountId); + if (account == null) { + return; } + final long sentboxId = findOrCreateMailboxOfType(accountId, Mailbox.TYPE_SENT); + Utility.runAsync(new Runnable() { + public void run() { + mLegacyController.sendPendingMessages(account, sentboxId, mLegacyListener); + } + }); } /** * Try to send all pending messages for a given account * - * @param accountId the account for which to send messages (-1 for all accounts) - * @param callback + * @param accountId the account for which to send messages */ public void sendPendingMessages(long accountId) { // 1. make sure we even have an outbox, exit early if not @@ -557,17 +581,7 @@ public class Controller { } } else { // MessagingController implementation - final EmailContent.Account account = - EmailContent.Account.restoreAccountWithId(mProviderContext, accountId); - if (account == null) { - return; - } - final long sentboxId = findOrCreateMailboxOfType(accountId, Mailbox.TYPE_SENT); - Utility.runAsync(new Runnable() { - public void run() { - mLegacyController.sendPendingMessages(account, sentboxId, mLegacyListener); - } - }); + sendPendingMessagesSmtp(accountId); } } @@ -846,12 +860,10 @@ public class Controller { public void loadAttachment(final long attachmentId, final long messageId, final long mailboxId, final long accountId) { - File saveToFile = AttachmentProvider.getAttachmentFilename(mProviderContext, - accountId, attachmentId); Attachment attachInfo = Attachment.restoreAttachmentWithId(mProviderContext, attachmentId); - - if (saveToFile.exists() && attachInfo.mContentUri != null) { + if (Utility.attachmentExists(mProviderContext, accountId, attachInfo)) { // The attachment has already been downloaded, so we will just "pretend" to download it + // This presumably is for POP3 messages synchronized (mListeners) { for (Result listener : mListeners) { listener.loadAttachmentCallback(null, messageId, attachmentId, 0); @@ -863,27 +875,10 @@ public class Controller { return; } - // Split here for target type (Service or MessagingController) - IEmailService service = getServiceForMessage(messageId); - if (service != null) { - // Service implementation - try { - service.loadAttachment(attachInfo.mId, saveToFile.getAbsolutePath(), - AttachmentProvider.getAttachmentUri(accountId, attachmentId).toString()); - } catch (RemoteException e) { - // TODO Change exception handling to be consistent with however this method - // is implemented for other protocols - Log.e("onDownloadAttachment", "RemoteException", e); - } - } else { - // MessagingController implementation - Utility.runAsync(new Runnable() { - public void run() { - mLegacyController.loadAttachment(accountId, messageId, mailboxId, attachmentId, - mLegacyListener); - } - }); - } + // Flag the attachment as needing download at the user's request + ContentValues cv = new ContentValues(); + cv.put(Attachment.FLAGS, attachInfo.mFlags | Attachment.FLAG_DOWNLOAD_USER_REQUEST); + attachInfo.update(mContext, cv); } /** @@ -1080,7 +1075,13 @@ public class Controller { * Support for receiving callbacks from MessagingController and dealing with UI going * out of scope. */ - private class LegacyListener extends MessagingListener { + public class LegacyListener extends MessagingListener implements MessageRetrievalListener { + public LegacyListener(long messageId, long attachmentId) { + super(messageId, attachmentId); + } + + public LegacyListener() { + } @Override public void listFoldersStarted(long accountId) { @@ -1192,6 +1193,11 @@ public class Controller { @Override public void loadAttachmentStarted(long accountId, long messageId, long attachmentId, boolean requiresDownload) { + try { + mCallbackProxy.loadAttachmentStatus(messageId, attachmentId, + EmailServiceStatus.IN_PROGRESS, 0); + } catch (RemoteException e) { + } synchronized (mListeners) { for (Result listener : mListeners) { listener.loadAttachmentCallback(null, messageId, attachmentId, 0); @@ -1201,6 +1207,11 @@ public class Controller { @Override public void loadAttachmentFinished(long accountId, long messageId, long attachmentId) { + try { + mCallbackProxy.loadAttachmentStatus(messageId, attachmentId, + EmailServiceStatus.SUCCESS, 100); + } catch (RemoteException e) { + } synchronized (mListeners) { for (Result listener : mListeners) { listener.loadAttachmentCallback(null, messageId, attachmentId, 100); @@ -1209,12 +1220,31 @@ public class Controller { } @Override - public void loadAttachmentFailed(long accountId, long messageId, long attachmentId, - String reason) { + public void loadAttachmentProgress(int progress) { synchronized (mListeners) { for (Result listener : mListeners) { - listener.loadAttachmentCallback(new MessagingException(reason), - messageId, attachmentId, 0); + listener.loadAttachmentCallback(null, messageId, attachmentId, progress); + } + } + } + + @Override + public void loadAttachmentFailed(long accountId, long messageId, long attachmentId, + MessagingException me) { + try { + // If the cause of the MessagingException is an IOException, we send a status of + // CONNECTION_ERROR; in this case, AttachmentDownloadService will try again to + // download the attachment. Otherwise, the error is considered non-recoverable. + int status = EmailServiceStatus.ATTACHMENT_NOT_FOUND; + if (me.getCause() instanceof IOException) { + status = EmailServiceStatus.CONNECTION_ERROR; + } + mCallbackProxy.loadAttachmentStatus(messageId, attachmentId, status, 0); + } catch (RemoteException e) { + } + synchronized (mListeners) { + for (Result listener : mListeners) { + listener.loadAttachmentCallback(me, messageId, attachmentId, 0); } } } @@ -1252,6 +1282,10 @@ public class Controller { } } } + + @Override + public void messageRetrieved(com.android.email.mail.Message message) { + } } /** @@ -1294,9 +1328,6 @@ public class Controller { */ public void sendMessageStatus(long messageId, String subject, int statusCode, int progress) { -// Log.d(Email.LOG_TAG, "sendMessageStatus: messageId=" + messageId -// + " statusCode=" + statusCode + " progress=" + progress); -// Log.d(Email.LOG_TAG, "sendMessageStatus: subject=" + subject); long accountId = -1; // This should be in the callback MessagingException result = mapStatusToException(statusCode); switch (statusCode) { @@ -1310,8 +1341,6 @@ public class Controller { } break; } -// Log.d(Email.LOG_TAG, "result=" + result + " messageId=" + messageId -// + " progress=" + progress); synchronized(mListeners) { for (Result listener : mListeners) { listener.sendMailCallback(result, accountId, messageId, progress); @@ -1352,9 +1381,7 @@ public class Controller { } break; } - // TODO where do we get "number of new messages" as well? // TODO should pass this back instead of looking it up here - // TODO smaller projection Mailbox mbx = Mailbox.restoreMailboxWithId(mProviderContext, mailboxId); // The mailbox could have disappeared if the server commanded it if (mbx == null) return; @@ -1393,4 +1420,154 @@ public class Controller { } } } + + private interface ServiceCallbackWrapper { + public void call(IEmailServiceCallback cb) throws RemoteException; + } + + /** + * Proxy that can be used to broadcast service callbacks; we currently use this only for + * loadAttachment callbacks + */ + private final IEmailServiceCallback.Stub mCallbackProxy = + new IEmailServiceCallback.Stub() { + + /** + * Broadcast a callback to the everyone that's registered + * + * @param wrapper the ServiceCallbackWrapper used in the broadcast + */ + private synchronized void broadcastCallback(ServiceCallbackWrapper wrapper) { + if (sCallbackList != null) { + // Call everyone on our callback list + // Exceptions can be safely ignored + int count = sCallbackList.beginBroadcast(); + for (int i = 0; i < count; i++) { + try { + wrapper.call(sCallbackList.getBroadcastItem(i)); + } catch (RemoteException e) { + } + } + sCallbackList.finishBroadcast(); + } + } + + public void loadAttachmentStatus(final long messageId, final long attachmentId, + final int status, final int progress) { + broadcastCallback(new ServiceCallbackWrapper() { + @Override + public void call(IEmailServiceCallback cb) throws RemoteException { + cb.loadAttachmentStatus(messageId, attachmentId, status, progress); + } + }); + } + + @Override + public void sendMessageStatus(long messageId, String subject, int statusCode, int progress) + throws RemoteException { + } + + @Override + public void syncMailboxListStatus(long accountId, int statusCode, int progress) + throws RemoteException { + } + + @Override + public void syncMailboxStatus(long mailboxId, int statusCode, int progress) + throws RemoteException { + } + }; + + /** + * Create our EmailService implementation here. For now, only loadAttachment is supported; the + * intention, however, is to move more functionality to the service interface + */ + private final IEmailService.Stub mBinder = new IEmailService.Stub() { + + public Bundle validate(String protocol, String host, String userName, String password, + int port, boolean ssl, boolean trustCertificates) throws RemoteException { + return null; + } + + public Bundle autoDiscover(String userName, String password) throws RemoteException { + return null; + } + + public void startSync(long mailboxId) throws RemoteException { + } + + public void stopSync(long mailboxId) throws RemoteException { + } + + public void loadAttachment(long attachmentId, String destinationFile, + String contentUriString) throws RemoteException { + if (Email.DEBUG) { + Log.d(TAG, "loadAttachment: " + attachmentId + " to " + destinationFile); + } + Attachment att = Attachment.restoreAttachmentWithId(Controller.this, attachmentId); + if (att != null) { + Message msg = Message.restoreMessageWithId(Controller.this, att.mMessageKey); + if (msg != null) { + // If the message is a forward and the attachment needs downloading, we need + // to retrieve the message from the source, rather than from the message + // itself + if ((msg.mFlags & Message.FLAG_TYPE_FORWARD) != 0) { + String[] cols = Utility.getRowColumns(Controller.this, Body.CONTENT_URI, + BODY_SOURCE_KEY_PROJECTION, WHERE_MESSAGE_KEY, + new String[] {Long.toString(msg.mId)}); + if (cols != null) { + msg = Message.restoreMessageWithId(Controller.this, + Long.parseLong(cols[BODY_SOURCE_KEY_COLUMN])); + if (msg == null) { + // TODO: We can try restoring from the deleted table at this point... + return; + } + } + } + MessagingController legacyController = sInstance.mLegacyController; + LegacyListener legacyListener = sInstance.mLegacyListener; + legacyController.loadAttachment(msg.mAccountKey, msg.mId, msg.mMailboxKey, + attachmentId, legacyListener); + } + } + } + + public void updateFolderList(long accountId) throws RemoteException { + } + + public void hostChanged(long accountId) throws RemoteException { + } + + public void setLogging(int on) throws RemoteException { + } + + public void sendMeetingResponse(long messageId, int response) throws RemoteException { + } + + public void loadMore(long messageId) throws RemoteException { + } + + // The following three methods are not implemented in this version + public boolean createFolder(long accountId, String name) throws RemoteException { + return false; + } + + public boolean deleteFolder(long accountId, String name) throws RemoteException { + return false; + } + + public boolean renameFolder(long accountId, String oldName, String newName) + throws RemoteException { + return false; + } + + public void setCallback(IEmailServiceCallback cb) throws RemoteException { + sCallbackList.register(cb); + } + }; + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } } diff --git a/src/com/android/email/GroupMessagingListener.java b/src/com/android/email/GroupMessagingListener.java index 8af824a41..8ee609c82 100644 --- a/src/com/android/email/GroupMessagingListener.java +++ b/src/com/android/email/GroupMessagingListener.java @@ -16,6 +16,8 @@ package com.android.email; +import com.android.email.mail.MessagingException; + import android.content.Context; import java.util.Set; @@ -169,6 +171,14 @@ public class GroupMessagingListener extends MessagingListener { } } + @Override + synchronized public void loadAttachmentProgress( + int progress) { + for (MessagingListener l : mListeners) { + l.loadAttachmentProgress(progress); + } + } + @Override synchronized public void loadAttachmentFinished( long accountId, @@ -184,9 +194,9 @@ public class GroupMessagingListener extends MessagingListener { long accountId, long messageId, long attachmentId, - String reason) { + MessagingException me) { for (MessagingListener l : mListeners) { - l.loadAttachmentFailed(accountId, messageId, attachmentId, reason); + l.loadAttachmentFailed(accountId, messageId, attachmentId, me); } } diff --git a/src/com/android/email/MessagingController.java b/src/com/android/email/MessagingController.java index 906cce811..6826ba1a5 100644 --- a/src/com/android/email/MessagingController.java +++ b/src/com/android/email/MessagingController.java @@ -50,7 +50,6 @@ import android.net.Uri; import android.os.Process; import android.util.Log; -import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Date; @@ -122,10 +121,11 @@ public class MessagingController implements Runnable { private final GroupMessagingListener mListeners = new GroupMessagingListener(); private boolean mBusy; private final Context mContext; + private final Controller mController; - protected MessagingController(Context _context) { + protected MessagingController(Context _context, Controller _controller) { mContext = _context.getApplicationContext(); - + mController = _controller; mThread = new Thread(this); mThread.start(); } @@ -134,9 +134,10 @@ public class MessagingController implements Runnable { * Gets or creates the singleton instance of MessagingController. Application is used to * provide a Context to classes that need it. */ - public synchronized static MessagingController getInstance(Context _context) { + public synchronized static MessagingController getInstance(Context _context, + Controller _controller) { if (sInstance == null) { - sInstance = new MessagingController(_context); + sInstance = new MessagingController(_context, _controller); } return sInstance; } @@ -636,6 +637,10 @@ public class MessagingController implements Runnable { "Error while storing downloaded message." + e.toString()); } } + + @Override + public void loadAttachmentProgress(int progress) { + } }); } @@ -768,6 +773,10 @@ public class MessagingController implements Runnable { copyOneMessageToProvider(message, account, folder, EmailContent.Message.FLAG_LOADED_COMPLETE); } + + @Override + public void loadAttachmentProgress(int progress) { + } }); // 14. Download large messages. We ask the server to give us the message structure, @@ -1816,16 +1825,14 @@ public class MessagingController implements Runnable { public void run() { try { //1. Check if the attachment is already here and return early in that case - File saveToFile = AttachmentProvider.getAttachmentFilename(mContext, accountId, - attachmentId); Attachment attachment = Attachment.restoreAttachmentWithId(mContext, attachmentId); if (attachment == null) { mListeners.loadAttachmentFailed(accountId, messageId, attachmentId, - "Attachment is null"); + new MessagingException("The attachment is null")); return; } - if (saveToFile.exists() && attachment.mContentUri != null) { + if (Utility.attachmentExists(mContext, accountId, attachment)) { mListeners.loadAttachmentFinished(accountId, messageId, attachmentId); return; } @@ -1841,14 +1848,11 @@ public class MessagingController implements Runnable { if (account == null || mailbox == null || message == null) { mListeners.loadAttachmentFailed(accountId, messageId, attachmentId, - "Account, mailbox, message or attachment are null"); + new MessagingException( + "Account, mailbox, message or attachment are null")); return; } - // Pruning. Policy is to have one downloaded attachment at a time, - // per account, to reduce disk storage pressure. - pruneCachedAttachments(accountId); - Store remoteStore = Store.getInstance(account.getStoreUri(mContext), mContext, null); Folder remoteFolder = remoteStore.getFolder(mailbox.mDisplayName); @@ -1879,7 +1883,14 @@ public class MessagingController implements Runnable { // 4. Now ask for the attachment to be fetched FetchProfile fp = new FetchProfile(); fp.add(storePart); - remoteFolder.fetch(new Message[] { storeMessage }, fp, null); + remoteFolder.fetch(new Message[] { storeMessage }, fp, + mController.new LegacyListener(messageId, attachmentId)); + + // If we failed to load the attachment, throw an Exception here, so that + // AttachmentDownloadService knows that we failed + if (storePart.getBody() == null) { + throw new MessagingException("Attachment not loaded."); + } // 5. Save the downloaded file and update the attachment as necessary LegacyConversions.saveAttachmentBody(mContext, storePart, attachment, @@ -1890,57 +1901,13 @@ public class MessagingController implements Runnable { } catch (MessagingException me) { if (Email.LOGD) Log.v(Email.LOG_TAG, "", me); - mListeners.loadAttachmentFailed(accountId, messageId, attachmentId, - me.getMessage()); + mListeners.loadAttachmentFailed(accountId, messageId, attachmentId, me); } catch (IOException ioe) { Log.e(Email.LOG_TAG, "Error while storing attachment." + ioe.toString()); } }}); } - /** - * Erase all stored attachments for a given account. Rules: - * 1. All files in attachment directory are up for deletion - * 2. If filename does not match an known attachment id, it's deleted - * 3. If the attachment has location data (implying that it's reloadable), it's deleted - */ - /* package */ void pruneCachedAttachments(long accountId) { - ContentResolver resolver = mContext.getContentResolver(); - File cacheDir = AttachmentProvider.getAttachmentDirectory(mContext, accountId); - File[] fileList = cacheDir.listFiles(); - // fileList can be null if the directory doesn't exist or if there's an IOException - if (fileList == null) return; - for (File file : fileList) { - if (file.exists()) { - long id; - try { - // the name of the file == the attachment id - id = Long.valueOf(file.getName()); - Uri uri = ContentUris.withAppendedId(Attachment.CONTENT_URI, id); - Cursor c = resolver.query(uri, PRUNE_ATTACHMENT_PROJECTION, null, null, null); - try { - if (c.moveToNext()) { - // if there is no way to reload the attachment, don't delete it - if (c.getString(0) == null) { - continue; - } - } - } finally { - c.close(); - } - // Clear the content URI field since we're losing the attachment - resolver.update(uri, PRUNE_ATTACHMENT_CV, null, null); - } catch (NumberFormatException nfe) { - // ignore filename != number error, and just delete it anyway - } - // This file can be safely deleted - if (!file.delete()) { - file.deleteOnExit(); - } - } - } - } - /** * Attempt to send any messages that are sitting in the Outbox. * @param account @@ -1996,6 +1963,14 @@ public class MessagingController implements Runnable { try { messageId = c.getLong(0); mListeners.sendPendingMessagesStarted(account.mId, messageId); + // Don't send messages with unloaded attachments + if (Utility.hasUnloadedAttachments(mContext, messageId)) { + if (Email.DEBUG) { + Log.d(Email.LOG_TAG, "Can't send #" + messageId + + "; unloaded attachments"); + } + continue; + } sender.sendMessage(messageId); } catch (MessagingException me) { // report error for this message, but keep trying others @@ -2006,6 +1981,15 @@ public class MessagingController implements Runnable { Uri syncedUri = ContentUris.withAppendedId(EmailContent.Message.SYNCED_CONTENT_URI, messageId); if (requireMoveMessageToSentFolder) { + // If this is a forwarded message and it has attachments, delete them, as they + // duplicate information found elsewhere (on the server). This saves storage. + EmailContent.Message msg = + EmailContent.Message.restoreMessageWithId(mContext, messageId); + if (msg != null && + ((msg.mFlags & EmailContent.Message.FLAG_TYPE_FORWARD) != 0)) { + AttachmentProvider.deleteAllAttachmentFiles(mContext, account.mId, + messageId); + } resolver.update(syncedUri, moveToSentValues, null, null); } else { AttachmentProvider.deleteAllAttachmentFiles(mContext, account.mId, messageId); diff --git a/src/com/android/email/MessagingListener.java b/src/com/android/email/MessagingListener.java index 6a5795776..9c5ee5f83 100644 --- a/src/com/android/email/MessagingListener.java +++ b/src/com/android/email/MessagingListener.java @@ -16,6 +16,8 @@ package com.android.email; +import com.android.email.mail.MessagingException; + import android.content.Context; /** @@ -26,6 +28,17 @@ import android.content.Context; * changes in this class. */ public class MessagingListener { + long messageId = -1; + long attachmentId = -1; + + public MessagingListener(long _messageId, long _attachmentId) { + messageId = _messageId; + attachmentId = _attachmentId; + } + + public MessagingListener() { + } + public void listFoldersStarted(long accountId) { } @@ -81,6 +94,8 @@ public class MessagingListener { boolean requiresDownload) { } + public void loadAttachmentProgress(int progress) {} + public void loadAttachmentFinished( long accountId, long messageId, @@ -91,7 +106,7 @@ public class MessagingListener { long accountId, long messageId, long attachmentId, - String reason) { + MessagingException me) { } /** diff --git a/src/com/android/email/mail/Folder.java b/src/com/android/email/mail/Folder.java index 46d776dec..6081e10e7 100644 --- a/src/com/android/email/mail/Folder.java +++ b/src/com/android/email/mail/Folder.java @@ -48,6 +48,7 @@ public abstract class Folder { */ public interface MessageRetrievalListener { public void messageRetrieved(Message message); + public void loadAttachmentProgress(int progress); } /** diff --git a/src/com/android/email/mail/store/ImapStore.java b/src/com/android/email/mail/store/ImapStore.java index 06c6dfccf..b7b9428af 100644 --- a/src/com/android/email/mail/store/ImapStore.java +++ b/src/com/android/email/mail/store/ImapStore.java @@ -21,6 +21,7 @@ import com.android.email.Preferences; import com.android.email.Utility; import com.android.email.VendorPolicyLoader; import com.android.email.mail.AuthenticationFailedException; +import com.android.email.mail.Body; import com.android.email.mail.CertificateValidationException; import com.android.email.mail.FetchProfile; import com.android.email.mail.Flag; @@ -30,6 +31,7 @@ import com.android.email.mail.MessagingException; import com.android.email.mail.Part; import com.android.email.mail.Store; import com.android.email.mail.Transport; +import com.android.email.mail.internet.BinaryTempFileBody; import com.android.email.mail.internet.MimeBodyPart; import com.android.email.mail.internet.MimeHeader; import com.android.email.mail.internet.MimeMessage; @@ -59,6 +61,7 @@ import android.util.Log; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; import java.nio.ByteBuffer; @@ -98,6 +101,8 @@ public class ImapStore extends Store { // Always check in FALSE private static final boolean DEBUG_FORCE_SEND_ID = false; + private static final int COPY_BUFFER_SIZE = 16*1024; + private static final Flag[] PERMANENT_FLAGS = { Flag.DELETED, Flag.SEEN, Flag.FLAGGED }; private final Context mContext; @@ -995,9 +1000,8 @@ public class ImapStore extends Store { // decodeBody creates BinaryTempFileBody, but we could avoid this // if we implement ImapStringBody. // (We'll need to share a temp file. Protect it with a ref-count.) - fetchPart.setBody(MimeUtility.decodeBody( - bodyStream, - contentTransferEncoding)); + fetchPart.setBody(decodeBody(bodyStream, contentTransferEncoding, + fetchPart.getSize(), listener)); } if (listener != null) { @@ -1012,6 +1016,30 @@ public class ImapStore extends Store { } } + /** + * Removes any content transfer encoding from the stream and returns a Body. + * This code is taken/condensed from MimeUtility.decodeBody + */ + private Body decodeBody(InputStream in, String contentTransferEncoding, int size, + MessageRetrievalListener listener) throws IOException { + // Get a properly wrapped input stream + in = MimeUtility.getInputStreamForContentTransferEncoding(in, contentTransferEncoding); + BinaryTempFileBody tempBody = new BinaryTempFileBody(); + OutputStream out = tempBody.getOutputStream(); + byte[] buffer = new byte[COPY_BUFFER_SIZE]; + int n = 0; + int count = 0; + while (-1 != (n = in.read(buffer))) { + out.write(buffer, 0, n); + count += n; + if (listener != null) { + listener.loadAttachmentProgress(count * 100 / size); + } + } + out.close(); + return tempBody; + } + @Override public Flag[] getPermanentFlags() throws MessagingException { return PERMANENT_FLAGS; diff --git a/src/com/android/email/service/AttachmentDownloadService.java b/src/com/android/email/service/AttachmentDownloadService.java index eeb7d3a8a..37e49ed7f 100644 --- a/src/com/android/email/service/AttachmentDownloadService.java +++ b/src/com/android/email/service/AttachmentDownloadService.java @@ -507,8 +507,7 @@ public class AttachmentDownloadService extends Service implements Runnable { if (protocol.equals("eas")) { serviceClass = SyncManager.class; } else { - // Uncomment this when Controller has been made a Service - //serviceClass = Controller.class; + serviceClass = com.android.email.Controller.class; } mAccountServiceMap.put(accountId, serviceClass); }