Moved code out of emailcommon to UnifedEmail.

This CL is mostly just deletion since the new code
is in a new project.

Change-Id: I4aee519187a6ed7514c97d2a7a85ed29ea29d25f
This commit is contained in:
Andrew Sapperstein 2013-05-31 16:25:02 -07:00
parent f5ed3d2c9d
commit 91973256c2
27 changed files with 1 additions and 3505 deletions

View File

@ -25,9 +25,6 @@ unified_email_src_dir := ../../UnifiedEmail/src
apache_src_dir := ../../UnifiedEmail/src/org
imported_unified_email_files := \
$(unified_email_unified_src_dir)/com/android/mail/utils/LogTag.java \
$(unified_email_src_dir)/com/android/mail/preferences/BasePreferenceMigrator.java \
$(unified_email_unified_src_dir)/com/android/mail/preferences/PreferenceMigrator.java \
$(unified_email_src_dir)/com/android/mail/utils/LogUtils.java \
$(unified_email_src_dir)/com/android/mail/providers/UIProvider.java
@ -41,6 +38,7 @@ LOCAL_SRC_FILES += \
src/com/android/emailcommon/service/IAccountService.aidl
LOCAL_SRC_FILES += $(call all-java-files-under, $(apache_src_dir))
LOCAL_SRC_FILES += $(imported_unified_email_files)
LOCAL_SRC_FILES += $(call all-java-files-under, $(unified_email_src_dir)/com/android/emailcommon)
LOCAL_SDK_VERSION := 14

View File

@ -1,42 +0,0 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon;
import android.content.Context;
import java.io.File;
/**
* TempDirectory caches the directory used for caching file. It is set up during application
* initialization.
*/
public class TempDirectory {
private static File sTempDirectory = null;
public static void setTempDirectory(Context context) {
sTempDirectory = context.getCacheDir();
}
public static File getTempDirectory() {
if (sTempDirectory == null) {
throw new RuntimeException(
"TempDirectory not set. " +
"If in a unit test, call Email.setTempDirectory(context) in setUp().");
}
return sTempDirectory;
}
}

View File

@ -1,89 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon.internet;
import com.android.emailcommon.TempDirectory;
import com.android.emailcommon.mail.Body;
import com.android.emailcommon.mail.MessagingException;
import org.apache.commons.io.IOUtils;
import android.util.Base64;
import android.util.Base64OutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* A Body that is backed by a temp file. The Body exposes a getOutputStream method that allows
* the user to write to the temp file. After the write the body is available via getInputStream
* and writeTo one time. After writeTo is called, or the InputStream returned from
* getInputStream is closed the file is deleted and the Body should be considered disposed of.
*/
public class BinaryTempFileBody implements Body {
private File mFile;
/**
* An alternate way to put data into a BinaryTempFileBody is to simply supply an already-
* created file. Note that this file will be deleted after it is read.
* @param filePath The file containing the data to be stored on disk temporarily
*/
public void setFile(String filePath) {
mFile = new File(filePath);
}
public OutputStream getOutputStream() throws IOException {
mFile = File.createTempFile("body", null, TempDirectory.getTempDirectory());
mFile.deleteOnExit();
return new FileOutputStream(mFile);
}
public InputStream getInputStream() throws MessagingException {
try {
return new BinaryTempFileBodyInputStream(new FileInputStream(mFile));
}
catch (IOException ioe) {
throw new MessagingException("Unable to open body", ioe);
}
}
public void writeTo(OutputStream out) throws IOException, MessagingException {
InputStream in = getInputStream();
Base64OutputStream base64Out = new Base64OutputStream(
out, Base64.CRLF | Base64.NO_CLOSE);
IOUtils.copy(in, base64Out);
base64Out.close();
mFile.delete();
}
class BinaryTempFileBodyInputStream extends FilterInputStream {
public BinaryTempFileBodyInputStream(InputStream in) {
super(in);
}
@Override
public void close() throws IOException {
super.close();
mFile.delete();
}
}
}

View File

@ -1,193 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon.internet;
import com.android.emailcommon.mail.Body;
import com.android.emailcommon.mail.BodyPart;
import com.android.emailcommon.mail.MessagingException;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.regex.Pattern;
/**
* TODO this is a close approximation of Message, need to update along with
* Message.
*/
public class MimeBodyPart extends BodyPart {
protected MimeHeader mHeader = new MimeHeader();
protected MimeHeader mExtendedHeader;
protected Body mBody;
protected int mSize;
// regex that matches content id surrounded by "<>" optionally.
private static final Pattern REMOVE_OPTIONAL_BRACKETS = Pattern.compile("^<?([^>]+)>?$");
// regex that matches end of line.
private static final Pattern END_OF_LINE = Pattern.compile("\r?\n");
public MimeBodyPart() throws MessagingException {
this(null);
}
public MimeBodyPart(Body body) throws MessagingException {
this(body, null);
}
public MimeBodyPart(Body body, String mimeType) throws MessagingException {
if (mimeType != null) {
setHeader(MimeHeader.HEADER_CONTENT_TYPE, mimeType);
}
setBody(body);
}
protected String getFirstHeader(String name) throws MessagingException {
return mHeader.getFirstHeader(name);
}
public void addHeader(String name, String value) throws MessagingException {
mHeader.addHeader(name, value);
}
public void setHeader(String name, String value) throws MessagingException {
mHeader.setHeader(name, value);
}
public String[] getHeader(String name) throws MessagingException {
return mHeader.getHeader(name);
}
public void removeHeader(String name) throws MessagingException {
mHeader.removeHeader(name);
}
public Body getBody() throws MessagingException {
return mBody;
}
public void setBody(Body body) throws MessagingException {
this.mBody = body;
if (body instanceof com.android.emailcommon.mail.Multipart) {
com.android.emailcommon.mail.Multipart multipart =
((com.android.emailcommon.mail.Multipart)body);
multipart.setParent(this);
setHeader(MimeHeader.HEADER_CONTENT_TYPE, multipart.getContentType());
}
else if (body instanceof TextBody) {
String contentType = String.format("%s;\n charset=utf-8", getMimeType());
String name = MimeUtility.getHeaderParameter(getContentType(), "name");
if (name != null) {
contentType += String.format(";\n name=\"%s\"", name);
}
setHeader(MimeHeader.HEADER_CONTENT_TYPE, contentType);
setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
}
}
public String getContentType() throws MessagingException {
String contentType = getFirstHeader(MimeHeader.HEADER_CONTENT_TYPE);
if (contentType == null) {
return "text/plain";
} else {
return contentType;
}
}
public String getDisposition() throws MessagingException {
String contentDisposition = getFirstHeader(MimeHeader.HEADER_CONTENT_DISPOSITION);
if (contentDisposition == null) {
return null;
} else {
return contentDisposition;
}
}
public String getContentId() throws MessagingException {
String contentId = getFirstHeader(MimeHeader.HEADER_CONTENT_ID);
if (contentId == null) {
return null;
} else {
// remove optionally surrounding brackets.
return REMOVE_OPTIONAL_BRACKETS.matcher(contentId).replaceAll("$1");
}
}
public String getMimeType() throws MessagingException {
return MimeUtility.getHeaderParameter(getContentType(), null);
}
public boolean isMimeType(String mimeType) throws MessagingException {
return getMimeType().equals(mimeType);
}
public void setSize(int size) {
this.mSize = size;
}
public int getSize() throws MessagingException {
return mSize;
}
/**
* Set extended header
*
* @param name Extended header name
* @param value header value - flattened by removing CR-NL if any
* remove header if value is null
* @throws MessagingException
*/
public void setExtendedHeader(String name, String value) throws MessagingException {
if (value == null) {
if (mExtendedHeader != null) {
mExtendedHeader.removeHeader(name);
}
return;
}
if (mExtendedHeader == null) {
mExtendedHeader = new MimeHeader();
}
mExtendedHeader.setHeader(name, END_OF_LINE.matcher(value).replaceAll(""));
}
/**
* Get extended header
*
* @param name Extended header name
* @return header value - null if header does not exist
* @throws MessagingException
*/
public String getExtendedHeader(String name) throws MessagingException {
if (mExtendedHeader == null) {
return null;
}
return mExtendedHeader.getFirstHeader(name);
}
/**
* Write the MimeMessage out in MIME format.
*/
public void writeTo(OutputStream out) throws IOException, MessagingException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
mHeader.writeTo(out);
writer.write("\r\n");
writer.flush();
if (mBody != null) {
mBody.writeTo(out);
}
}
}

View File

@ -1,153 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon.internet;
import com.android.emailcommon.mail.MessagingException;
import com.android.emailcommon.utility.Utility;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
public class MimeHeader {
/**
* Application specific header that contains Store specific information about an attachment.
* In IMAP this contains the IMAP BODYSTRUCTURE part id so that the ImapStore can later
* retrieve the attachment at will from the server.
* The info is recorded from this header on LocalStore.appendMessages and is put back
* into the MIME data by LocalStore.fetch.
*/
public static final String HEADER_ANDROID_ATTACHMENT_STORE_DATA = "X-Android-Attachment-StoreData";
/**
* Application specific header that is used to tag body parts for quoted/forwarded messages.
*/
public static final String HEADER_ANDROID_BODY_QUOTED_PART = "X-Android-Body-Quoted-Part";
public static final String HEADER_CONTENT_TYPE = "Content-Type";
public static final String HEADER_CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding";
public static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition";
public static final String HEADER_CONTENT_ID = "Content-ID";
/**
* Fields that should be omitted when writing the header using writeTo()
*/
private static final String[] WRITE_OMIT_FIELDS = {
// HEADER_ANDROID_ATTACHMENT_DOWNLOADED,
// HEADER_ANDROID_ATTACHMENT_ID,
HEADER_ANDROID_ATTACHMENT_STORE_DATA
};
protected final ArrayList<Field> mFields = new ArrayList<Field>();
public void clear() {
mFields.clear();
}
public String getFirstHeader(String name) throws MessagingException {
String[] header = getHeader(name);
if (header == null) {
return null;
}
return header[0];
}
public void addHeader(String name, String value) throws MessagingException {
mFields.add(new Field(name, value));
}
public void setHeader(String name, String value) throws MessagingException {
if (name == null || value == null) {
return;
}
removeHeader(name);
addHeader(name, value);
}
public String[] getHeader(String name) throws MessagingException {
ArrayList<String> values = new ArrayList<String>();
for (Field field : mFields) {
if (field.name.equalsIgnoreCase(name)) {
values.add(field.value);
}
}
if (values.size() == 0) {
return null;
}
return values.toArray(new String[] {});
}
public void removeHeader(String name) throws MessagingException {
ArrayList<Field> removeFields = new ArrayList<Field>();
for (Field field : mFields) {
if (field.name.equalsIgnoreCase(name)) {
removeFields.add(field);
}
}
mFields.removeAll(removeFields);
}
/**
* Write header into String
*
* @return CR-NL separated header string except the headers in writeOmitFields
* null if header is empty
*/
public String writeToString() {
if (mFields.size() == 0) {
return null;
}
StringBuilder builder = new StringBuilder();
for (Field field : mFields) {
if (!Utility.arrayContains(WRITE_OMIT_FIELDS, field.name)) {
builder.append(field.name + ": " + field.value + "\r\n");
}
}
return builder.toString();
}
public void writeTo(OutputStream out) throws IOException, MessagingException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
for (Field field : mFields) {
if (!Utility.arrayContains(WRITE_OMIT_FIELDS, field.name)) {
writer.write(field.name + ": " + field.value + "\r\n");
}
}
writer.flush();
}
private static class Field {
final String name;
final String value;
public Field(String name, String value) {
this.name = name;
this.value = value;
}
@Override
public String toString() {
return name + "=" + value;
}
}
@Override
public String toString() {
return (mFields == null) ? null : mFields.toString();
}
}

