@Override public void loadMore(long messageId) throws RemoteException { /// M: We Can't load more in low storage state @{ if (StorageLowState.checkIfStorageLow(mContext)) { LogUtils.e(Logging.LOG_TAG, "Can't load more due to low storage"); return; } /// @} // Load a message for view... try { // 1. Resample the message, in case it disappeared or synced while // this command was in queue final EmailContent.Message message = EmailContent.Message.restoreMessageWithId(mContext, messageId); if (message == null) { return; } if (message.mFlagLoaded == EmailContent.Message.FLAG_LOADED_COMPLETE) { // We should NEVER get here return; } // 2. Open the remote folder. // TODO combine with common code in loadAttachment final Account account = Account.restoreAccountWithId(mContext, message.mAccountKey); final Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, message.mMailboxKey); if (account == null || mailbox == null) { // mListeners.loadMessageForViewFailed(messageId, "null account or mailbox"); return; } TrafficStats.setThreadStatsTag(TrafficFlags.getSyncFlags(mContext, account)); final Store remoteStore = Store.getInstance(account, mContext); final String remoteServerId; // If this is a search result, use the protocolSearchInfo field to get the // correct remote location if (!TextUtils.isEmpty(message.mProtocolSearchInfo)) { remoteServerId = message.mProtocolSearchInfo; } else { remoteServerId = mailbox.mServerId; } final Folder remoteFolder = remoteStore.getFolder(remoteServerId); remoteFolder.open(OpenMode.READ_WRITE); // 3. Set up to download the entire message final Message remoteMessage = remoteFolder.getMessage(message.mServerId); final FetchProfile fp = new FetchProfile(); fp.add(FetchProfile.Item.BODY); remoteFolder.fetch(new Message[] {remoteMessage}, fp, null); // 4. Write to provider Utilities.copyOneMessageToProvider( mContext, remoteMessage, account, mailbox, EmailContent.Message.FLAG_LOADED_COMPLETE); } catch (MessagingException me) { if (Logging.LOGD) LogUtils.v(Logging.LOG_TAG, "", me); } catch (RuntimeException rte) { LogUtils.d(Logging.LOG_TAG, "RTE During loadMore"); } }
public static void sendMailImpl(Context context, long accountId) { /// M: We Can't send mail in low storage state @{ if (StorageLowState.checkIfStorageLow(context)) { LogUtils.e(Logging.LOG_TAG, "Can't send mail due to low storage"); return; } /// @} /** M: Get the sendable mails count of the account and notify this sending @{ */ final int count = getSendableMessageCount(context, accountId); LogUtils.logFeature(LogTag.SENDMAIL_TAG, "sendable message count [%d]", count); if (count <= 0) { return; } SendNotificationProxy.getInstance(context) .showSendingNotification(accountId, NotificationController.SEND_MAIL, count); /** @} */ final Account account = Account.restoreAccountWithId(context, accountId); TrafficStats.setThreadStatsTag(TrafficFlags.getSmtpFlags(context, account)); final NotificationController nc = NotificationController.getInstance(context); // 1. Loop through all messages in the account's outbox final long outboxId = Mailbox.findMailboxOfType(context, account.mId, Mailbox.TYPE_OUTBOX); if (outboxId == Mailbox.NO_MAILBOX) { return; } final ContentResolver resolver = context.getContentResolver(); final Cursor c = resolver.query( EmailContent.Message.CONTENT_URI, EmailContent.Message.ID_COLUMN_PROJECTION, EmailContent.Message.MAILBOX_KEY + "=?", new String[] {Long.toString(outboxId)}, null); try { // 2. exit early if (c.getCount() <= 0) { return; } final Sender sender = Sender.getInstance(context, account); final Store remoteStore = Store.getInstance(account, context); final ContentValues moveToSentValues; if (remoteStore.requireCopyMessageToSentFolder()) { Mailbox sentFolder = Mailbox.restoreMailboxOfType(context, accountId, Mailbox.TYPE_SENT); moveToSentValues = new ContentValues(); moveToSentValues.put(MessageColumns.MAILBOX_KEY, sentFolder.mId); } else { moveToSentValues = null; } // 3. loop through the available messages and send them /** M: mark should we cancel the Login Failed Notification. */ boolean shouldCancelNf = false; while (c.moveToNext()) { long messageId = -1; if (moveToSentValues != null) { moveToSentValues.remove(EmailContent.MessageColumns.FLAGS); } try { messageId = c.getLong(0); // Don't send messages with unloaded attachments if (Utility.hasUnloadedAttachments(context, messageId)) { LogUtils.logFeature( LogTag.SENDMAIL_TAG, "Can't send #" + messageId + "; unloaded attachments"); continue; } sender.sendMessage(messageId); } catch (MessagingException me) { LogUtils.logFeature( LogTag.SENDMAIL_TAG, "<<< Smtp send message failed id [%s], exception: %s", messageId, me); // report error for this message, but keep trying others if (me instanceof AuthenticationFailedException) { shouldCancelNf = false; nc.showLoginFailedNotification(account.mId); } /// M: One mail sent failed SendNotificationProxy.getInstance(context) .showSendingNotification(account.mId, NotificationController.SEND_FAILED, 1); continue; } /// M: One mail sent complete SendNotificationProxy.getInstance(context) .showSendingNotification(account.mId, NotificationController.SEND_COMPLETE, 1); // 4. move to sent, or delete final Uri syncedUri = ContentUris.withAppendedId(EmailContent.Message.SYNCED_CONTENT_URI, messageId); // Delete all cached files AttachmentUtilities.deleteAllCachedAttachmentFiles(context, account.mId, messageId); if (moveToSentValues != null) { // If this is a forwarded message and it has attachments, delete them, as they // duplicate information found elsewhere (on the server). This saves storage. final EmailContent.Message msg = EmailContent.Message.restoreMessageWithId(context, messageId); if ((msg.mFlags & EmailContent.Message.FLAG_TYPE_FORWARD) != 0) { AttachmentUtilities.deleteAllAttachmentFiles(context, account.mId, messageId); } /// M: un-mark sending status after sending final int flags = msg.mFlags & ~(EmailContent.Message.FLAG_TYPE_REPLY | EmailContent.Message.FLAG_TYPE_FORWARD | EmailContent.Message.FLAG_TYPE_REPLY_ALL | EmailContent.Message.FLAG_TYPE_ORIGINAL | EmailContent.Message.FLAG_STATUS_SENDING); moveToSentValues.put(EmailContent.MessageColumns.FLAGS, flags); resolver.update(syncedUri, moveToSentValues, null, null); } else { AttachmentUtilities.deleteAllAttachmentFiles(context, account.mId, messageId); final Uri uri = ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI, messageId); resolver.delete(uri, null, null); resolver.delete(syncedUri, null, null); } shouldCancelNf = true; } if (shouldCancelNf) { nc.cancelLoginFailedNotification(account.mId); } } catch (MessagingException me) { if (me instanceof AuthenticationFailedException) { nc.showLoginFailedNotification(account.mId); } /// M: All mails failed to be sent, caused by fail to get instance of store SendNotificationProxy.getInstance(context) .showSendingNotification(account.mId, NotificationController.SEND_FAILED, c.getCount()); } finally { c.close(); } }
/// M: The folder sync must be synchronized, otherwise the folders may be duplicate @Override public synchronized void updateFolderList(long accountId) throws RemoteException { /// M: We Can't updateFolderList in low storage state @{ if (StorageLowState.checkIfStorageLow(mContext)) { LogUtils.e(Logging.LOG_TAG, "Can't updateFolderList due to low storage"); return; } /// @} final Account account = Account.restoreAccountWithId(mContext, accountId); if (account == null) { return; } long inboxId = -1; TrafficStats.setThreadStatsTag(TrafficFlags.getSyncFlags(mContext, account)); Cursor localFolderCursor = null; try { // Step 0: Make sure the default system mailboxes exist. for (final int type : Mailbox.REQUIRED_FOLDER_TYPES) { if (Mailbox.findMailboxOfType(mContext, accountId, type) == Mailbox.NO_MAILBOX) { final Mailbox mailbox = Mailbox.newSystemMailbox(mContext, accountId, type); mailbox.save(mContext); if (type == Mailbox.TYPE_INBOX) { inboxId = mailbox.mId; } } } // Step 1: Get remote mailboxes final Store store = Store.getInstance(account, mContext); final Folder[] remoteFolders = store.updateFolders(); final HashSet<String> remoteFolderNames = new HashSet<String>(); for (final Folder remoteFolder : remoteFolders) { remoteFolderNames.add(remoteFolder.getName()); } // Step 2: Get local mailboxes localFolderCursor = mContext .getContentResolver() .query( Mailbox.CONTENT_URI, MAILBOX_PROJECTION, EmailContent.MailboxColumns.ACCOUNT_KEY + "=?", new String[] {String.valueOf(account.mId)}, null); // Step 3: Remove any local mailbox not on the remote list while (localFolderCursor.moveToNext()) { final String mailboxPath = localFolderCursor.getString(MAILBOX_COLUMN_SERVER_ID); // Short circuit if we have a remote mailbox with the same name if (remoteFolderNames.contains(mailboxPath)) { continue; } final int mailboxType = localFolderCursor.getInt(MAILBOX_COLUMN_TYPE); final long mailboxId = localFolderCursor.getLong(MAILBOX_COLUMN_ID); switch (mailboxType) { case Mailbox.TYPE_INBOX: case Mailbox.TYPE_DRAFTS: case Mailbox.TYPE_OUTBOX: case Mailbox.TYPE_SENT: case Mailbox.TYPE_TRASH: case Mailbox.TYPE_SEARCH: // Never, ever delete special mailboxes break; default: // Drop all attachment files related to this mailbox AttachmentUtilities.deleteAllMailboxAttachmentFiles(mContext, accountId, mailboxId); // Delete the mailbox; database triggers take care of related // Message, Body and Attachment records Uri uri = ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId); mContext.getContentResolver().delete(uri, null, null); break; } } } catch (MessagingException me) { LogUtils.i(Logging.LOG_TAG, me, "Error in updateFolderList"); // We'll hope this is temporary } finally { if (localFolderCursor != null) { localFolderCursor.close(); } // If we just created the inbox, sync it if (inboxId != -1) { startSync(inboxId, true, 0); } } }
@Override public void loadAttachment( final IEmailServiceCallback cb, final long attachmentId, final boolean background) throws RemoteException { /// M: We Can't load attachment in low storage state @{ if (StorageLowState.checkIfStorageLow(mContext)) { LogUtils.e(Logging.LOG_TAG, "Can't load attachment due to low storage"); cb.loadAttachmentStatus(0, attachmentId, EmailServiceStatus.SUCCESS, 0); return; } /// @} Folder remoteFolder = null; try { // 1. Check if the attachment is already here and return early in that case Attachment attachment = Attachment.restoreAttachmentWithId(mContext, attachmentId); if (attachment == null) { cb.loadAttachmentStatus(0, attachmentId, EmailServiceStatus.ATTACHMENT_NOT_FOUND, 0); return; } final long messageId = attachment.mMessageKey; final EmailContent.Message message = EmailContent.Message.restoreMessageWithId(mContext, attachment.mMessageKey); if (message == null) { cb.loadAttachmentStatus(messageId, attachmentId, EmailServiceStatus.MESSAGE_NOT_FOUND, 0); return; } // If the message is loaded, just report that we're finished if (Utility.attachmentExists(mContext, attachment) && attachment.mUiState == UIProvider.AttachmentState.SAVED) { cb.loadAttachmentStatus(messageId, attachmentId, EmailServiceStatus.SUCCESS, 0); return; } // Say we're starting... cb.loadAttachmentStatus(messageId, attachmentId, EmailServiceStatus.IN_PROGRESS, 0); // 2. Open the remote folder. final Account account = Account.restoreAccountWithId(mContext, message.mAccountKey); Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, message.mMailboxKey); if (mailbox.mType == Mailbox.TYPE_OUTBOX /// M: View an attachment which comes from refMessage need sourceKey to identify || mailbox.mType == Mailbox.TYPE_DRAFTS) { long sourceId = Utility.getFirstRowLong( mContext, Body.CONTENT_URI, new String[] {BodyColumns.SOURCE_MESSAGE_KEY}, BodyColumns.MESSAGE_KEY + "=?", new String[] {Long.toString(messageId)}, null, 0, -1L); if (sourceId != -1) { EmailContent.Message sourceMsg = EmailContent.Message.restoreMessageWithId(mContext, sourceId); if (sourceMsg != null) { mailbox = Mailbox.restoreMailboxWithId(mContext, sourceMsg.mMailboxKey); message.mServerId = sourceMsg.mServerId; } } } else if (mailbox.mType == Mailbox.TYPE_SEARCH && message.mMainMailboxKey != 0) { mailbox = Mailbox.restoreMailboxWithId(mContext, message.mMainMailboxKey); } if (account == null || mailbox == null) { // If the account/mailbox are gone, just report success; the UI handles this cb.loadAttachmentStatus(messageId, attachmentId, EmailServiceStatus.SUCCESS, 0); return; } TrafficStats.setThreadStatsTag(TrafficFlags.getAttachmentFlags(mContext, account)); final Store remoteStore = Store.getInstance(account, mContext); remoteFolder = remoteStore.getFolder(mailbox.mServerId); remoteFolder.open(OpenMode.READ_WRITE); // 3. Generate a shell message in which to retrieve the attachment, // and a shell BodyPart for the attachment. Then glue them together. final Message storeMessage = remoteFolder.createMessage(message.mServerId); final MimeBodyPart storePart = new MimeBodyPart(); storePart.setSize((int) attachment.mSize); storePart.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, attachment.mLocation); storePart.setHeader( MimeHeader.HEADER_CONTENT_TYPE, String.format("%s;\n name=\"%s\"", attachment.mMimeType, attachment.mFileName)); // TODO is this always true for attachments? I think we dropped the // true encoding along the way /// M: set encoding type according to data base record. String encoding = attachment.mEncoding; if (TextUtils.isEmpty(encoding)) { encoding = "base64"; } storePart.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, encoding); final MimeMultipart multipart = new MimeMultipart(); multipart.setSubType("mixed"); multipart.addBodyPart(storePart); storeMessage.setHeader(MimeHeader.HEADER_CONTENT_TYPE, "multipart/mixed"); storeMessage.setBody(multipart); // 4. Now ask for the attachment to be fetched final FetchProfile fp = new FetchProfile(); fp.add(storePart); remoteFolder.fetch( new Message[] {storeMessage}, fp, new MessageRetrievalListenerBridge(messageId, attachmentId, cb)); // 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."); } // Save the attachment to wherever it's going AttachmentUtilities.saveAttachment( mContext, storePart.getBody().getInputStream(), attachment); // 6. Report success cb.loadAttachmentStatus(messageId, attachmentId, EmailServiceStatus.SUCCESS, 0); } catch (MessagingException me) { LogUtils.i(Logging.LOG_TAG, me, "Error loading attachment"); final ContentValues cv = new ContentValues(1); cv.put(AttachmentColumns.UI_STATE, UIProvider.AttachmentState.FAILED); final Uri uri = ContentUris.withAppendedId(Attachment.CONTENT_URI, attachmentId); mContext.getContentResolver().update(uri, cv, null, null); cb.loadAttachmentStatus(0, attachmentId, EmailServiceStatus.CONNECTION_ERROR, 0); } finally { if (remoteFolder != null) { remoteFolder.close(false); } } }