/** * Add a single attachment part to the message * * <p>This will skip adding attachments if they are already found in the attachments table. The * heuristic for this will fail (false-positive) if two identical attachments are included in a * single POP3 message. TODO: Fix that, by (elsewhere) simulating an mLocation value based on the * attachments position within the list of multipart/mixed elements. This would make every POP3 * attachment unique, and might also simplify the code (since we could just look at the positions, * and ignore the filename, etc.) * * <p>TODO: Take a closer look at encoding and deal with it if necessary. * * @param context a context for file operations * @param localMessage the attachments will be built against this message * @param part a single attachment part from POP or IMAP */ public static void addOneAttachment( final Context context, final EmailContent.Message localMessage, final Part part) throws MessagingException, IOException { final Attachment localAttachment = mimePartToAttachment(part); localAttachment.mMessageKey = localMessage.mId; localAttachment.mAccountKey = localMessage.mAccountKey; if (DEBUG_ATTACHMENTS) { LogUtils.d(Logging.LOG_TAG, "Add attachment " + localAttachment); } // To prevent duplication - do we already have a matching attachment? // The fields we'll check for equality are: // mFileName, mMimeType, mContentId, mMessageKey, mLocation // NOTE: This will false-positive if you attach the exact same file, twice, to a POP3 // message. We can live with that - you'll get one of the copies. final Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, localMessage.mId); final Cursor cursor = context.getContentResolver().query(uri, Attachment.CONTENT_PROJECTION, null, null, null); boolean attachmentFoundInDb = false; try { while (cursor.moveToNext()) { final Attachment dbAttachment = new Attachment(); dbAttachment.restore(cursor); // We test each of the fields here (instead of in SQL) because they may be // null, or may be strings. if (!TextUtils.equals(dbAttachment.mFileName, localAttachment.mFileName) || !TextUtils.equals(dbAttachment.mMimeType, localAttachment.mMimeType) || !TextUtils.equals(dbAttachment.mContentId, localAttachment.mContentId) || !TextUtils.equals(dbAttachment.mLocation, localAttachment.mLocation)) { continue; } // We found a match, so use the existing attachment id, and stop looking/looping attachmentFoundInDb = true; localAttachment.mId = dbAttachment.mId; if (DEBUG_ATTACHMENTS) { LogUtils.d(Logging.LOG_TAG, "Skipped, found db attachment " + dbAttachment); } break; } } finally { cursor.close(); } // Save the attachment (so far) in order to obtain an id if (!attachmentFoundInDb) { localAttachment.save(context); } // If an attachment body was actually provided, we need to write the file now saveAttachmentBody(context, part, localAttachment, localMessage.mAccountKey); if (localMessage.mAttachments == null) { localMessage.mAttachments = new ArrayList<Attachment>(); } localMessage.mAttachments.add(localAttachment); localMessage.mFlagAttachment = true; }
/** * Copy attachments from MimeMessage to provider Message. * * @param context a context for file operations * @param localMessage the attachments will be built against this message * @param attachments the attachments to add */ public static void updateAttachments( final Context context, final EmailContent.Message localMessage, final ArrayList<Part> attachments) throws MessagingException, IOException { localMessage.mAttachments = null; for (Part attachmentPart : attachments) { addOneAttachment(context, localMessage, attachmentPart); } }
public void addData(EmailContent.Message msg, int endingTag) throws IOException { ArrayList<EmailContent.Attachment> atts = new ArrayList<EmailContent.Attachment>(); boolean truncated = false; while (nextTag(endingTag) != END) { switch (tag) { case Tags.EMAIL_ATTACHMENTS: case Tags.BASE_ATTACHMENTS: // BASE_ATTACHMENTS is used in EAS 12.0 and up attachmentsParser(atts, msg, tag); break; case Tags.EMAIL_TO: msg.mTo = Address.pack(Address.parse(getValue(), false)); break; case Tags.EMAIL_FROM: Address[] froms = Address.parse(getValue(), false); if (froms != null && froms.length > 0) { msg.mDisplayName = froms[0].toFriendly(); } msg.mFrom = Address.toString(froms); break; case Tags.EMAIL_CC: msg.mCc = Address.pack(Address.parse(getValue(), false)); break; case Tags.EMAIL_REPLY_TO: msg.mReplyTo = Address.pack(Address.parse(getValue(), false)); break; case Tags.EMAIL_DATE_RECEIVED: try { msg.mTimeStamp = Utility.parseEmailDateTimeToMillis(getValue()); } catch (ParseException e) { LogUtils.w(TAG, "Parse error for EMAIL_DATE_RECEIVED tag.", e); } break; case Tags.EMAIL_SUBJECT: msg.mSubject = getValue(); break; case Tags.EMAIL_READ: msg.mFlagRead = getValueInt() == 1; break; case Tags.BASE_BODY: bodyParser(msg); break; case Tags.EMAIL_FLAG: msg.mFlagFavorite = flagParser(); break; case Tags.EMAIL_MIME_TRUNCATED: truncated = getValueInt() == 1; break; case Tags.EMAIL_MIME_DATA: // We get MIME data for EAS 2.5. First we parse it, then we take the // html and/or plain text data and store it in the message if (truncated) { // If the MIME data is truncated, don't bother parsing it, because // it will take time and throw an exception anyway when EOF is reached // In this case, we will load the body separately by tagging the message // "partially loaded". // Get the data (and ignore it) getValue(); userLog("Partially loaded: ", msg.mServerId); msg.mFlagLoaded = EmailContent.Message.FLAG_LOADED_PARTIAL; mFetchNeeded = true; } else { mimeBodyParser(msg, getValue()); } break; case Tags.EMAIL_BODY: String text = getValue(); msg.mText = text; break; case Tags.EMAIL_MESSAGE_CLASS: String messageClass = getValue(); if (messageClass.equals("IPM.Schedule.Meeting.Request")) { msg.mFlags |= EmailContent.Message.FLAG_INCOMING_MEETING_INVITE; } else if (messageClass.equals("IPM.Schedule.Meeting.Canceled")) { msg.mFlags |= EmailContent.Message.FLAG_INCOMING_MEETING_CANCEL; } break; case Tags.EMAIL_MEETING_REQUEST: meetingRequestParser(msg); break; case Tags.EMAIL_THREAD_TOPIC: msg.mThreadTopic = getValue(); break; case Tags.RIGHTS_LICENSE: skipParser(tag); break; case Tags.EMAIL2_CONVERSATION_ID: msg.mServerConversationId = Base64.encodeToString(getValueBytes(), Base64.URL_SAFE); break; case Tags.EMAIL2_CONVERSATION_INDEX: // Ignore this byte array since we're not constructing a tree. getValueBytes(); break; case Tags.EMAIL2_LAST_VERB_EXECUTED: int val = getValueInt(); if (val == LAST_VERB_REPLY || val == LAST_VERB_REPLY_ALL) { // We aren't required to distinguish between reply and reply all here msg.mFlags |= EmailContent.Message.FLAG_REPLIED_TO; } else if (val == LAST_VERB_FORWARD) { msg.mFlags |= EmailContent.Message.FLAG_FORWARDED; } break; default: skipTag(); } } if (atts.size() > 0) { msg.mAttachments = atts; } if ((msg.mFlags & EmailContent.Message.FLAG_INCOMING_MEETING_MASK) != 0) { String text = TextUtilities.makeSnippetFromHtmlText(msg.mText != null ? msg.mText : msg.mHtml); if (TextUtils.isEmpty(text)) { // Create text for this invitation String meetingInfo = msg.mMeetingInfo; if (!TextUtils.isEmpty(meetingInfo)) { PackedString ps = new PackedString(meetingInfo); ContentValues values = new ContentValues(); putFromMeeting( ps, MeetingInfo.MEETING_LOCATION, values, CalendarContract.Events.EVENT_LOCATION); String dtstart = ps.get(MeetingInfo.MEETING_DTSTART); if (!TextUtils.isEmpty(dtstart)) { try { final long startTime = Utility.parseEmailDateTimeToMillis(dtstart); values.put(CalendarContract.Events.DTSTART, startTime); } catch (ParseException e) { LogUtils.w(TAG, "Parse error for MEETING_DTSTART tag.", e); } } putFromMeeting(ps, MeetingInfo.MEETING_ALL_DAY, values, CalendarContract.Events.ALL_DAY); msg.mText = CalendarUtilities.buildMessageTextFromEntityValues(mContext, values, null); msg.mHtml = Html.toHtml(new SpannedString(msg.mText)); } } } }