View File

@ -1,644 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon.internet;
import com.android.emailcommon.mail.Address;
import com.android.emailcommon.mail.Body;
import com.android.emailcommon.mail.BodyPart;
import com.android.emailcommon.mail.Message;
import com.android.emailcommon.mail.MessagingException;
import com.android.emailcommon.mail.Multipart;
import com.android.emailcommon.mail.Part;
import org.apache.james.mime4j.BodyDescriptor;
import org.apache.james.mime4j.ContentHandler;
import org.apache.james.mime4j.EOLConvertingInputStream;
import org.apache.james.mime4j.MimeStreamParser;
import org.apache.james.mime4j.field.DateTimeField;
import org.apache.james.mime4j.field.Field;
import android.text.TextUtils;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Stack;
import java.util.regex.Pattern;
/**
* An implementation of Message that stores all of its metadata in RFC 822 and
* RFC 2045 style headers.
*
* NOTE: Automatic generation of a local message-id is becoming unwieldy and should be removed.
* It would be better to simply do it explicitly on local creation of new outgoing messages.
*/
public class MimeMessage extends Message {
private MimeHeader mHeader;
private MimeHeader mExtendedHeader;
// NOTE: The fields here are transcribed out of headers, and values stored here will supercede
// the values found in the headers. Use caution to prevent any out-of-phase errors. In
// particular, any adds/changes/deletes here must be echoed by changes in the parse() function.
private Address[] mFrom;
private Address[] mTo;
private Address[] mCc;
private Address[] mBcc;
private Address[] mReplyTo;
private Date mSentDate;
private Body mBody;
protected int mSize;
private boolean mInhibitLocalMessageId = false;
private boolean mComplete = true;
// Shared random source for generating local message-id values
private static final java.util.Random sRandom = new java.util.Random();
// In MIME, en_US-like date format should be used. In other words "MMM" should be encoded to
// "Jan", not the other localized format like "Ene" (meaning January in locale es).
// This conversion is used when generating outgoing MIME messages. Incoming MIME date
// headers are parsed by org.apache.james.mime4j.field.DateTimeField which does not have any
// localization code.
private static final SimpleDateFormat DATE_FORMAT =
new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);
// regex that matches content id surrounded by "<>" optionally.
private static final Pattern REMOVE_OPTIONAL_BRACKETS = Pattern.compile("^<?([^>]+)>?$");
// regex that matches end of line.
private static final Pattern END_OF_LINE = Pattern.compile("\r?\n");
public MimeMessage() {
mHeader = null;
}
/**
* Generate a local message id. This is only used when none has been assigned, and is
* installed lazily. Any remote (typically server-assigned) message id takes precedence.
* @return a long, locally-generated message-ID value
*/
private String generateMessageId() {
StringBuffer sb = new StringBuffer();
sb.append("<");
for (int i = 0; i < 24; i++) {
// We'll use a 5-bit range (0..31)
int value = sRandom.nextInt() & 31;
char c = "0123456789abcdefghijklmnopqrstuv".charAt(value);
sb.append(c);
}
sb.append(".");
sb.append(Long.toString(System.currentTimeMillis()));
sb.append("@email.android.com>");
return sb.toString();
}
/**
* Parse the given InputStream using Apache Mime4J to build a MimeMessage.
*
* @param in
* @throws IOException
* @throws MessagingException
*/
public MimeMessage(InputStream in) throws IOException, MessagingException {
parse(in);
}
private MimeStreamParser init() {
// Before parsing the input stream, clear all local fields that may be superceded by
// the new incoming message.
getMimeHeaders().clear();
mInhibitLocalMessageId = true;
mFrom = null;
mTo = null;
mCc = null;
mBcc = null;
mReplyTo = null;
mSentDate = null;
mBody = null;
MimeStreamParser parser = new MimeStreamParser();
parser.setContentHandler(new MimeMessageBuilder());
return parser;
}
protected void parse(InputStream in) throws IOException, MessagingException {
MimeStreamParser parser = init();
parser.parse(new EOLConvertingInputStream(in));
mComplete = !parser.getPrematureEof();
}
public void parse(InputStream in, EOLConvertingInputStream.Callback callback)
throws IOException, MessagingException {
MimeStreamParser parser = init();
parser.parse(new EOLConvertingInputStream(in, getSize(), callback));
mComplete = !parser.getPrematureEof();
}
/**
* Return the internal mHeader value, with very lazy initialization.
* The goal is to save memory by not creating the headers until needed.
*/
private MimeHeader getMimeHeaders() {
if (mHeader == null) {
mHeader = new MimeHeader();
}
return mHeader;
}
@Override
public Date getReceivedDate() throws MessagingException {
return null;
}
@Override
public Date getSentDate() throws MessagingException {
if (mSentDate == null) {
try {
DateTimeField field = (DateTimeField)Field.parse("Date: "
+ MimeUtility.unfoldAndDecode(getFirstHeader("Date")));
mSentDate = field.getDate();
} catch (Exception e) {
}
}
return mSentDate;
}
@Override
public void setSentDate(Date sentDate) throws MessagingException {
setHeader("Date", DATE_FORMAT.format(sentDate));
this.mSentDate = sentDate;
}
@Override
public String getContentType() throws MessagingException {
String contentType = getFirstHeader(MimeHeader.HEADER_CONTENT_TYPE);
if (contentType == null) {
return "text/plain";
} else {
return contentType;
}
}
public String getDisposition() throws MessagingException {
String contentDisposition = getFirstHeader(MimeHeader.HEADER_CONTENT_DISPOSITION);
if (contentDisposition == null) {
return null;
} else {
return contentDisposition;
}
}
public String getContentId() throws MessagingException {
String contentId = getFirstHeader(MimeHeader.HEADER_CONTENT_ID);
if (contentId == null) {
return null;
} else {
// remove optionally surrounding brackets.
return REMOVE_OPTIONAL_BRACKETS.matcher(contentId).replaceAll("$1");
}
}
public boolean isComplete() {
return mComplete;
}
public String getMimeType() throws MessagingException {
return MimeUtility.getHeaderParameter(getContentType(), null);
}
public int getSize() throws MessagingException {
return mSize;
}
/**
* Returns a list of the given recipient type from this message. If no addresses are
* found the method returns an empty array.
*/
@Override
public Address[] getRecipients(RecipientType type) throws MessagingException {
if (type == RecipientType.TO) {
if (mTo == null) {
mTo = Address.parse(MimeUtility.unfold(getFirstHeader("To")));
}
return mTo;
} else if (type == RecipientType.CC) {
if (mCc == null) {
mCc = Address.parse(MimeUtility.unfold(getFirstHeader("CC")));
}
return mCc;
} else if (type == RecipientType.BCC) {
if (mBcc == null) {
mBcc = Address.parse(MimeUtility.unfold(getFirstHeader("BCC")));
}
return mBcc;
} else {
throw new MessagingException("Unrecognized recipient type.");
}
}
@Override
public void setRecipients(RecipientType type, Address[] addresses) throws MessagingException {
final int TO_LENGTH = 4; // "To: "
final int CC_LENGTH = 4; // "Cc: "
final int BCC_LENGTH = 5; // "Bcc: "
if (type == RecipientType.TO) {
if (addresses == null || addresses.length == 0) {
removeHeader("To");
this.mTo = null;
} else {
setHeader("To", MimeUtility.fold(Address.toHeader(addresses), TO_LENGTH));
this.mTo = addresses;
}
} else if (type == RecipientType.CC) {
if (addresses == null || addresses.length == 0) {
removeHeader("CC");
this.mCc = null;
} else {
setHeader("CC", MimeUtility.fold(Address.toHeader(addresses), CC_LENGTH));
this.mCc = addresses;
}
} else if (type == RecipientType.BCC) {
if (addresses == null || addresses.length == 0) {
removeHeader("BCC");
this.mBcc = null;
} else {
setHeader("BCC", MimeUtility.fold(Address.toHeader(addresses), BCC_LENGTH));
this.mBcc = addresses;
}
} else {
throw new MessagingException("Unrecognized recipient type.");
}
}
/**
* Returns the unfolded, decoded value of the Subject header.
*/
@Override
public String getSubject() throws MessagingException {
return MimeUtility.unfoldAndDecode(getFirstHeader("Subject"));
}
@Override
public void setSubject(String subject) throws MessagingException {
final int HEADER_NAME_LENGTH = 9; // "Subject: "
setHeader("Subject", MimeUtility.foldAndEncode2(subject, HEADER_NAME_LENGTH));
}
@Override
public Address[] getFrom() throws MessagingException {
if (mFrom == null) {
String list = MimeUtility.unfold(getFirstHeader("From"));
if (list == null || list.length() == 0) {
list = MimeUtility.unfold(getFirstHeader("Sender"));
}
mFrom = Address.parse(list);
}
return mFrom;
}
@Override
public void setFrom(Address from) throws MessagingException {
final int FROM_LENGTH = 6; // "From: "
if (from != null) {
setHeader("From", MimeUtility.fold(from.toHeader(), FROM_LENGTH));
this.mFrom = new Address[] {
from
};
} else {
this.mFrom = null;
}
}
@Override
public Address[] getReplyTo() throws MessagingException {
if (mReplyTo == null) {
mReplyTo = Address.parse(MimeUtility.unfold(getFirstHeader("Reply-to")));
}
return mReplyTo;
}
@Override
public void setReplyTo(Address[] replyTo) throws MessagingException {
final int REPLY_TO_LENGTH = 10; // "Reply-to: "
if (replyTo == null || replyTo.length == 0) {
removeHeader("Reply-to");
mReplyTo = null;
} else {
setHeader("Reply-to", MimeUtility.fold(Address.toHeader(replyTo), REPLY_TO_LENGTH));
mReplyTo = replyTo;
}
}
/**
* Set the mime "Message-ID" header
* @param messageId the new Message-ID value
* @throws MessagingException
*/
@Override
public void setMessageId(String messageId) throws MessagingException {
setHeader("Message-ID", messageId);
}
/**
* Get the mime "Message-ID" header. This value will be preloaded with a locally-generated
* random ID, if the value has not previously been set. Local generation can be inhibited/
* overridden by explicitly clearing the headers, removing the message-id header, etc.
* @return the Message-ID header string, or null if explicitly has been set to null
*/
@Override
public String getMessageId() throws MessagingException {
String messageId = getFirstHeader("Message-ID");
if (messageId == null && !mInhibitLocalMessageId) {
messageId = generateMessageId();
setMessageId(messageId);
}
return messageId;
}
@Override
public void saveChanges() throws MessagingException {
throw new MessagingException("saveChanges not yet implemented");
}
@Override
public Body getBody() throws MessagingException {
return mBody;
}
@Override
public void setBody(Body body) throws MessagingException {
this.mBody = body;
if (body instanceof Multipart) {
Multipart multipart = ((Multipart)body);
multipart.setParent(this);
setHeader(MimeHeader.HEADER_CONTENT_TYPE, multipart.getContentType());
setHeader("MIME-Version", "1.0");
}
else if (body instanceof TextBody) {
setHeader(MimeHeader.HEADER_CONTENT_TYPE, String.format("%s;\n charset=utf-8",
getMimeType()));
setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
}
}
protected String getFirstHeader(String name) throws MessagingException {
return getMimeHeaders().getFirstHeader(name);
}
@Override
public void addHeader(String name, String value) throws MessagingException {
getMimeHeaders().addHeader(name, value);
}
@Override
public void setHeader(String name, String value) throws MessagingException {
getMimeHeaders().setHeader(name, value);
}
@Override
public String[] getHeader(String name) throws MessagingException {
return getMimeHeaders().getHeader(name);
}
@Override
public void removeHeader(String name) throws MessagingException {
getMimeHeaders().removeHeader(name);
if ("Message-ID".equalsIgnoreCase(name)) {
mInhibitLocalMessageId = true;
}
}
/**
* Set extended header
*
* @param name Extended header name
* @param value header value - flattened by removing CR-NL if any
* remove header if value is null
* @throws MessagingException
*/
public void setExtendedHeader(String name, String value) throws MessagingException {
if (value == null) {
if (mExtendedHeader != null) {
mExtendedHeader.removeHeader(name);
}
return;
}
if (mExtendedHeader == null) {
mExtendedHeader = new MimeHeader();
}
mExtendedHeader.setHeader(name, END_OF_LINE.matcher(value).replaceAll(""));
}
/**
* Get extended header
*
* @param name Extended header name
* @return header value - null if header does not exist
* @throws MessagingException
*/
public String getExtendedHeader(String name) throws MessagingException {
if (mExtendedHeader == null) {
return null;
}
return mExtendedHeader.getFirstHeader(name);
}
/**
* Set entire extended headers from String
*
* @param headers Extended header and its value - "CR-NL-separated pairs
* if null or empty, remove entire extended headers
* @throws MessagingException
*/
public void setExtendedHeaders(String headers) throws MessagingException {
if (TextUtils.isEmpty(headers)) {
mExtendedHeader = null;
} else {
mExtendedHeader = new MimeHeader();
for (String header : END_OF_LINE.split(headers)) {
String[] tokens = header.split(":", 2);
if (tokens.length != 2) {
throw new MessagingException("Illegal extended headers: " + headers);
}
mExtendedHeader.setHeader(tokens[0].trim(), tokens[1].trim());
}
}
}
/**
* Get entire extended headers as String
*
* @return "CR-NL-separated extended headers - null if extended header does not exist
*/
public String getExtendedHeaders() {
if (mExtendedHeader != null) {
return mExtendedHeader.writeToString();
}
return null;
}
/**
* Write message header and body to output stream
*
* @param out Output steam to write message header and body.
*/
public void writeTo(OutputStream out) throws IOException, MessagingException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
// Force creation of local message-id
getMessageId();
getMimeHeaders().writeTo(out);
// mExtendedHeader will not be write out to external output stream,
// because it is intended to internal use.
writer.write("\r\n");
writer.flush();
if (mBody != null) {
mBody.writeTo(out);
}
}
public InputStream getInputStream() throws MessagingException {
return null;
}
class MimeMessageBuilder implements ContentHandler {
private Stack<Object> stack = new Stack<Object>();
public MimeMessageBuilder() {
}
private void expect(Class c) {
if (!c.isInstance(stack.peek())) {
throw new IllegalStateException("Internal stack error: " + "Expected '"
+ c.getName() + "' found '" + stack.peek().getClass().getName() + "'");
}
}
public void startMessage() {
if (stack.isEmpty()) {
stack.push(MimeMessage.this);
} else {
expect(Part.class);
try {
MimeMessage m = new MimeMessage();
((Part)stack.peek()).setBody(m);
stack.push(m);
} catch (MessagingException me) {
throw new Error(me);
}
}
}
public void endMessage() {
expect(MimeMessage.class);
stack.pop();
}
public void startHeader() {
expect(Part.class);
}
public void field(String fieldData) {
expect(Part.class);
try {
String[] tokens = fieldData.split(":", 2);
((Part)stack.peek()).addHeader(tokens[0], tokens[1].trim());
} catch (MessagingException me) {
throw new Error(me);
}
}
public void endHeader() {
expect(Part.class);
}
public void startMultipart(BodyDescriptor bd) {
expect(Part.class);
Part e = (Part)stack.peek();
try {
MimeMultipart multiPart = new MimeMultipart(e.getContentType());
e.setBody(multiPart);
stack.push(multiPart);
} catch (MessagingException me) {
throw new Error(me);
}
}
public void body(BodyDescriptor bd, InputStream in) throws IOException {
expect(Part.class);
Body body = MimeUtility.decodeBody(in, bd.getTransferEncoding());
try {
((Part)stack.peek()).setBody(body);
} catch (MessagingException me) {
throw new Error(me);
}
}
public void endMultipart() {
stack.pop();
}
public void startBodyPart() {
expect(MimeMultipart.class);
try {
MimeBodyPart bodyPart = new MimeBodyPart();
((MimeMultipart)stack.peek()).addBodyPart(bodyPart);
stack.push(bodyPart);
} catch (MessagingException me) {
throw new Error(me);
}
}
public void endBodyPart() {
expect(BodyPart.class);
stack.pop();
}
public void epilogue(InputStream is) throws IOException {
expect(MimeMultipart.class);
StringBuffer sb = new StringBuffer();
int b;
while ((b = is.read()) != -1) {
sb.append((char)b);
}
// ((Multipart) stack.peek()).setEpilogue(sb.toString());
}
public void preamble(InputStream is) throws IOException {
expect(MimeMultipart.class);
StringBuffer sb = new StringBuffer();
int b;
while ((b = is.read()) != -1) {
sb.append((char)b);
}
try {
((MimeMultipart)stack.peek()).setPreamble(sb.toString());
} catch (MessagingException me) {
throw new Error(me);
}
}
public void raw(InputStream is) throws IOException {
throw new UnsupportedOperationException("Not supported");
}
}
}

