@Override public void sendMessage(Message message) throws MessagingException { ArrayList<Address> addresses = new ArrayList<Address>(); { addresses.addAll(Arrays.asList(message.getRecipients(RecipientType.TO))); addresses.addAll(Arrays.asList(message.getRecipients(RecipientType.CC))); addresses.addAll(Arrays.asList(message.getRecipients(RecipientType.BCC))); } message.setRecipients(RecipientType.BCC, null); HashMap<String, ArrayList<String>> charsetAddressesMap = new HashMap<String, ArrayList<String>>(); for (Address address : addresses) { String addressString = address.getAddress(); String charset = MimeUtility.getCharsetFromAddress(addressString); ArrayList<String> addressesOfCharset = charsetAddressesMap.get(charset); if (addressesOfCharset == null) { addressesOfCharset = new ArrayList<String>(); charsetAddressesMap.put(charset, addressesOfCharset); } addressesOfCharset.add(addressString); } for (Map.Entry<String, ArrayList<String>> charsetAddressesMapEntry : charsetAddressesMap.entrySet()) { String charset = charsetAddressesMapEntry.getKey(); ArrayList<String> addressesOfCharset = charsetAddressesMapEntry.getValue(); message.setCharset(charset); sendMessageTo(addressesOfCharset, message); } }
@Override public void setFlags(List<? extends Message> messages, final Set<Flag> flags, boolean value) throws MessagingException { if (!value || !flags.contains(Flag.DELETED)) { /* * The only flagging we support is setting the Deleted flag. */ return; } List<String> uids = new ArrayList<String>(); try { for (Message message : messages) { uids.add(message.getUid()); } indexUids(uids); } catch (IOException ioe) { throw new MessagingException("Could not get message number for uid " + uids, ioe); } for (Message message : messages) { Integer msgNum = mUidToMsgNumMap.get(message.getUid()); if (msgNum == null) { MessagingException me = new MessagingException( "Could not delete message " + message.getUid() + " because no msgNum found; permanent error"); me.setPermanentFailure(true); throw me; } executeSimpleCommand(String.format(DELE_COMMAND + " %s", msgNum)); } }
private void handleServerFolderChange(Folder folder) throws AuthenticationFailedException { long accountId = folder.accountId; List<Message> m = new ArrayList<Message>(); List<String> remoteSIds = new ArrayList<String>(); List<String> localSids = Note.getAllSyncedNoteSidByFolderId(folder.id, accountId, application.getDBHelper()); try { Message[] messages = imapStoreClient.getMessages(folder.name); for (Message msg : messages) { remoteSIds.add(msg.getUid()); if (!localSids.contains(msg.getUid())) m.add(msg); } if (m.size() > 0) { fetMessages(m, folder); } } catch (AuthenticationFailedException e) { Log.e(TAG, "", e); throw new AuthenticationFailedException(e.getLocalizedMessage()); } catch (MessagingException e) { Log.e(TAG, "", e); throw new AuthenticationFailedException(e.getLocalizedMessage()); } handleRemoteDeletedNotes(accountId, folder.id, remoteSIds); }
private void sendMessageTo(ArrayList<String> addresses, Message message) throws MessagingException { boolean possibleSend = false; close(); open(); message.setEncoding(m8bitEncodingAllowed ? "8bit" : null); // If the message has attachments and our server has told us about a limit on // the size of messages, count the message's size before sending it if (mLargestAcceptableMessage > 0 && ((LocalMessage) message).hasAttachments()) { if (message.calculateSize() > mLargestAcceptableMessage) { MessagingException me = new MessagingException("Message too large for server"); me.setPermanentFailure(possibleSend); throw me; } } Address[] from = message.getFrom(); try { // TODO: Add BODY=8BITMIME parameter if appropriate? executeSimpleCommand("MAIL FROM:" + "<" + from[0].getAddress() + ">"); for (String address : addresses) { executeSimpleCommand("RCPT TO:" + "<" + address + ">"); } executeSimpleCommand("DATA"); EOLConvertingOutputStream msgOut = new EOLConvertingOutputStream( new SmtpDataStuffing( new LineWrapOutputStream(new BufferedOutputStream(mOut, 1024), 1000))); message.writeTo(msgOut); // We use BufferedOutputStream. So make sure to call flush() ! msgOut.flush(); possibleSend = true; // After the "\r\n." is attempted, we may have sent the message executeSimpleCommand("\r\n."); } catch (Exception e) { MessagingException me = new MessagingException("Unable to send message", e); // "5xx text" -responses are permanent failures String msg = e.getMessage(); if (msg != null && msg.startsWith("5")) { Log.w(K9.LOG_TAG, "handling 5xx SMTP error code as a permanent failure"); possibleSend = false; } me.setPermanentFailure(possibleSend); throw me; } finally { close(); } }
@Override public String getField(final MessageInfoHolder source) { final Message message = source.message; return CONTENT_URI + "/delete_message/" + message.getFolder().getAccount().getAccountNumber() + "/" + message.getFolder().getName() + "/" + message.getUid(); }
public void onFlag() { if (mMessage != null) { boolean newState = !mMessage.isSet(Flag.FLAGGED); mController.setFlag( mAccount, mMessage.getFolder().getName(), new Message[] {mMessage}, Flag.FLAGGED, newState); mMessageView.setHeaders(mMessage, mAccount); } }
private List<HeaderEntry> getAdditionalHeaders(final Message message) throws MessagingException { List<HeaderEntry> additionalHeaders = new LinkedList<HeaderEntry>(); Set<String> headerNames = new LinkedHashSet<String>(message.getHeaderNames()); for (String headerName : headerNames) { String[] headerValues = message.getHeader(headerName); for (String headerValue : headerValues) { additionalHeaders.add(new HeaderEntry(headerName, headerValue)); } } return additionalHeaders; }
private void onToggleRead() { if (mMessage != null) { mController.setFlag( mAccount, mMessage.getFolder().getName(), new Message[] {mMessage}, Flag.SEEN, !mMessage.isSet(Flag.SEEN)); mMessageView.setHeaders(mMessage, mAccount); String subject = mMessage.getSubject(); displayMessageSubject(subject); updateUnreadToggleTitle(); } }
/** * Set up and then show the additional headers view. Called by {@link #onShowAdditionalHeaders()} * (when switching between messages). */ private void showAdditionalHeaders() { Integer messageToShow = null; try { // Retrieve additional headers boolean allHeadersDownloaded = mMessage.isSet(Flag.X_GOT_ALL_HEADERS); List<HeaderEntry> additionalHeaders = getAdditionalHeaders(mMessage); if (!additionalHeaders.isEmpty()) { // Show the additional headers that we have got. populateAdditionalHeadersView(additionalHeaders); mAdditionalHeadersView.setVisibility(View.VISIBLE); } if (!allHeadersDownloaded) { /* * Tell the user about the "save all headers" setting * * NOTE: This is only a temporary solution... in fact, * the system should download headers on-demand when they * have not been saved in their entirety initially. */ messageToShow = R.string.message_additional_headers_not_downloaded; } else if (additionalHeaders.isEmpty()) { // All headers have been downloaded, but there are no additional headers. messageToShow = R.string.message_no_additional_headers_available; } } catch (Exception e) { messageToShow = R.string.message_additional_headers_retrieval_failed; } // Show a message to the user, if any if (messageToShow != null) { Toast toast = Toast.makeText(mContext, messageToShow, Toast.LENGTH_LONG); toast.setGravity(Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL, 0, 0); toast.show(); } }
/** Called from UI thread when user select Delete */ public void onDelete() { if (K9.confirmDelete() || (K9.confirmDeleteStarred() && mMessage.isSet(Flag.FLAGGED))) { showDialog(R.id.dialog_confirm_delete); } else { delete(); } }
private void onDownloadRemainder() { if (mMessage.isSet(Flag.X_DOWNLOADED_FULL)) { return; } mMessageView.downloadRemainderButton().setEnabled(false); mController.loadMessageForViewRemote( mAccount, mMessageReference.folderName, mMessageReference.uid, mListener); }
public ContentValues messageToContentValues(final Message message) throws IOException, MessagingException { if (message == null) throw new MessagingException("message is null"); final ContentValues values = new ContentValues(); switch (getDataType(message)) { case SMS: if (message.getBody() == null) throw new MessagingException("body is null"); InputStream is = message.getBody().getInputStream(); if (is == null) { throw new MessagingException("body.getInputStream() is null for " + message.getBody()); } final String body = IOUtils.toString(is); final String address = Headers.get(message, Headers.ADDRESS); values.put(SmsConsts.BODY, body); values.put(SmsConsts.ADDRESS, address); values.put(SmsConsts.TYPE, Headers.get(message, Headers.TYPE)); values.put(SmsConsts.PROTOCOL, Headers.get(message, Headers.PROTOCOL)); values.put(SmsConsts.SERVICE_CENTER, Headers.get(message, Headers.SERVICE_CENTER)); values.put(SmsConsts.DATE, Headers.get(message, Headers.DATE)); values.put(SmsConsts.STATUS, Headers.get(message, Headers.STATUS)); values.put(SmsConsts.THREAD_ID, threadHelper.getThreadId(mContext, address)); values.put(SmsConsts.READ, mMarkAsReadOnRestore ? "1" : Headers.get(message, Headers.READ)); break; case CALLLOG: values.put(CallLog.Calls.NUMBER, Headers.get(message, Headers.ADDRESS)); values.put(CallLog.Calls.TYPE, Integer.valueOf(Headers.get(message, Headers.TYPE))); values.put(CallLog.Calls.DATE, Headers.get(message, Headers.DATE)); values.put(CallLog.Calls.DURATION, Long.valueOf(Headers.get(message, Headers.DURATION))); values.put(CallLog.Calls.NEW, 0); PersonRecord record = mPersonLookup.lookupPerson(Headers.get(message, Headers.ADDRESS)); if (!record.isUnknown()) { values.put(CallLog.Calls.CACHED_NAME, record.getName()); values.put(CallLog.Calls.CACHED_NUMBER_TYPE, -2); } break; default: throw new MessagingException("don't know how to restore " + getDataType(message)); } return values; }
/** * Set the title of the "Toggle Unread" menu item based upon the current read state of the * message. */ public void updateUnreadToggleTitle() { if (mMessage != null && mMenu != null) { if (mMessage.isSet(Flag.SEEN)) { mMenu.findItem(R.id.toggle_unread).setTitle(R.string.mark_as_unread_action); } else { mMenu.findItem(R.id.toggle_unread).setTitle(R.string.mark_as_read_action); } } }
private void onAddSenderToContacts() { if (mMessage != null) { try { final Address senderEmail = mMessage.getFrom()[0]; mContacts.createContact(senderEmail); } catch (Exception e) { Log.e(K9.LOG_TAG, "Couldn't create contact", e); } } }
public @NotNull ConversionResult convertMessages(final Cursor cursor, DataType dataType) throws MessagingException { final Map<String, String> msgMap = getMessageMap(cursor); final Message m; switch (dataType) { case WHATSAPP: m = mMessageGenerator.messageFromMapWhatsApp(cursor); break; default: m = mMessageGenerator.messageForDataType(msgMap, dataType); break; } final ConversionResult result = new ConversionResult(dataType); if (m != null) { m.setFlag(Flag.SEEN, mMarkAsRead); result.add(m, msgMap); } return result; }
@Override public void listLocalMessagesAddMessages( final Account account, final String folderName, final List<Message> messages) { // cache fields into local variables for faster access on JVM without JIT final MessageHelper helper = mMessageHelper; final List<MessageInfoHolder> holders = mHolders; final Context context = getContext(); for (final Message message : messages) { final MessageInfoHolder messageInfoHolder = new MessageInfoHolder(); final Folder messageFolder = message.getFolder(); final Account messageAccount = messageFolder.getAccount(); helper.populate( messageInfoHolder, message, new FolderInfoHolder(context, messageFolder, messageAccount), messageAccount); holders.add(messageInfoHolder); } }
@Override public void loadMessageForViewHeadersAvailable( final Account account, String folder, String uid, final Message message) { if (!mMessageReference.uid.equals(uid) || !mMessageReference.folderName.equals(folder) || !mMessageReference.accountUuid.equals(account.getUuid())) { return; } /* * Clone the message object because the original could be modified by * MessagingController later. This could lead to a ConcurrentModificationException * when that same object is accessed by the UI thread (below). * * See issue 3953 * * This is just an ugly hack to get rid of the most pressing problem. A proper way to * fix this is to make Message thread-safe. Or, even better, rewriting the UI code to * access messages via a ContentProvider. * */ final Message clonedMessage = message.clone(); mHandler.post( new Runnable() { public void run() { if (!clonedMessage.isSet(Flag.X_DOWNLOADED_FULL) && !clonedMessage.isSet(Flag.X_DOWNLOADED_PARTIAL)) { String text = getString(R.string.message_view_downloading); mMessageView.showStatusMessage(text); } mMessageView.setHeaders(clonedMessage, account); final String subject = clonedMessage.getSubject(); if (subject == null || subject.equals("")) { displayMessageSubject(getString(R.string.general_no_subject)); } else { displayMessageSubject(clonedMessage.getSubject()); } mMessageView.setOnFlagListener( new OnClickListener() { @Override public void onClick(View v) { onFlag(); } }); } }); }
public int compare(final Message m1, final Message m2) { final Date d1 = m1 == null ? EARLY : m1.getSentDate() != null ? m1.getSentDate() : EARLY; final Date d2 = m2 == null ? EARLY : m2.getSentDate() != null ? m2.getSentDate() : EARLY; return d1.compareTo(d2); }
public void populate(final Message message, final Account account) throws MessagingException { final Contacts contacts = K9.showContactName() ? mContacts : null; final CharSequence from = Address.toFriendly(message.getFrom(), contacts); final CharSequence to = Address.toFriendly(message.getRecipients(Message.RecipientType.TO), contacts); final CharSequence cc = Address.toFriendly(message.getRecipients(Message.RecipientType.CC), contacts); Address[] fromAddrs = message.getFrom(); Address[] toAddrs = message.getRecipients(Message.RecipientType.TO); Address[] ccAddrs = message.getRecipients(Message.RecipientType.CC); boolean fromMe = mMessageHelper.toMe(account, fromAddrs); String counterpartyAddress = null; if (fromMe) { if (toAddrs.length > 0) { counterpartyAddress = toAddrs[0].getAddress(); } else if (ccAddrs.length > 0) { counterpartyAddress = ccAddrs[0].getAddress(); } } else if (fromAddrs.length > 0) { counterpartyAddress = fromAddrs[0].getAddress(); } /* * Only reset visibility of the subject if populate() was called because a new * message is shown. If it is the same, do not force the subject visible, because * this breaks the MessageTitleView in the action bar, which may hide our subject * if it fits in the action bar but is only called when a new message is shown * or the device is rotated. */ if (mMessage == null || mMessage.getId() != message.getId()) { mSubjectView.setVisibility(VISIBLE); } mMessage = message; mAccount = account; if (K9.showContactPicture()) { mContactBadge.setVisibility(View.VISIBLE); mContactsPictureLoader = new ContactPictureLoader(mContext, R.drawable.ic_contact_picture); } else { mContactBadge.setVisibility(View.GONE); } final String subject = message.getSubject(); if (StringUtils.isNullOrEmpty(subject)) { mSubjectView.setText(mContext.getText(R.string.general_no_subject)); } else { mSubjectView.setText(subject); } mSubjectView.setTextColor(0xff000000 | defaultSubjectColor); String dateTime = DateUtils.formatDateTime( mContext, message.getSentDate().getTime(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_YEAR); mDateView.setText(dateTime); if (K9.showContactPicture()) { mContactBadge.assignContactFromEmail(counterpartyAddress, true); if (counterpartyAddress != null) { mContactsPictureLoader.loadContactPicture(counterpartyAddress, mContactBadge); } else { mContactBadge.setImageResource(R.drawable.ic_contact_picture); } } mFromView.setText(from); updateAddressField(mToView, to, mToLabel); updateAddressField(mCcView, cc, mCcLabel); mAnsweredIcon.setVisibility(message.isSet(Flag.ANSWERED) ? View.VISIBLE : View.GONE); mForwardedIcon.setVisibility(message.isSet(Flag.FORWARDED) ? View.VISIBLE : View.GONE); mFlagged.setChecked(message.isSet(Flag.FLAGGED)); int chipColor = mAccount.getChipColor(); int chipColorAlpha = (!message.isSet(Flag.SEEN)) ? 255 : 127; mChip.setBackgroundColor(chipColor); mChip.getBackground().setAlpha(chipColorAlpha); setVisibility(View.VISIBLE); if (mSavedState != null) { if (mSavedState.additionalHeadersVisible) { showAdditionalHeaders(); } mSavedState = null; } else { hideAdditionalHeaders(); } }
@Override public void onResendMessage(Message message) { MessageCompose.actionEditDraft(this, message.makeMessageReference()); }
public List<? extends Message> appendWebDavMessages(List<? extends Message> messages) throws MessagingException { List<Message> retMessages = new ArrayList<Message>(messages.size()); WebDavHttpClient httpclient = store.getHttpClient(); for (Message message : messages) { HttpGeneric httpmethod; HttpResponse response; StringEntity bodyEntity; int statusCode; try { ByteArrayOutputStream out; out = new ByteArrayOutputStream(message.getSize()); open(Folder.OPEN_MODE_RW); EOLConvertingOutputStream msgOut = new EOLConvertingOutputStream(new BufferedOutputStream(out, 1024)); message.writeTo(msgOut); msgOut.flush(); bodyEntity = new StringEntity(out.toString(), "UTF-8"); bodyEntity.setContentType("message/rfc822"); String messageURL = mFolderUrl; if (!messageURL.endsWith("/")) { messageURL += "/"; } messageURL += encodeUtf8(message.getUid() + ":" + System.currentTimeMillis() + ".eml"); Log.i(LOG_TAG, "Uploading message as " + messageURL); httpmethod = new HttpGeneric(messageURL); httpmethod.setMethod("PUT"); httpmethod.setEntity(bodyEntity); String mAuthString = store.getAuthString(); if (mAuthString != null) { httpmethod.setHeader("Authorization", mAuthString); } response = httpclient.executeOverride(httpmethod, store.getContext()); statusCode = response.getStatusLine().getStatusCode(); if (statusCode < 200 || statusCode > 300) { // TODO: Could we handle a login timeout here? throw new IOException( "Error with status code " + statusCode + " while sending/appending message. Response = " + response.getStatusLine().toString() + " for message " + messageURL); } WebDavMessage retMessage = new WebDavMessage(message.getUid(), this); retMessage.setUrl(messageURL); retMessages.add(retMessage); } catch (Exception e) { throw new MessagingException("Unable to append", e); } } return retMessages; }
private void fetchEnvelope( List<Pop3Message> messages, MessageRetrievalListener<Pop3Message> listener) throws IOException, MessagingException { int unsizedMessages = 0; for (Message message : messages) { if (message.getSize() == -1) { unsizedMessages++; } } if (unsizedMessages == 0) { return; } if (unsizedMessages < 50 && mMessageCount > 5000) { /* * In extreme cases we'll do a command per message instead of a bulk request * to hopefully save some time and bandwidth. */ for (int i = 0, count = messages.size(); i < count; i++) { Pop3Message message = messages.get(i); if (listener != null) { listener.messageStarted(message.getUid(), i, count); } String response = executeSimpleCommand( String.format( Locale.US, LIST_COMMAND + " %d", mUidToMsgNumMap.get(message.getUid()))); String[] listParts = response.split(" "); // int msgNum = Integer.parseInt(listParts[1]); int msgSize = Integer.parseInt(listParts[2]); message.setSize(msgSize); if (listener != null) { listener.messageFinished(message, i, count); } } } else { Set<String> msgUidIndex = new HashSet<String>(); for (Message message : messages) { msgUidIndex.add(message.getUid()); } int i = 0, count = messages.size(); String response = executeSimpleCommand(LIST_COMMAND); while ((response = readLine()) != null) { if (response.equals(".")) { break; } String[] listParts = response.split(" "); int msgNum = Integer.parseInt(listParts[0]); int msgSize = Integer.parseInt(listParts[1]); Pop3Message pop3Message = mMsgNumToMsgMap.get(msgNum); if (pop3Message != null && msgUidIndex.contains(pop3Message.getUid())) { if (listener != null) { listener.messageStarted(pop3Message.getUid(), i, count); } pop3Message.setSize(msgSize); if (listener != null) { listener.messageFinished(pop3Message, i, count); } i++; } } } }
/** * Fetch the items contained in the FetchProfile into the given set of Messages in as efficient * a manner as possible. * * @param messages * @param fp * @throws MessagingException */ @Override public void fetch( List<Pop3Message> messages, FetchProfile fp, MessageRetrievalListener<Pop3Message> listener) throws MessagingException { if (messages == null || messages.isEmpty()) { return; } List<String> uids = new ArrayList<String>(); for (Message message : messages) { uids.add(message.getUid()); } try { indexUids(uids); } catch (IOException ioe) { throw new MessagingException("fetch", ioe); } try { if (fp.contains(FetchProfile.Item.ENVELOPE)) { /* * We pass the listener only if there are other things to do in the * FetchProfile. Since fetchEnvelop works in bulk and eveything else * works one at a time if we let fetchEnvelope send events the * event would get sent twice. */ fetchEnvelope(messages, fp.size() == 1 ? listener : null); } } catch (IOException ioe) { throw new MessagingException("fetch", ioe); } for (int i = 0, count = messages.size(); i < count; i++) { Pop3Message pop3Message = messages.get(i); try { if (listener != null && !fp.contains(FetchProfile.Item.ENVELOPE)) { listener.messageStarted(pop3Message.getUid(), i, count); } if (fp.contains(FetchProfile.Item.BODY)) { fetchBody(pop3Message, -1); } else if (fp.contains(FetchProfile.Item.BODY_SANE)) { /* * To convert the suggested download size we take the size * divided by the maximum line size (76). */ if (mStoreConfig.getMaximumAutoDownloadMessageSize() > 0) { fetchBody(pop3Message, (mStoreConfig.getMaximumAutoDownloadMessageSize() / 76)); } else { fetchBody(pop3Message, -1); } } else if (fp.contains(FetchProfile.Item.STRUCTURE)) { /* * If the user is requesting STRUCTURE we are required to set the body * to null since we do not support the function. */ pop3Message.setBody(null); } if (listener != null && !(fp.contains(FetchProfile.Item.ENVELOPE) && fp.size() == 1)) { listener.messageFinished(pop3Message, i, count); } } catch (IOException ioe) { throw new MessagingException("Unable to fetch message", ioe); } } }
@Override public void onForward(Message message) { MessageCompose.actionForward(this, message.getFolder().getAccount(), message, null); }
@Override public void onReplyAll(Message message) { MessageCompose.actionReply(this, message.getFolder().getAccount(), message, true, null); }