/**
   * Copy field-by-field from a "store" message to a "provider" message
   *
   * @param message The message we've just downloaded (must be a MimeMessage)
   * @param localMessage The message we'd like to write into the DB
   * @return true if dirty (changes were made)
   */
  public static boolean updateMessageFields(
      final EmailContent.Message localMessage,
      final Message message,
      final long accountId,
      final long mailboxId)
      throws MessagingException {

    final Address[] from = message.getFrom();
    final Address[] to = message.getRecipients(Message.RecipientType.TO);
    final Address[] cc = message.getRecipients(Message.RecipientType.CC);
    final Address[] bcc = message.getRecipients(Message.RecipientType.BCC);
    final Address[] replyTo = message.getReplyTo();
    final String subject = message.getSubject();
    final Date sentDate = message.getSentDate();
    final Date internalDate = message.getInternalDate();

    if (from != null && from.length > 0) {
      localMessage.mDisplayName = from[0].toFriendly();
    }
    if (sentDate != null) {
      localMessage.mTimeStamp = sentDate.getTime();
    } else if (internalDate != null) {
      LogUtils.w(Logging.LOG_TAG, "No sentDate, falling back to internalDate");
      localMessage.mTimeStamp = internalDate.getTime();
    }
    if (subject != null) {
      localMessage.mSubject = subject;
    }
    localMessage.mFlagRead = message.isSet(Flag.SEEN);
    if (message.isSet(Flag.ANSWERED)) {
      localMessage.mFlags |= EmailContent.Message.FLAG_REPLIED_TO;
    }

    // Keep the message in the "unloaded" state until it has (at least) a display name.
    // This prevents early flickering of empty messages in POP download.
    if (localMessage.mFlagLoaded != EmailContent.Message.FLAG_LOADED_COMPLETE) {
      if (localMessage.mDisplayName == null || "".equals(localMessage.mDisplayName)) {
        localMessage.mFlagLoaded = EmailContent.Message.FLAG_LOADED_UNLOADED;
      } else {
        localMessage.mFlagLoaded = EmailContent.Message.FLAG_LOADED_PARTIAL;
      }
    }
    localMessage.mFlagFavorite = message.isSet(Flag.FLAGGED);
    //        public boolean mFlagAttachment = false;
    //        public int mFlags = 0;

    localMessage.mServerId = message.getUid();
    if (internalDate != null) {
      localMessage.mServerTimeStamp = internalDate.getTime();
    }
    //        public String mClientId;

    // Only replace the local message-id if a new one was found.  This is seen in some ISP's
    // which may deliver messages w/o a message-id header.
    final String messageId = message.getMessageId();
    if (messageId != null) {
      localMessage.mMessageId = messageId;
    }

    //        public long mBodyKey;
    localMessage.mMailboxKey = mailboxId;
    localMessage.mAccountKey = accountId;

    if (from != null && from.length > 0) {
      localMessage.mFrom = Address.toString(from);
    }

    localMessage.mTo = Address.toString(to);
    localMessage.mCc = Address.toString(cc);
    localMessage.mBcc = Address.toString(bcc);
    localMessage.mReplyTo = Address.toString(replyTo);

    //        public String mText;
    //        public String mHtml;
    //        public String mTextReply;
    //        public String mHtmlReply;

    //        // Can be used while building messages, but is NOT saved by the Provider
    //        transient public ArrayList<Attachment> mAttachments = null;

    return true;
  }
  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));
        }
      }
    }
  }
  /**
   * Read a complete Provider message into a legacy message (for IMAP upload). This is basically the
   * equivalent of LocalFolder.getMessages() + LocalFolder.fetch().
   */
  public static Message makeMessage(final Context context, final EmailContent.Message localMessage)
      throws MessagingException {
    final MimeMessage message = new MimeMessage();

    // LocalFolder.getMessages() equivalent:  Copy message fields
    message.setSubject(localMessage.mSubject == null ? "" : localMessage.mSubject);
    final Address[] from = Address.fromHeader(localMessage.mFrom);
    if (from.length > 0) {
      message.setFrom(from[0]);
    }
    message.setSentDate(new Date(localMessage.mTimeStamp));
    message.setUid(localMessage.mServerId);
    message.setFlag(
        Flag.DELETED, localMessage.mFlagLoaded == EmailContent.Message.FLAG_LOADED_DELETED);
    message.setFlag(Flag.SEEN, localMessage.mFlagRead);
    message.setFlag(Flag.FLAGGED, localMessage.mFlagFavorite);
    //      message.setFlag(Flag.DRAFT, localMessage.mMailboxKey == draftMailboxKey);
    message.setRecipients(RecipientType.TO, Address.fromHeader(localMessage.mTo));
    message.setRecipients(RecipientType.CC, Address.fromHeader(localMessage.mCc));
    message.setRecipients(RecipientType.BCC, Address.fromHeader(localMessage.mBcc));
    message.setReplyTo(Address.fromHeader(localMessage.mReplyTo));
    message.setInternalDate(new Date(localMessage.mServerTimeStamp));
    message.setMessageId(localMessage.mMessageId);

    // LocalFolder.fetch() equivalent: build body parts
    message.setHeader(MimeHeader.HEADER_CONTENT_TYPE, "multipart/mixed");
    final MimeMultipart mp = new MimeMultipart();
    mp.setSubType("mixed");
    message.setBody(mp);

    try {
      addTextBodyPart(
          mp,
          "text/html",
          EmailContent.Body.restoreBodyHtmlWithMessageId(context, localMessage.mId));
    } catch (RuntimeException rte) {
      LogUtils.d(Logging.LOG_TAG, "Exception while reading html body " + rte.toString());
    }

    try {
      addTextBodyPart(
          mp,
          "text/plain",
          EmailContent.Body.restoreBodyTextWithMessageId(context, localMessage.mId));
    } catch (RuntimeException rte) {
      LogUtils.d(Logging.LOG_TAG, "Exception while reading text body " + rte.toString());
    }

    // Attachments
    final Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, localMessage.mId);
    final Cursor attachments =
        context.getContentResolver().query(uri, Attachment.CONTENT_PROJECTION, null, null, null);

    try {
      while (attachments != null && attachments.moveToNext()) {
        final Attachment att = new Attachment();
        att.restore(attachments);
        try {
          final InputStream content;
          if (att.mContentBytes != null) {
            // This is generally only the case for synthetic attachments, such as those
            // generated by unit tests or calendar invites
            content = new ByteArrayInputStream(att.mContentBytes);
          } else {
            String contentUriString = att.getCachedFileUri();
            if (TextUtils.isEmpty(contentUriString)) {
              contentUriString = att.getContentUri();
            }
            if (TextUtils.isEmpty(contentUriString)) {
              content = null;
            } else {
              final Uri contentUri = Uri.parse(contentUriString);
              content = context.getContentResolver().openInputStream(contentUri);
            }
          }
          final String mimeType = att.mMimeType;
          final Long contentSize = att.mSize;
          final String contentId = att.mContentId;
          final String filename = att.mFileName;
          if (content != null) {
            addAttachmentPart(mp, mimeType, contentSize, filename, contentId, content);
          } else {
            LogUtils.e(LogUtils.TAG, "Could not open attachment file for upsync");
          }
        } catch (final FileNotFoundException e) {
          LogUtils.e(
              LogUtils.TAG,
              "File Not Found error on %s while upsyncing message",
              att.getCachedFileUri());
        }
      }
    } finally {
      if (attachments != null) {
        attachments.close();
      }
    }

    return message;
  }