View File

@ -1,111 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon.internet;
import com.android.emailcommon.mail.BodyPart;
import com.android.emailcommon.mail.MessagingException;
import com.android.emailcommon.mail.Multipart;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
public class MimeMultipart extends Multipart {
protected String mPreamble;
protected String mContentType;
protected String mBoundary;
protected String mSubType;
public MimeMultipart() throws MessagingException {
mBoundary = generateBoundary();
setSubType("mixed");
}
public MimeMultipart(String contentType) throws MessagingException {
this.mContentType = contentType;
try {
mSubType = MimeUtility.getHeaderParameter(contentType, null).split("/")[1];
mBoundary = MimeUtility.getHeaderParameter(contentType, "boundary");
if (mBoundary == null) {
throw new MessagingException("MultiPart does not contain boundary: " + contentType);
}
} catch (Exception e) {
throw new MessagingException(
"Invalid MultiPart Content-Type; must contain subtype and boundary. ("
+ contentType + ")", e);
}
}
public String generateBoundary() {
StringBuffer sb = new StringBuffer();
sb.append("----");
for (int i = 0; i < 30; i++) {
sb.append(Integer.toString((int)(Math.random() * 35), 36));
}
return sb.toString().toUpperCase();
}
public String getPreamble() throws MessagingException {
return mPreamble;
}
public void setPreamble(String preamble) throws MessagingException {
this.mPreamble = preamble;
}
@Override
public String getContentType() throws MessagingException {
return mContentType;
}
public void setSubType(String subType) throws MessagingException {
this.mSubType = subType;
mContentType = String.format("multipart/%s; boundary=\"%s\"", subType, mBoundary);
}
public void writeTo(OutputStream out) throws IOException, MessagingException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
if (mPreamble != null) {
writer.write(mPreamble + "\r\n");
}
for (int i = 0, count = mParts.size(); i < count; i++) {
BodyPart bodyPart = mParts.get(i);
writer.write("--" + mBoundary + "\r\n");
writer.flush();
bodyPart.writeTo(out);
writer.write("\r\n");
}
writer.write("--" + mBoundary + "--\r\n");
writer.flush();
}
public InputStream getInputStream() throws MessagingException {
return null;
}
public String getSubTypeForTest() {
return mSubType;
}
}

View File

