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);
}