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