@ -1,453 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon.internet;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Base64DataException;
import android.util.Base64InputStream;
import android.util.Log;
import com.android.emailcommon.Logging;
import com.android.emailcommon.mail.Body;
import com.android.emailcommon.mail.BodyPart;
import com.android.emailcommon.mail.Message;
import com.android.emailcommon.mail.MessagingException;
import com.android.emailcommon.mail.Multipart;
import com.android.emailcommon.mail.Part;
import org.apache.commons.io.IOUtils;
import org.apache.james.mime4j.codec.EncoderUtil;
import org.apache.james.mime4j.decoder.DecoderUtil;
import org.apache.james.mime4j.decoder.QuotedPrintableInputStream;
import org.apache.james.mime4j.util.CharsetUtil;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class MimeUtility {
public static final String MIME_TYPE_RFC822 = "message/rfc822";
private final static Pattern PATTERN_CR_OR_LF = Pattern.compile("\r|\n");
/**
* Replace sequences of CRLF+WSP with WSP. Tries to preserve original string
* object whenever possible.
*/
public static String unfold(String s) {
if (s == null) {
return null;
}
Matcher patternMatcher = PATTERN_CR_OR_LF.matcher(s);
if (patternMatcher.find()) {
patternMatcher.reset();
s = patternMatcher.replaceAll("");
}
return s;
}
public static String decode(String s) {
if (s == null) {
return null;
}
return DecoderUtil.decodeEncodedWords(s);
}
public static String unfoldAndDecode(String s) {
return decode(unfold(s));
}
// TODO implement proper foldAndEncode
// NOTE: When this really works, we *must* remove all calls to foldAndEncode2() to prevent
// duplication of encoding.
public static String foldAndEncode(String s) {
return s;
}
/**
* INTERIM version of foldAndEncode that will be used only by Subject: headers.
* This is safer than implementing foldAndEncode() (see above) and risking unknown damage
* to other headers.
*
* TODO: Copy this code to foldAndEncode(), get rid of this function, confirm all working OK.
*
* @param s original string to encode and fold
* @param usedCharacters number of characters already used up by header name
* @return the String ready to be transmitted
*/
public static String foldAndEncode2(String s, int usedCharacters) {
// james.mime4j.codec.EncoderUtil.java
// encode: encodeIfNecessary(text, usage, numUsedInHeaderName)
// Usage.TEXT_TOKENlooks like the right thing for subjects
// use WORD_ENTITY for address/names
String encoded = EncoderUtil.encodeIfNecessary(s, EncoderUtil.Usage.TEXT_TOKEN,
usedCharacters);
return fold(encoded, usedCharacters);
}
/**
* INTERIM: From newer version of org.apache.james (but we don't want to import
* the entire MimeUtil class).
*
* Splits the specified string into a multiple-line representation with
* lines no longer than 76 characters (because the line might contain
* encoded words; see <a href='http://www.faqs.org/rfcs/rfc2047.html'>RFC
* 2047</a> section 2). If the string contains non-whitespace sequences
* longer than 76 characters a line break is inserted at the whitespace
* character following the sequence resulting in a line longer than 76
* characters.
*
* @param s
* string to split.
* @param usedCharacters
* number of characters already used up. Usually the number of
* characters for header field name plus colon and one space.
* @return a multiple-line representation of the given string.
*/
public static String fold(String s, int usedCharacters) {
final int maxCharacters = 76;
final int length = s.length();
if (usedCharacters + length <= maxCharacters)
return s;
StringBuilder sb = new StringBuilder();
int lastLineBreak = -usedCharacters;
int wspIdx = indexOfWsp(s, 0);
while (true) {
if (wspIdx == length) {
sb.append(s.substring(Math.max(0, lastLineBreak)));
return sb.toString();
}
int nextWspIdx = indexOfWsp(s, wspIdx + 1);
if (nextWspIdx - lastLineBreak > maxCharacters) {
sb.append(s.substring(Math.max(0, lastLineBreak), wspIdx));
sb.append("\r\n");
lastLineBreak = wspIdx;
}
wspIdx = nextWspIdx;
}
}
/**
* INTERIM: From newer version of org.apache.james (but we don't want to import
* the entire MimeUtil class).
*
* Search for whitespace.
*/
private static int indexOfWsp(String s, int fromIndex) {
final int len = s.length();
for (int index = fromIndex; index < len; index++) {
char c = s.charAt(index);
if (c == ' ' || c == '\t')
return index;
}
return len;
}
/**
* Returns the named parameter of a header field. If name is null the first
* parameter is returned, or if there are no additional parameters in the
* field the entire field is returned. Otherwise the named parameter is
* searched for in a case insensitive fashion and returned. If the parameter
* cannot be found the method returns null.
*
* TODO: quite inefficient with the inner trimming & splitting.
* TODO: Also has a latent bug: uses "startsWith" to match the name, which can false-positive.
* TODO: The doc says that for a null name you get the first param, but you get the header.
* Should probably just fix the doc, but if other code assumes that behavior, fix the code.
* TODO: Need to decode %-escaped strings, as in: filename="ab%22d".
* ('+' -> ' ' conversion too? check RFC)
*
* @param header
* @param name
* @return the entire header (if name=null), the found parameter, or null
*/
public static String getHeaderParameter(String header, String name) {
if (header == null) {
return null;
}
String[] parts = unfold(header).split(";");
if (name == null) {
return parts[0].trim();
}
String lowerCaseName = name.toLowerCase();
for (String part : parts) {
if (part.trim().toLowerCase().startsWith(lowerCaseName)) {
String[] parameterParts = part.split("=", 2);
if (parameterParts.length < 2) {
return null;
}
String parameter = parameterParts[1].trim();
if (parameter.startsWith("\"") && parameter.endsWith("\"")) {
return parameter.substring(1, parameter.length() - 1);
} else {
return parameter;
}
}
}
return null;
}
public static Part findFirstPartByMimeType(Part part, String mimeType)
throws MessagingException {
if (part.getBody() instanceof Multipart) {
Multipart multipart = (Multipart)part.getBody();
for (int i = 0, count = multipart.getCount(); i < count; i++) {
BodyPart bodyPart = multipart.getBodyPart(i);
Part ret = findFirstPartByMimeType(bodyPart, mimeType);
if (ret != null) {
return ret;
}
}
}
else if (part.getMimeType().equalsIgnoreCase(mimeType)) {
return part;
}
return null;
}
public static Part findPartByContentId(Part part, String contentId) throws Exception {
if (part.getBody() instanceof Multipart) {
Multipart multipart = (Multipart)part.getBody();
for (int i = 0, count = multipart.getCount(); i < count; i++) {
BodyPart bodyPart = multipart.getBodyPart(i);
Part ret = findPartByContentId(bodyPart, contentId);
if (ret != null) {
return ret;
}
}
}
String cid = part.getContentId();
if (contentId.equals(cid)) {
return part;
}
return null;
}
/**
* Reads the Part's body and returns a String based on any charset conversion that needed
* to be done.
* @param part The part containing a body
* @return a String containing the converted text in the body, or null if there was no text
* or an error during conversion.
*/
public static String getTextFromPart(Part part) {
try {
if (part != null && part.getBody() != null) {
InputStream in = part.getBody().getInputStream();
String mimeType = part.getMimeType();
if (mimeType != null && MimeUtility.mimeTypeMatches(mimeType, "text/*")) {
/*
* Now we read the part into a buffer for further processing. Because
* the stream is now wrapped we'll remove any transfer encoding at this point.
*/
ByteArrayOutputStream out = new ByteArrayOutputStream();
IOUtils.copy(in, out);
in.close();
in = null; // we want all of our memory back, and close might not release
/*
* We've got a text part, so let's see if it needs to be processed further.
*/
String charset = getHeaderParameter(part.getContentType(), "charset");
if (charset != null) {
/*
* See if there is conversion from the MIME charset to the Java one.
*/
charset = CharsetUtil.toJavaCharset(charset);
}
/*
* No encoding, so use us-ascii, which is the standard.
*/
if (charset == null) {
charset = "ASCII";
}
/*
* Convert and return as new String
*/
String result = out.toString(charset);
out.close();
return result;
}
}
}
catch (OutOfMemoryError oom) {
/*
* If we are not able to process the body there's nothing we can do about it. Return
* null and let the upper layers handle the missing content.
*/
Log.e(Logging.LOG_TAG, "Unable to getTextFromPart " + oom.toString());
}
catch (Exception e) {
/*
* If we are not able to process the body there's nothing we can do about it. Return
* null and let the upper layers handle the missing content.
*/
Log.e(Logging.LOG_TAG, "Unable to getTextFromPart " + e.toString());
}
return null;
}
/**
* Returns true if the given mimeType matches the matchAgainst specification. The comparison
* ignores case and the matchAgainst string may include "*" for a wildcard (e.g. "image/*").
*
* @param mimeType A MIME type to check.
* @param matchAgainst A MIME type to check against. May include wildcards.
* @return true if the mimeType matches
*/
public static boolean mimeTypeMatches(String mimeType, String matchAgainst) {
Pattern p = Pattern.compile(matchAgainst.replaceAll("\\*", "\\.\\*"),
Pattern.CASE_INSENSITIVE);
return p.matcher(mimeType).matches();
}
/**
* Returns true if the given mimeType matches any of the matchAgainst specifications. The
* comparison ignores case and the matchAgainst strings may include "*" for a wildcard
* (e.g. "image/*").
*
* @param mimeType A MIME type to check.
* @param matchAgainst An array of MIME types to check against. May include wildcards.
* @return true if the mimeType matches any of the matchAgainst strings
*/
public static boolean mimeTypeMatches(String mimeType, String[] matchAgainst) {
for (String matchType : matchAgainst) {
if (mimeTypeMatches(mimeType, matchType)) {
return true;
}
}
return false;
}
/**
* Given an input stream and a transfer encoding, return a wrapped input stream for that
* encoding (or the original if none is required)
* @param in the input stream
* @param contentTransferEncoding the content transfer encoding
* @return a properly wrapped stream
*/
public static InputStream getInputStreamForContentTransferEncoding(InputStream in,
String contentTransferEncoding) {
if (contentTransferEncoding != null) {
contentTransferEncoding =
MimeUtility.getHeaderParameter(contentTransferEncoding, null);
if ("quoted-printable".equalsIgnoreCase(contentTransferEncoding)) {
in = new QuotedPrintableInputStream(in);
}
else if ("base64".equalsIgnoreCase(contentTransferEncoding)) {
in = new Base64InputStream(in, Base64.DEFAULT);
}
}
return in;
}
/**
* Removes any content transfer encoding from the stream and returns a Body.
*/
public static Body decodeBody(InputStream in, String contentTransferEncoding)
throws IOException {
/*
* We'll remove any transfer encoding by wrapping the stream.
*/
in = getInputStreamForContentTransferEncoding(in, contentTransferEncoding);
BinaryTempFileBody tempBody = new BinaryTempFileBody();
OutputStream out = tempBody.getOutputStream();
try {
IOUtils.copy(in, out);
} catch (Base64DataException bde) {
// TODO Need to fix this somehow
//String warning = "\n\n" + Email.getMessageDecodeErrorString();
//out.write(warning.getBytes());
} finally {
out.close();
}
return tempBody;
}
/**
* Recursively scan a Part (usually a Message) and sort out which of its children will be
* "viewable" and which will be attachments.
*
* @param part The part to be broken down
* @param viewables This arraylist will be populated with all parts that appear to be
* the "message" (e.g. text/plain & text/html)
* @param attachments This arraylist will be populated with all parts that appear to be
* attachments (including inlines)
* @throws MessagingException
*/
public static void collectParts(Part part, ArrayList<Part> viewables,
ArrayList<Part> attachments) throws MessagingException {
String disposition = part.getDisposition();
String dispositionType = MimeUtility.getHeaderParameter(disposition, null);
// If a disposition is not specified, default to "inline"
boolean inline =
TextUtils.isEmpty(dispositionType) || "inline".equalsIgnoreCase(dispositionType);
// The lower-case mime type
String mimeType = part.getMimeType().toLowerCase();
if (part.getBody() instanceof Multipart) {
// If the part is Multipart but not alternative it's either mixed or
// something we don't know about, which means we treat it as mixed
// per the spec. We just process its pieces recursively.
MimeMultipart mp = (MimeMultipart)part.getBody();
boolean foundHtml = false;
if (mp.getSubTypeForTest().equals("alternative")) {
for (int i = 0; i < mp.getCount(); i++) {
if (mp.getBodyPart(i).isMimeType("text/html")) {
foundHtml = true;
break;
}
}
}
for (int i = 0; i < mp.getCount(); i++) {
// See if we have text and html
BodyPart bp = mp.getBodyPart(i);
// If there's html, don't bother loading text
if (foundHtml && bp.isMimeType("text/plain")) {
continue;
}
collectParts(bp, viewables, attachments);
}
} else if (part.getBody() instanceof Message) {
// If the part is an embedded message we just continue to process
// it, pulling any viewables or attachments into the running list.
Message message = (Message)part.getBody();
collectParts(message, viewables, attachments);
} else if (inline && (mimeType.startsWith("text") || (mimeType.startsWith("image")))) {
// We'll treat text and images as viewables
viewables.add(part);
} else {
// Everything else is an attachment.
attachments.add(part);
}
}
}

