private void bodyParser(EmailContent.Message msg) throws IOException {
    String bodyType = Eas.BODY_PREFERENCE_TEXT;
    String body = "";
    while (nextTag(Tags.BASE_BODY) != END) {
      switch (tag) {
        case Tags.BASE_TYPE:
          bodyType = getValue();
          break;
        case Tags.BASE_DATA:
          body = getValue();
          break;
        case Tags.BASE_TRUNCATED:
          // M: Message is partial loaded if AirSyncBaseBody::Truncated is True;
          // Otherwise message is complete loaded if False
          String bodyTruncated = getValue();
          if ("1".equals(bodyTruncated) || "true".equals(bodyTruncated)) {
            msg.mFlagLoaded = EmailContent.Message.FLAG_LOADED_PARTIAL;
          } else {
            msg.mFlagLoaded = EmailContent.Message.FLAG_LOADED_COMPLETE;
          }
          LogUtils.d(TAG, "_____________ EAS 12+ body truncated: " + bodyTruncated);
          break;
        default:
          skipTag();
      }
    }
    // We always ask for TEXT or HTML; there's no third option
    if (bodyType.equals(Eas.BODY_PREFERENCE_HTML)) {
      msg.mHtml = body;
    } else {
      msg.mText = body;
    }

    /**
     * M: What if a mail with bodyTruncated tag, but both body html and text are empty? In this
     * case, client should set a suitable load flag: FLAG_LOADED_COMPLETE. @{
     */
    if (TextUtils.isEmpty(msg.mHtml) && TextUtils.isEmpty(msg.mText)) {
      LogUtils.d(TAG, " for empty mailbody, reset load complete tag.");
      msg.mFlagLoaded = EmailContent.Message.FLAG_LOADED_COMPLETE;
    }
    /** @} */
  }
 /**
  * Parses untruncated MIME data, saving away the text parts
  *
  * @param msg the message we're building
  * @param mimeData the MIME data we've received from the server
  * @throws IOException
  */
 private static void mimeBodyParser(EmailContent.Message msg, String mimeData) throws IOException {
   try {
     ByteArrayInputStream in = new ByteArrayInputStream(mimeData.getBytes());
     // The constructor parses the message
     MimeMessage mimeMessage = new MimeMessage(in);
     // Now process body parts & attachments
     ArrayList<Part> viewables = new ArrayList<Part>();
     // We'll ignore the attachments, as we'll get them directly from EAS
     ArrayList<Part> attachments = new ArrayList<Part>();
     MimeUtility.collectParts(mimeMessage, viewables, attachments);
     // parseBodyFields fills in the content fields of the Body
     ConversionUtilities.BodyFieldData data = ConversionUtilities.parseBodyFields(viewables);
     // But we need them in the message itself for handling during commit()
     msg.setFlags(data.isQuotedReply, data.isQuotedForward);
     msg.mSnippet = data.snippet;
     msg.mHtml = data.htmlContent;
     msg.mText = data.textContent;
   } catch (MessagingException e) {
     // This would most likely indicate a broken stream
     throw new IOException(e);
   }
 }
  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));
        }
      }
    }
  }