View File

@ -1,62 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon.internet;
import com.android.emailcommon.mail.Body;
import com.android.emailcommon.mail.MessagingException;
import android.util.Base64;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
public class TextBody implements Body {
String mBody;
public TextBody(String body) {
this.mBody = body;
}
public void writeTo(OutputStream out) throws IOException, MessagingException {
byte[] bytes = mBody.getBytes("UTF-8");
out.write(Base64.encode(bytes, Base64.CRLF));
}
/**
* Get the text of the body in it's unencoded format.
* @return
*/
public String getText() {
return mBody;
}
/**
* Returns an InputStream that reads this body's text in UTF-8 format.
*/
public InputStream getInputStream() throws MessagingException {
try {
byte[] b = mBody.getBytes("UTF-8");
return new ByteArrayInputStream(b);
}
catch (UnsupportedEncodingException usee) {
return null;
}
}
}

View File

@ -1,428 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon.mail;
import android.text.TextUtils;
import android.text.util.Rfc822Token;
import android.text.util.Rfc822Tokenizer;
import com.android.emailcommon.utility.Utility;
import com.google.common.annotations.VisibleForTesting;
import org.apache.james.mime4j.codec.EncoderUtil;
import org.apache.james.mime4j.decoder.DecoderUtil;
import java.util.ArrayList;
import java.util.regex.Pattern;
/**
* This class represent email address.
*
* RFC822 email address may have following format.
* "name" <address> (comment)
* "name" <address>
* name <address>
* address
* Name and comment part should be MIME/base64 encoded in header if necessary.
*
*/
public class Address {
/**
* Address part, in the form local_part@domain_part. No surrounding angle brackets.
*/
private String mAddress;
/**
* Name part. No surrounding double quote, and no MIME/base64 encoding.
* This must be null if Address has no name part.
*/
private String mPersonal;
// Regex that matches address surrounded by '<>' optionally. '^<?([^>]+)>?$'
private static final Pattern REMOVE_OPTIONAL_BRACKET = Pattern.compile("^<?([^>]+)>?$");
// Regex that matches personal name surrounded by '""' optionally. '^"?([^"]+)"?$'
private static final Pattern REMOVE_OPTIONAL_DQUOTE = Pattern.compile("^\"?([^\"]*)\"?$");
// Regex that matches escaped character '\\([\\"])'
private static final Pattern UNQUOTE = Pattern.compile("\\\\([\\\\\"])");
private static final Address[] EMPTY_ADDRESS_ARRAY = new Address[0];
// delimiters are chars that do not appear in an email address, used by pack/unpack
private static final char LIST_DELIMITER_EMAIL = '\1';
private static final char LIST_DELIMITER_PERSONAL = '\2';
public Address(String address, String personal) {
setAddress(address);
setPersonal(personal);
}
public Address(String address) {
setAddress(address);
}
public String getAddress() {
return mAddress;
}
public void setAddress(String address) {
mAddress = REMOVE_OPTIONAL_BRACKET.matcher(address).replaceAll("$1");
}
/**
* Get name part as UTF-16 string. No surrounding double quote, and no MIME/base64 encoding.
*
* @return Name part of email address. Returns null if it is omitted.
*/
public String getPersonal() {
return mPersonal;
}
/**
* Set name part from UTF-16 string. Optional surrounding double quote will be removed.
* It will be also unquoted and MIME/base64 decoded.
*
* @param personal name part of email address as UTF-16 string. Null is acceptable.
*/
public void setPersonal(String personal) {
if (personal != null) {
personal = REMOVE_OPTIONAL_DQUOTE.matcher(personal).replaceAll("$1");
personal = UNQUOTE.matcher(personal).replaceAll("$1");
personal = DecoderUtil.decodeEncodedWords(personal);
if (personal.length() == 0) {
personal = null;
}
}
mPersonal = personal;
}
/**
* This method is used to check that all the addresses that the user
* entered in a list (e.g. To:) are valid, so that none is dropped.
*/
public static boolean isAllValid(String addressList) {
// This code mimics the parse() method below.
// I don't know how to better avoid the code-duplication.
if (addressList != null && addressList.length() > 0) {
Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(addressList);
for (int i = 0, length = tokens.length; i < length; ++i) {
Rfc822Token token = tokens[i];
String address = token.getAddress();
if (!TextUtils.isEmpty(address) && !isValidAddress(address)) {
return false;
}
}
}
return true;
}
/**
* Parse a comma-delimited list of addresses in RFC822 format and return an
* array of Address objects.
*
* @param addressList Address list in comma-delimited string.
* @return An array of 0 or more Addresses.
*/
public static Address[] parse(String addressList) {
if (addressList == null || addressList.length() == 0) {
return EMPTY_ADDRESS_ARRAY;
}
Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(addressList);
ArrayList<Address> addresses = new ArrayList<Address>();
for (int i = 0, length = tokens.length; i < length; ++i) {
Rfc822Token token = tokens[i];
String address = token.getAddress();
if (!TextUtils.isEmpty(address)) {
if (isValidAddress(address)) {
String name = token.getName();
if (TextUtils.isEmpty(name)) {
name = null;
}
addresses.add(new Address(address, name));
}
}
}
return addresses.toArray(new Address[] {});
}
/**
* Checks whether a string email address is valid.
* E.g. name@domain.com is valid.
*/
@VisibleForTesting
static boolean isValidAddress(String address) {
// Note: Some email provider may violate the standard, so here we only check that
// address consists of two part that are separated by '@', and domain part contains
// at least one '.'.
int len = address.length();
int firstAt = address.indexOf('@');
int lastAt = address.lastIndexOf('@');
int firstDot = address.indexOf('.', lastAt + 1);
int lastDot = address.lastIndexOf('.');
return firstAt > 0 && firstAt == lastAt && lastAt + 1 < firstDot
&& firstDot <= lastDot && lastDot < len - 1;
}
@Override
public boolean equals(Object o) {
if (o instanceof Address) {
// It seems that the spec says that the "user" part is case-sensitive,
// while the domain part in case-insesitive.
// So foo@yahoo.com and Foo@yahoo.com are different.
// This may seem non-intuitive from the user POV, so we
// may re-consider it if it creates UI trouble.
// A problem case is "replyAll" sending to both
// a@b.c and to A@b.c, which turn out to be the same on the server.
// Leave unchanged for now (i.e. case-sensitive).
return getAddress().equals(((Address) o).getAddress());
}
return super.equals(o);
}
public int hashCode() {
return getAddress().hashCode();
}
/**
* Get human readable address string.
* Do not use this for email header.
*
* @return Human readable address string. Not quoted and not encoded.
*/
@Override
public String toString() {
if (mPersonal != null && !mPersonal.equals(mAddress)) {
if (mPersonal.matches(".*[\\(\\)<>@,;:\\\\\".\\[\\]].*")) {
return Utility.quoteString(mPersonal) + " <" + mAddress + ">";
} else {
return mPersonal + " <" + mAddress + ">";
}
} else {
return mAddress;
}
}
/**
* Get human readable comma-delimited address string.
*
* @param addresses Address array
* @return Human readable comma-delimited address string.
*/
public static String toString(Address[] addresses) {
return toString(addresses, ",");
}
/**
* Get human readable address strings joined with the specified separator.
*
* @param addresses Address array
* @param separator Separator
* @return Human readable comma-delimited address string.
*/
public static String toString(Address[] addresses, String separator) {
if (addresses == null || addresses.length == 0) {
return null;
}
if (addresses.length == 1) {
return addresses[0].toString();
}
StringBuffer sb = new StringBuffer(addresses[0].toString());
for (int i = 1; i < addresses.length; i++) {
sb.append(separator);
// TODO: investigate why this .trim() is needed.
sb.append(addresses[i].toString().trim());
}
return sb.toString();
}
/**
* Get RFC822/MIME compatible address string.
*
* @return RFC822/MIME compatible address string.
* It may be surrounded by double quote or quoted and MIME/base64 encoded if necessary.
*/
public String toHeader() {
if (mPersonal != null) {
return EncoderUtil.encodeAddressDisplayName(mPersonal) + " <" + mAddress + ">";
} else {
return mAddress;
}
}
/**
* Get RFC822/MIME compatible comma-delimited address string.
*
* @param addresses Address array
* @return RFC822/MIME compatible comma-delimited address string.
* it may be surrounded by double quoted or quoted and MIME/base64 encoded if necessary.
*/
public static String toHeader(Address[] addresses) {
if (addresses == null || addresses.length == 0) {
return null;
}
if (addresses.length == 1) {
return addresses[0].toHeader();
}
StringBuffer sb = new StringBuffer(addresses[0].toHeader());
for (int i = 1; i < addresses.length; i++) {
// We need space character to be able to fold line.
sb.append(", ");
sb.append(addresses[i].toHeader());
}
return sb.toString();
}
/**
* Get Human friendly address string.
*
* @return the personal part of this Address, or the address part if the
* personal part is not available
*/
public String toFriendly() {
if (mPersonal != null && mPersonal.length() > 0) {
return mPersonal;
} else {
return mAddress;
}
}
/**
* Creates a comma-delimited list of addresses in the "friendly" format (see toFriendly() for
* details on the per-address conversion).
*
* @param addresses Array of Address[] values
* @return A comma-delimited string listing all of the addresses supplied. Null if source
* was null or empty.
*/
public static String toFriendly(Address[] addresses) {
if (addresses == null || addresses.length == 0) {
return null;
}
if (addresses.length == 1) {
return addresses[0].toFriendly();
}
StringBuffer sb = new StringBuffer(addresses[0].toFriendly());
for (int i = 1; i < addresses.length; i++) {
sb.append(", ");
sb.append(addresses[i].toFriendly());
}
return sb.toString();
}
/**
* Returns exactly the same result as Address.toString(Address.unpack(packedList)).
*/
public static String unpackToString(String packedList) {
return toString(unpack(packedList));
}
/**
* Returns exactly the same result as Address.pack(Address.parse(textList)).
*/
public static String parseAndPack(String textList) {
return Address.pack(Address.parse(textList));
}
/**
* Returns null if the packedList has 0 addresses, otherwise returns the first address.
* The same as Address.unpack(packedList)[0] for non-empty list.
* This is an utility method that offers some performance optimization opportunities.
*/
public static Address unpackFirst(String packedList) {
Address[] array = unpack(packedList);
return array.length > 0 ? array[0] : null;
}
/**
* Convert a packed list of addresses to a form suitable for use in an RFC822 header.
* This implementation is brute-force, and could be replaced with a more efficient version
* if desired.
*/
public static String packedToHeader(String packedList) {
return toHeader(unpack(packedList));
}
/**
* Unpacks an address list that is either CSV of RFC822 addresses OR (for backward
* compatibility) previously packed with pack()
* @param addressList string packed with pack() or CSV of RFC822 addresses
* @return array of addresses resulting from unpack
*/
public static Address[] unpack(String addressList) {
if (addressList == null || addressList.length() == 0) {
return EMPTY_ADDRESS_ARRAY;
}
// IF we're CSV, just parse
if ((addressList.indexOf(LIST_DELIMITER_PERSONAL) == -1) &&
(addressList.indexOf(LIST_DELIMITER_EMAIL) == -1)) {
return Address.parse(addressList);
}
// Otherwise, do backward-compatibile unpack
ArrayList<Address> addresses = new ArrayList<Address>();
int length = addressList.length();
int pairStartIndex = 0;
int pairEndIndex = 0;
/* addressEndIndex is only re-scanned (indexOf()) when a LIST_DELIMITER_PERSONAL
is used, not for every email address; i.e. not for every iteration of the while().
This reduces the theoretical complexity from quadratic to linear,
and provides some speed-up in practice by removing redundant scans of the string.
*/
int addressEndIndex = addressList.indexOf(LIST_DELIMITER_PERSONAL);
while (pairStartIndex < length) {
pairEndIndex = addressList.indexOf(LIST_DELIMITER_EMAIL, pairStartIndex);
if (pairEndIndex == -1) {
pairEndIndex = length;
}
Address address;
if (addressEndIndex == -1 || pairEndIndex <= addressEndIndex) {
// in this case the DELIMITER_PERSONAL is in a future pair,
// so don't use personal, and don't update addressEndIndex
address = new Address(addressList.substring(pairStartIndex, pairEndIndex), null);
} else {
address = new Address(addressList.substring(pairStartIndex, addressEndIndex),
addressList.substring(addressEndIndex + 1, pairEndIndex));
// only update addressEndIndex when we use the LIST_DELIMITER_PERSONAL
addressEndIndex = addressList.indexOf(LIST_DELIMITER_PERSONAL, pairEndIndex + 1);
}
addresses.add(address);
pairStartIndex = pairEndIndex + 1;
}
return addresses.toArray(EMPTY_ADDRESS_ARRAY);
}
/**
* Generate a String containing RFC822 addresses separated by commas
* NOTE: We used to "pack" these addresses in an app-specific format, but no longer do so
*/
public static String pack(Address[] addresses) {
return Address.toHeader(addresses);
}
/**
* Produces the same result as pack(array), but only packs one (this) address.
*/
public String pack() {
final String address = getAddress();
final String personal = getPersonal();
if (personal == null) {
return address;
} else {
return address + LIST_DELIMITER_PERSONAL + personal;
}
}
}

View File

@ -1,34 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon.mail;
public class AuthenticationFailedException extends MessagingException {
public static final long serialVersionUID = -1;
public AuthenticationFailedException(String message) {
super(MessagingException.AUTHENTICATION_FAILED, message);
}
public AuthenticationFailedException(int exceptionType, String message) {
super(exceptionType, message);
}
public AuthenticationFailedException(String message, Throwable throwable) {
super(MessagingException.AUTHENTICATION_FAILED, message, throwable);
}
}

View File

@ -1,26 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon.mail;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public interface Body {
public InputStream getInputStream() throws MessagingException;
public void writeTo(OutputStream out) throws IOException, MessagingException;
}

View File

@ -1,25 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon.mail;
public abstract class BodyPart implements Part {
protected Multipart mParent;
public Multipart getParent() {
return mParent;
}
}

View File

@ -1,30 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon.mail;
public class CertificateValidationException extends MessagingException {
public static final long serialVersionUID = -1;
public CertificateValidationException(String message) {
super(MessagingException.CERTIFICATE_VALIDATION_ERROR, message);
}
public CertificateValidationException(String message, Throwable throwable) {
super(MessagingException.CERTIFICATE_VALIDATION_ERROR, message, throwable);
}
}

View File

@ -1,85 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon.mail;
import java.util.ArrayList;
/**
* <pre>
* A FetchProfile is a list of items that should be downloaded in bulk for a set of messages.
* FetchProfile can contain the following objects:
* FetchProfile.Item: Described below.
* Message: Indicates that the body of the entire message should be fetched.
* Synonymous with FetchProfile.Item.BODY.
* Part: Indicates that the given Part should be fetched. The provider
* is expected have previously created the given BodyPart and stored
* any information it needs to download the content.
* </pre>
*/
public class FetchProfile extends ArrayList<Fetchable> {
/**
* Default items available for pre-fetching. It should be expected that any
* item fetched by using these items could potentially include all of the
* previous items.
*/
public enum Item implements Fetchable {
/**
* Download the flags of the message.
*/
FLAGS,
/**
* Download the envelope of the message. This should include at minimum
* the size and the following headers: date, subject, from, content-type, to, cc
*/
ENVELOPE,
/**
* Download the structure of the message. This maps directly to IMAP's BODYSTRUCTURE
* and may map to other providers.
* The provider should, if possible, fill in a properly formatted MIME structure in
* the message without actually downloading any message data. If the provider is not
* capable of this operation it should specifically set the body of the message to null
* so that upper levels can detect that a full body download is needed.
*/
STRUCTURE,
/**
* A sane portion of the entire message, cut off at a provider determined limit.
* This should generaly be around 50kB.
*/
BODY_SANE,
/**
* The entire message.
*/
BODY,
}
/**
* @return the first {@link Part} in this collection, or null if it doesn't contain
* {@link Part}.
*/
public Part getFirstPart() {
for (Fetchable o : this) {
if (o instanceof Part) {
return (Part) o;
}
}
return null;
}
}

View File

@ -1,24 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon.mail;
/**
* Interface for classes that can be added to {@link FetchProfile}.
* i.e. {@link Part} and its subclasses, and {@link FetchProfile.Item}.
*/
public interface Fetchable {
}

View File

@ -1,79 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon.mail;
/**
* Flags that can be applied to Messages.
*/
public enum Flag {
// If adding new flags: ALL FLAGS MUST BE UPPER CASE.
DELETED,
SEEN,
ANSWERED,
FLAGGED,
DRAFT,
RECENT,
/*
* The following flags are for internal library use only.
* TODO Eventually we should creates a Flags class that extends ArrayList that allows
* these flags and Strings to represent user defined flags. At that point the below
* flags should become user defined flags.
*/
/**
* Delete and remove from the LocalStore immediately.
*/
X_DESTROYED,
/**
* Sending of an unsent message failed. It will be retried. Used to show status.
*/
X_SEND_FAILED,
/**
* Sending of an unsent message is in progress.
*/
X_SEND_IN_PROGRESS,
/**
* Indicates that a message is fully downloaded from the server and can be viewed normally.
* This does not include attachments, which are never downloaded fully.
*/
X_DOWNLOADED_FULL,
/**
* Indicates that a message is partially downloaded from the server and can be viewed but
* more content is available on the server.
* This does not include attachments, which are never downloaded fully.
*/
X_DOWNLOADED_PARTIAL,
/**
* General purpose flag that can be used by any remote store. The flag will be
* saved and restored by the LocalStore.
*/
X_STORE_1,
/**
* General purpose flag that can be used by any remote store. The flag will be
* saved and restored by the LocalStore.
*/
X_STORE_2,
}

View File

@ -1,209 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon.mail;
import com.android.emailcommon.service.SearchParams;
import com.google.common.annotations.VisibleForTesting;
public abstract class Folder {
public enum OpenMode {
READ_WRITE, READ_ONLY,
}
public enum FolderType {
HOLDS_FOLDERS, HOLDS_MESSAGES,
}
/**
* Identifiers of "special" folders.
*/
public enum FolderRole {
INBOX, // NOTE: The folder's name must be INBOX
TRASH,
SENT,
DRAFTS,
OUTBOX, // Local folders only - not used in remote Stores
OTHER, // this folder has no specific role
UNKNOWN // the role of this folder is unknown
}
/**
* Callback for each message retrieval.
*
* Not all {@link Folder} implementations may invoke it.
*/
public interface MessageRetrievalListener {
public void messageRetrieved(Message message);
public void loadAttachmentProgress(int progress);
}
/**
* Forces an open of the MailProvider. If the provider is already open this
* function returns without doing anything.
*
* @param mode READ_ONLY or READ_WRITE
* @param callbacks Pointer to callbacks class. This may be used by the folder between this
* time and when close() is called. This is only used for remote stores - should be null
* for LocalStore.LocalFolder.
*/
public abstract void open(OpenMode mode)
throws MessagingException;
/**
* Forces a close of the MailProvider. Any further access will attempt to
* reopen the MailProvider.
*
* @param expunge If true all deleted messages will be expunged.
*/
public abstract void close(boolean expunge) throws MessagingException;
/**
* @return True if further commands are not expected to have to open the
* connection.
*/
@VisibleForTesting
public abstract boolean isOpen();
/**
* Returns the mode the folder was opened with. This may be different than the mode the open
* was requested with.
*/
public abstract OpenMode getMode() throws MessagingException;
/**
* Reports if the Store is able to create folders of the given type.
* Does not actually attempt to create a folder.
* @param type
* @return true if can create, false if cannot create
*/
public abstract boolean canCreate(FolderType type);
/**
* Attempt to create the given folder remotely using the given type.
* @return true if created, false if cannot create (e.g. server side)
*/
public abstract boolean create(FolderType type) throws MessagingException;
public abstract boolean exists() throws MessagingException;
/**
* Returns the number of messages in the selected folder.
*/
public abstract int getMessageCount() throws MessagingException;
public abstract int getUnreadMessageCount() throws MessagingException;
public abstract Message getMessage(String uid) throws MessagingException;
/**
* Fetches the given list of messages. The specified listener is notified as
* each fetch completes. Messages are downloaded as (as) lightweight (as
* possible) objects to be filled in with later requests. In most cases this
* means that only the UID is downloaded.
*/
public abstract Message[] getMessages(int start, int end, MessageRetrievalListener listener)
throws MessagingException;
public abstract Message[] getMessages(SearchParams params,MessageRetrievalListener listener)
throws MessagingException;
public abstract Message[] getMessages(String[] uids, MessageRetrievalListener listener)
throws MessagingException;
/**
* Return a set of messages based on the state of the flags.
* Note: Not typically implemented in remote stores, so not abstract.
*
* @param setFlags The flags that should be set for a message to be selected (can be null)
* @param clearFlags The flags that should be clear for a message to be selected (can be null)
* @param listener
* @return A list of messages matching the desired flag states.
* @throws MessagingException
*/
public Message[] getMessages(Flag[] setFlags, Flag[] clearFlags,
MessageRetrievalListener listener) throws MessagingException {
throw new MessagingException("Not implemented");
}
public abstract void appendMessages(Message[] messages) throws MessagingException;
/**
* Copies the given messages to the destination folder.
*/
public abstract void copyMessages(Message[] msgs, Folder folder,
MessageUpdateCallbacks callbacks) throws MessagingException;
public abstract void setFlags(Message[] messages, Flag[] flags, boolean value)
throws MessagingException;
public abstract Message[] expunge() throws MessagingException;
public abstract void fetch(Message[] messages, FetchProfile fp,
MessageRetrievalListener listener) throws MessagingException;
public abstract void delete(boolean recurse) throws MessagingException;
public abstract String getName();
public abstract Flag[] getPermanentFlags() throws MessagingException;
/**
* This method returns a string identifying the name of a "role" folder
* (such as inbox, draft, sent, or trash). Stores that do not implement this
* feature can be used - the account UI will provide default strings. To
* let the server identify specific folder roles, simply override this method.
*
* @return The server- or protocol- specific role for this folder. If some roles are known
* but this is not one of them, return FolderRole.OTHER. If roles are unsupported here,
* return FolderRole.UNKNOWN.
*/
public FolderRole getRole() {
return FolderRole.UNKNOWN;
}
/**
* Create an empty message of the appropriate type for the Folder.
*/
public abstract Message createMessage(String uid) throws MessagingException;
/**
* Callback interface by which a folder can report UID changes caused by certain operations.
*/
public interface MessageUpdateCallbacks {
/**
* The operation caused the message's UID to change
* @param message The message for which the UID changed
* @param newUid The new UID for the message
*/
public void onMessageUidChange(Message message, String newUid) throws MessagingException;
/**
* The operation could not be completed because the message doesn't exist
* (for example, it was already deleted from the server side.)
* @param message The message that does not exist
* @throws MessagingException
*/
public void onMessageNotFound(Message message) throws MessagingException;
}
@Override
public String toString() {
return getName();
}
}

View File

@ -1,30 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon.mail;
public class MeetingInfo {
// Predefined tags; others can be added
public static final String MEETING_DTSTAMP = "DTSTAMP";
public static final String MEETING_UID = "UID";
public static final String MEETING_ORGANIZER_EMAIL = "ORGMAIL";
public static final String MEETING_DTSTART = "DTSTART";
public static final String MEETING_DTEND = "DTEND";
public static final String MEETING_TITLE = "TITLE";
public static final String MEETING_LOCATION = "LOC";
public static final String MEETING_RESPONSE_REQUESTED = "RESPONSE";
public static final String MEETING_ALL_DAY = "ALLDAY";
}

View File

@ -1,159 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon.mail;
import java.util.Date;
import java.util.HashSet;
public abstract class Message implements Part, Body {
public static final Message[] EMPTY_ARRAY = new Message[0];
public enum RecipientType {
TO, CC, BCC,
}
protected String mUid;
private HashSet<Flag> mFlags = null;
protected Date mInternalDate;
protected Folder mFolder;
public String getUid() {
return mUid;
}
public void setUid(String uid) {
this.mUid = uid;
}
public Folder getFolder() {
return mFolder;
}
public abstract String getSubject() throws MessagingException;
public abstract void setSubject(String subject) throws MessagingException;
public Date getInternalDate() {
return mInternalDate;
}
public void setInternalDate(Date internalDate) {
this.mInternalDate = internalDate;
}
public abstract Date getReceivedDate() throws MessagingException;
public abstract Date getSentDate() throws MessagingException;
public abstract void setSentDate(Date sentDate) throws MessagingException;
public abstract Address[] getRecipients(RecipientType type) throws MessagingException;
public abstract void setRecipients(RecipientType type, Address[] addresses)
throws MessagingException;
public void setRecipient(RecipientType type, Address address) throws MessagingException {
setRecipients(type, new Address[] {
address
});
}
public abstract Address[] getFrom() throws MessagingException;
public abstract void setFrom(Address from) throws MessagingException;
public abstract Address[] getReplyTo() throws MessagingException;
public abstract void setReplyTo(Address[] from) throws MessagingException;
public abstract Body getBody() throws MessagingException;
public abstract String getContentType() throws MessagingException;
public abstract void addHeader(String name, String value) throws MessagingException;
public abstract void setHeader(String name, String value) throws MessagingException;
public abstract String[] getHeader(String name) throws MessagingException;
public abstract void removeHeader(String name) throws MessagingException;
// Always use these instead of getHeader("Message-ID") or setHeader("Message-ID");
public abstract void setMessageId(String messageId) throws MessagingException;
public abstract String getMessageId() throws MessagingException;
public abstract void setBody(Body body) throws MessagingException;
public boolean isMimeType(String mimeType) throws MessagingException {
return getContentType().startsWith(mimeType);
}
private HashSet<Flag> getFlagSet() {
if (mFlags == null) {
mFlags = new HashSet<Flag>();
}
return mFlags;
}
/*
* TODO Refactor Flags at some point to be able to store user defined flags.
*/
public Flag[] getFlags() {
return getFlagSet().toArray(new Flag[] {});
}
/**
* Set/clear a flag directly, without involving overrides of {@link #setFlag} in subclasses.
* Only used for testing.
*/
public final void setFlagDirectlyForTest(Flag flag, boolean set) throws MessagingException {
if (set) {
getFlagSet().add(flag);
} else {
getFlagSet().remove(flag);
}
}
public void setFlag(Flag flag, boolean set) throws MessagingException {
setFlagDirectlyForTest(flag, set);
}
/**
* This method calls setFlag(Flag, boolean)
* @param flags
* @param set
*/
public void setFlags(Flag[] flags, boolean set) throws MessagingException {
for (Flag flag : flags) {
setFlag(flag, set);
}
}
public boolean isSet(Flag flag) {
return getFlagSet().contains(flag);
}
public abstract void saveChanges() throws MessagingException;
@Override
public String toString() {
return getClass().getSimpleName() + ':' + mUid;
}
}

View File

@ -1,34 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon.mail;
import java.util.Comparator;
public class MessageDateComparator implements Comparator<Message> {
public int compare(Message o1, Message o2) {
try {
if (o1.getSentDate() == null) {
return 1;
} else if (o2.getSentDate() == null) {
return -1;
} else
return o2.getSentDate().compareTo(o1.getSentDate());
} catch (Exception e) {
return 0;
}
}
}

View File

@ -1,136 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon.mail;
/**
* This exception is used for most types of failures that occur during server interactions.
*
* Data passed through this exception should be considered non-localized. Any strings should
* either be internal-only (for debugging) or server-generated.
*
* TO DO: Does it make sense to further collapse AuthenticationFailedException and
* CertificateValidationException and any others into this?
*/
public class MessagingException extends Exception {
public static final long serialVersionUID = -1;
public static final int NO_ERROR = -1;
/** Any exception that does not specify a specific issue */
public static final int UNSPECIFIED_EXCEPTION = 0;
/** Connection or IO errors */
public static final int IOERROR = 1;
/** The configuration requested TLS but the server did not support it. */
public static final int TLS_REQUIRED = 2;
/** Authentication is required but the server did not support it. */
public static final int AUTH_REQUIRED = 3;
/** General security failures */
public static final int GENERAL_SECURITY = 4;
/** Authentication failed */
public static final int AUTHENTICATION_FAILED = 5;
/** Attempt to create duplicate account */
public static final int DUPLICATE_ACCOUNT = 6;
/** Required security policies reported - advisory only */
public static final int SECURITY_POLICIES_REQUIRED = 7;
/** Required security policies not supported */
public static final int SECURITY_POLICIES_UNSUPPORTED = 8;
/** The protocol (or protocol version) isn't supported */
public static final int PROTOCOL_VERSION_UNSUPPORTED = 9;
/** The server's SSL certificate couldn't be validated */
public static final int CERTIFICATE_VALIDATION_ERROR = 10;
/** Authentication failed during autodiscover */
public static final int AUTODISCOVER_AUTHENTICATION_FAILED = 11;
/** Autodiscover completed with a result (non-error) */
public static final int AUTODISCOVER_AUTHENTICATION_RESULT = 12;
/** Ambiguous failure; server error or bad credentials */
public static final int AUTHENTICATION_FAILED_OR_SERVER_ERROR = 13;
/** The server refused access */
public static final int ACCESS_DENIED = 14;
/** The server refused access */
public static final int ATTACHMENT_NOT_FOUND = 15;
/** A client SSL certificate is required for connections to the server */
public static final int CLIENT_CERTIFICATE_REQUIRED = 16;
/** The client SSL certificate specified is invalid */
public static final int CLIENT_CERTIFICATE_ERROR = 17;
protected int mExceptionType;
// Exception type-specific data
protected Object mExceptionData;
public MessagingException(String message, Throwable throwable) {
this(UNSPECIFIED_EXCEPTION, message, throwable);
}
public MessagingException(int exceptionType, String message, Throwable throwable) {
super(message, throwable);
mExceptionType = exceptionType;
mExceptionData = null;
}
/**
* Constructs a MessagingException with an exceptionType and a null message.
* @param exceptionType The exception type to set for this exception.
*/
public MessagingException(int exceptionType) {
this(exceptionType, null, null);
}
/**
* Constructs a MessagingException with a message.
* @param message the message for this exception
*/
public MessagingException(String message) {
this(UNSPECIFIED_EXCEPTION, message, null);
}
/**
* Constructs a MessagingException with an exceptionType and a message.
* @param exceptionType The exception type to set for this exception.
*/
public MessagingException(int exceptionType, String message) {
this(exceptionType, message, null);
}
/**
* Constructs a MessagingException with an exceptionType, a message, and data
* @param exceptionType The exception type to set for this exception.
* @param message the message for the exception (or null)
* @param data exception-type specific data for the exception (or null)
*/
public MessagingException(int exceptionType, String message, Object data) {
super(message);
mExceptionType = exceptionType;
mExceptionData = data;
}
/**
* Return the exception type. Will be OTHER_EXCEPTION if not explicitly set.
*
* @return Returns the exception type.
*/
public int getExceptionType() {
return mExceptionType;
}
/**
* Return the exception data. Will be null if not explicitly set.
*
* @return Returns the exception data.
*/
public Object getExceptionData() {
return mExceptionData;
}
}

View File

@ -1,63 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon.mail;
import java.util.ArrayList;
public abstract class Multipart implements Body {
protected Part mParent;
protected ArrayList<BodyPart> mParts = new ArrayList<BodyPart>();
protected String mContentType;
public void addBodyPart(BodyPart part) throws MessagingException {
mParts.add(part);
}
public void addBodyPart(BodyPart part, int index) throws MessagingException {
mParts.add(index, part);
}
public BodyPart getBodyPart(int index) throws MessagingException {
return mParts.get(index);
}
public String getContentType() throws MessagingException {
return mContentType;
}
public int getCount() throws MessagingException {
return mParts.size();
}
public boolean removeBodyPart(BodyPart part) throws MessagingException {
return mParts.remove(part);
}
public void removeBodyPart(int index) throws MessagingException {
mParts.remove(index);
}
public Part getParent() throws MessagingException {
return mParent;
}
public void setParent(Part parent) throws MessagingException {
this.mParent = parent;
}
}

View File

@ -1,176 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon.mail;
import java.util.HashMap;
import java.util.Map;
/**
* A utility class for creating and modifying Strings that are tagged and packed together.
*
* Uses non-printable (control chars) for internal delimiters; Intended for regular displayable
* strings only, so please use base64 or other encoding if you need to hide any binary data here.
*
* Binary compatible with Address.pack() format, which should migrate to use this code.
*/
public class PackedString {
/**
* Packing format is:
* element : [ value ] or [ value TAG-DELIMITER tag ]
* packed-string : [ element ] [ ELEMENT-DELIMITER [ element ] ]*
*/
private static final char DELIMITER_ELEMENT = '\1';
private static final char DELIMITER_TAG = '\2';
private String mString;
private HashMap<String, String> mExploded;
private static final HashMap<String, String> EMPTY_MAP = new HashMap<String, String>();
/**
* Create a packed string using an already-packed string (e.g. from database)
* @param string packed string
*/
public PackedString(String string) {
mString = string;
mExploded = null;
}
/**
* Get the value referred to by a given tag. If the tag does not exist, return null.
* @param tag identifier of string of interest
* @return returns value, or null if no string is found
*/
public String get(String tag) {
if (mExploded == null) {
mExploded = explode(mString);
}
return mExploded.get(tag);
}
/**
* Return a map of all of the values referred to by a given tag. This is a shallow
* copy, don't edit the values.
* @return a map of the values in the packed string
*/
public Map<String, String> unpack() {
if (mExploded == null) {
mExploded = explode(mString);
}
return new HashMap<String,String>(mExploded);
}
/**
* Read out all values into a map.
*/
private static HashMap<String, String> explode(String packed) {
if (packed == null || packed.length() == 0) {
return EMPTY_MAP;
}
HashMap<String, String> map = new HashMap<String, String>();
int length = packed.length();
int elementStartIndex = 0;
int elementEndIndex = 0;
int tagEndIndex = packed.indexOf(DELIMITER_TAG);
while (elementStartIndex < length) {
elementEndIndex = packed.indexOf(DELIMITER_ELEMENT, elementStartIndex);
if (elementEndIndex == -1) {
elementEndIndex = length;
}
String tag;
String value;
if (tagEndIndex == -1 || elementEndIndex <= tagEndIndex) {
// in this case the DELIMITER_PERSONAL is in a future pair (or not found)
// so synthesize a positional tag for the value, and don't update tagEndIndex
value = packed.substring(elementStartIndex, elementEndIndex);
tag = Integer.toString(map.size());
} else {
value = packed.substring(elementStartIndex, tagEndIndex);
tag = packed.substring(tagEndIndex + 1, elementEndIndex);
// scan forward for next tag, if any
tagEndIndex = packed.indexOf(DELIMITER_TAG, elementEndIndex + 1);
}
map.put(tag, value);
elementStartIndex = elementEndIndex + 1;
}
return map;
}
/**
* Builder class for creating PackedString values. Can also be used for editing existing
* PackedString representations.
*/
static public class Builder {
HashMap<String, String> mMap;
/**
* Create a builder that's empty (for filling)
*/
public Builder() {
mMap = new HashMap<String, String>();
}
/**
* Create a builder using the values of an existing PackedString (for editing).
*/
public Builder(String packed) {
mMap = explode(packed);
}
/**
* Add a tagged value
* @param tag identifier of string of interest
* @param value the value to record in this position. null to delete entry.
*/
public void put(String tag, String value) {
if (value == null) {
mMap.remove(tag);
} else {
mMap.put(tag, value);
}
}
/**
* Get the value referred to by a given tag. If the tag does not exist, return null.
* @param tag identifier of string of interest
* @return returns value, or null if no string is found
*/
public String get(String tag) {
return mMap.get(tag);
}
/**
* Pack the values and return a single, encoded string
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String,String> entry : mMap.entrySet()) {
if (sb.length() > 0) {
sb.append(DELIMITER_ELEMENT);
}
sb.append(entry.getValue());
sb.append(DELIMITER_TAG);
sb.append(entry.getKey());
}
return sb.toString();
}
}
}

View File

@ -1,52 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon.mail;
import java.io.IOException;
import java.io.OutputStream;
public interface Part extends Fetchable {
public void addHeader(String name, String value) throws MessagingException;
public void removeHeader(String name) throws MessagingException;
public void setHeader(String name, String value) throws MessagingException;
public Body getBody() throws MessagingException;
public String getContentType() throws MessagingException;
public String getDisposition() throws MessagingException;
public String getContentId() throws MessagingException;
public String[] getHeader(String name) throws MessagingException;
public void setExtendedHeader(String name, String value) throws MessagingException;
public String getExtendedHeader(String name) throws MessagingException;
public int getSize() throws MessagingException;
public boolean isMimeType(String mimeType) throws MessagingException;
public String getMimeType() throws MessagingException;
public void setBody(Body body) throws MessagingException;
public void writeTo(OutputStream out) throws IOException, MessagingException;
}

View File

@ -1,137 +0,0 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon.service;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.emailcommon.provider.Mailbox;
import com.google.common.base.Objects;
public class SearchParams implements Parcelable {
public static final long ALL_MAILBOXES = Mailbox.NO_MAILBOX;
private static final int DEFAULT_LIMIT = 10; // Need input on what this number should be
private static final int DEFAULT_OFFSET = 0;
// The id of the mailbox to be searched; if -1, all mailboxes MUST be searched
public final long mMailboxId;
// If true, all subfolders of the specified mailbox MUST be searched
public boolean mIncludeChildren = true;
// The search terms (the search MUST only select messages whose contents include all of the
// search terms in the query)
public final String mFilter;
// The maximum number of results to be created by this search
public int mLimit = DEFAULT_LIMIT;
// If zero, specifies a "new" search; otherwise, asks for a continuation of the previous
// query(ies) starting with the mOffset'th match (0 based)
public int mOffset = DEFAULT_OFFSET;
// The total number of results for this search
public int mTotalCount = 0;
// The id of the "search" mailbox being used
public long mSearchMailboxId;
/**
* Error codes returned by the searchMessages API
*/
public static class SearchParamsError {
public static final int CANT_SEARCH_ALL_MAILBOXES = -1;
public static final int CANT_SEARCH_CHILDREN = -2;
}
public SearchParams(long mailboxId, String filter) {
mMailboxId = mailboxId;
mFilter = filter;
}
public SearchParams(long mailboxId, String filter, long searchMailboxId) {
mMailboxId = mailboxId;
mFilter = filter;
mSearchMailboxId = searchMailboxId;
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if ((o == null) || !(o instanceof SearchParams)) {
return false;
}
SearchParams os = (SearchParams) o;
return mMailboxId == os.mMailboxId
&& mIncludeChildren == os.mIncludeChildren
&& mFilter.equals(os.mFilter)
&& mLimit == os.mLimit
&& mOffset == os.mOffset;
}
@Override
public int hashCode() {
return Objects.hashCode(mMailboxId, mFilter, mOffset);
}
@Override
public String toString() {
return "[SearchParams " + mMailboxId + ":" + mFilter + " (" + mOffset + ", " + mLimit + "]";
}
@Override
public int describeContents() {
return 0;
}
/**
* Supports Parcelable
*/
public static final Parcelable.Creator<SearchParams> CREATOR
= new Parcelable.Creator<SearchParams>() {
@Override
public SearchParams createFromParcel(Parcel in) {
return new SearchParams(in);
}
@Override
public SearchParams[] newArray(int size) {
return new SearchParams[size];
}
};
/**
* Supports Parcelable
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(mMailboxId);
dest.writeInt(mIncludeChildren ? 1 : 0);
dest.writeString(mFilter);
dest.writeInt(mLimit);
dest.writeInt(mOffset);
}
/**
* Supports Parcelable
*/
public SearchParams(Parcel in) {
mMailboxId = in.readLong();
mIncludeChildren = in.readInt() == 1;
mFilter = in.readString();
mLimit = in.readInt();
mOffset = in.readInt();
}
}

View File

@ -211,34 +211,6 @@ public class Utility {
}
}
/**
* Ensures that the given string starts and ends with the double quote character. The string is
* not modified in any way except to add the double quote character to start and end if it's not
* already there.
*
* TODO: Rename this, because "quoteString()" can mean so many different things.
*
* sample -> "sample"
* "sample" -> "sample"
* ""sample"" -> "sample"
* "sample"" -> "sample"
* sa"mp"le -> "sa"mp"le"
* "sa"mp"le" -> "sa"mp"le"
* (empty string) -> ""
* " -> ""
*/
public static String quoteString(String s) {
if (s == null) {
return null;
}
if (!s.matches("^\".*\"$")) {
return "\"" + s + "\"";
}
else {
return s;
}
}
/**
* A fast version of URLDecoder.decode() that works only with UTF-8 and does only two
* allocations. This version is around 3x as fast as the standard one and I'm using it