();
-
- /* package */ void add(ImapElement e) {
- if (e == null) {
- throw new RuntimeException("Can't add null");
- }
- mList.add(e);
- }
-
- @Override
- public final boolean isString() {
- return false;
- }
-
- @Override
- public final boolean isList() {
- return true;
- }
-
- public final int size() {
- return mList.size();
- }
-
- public final boolean isEmpty() {
- return size() == 0;
- }
-
- /**
- * Return true if the element at {@code index} exists, is string, and equals to {@code s}.
- * (case insensitive)
- */
- public final boolean is(int index, String s) {
- return is(index, s, false);
- }
-
- /**
- * Same as {@link #is(int, String)}, but does the prefix match if {@code prefixMatch}.
- */
- public final boolean is(int index, String s, boolean prefixMatch) {
- if (!prefixMatch) {
- return getStringOrEmpty(index).is(s);
- } else {
- return getStringOrEmpty(index).startsWith(s);
- }
- }
-
- /**
- * Return the element at {@code index}.
- * If {@code index} is out of range, returns {@link ImapElement#NONE}.
- */
- public final ImapElement getElementOrNone(int index) {
- return (index >= mList.size()) ? ImapElement.NONE : mList.get(index);
- }
-
- /**
- * Return the element at {@code index} if it's a list.
- * If {@code index} is out of range or not a list, returns {@link ImapList#EMPTY}.
- */
- public final ImapList getListOrEmpty(int index) {
- ImapElement el = getElementOrNone(index);
- return el.isList() ? (ImapList) el : EMPTY;
- }
-
- /**
- * Return the element at {@code index} if it's a string.
- * If {@code index} is out of range or not a string, returns {@link ImapString#EMPTY}.
- */
- public final ImapString getStringOrEmpty(int index) {
- ImapElement el = getElementOrNone(index);
- return el.isString() ? (ImapString) el : ImapString.EMPTY;
- }
-
- /**
- * Return an element keyed by {@code key}. Return null if not found. {@code key} has to be
- * at an even index.
- */
- /* package */ final ImapElement getKeyedElementOrNull(String key, boolean prefixMatch) {
- for (int i = 1; i < size(); i += 2) {
- if (is(i-1, key, prefixMatch)) {
- return mList.get(i);
- }
- }
- return null;
- }
-
- /**
- * Return an {@link ImapList} keyed by {@code key}.
- * Return {@link ImapList#EMPTY} if not found.
- */
- public final ImapList getKeyedListOrEmpty(String key) {
- return getKeyedListOrEmpty(key, false);
- }
-
- /**
- * Return an {@link ImapList} keyed by {@code key}.
- * Return {@link ImapList#EMPTY} if not found.
- */
- public final ImapList getKeyedListOrEmpty(String key, boolean prefixMatch) {
- ImapElement e = getKeyedElementOrNull(key, prefixMatch);
- return (e != null) ? ((ImapList) e) : ImapList.EMPTY;
- }
-
- /**
- * Return an {@link ImapString} keyed by {@code key}.
- * Return {@link ImapString#EMPTY} if not found.
- */
- public final ImapString getKeyedStringOrEmpty(String key) {
- return getKeyedStringOrEmpty(key, false);
- }
-
- /**
- * Return an {@link ImapString} keyed by {@code key}.
- * Return {@link ImapString#EMPTY} if not found.
- */
- public final ImapString getKeyedStringOrEmpty(String key, boolean prefixMatch) {
- ImapElement e = getKeyedElementOrNull(key, prefixMatch);
- return (e != null) ? ((ImapString) e) : ImapString.EMPTY;
- }
-
- /**
- * Return true if it contains {@code s}.
- */
- public final boolean contains(String s) {
- for (int i = 0; i < size(); i++) {
- if (getStringOrEmpty(i).is(s)) {
- return true;
- }
- }
- return false;
- }
-
- @Override
- public void destroy() {
- if (mList != null) {
- for (ImapElement e : mList) {
- e.destroy();
- }
- mList = null;
- }
- super.destroy();
- }
-
- @Override
- public String toString() {
- return mList.toString();
- }
-
- /**
- * Return the text representations of the contents concatenated with ",".
- */
- public final String flatten() {
- return flatten(new StringBuilder()).toString();
- }
-
- /**
- * Returns text representations (i.e. getString()) of contents joined together with
- * "," as the separator.
- *
- * Only used for building the capability string passed to vendor policies.
- *
- * We can't use toString(), because it's for debugging (meaning the format may change any time),
- * and it won't expand literals.
- */
- private final StringBuilder flatten(StringBuilder sb) {
- sb.append('[');
- for (int i = 0; i < mList.size(); i++) {
- if (i > 0) {
- sb.append(',');
- }
- final ImapElement e = getElementOrNone(i);
- if (e.isList()) {
- getListOrEmpty(i).flatten(sb);
- } else if (e.isString()) {
- sb.append(getStringOrEmpty(i).getString());
- }
- }
- sb.append(']');
- return sb;
- }
-
- @Override
- public boolean equalsForTest(ImapElement that) {
- if (!super.equalsForTest(that)) {
- return false;
- }
- ImapList thatList = (ImapList) that;
- if (size() != thatList.size()) {
- return false;
- }
- for (int i = 0; i < size(); i++) {
- if (!mList.get(i).equalsForTest(thatList.getElementOrNone(i))) {
- return false;
- }
- }
- return true;
- }
-}
diff --git a/src/com/android/email/mail/store/imap/ImapMemoryLiteral.java b/src/com/android/email/mail/store/imap/ImapMemoryLiteral.java
deleted file mode 100644
index ea62d52d1..000000000
--- a/src/com/android/email/mail/store/imap/ImapMemoryLiteral.java
+++ /dev/null
@@ -1,72 +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.email.mail.store.imap;
-
-import com.android.email.FixedLengthInputStream;
-import com.android.emailcommon.Logging;
-import com.android.emailcommon.utility.Utility;
-
-import android.util.Log;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Subclass of {@link ImapString} used for literals backed by an in-memory byte array.
- */
-public class ImapMemoryLiteral extends ImapString {
- private byte[] mData;
-
- /* package */ ImapMemoryLiteral(FixedLengthInputStream in) throws IOException {
- // We could use ByteArrayOutputStream and IOUtils.copy, but it'd perform an unnecessary
- // copy....
- mData = new byte[in.getLength()];
- int pos = 0;
- while (pos < mData.length) {
- int read = in.read(mData, pos, mData.length - pos);
- if (read < 0) {
- break;
- }
- pos += read;
- }
- if (pos != mData.length) {
- Log.w(Logging.LOG_TAG, "");
- }
- }
-
- @Override
- public void destroy() {
- mData = null;
- super.destroy();
- }
-
- @Override
- public String getString() {
- return Utility.fromAscii(mData);
- }
-
- @Override
- public InputStream getAsStream() {
- return new ByteArrayInputStream(mData);
- }
-
- @Override
- public String toString() {
- return String.format("{%d byte literal(memory)}", mData.length);
- }
-}
diff --git a/src/com/android/email/mail/store/imap/ImapResponse.java b/src/com/android/email/mail/store/imap/ImapResponse.java
deleted file mode 100644
index 05bf594e6..000000000
--- a/src/com/android/email/mail/store/imap/ImapResponse.java
+++ /dev/null
@@ -1,152 +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.email.mail.store.imap;
-
-
-/**
- * Class represents an IMAP response.
- */
-public class ImapResponse extends ImapList {
- private final String mTag;
- private final boolean mIsContinuationRequest;
-
- /* package */ ImapResponse(String tag, boolean isContinuationRequest) {
- mTag = tag;
- mIsContinuationRequest = isContinuationRequest;
- }
-
- /* package */ static boolean isStatusResponse(String symbol) {
- return ImapConstants.OK.equalsIgnoreCase(symbol)
- || ImapConstants.NO.equalsIgnoreCase(symbol)
- || ImapConstants.BAD.equalsIgnoreCase(symbol)
- || ImapConstants.PREAUTH.equalsIgnoreCase(symbol)
- || ImapConstants.BYE.equalsIgnoreCase(symbol);
- }
-
- /**
- * @return whether it's a tagged response.
- */
- public boolean isTagged() {
- return mTag != null;
- }
-
- /**
- * @return whether it's a continuation request.
- */
- public boolean isContinuationRequest() {
- return mIsContinuationRequest;
- }
-
- public boolean isStatusResponse() {
- return isStatusResponse(getStringOrEmpty(0).getString());
- }
-
- /**
- * @return whether it's an OK response.
- */
- public boolean isOk() {
- return is(0, ImapConstants.OK);
- }
-
- /**
- * @return whether it's an BAD response.
- */
- public boolean isBad() {
- return is(0, ImapConstants.BAD);
- }
-
- /**
- * @return whether it's an NO response.
- */
- public boolean isNo() {
- return is(0, ImapConstants.NO);
- }
-
- /**
- * @return whether it's an {@code responseType} data response. (i.e. not tagged).
- * @param index where {@code responseType} should appear. e.g. 1 for "FETCH"
- * @param responseType e.g. "FETCH"
- */
- public final boolean isDataResponse(int index, String responseType) {
- return !isTagged() && getStringOrEmpty(index).is(responseType);
- }
-
- /**
- * @return Response code (RFC 3501 7.1) if it's a status response.
- *
- * e.g. "ALERT" for "* OK [ALERT] System shutdown in 10 minutes"
- */
- public ImapString getResponseCodeOrEmpty() {
- if (!isStatusResponse()) {
- return ImapString.EMPTY; // Not a status response.
- }
- return getListOrEmpty(1).getStringOrEmpty(0);
- }
-
- /**
- * @return Alert message it it has ALERT response code.
- *
- * e.g. "System shutdown in 10 minutes" for "* OK [ALERT] System shutdown in 10 minutes"
- */
- public ImapString getAlertTextOrEmpty() {
- if (!getResponseCodeOrEmpty().is(ImapConstants.ALERT)) {
- return ImapString.EMPTY; // Not an ALERT
- }
- // The 3rd element contains all the rest of line.
- return getStringOrEmpty(2);
- }
-
- /**
- * @return Response text in a status response.
- */
- public ImapString getStatusResponseTextOrEmpty() {
- if (!isStatusResponse()) {
- return ImapString.EMPTY;
- }
- return getStringOrEmpty(getElementOrNone(1).isList() ? 2 : 1);
- }
-
- @Override
- public String toString() {
- String tag = mTag;
- if (isContinuationRequest()) {
- tag = "+";
- }
- return "#" + tag + "# " + super.toString();
- }
-
- @Override
- public boolean equalsForTest(ImapElement that) {
- if (!super.equalsForTest(that)) {
- return false;
- }
- final ImapResponse thatResponse = (ImapResponse) that;
- if (mTag == null) {
- if (thatResponse.mTag != null) {
- return false;
- }
- } else {
- if (!mTag.equals(thatResponse.mTag)) {
- return false;
- }
- }
- if (mIsContinuationRequest != thatResponse.mIsContinuationRequest) {
- return false;
- }
- return true;
- }
-}
diff --git a/src/com/android/email/mail/store/imap/ImapResponseParser.java b/src/com/android/email/mail/store/imap/ImapResponseParser.java
deleted file mode 100644
index 078cf9f76..000000000
--- a/src/com/android/email/mail/store/imap/ImapResponseParser.java
+++ /dev/null
@@ -1,450 +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.email.mail.store.imap;
-
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.email.FixedLengthInputStream;
-import com.android.email.PeekableInputStream;
-import com.android.email.mail.transport.DiscourseLogger;
-import com.android.email2.ui.MailActivityEmail;
-import com.android.emailcommon.Logging;
-import com.android.emailcommon.mail.MessagingException;
-import com.android.emailcommon.utility.LoggingInputStream;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-
-/**
- * IMAP response parser.
- */
-public class ImapResponseParser {
- private static final boolean DEBUG_LOG_RAW_STREAM = false; // DO NOT RELEASE AS 'TRUE'
-
- /**
- * Literal larger than this will be stored in temp file.
- */
- public static final int LITERAL_KEEP_IN_MEMORY_THRESHOLD = 2 * 1024 * 1024;
-
- /** Input stream */
- private final PeekableInputStream mIn;
-
- /**
- * To log network activities when the parser crashes.
- *
- * We log all bytes received from the server, except for the part sent as literals.
- */
- private final DiscourseLogger mDiscourseLogger;
-
- private final int mLiteralKeepInMemoryThreshold;
-
- /** StringBuilder used by readUntil() */
- private final StringBuilder mBufferReadUntil = new StringBuilder();
-
- /** StringBuilder used by parseBareString() */
- private final StringBuilder mParseBareString = new StringBuilder();
-
- /**
- * We store all {@link ImapResponse} in it. {@link #destroyResponses()} must be called from
- * time to time to destroy them and clear it.
- */
- private final ArrayList mResponsesToDestroy = new ArrayList();
-
- /**
- * Exception thrown when we receive BYE. It derives from IOException, so it'll be treated
- * in the same way EOF does.
- */
- public static class ByeException extends IOException {
- public static final String MESSAGE = "Received BYE";
- public ByeException() {
- super(MESSAGE);
- }
- }
-
- /**
- * Public constructor for normal use.
- */
- public ImapResponseParser(InputStream in, DiscourseLogger discourseLogger) {
- this(in, discourseLogger, LITERAL_KEEP_IN_MEMORY_THRESHOLD);
- }
-
- /**
- * Constructor for testing to override the literal size threshold.
- */
- /* package for test */ ImapResponseParser(InputStream in, DiscourseLogger discourseLogger,
- int literalKeepInMemoryThreshold) {
- if (DEBUG_LOG_RAW_STREAM && MailActivityEmail.DEBUG) {
- in = new LoggingInputStream(in);
- }
- mIn = new PeekableInputStream(in);
- mDiscourseLogger = discourseLogger;
- mLiteralKeepInMemoryThreshold = literalKeepInMemoryThreshold;
- }
-
- private static IOException newEOSException() {
- final String message = "End of stream reached";
- if (MailActivityEmail.DEBUG) {
- Log.d(Logging.LOG_TAG, message);
- }
- return new IOException(message);
- }
-
- /**
- * Peek next one byte.
- *
- * Throws IOException() if reaches EOF. As long as logical response lines end with \r\n,
- * we shouldn't see EOF during parsing.
- */
- private int peek() throws IOException {
- final int next = mIn.peek();
- if (next == -1) {
- throw newEOSException();
- }
- return next;
- }
-
- /**
- * Read and return one byte from {@link #mIn}, and put it in {@link #mDiscourseLogger}.
- *
- * Throws IOException() if reaches EOF. As long as logical response lines end with \r\n,
- * we shouldn't see EOF during parsing.
- */
- private int readByte() throws IOException {
- int next = mIn.read();
- if (next == -1) {
- throw newEOSException();
- }
- mDiscourseLogger.addReceivedByte(next);
- return next;
- }
-
- /**
- * Destroy all the {@link ImapResponse}s stored in the internal storage and clear it.
- *
- * @see #readResponse()
- */
- public void destroyResponses() {
- for (ImapResponse r : mResponsesToDestroy) {
- r.destroy();
- }
- mResponsesToDestroy.clear();
- }
-
- /**
- * Reads the next response available on the stream and returns an
- * {@link ImapResponse} object that represents it.
- *
- * When this method successfully returns an {@link ImapResponse}, the {@link ImapResponse}
- * is stored in the internal storage. When the {@link ImapResponse} is no longer used
- * {@link #destroyResponses} should be called to destroy all the responses in the array.
- *
- * @return the parsed {@link ImapResponse} object.
- * @exception ByeException when detects BYE.
- */
- public ImapResponse readResponse() throws IOException, MessagingException {
- ImapResponse response = null;
- try {
- response = parseResponse();
- if (MailActivityEmail.DEBUG) {
- Log.d(Logging.LOG_TAG, "<<< " + response.toString());
- }
-
- } catch (RuntimeException e) {
- // Parser crash -- log network activities.
- onParseError(e);
- throw e;
- } catch (IOException e) {
- // Network error, or received an unexpected char.
- onParseError(e);
- throw e;
- }
-
- // Handle this outside of try-catch. We don't have to dump protocol log when getting BYE.
- if (response.is(0, ImapConstants.BYE)) {
- Log.w(Logging.LOG_TAG, ByeException.MESSAGE);
- response.destroy();
- throw new ByeException();
- }
- mResponsesToDestroy.add(response);
- return response;
- }
-
- private void onParseError(Exception e) {
- // Read a few more bytes, so that the log will contain some more context, even if the parser
- // crashes in the middle of a response.
- // This also makes sure the byte in question will be logged, no matter where it crashes.
- // e.g. when parseAtom() peeks and finds at an unexpected char, it throws an exception
- // before actually reading it.
- // However, we don't want to read too much, because then it may get into an email message.
- try {
- for (int i = 0; i < 4; i++) {
- int b = readByte();
- if (b == -1 || b == '\n') {
- break;
- }
- }
- } catch (IOException ignore) {
- }
- Log.w(Logging.LOG_TAG, "Exception detected: " + e.getMessage());
- mDiscourseLogger.logLastDiscourse();
- }
-
- /**
- * Read next byte from stream and throw it away. If the byte is different from {@code expected}
- * throw {@link MessagingException}.
- */
- /* package for test */ void expect(char expected) throws IOException {
- final int next = readByte();
- if (expected != next) {
- throw new IOException(String.format("Expected %04x (%c) but got %04x (%c)",
- (int) expected, expected, next, (char) next));
- }
- }
-
- /**
- * Read bytes until we find {@code end}, and return all as string.
- * The {@code end} will be read (rather than peeked) and won't be included in the result.
- */
- /* package for test */ String readUntil(char end) throws IOException {
- mBufferReadUntil.setLength(0);
- for (;;) {
- final int ch = readByte();
- if (ch != end) {
- mBufferReadUntil.append((char) ch);
- } else {
- return mBufferReadUntil.toString();
- }
- }
- }
-
- /**
- * Read all bytes until \r\n.
- */
- /* package */ String readUntilEol() throws IOException {
- String ret = readUntil('\r');
- expect('\n'); // TODO Should this really be error?
- return ret;
- }
-
- /**
- * Parse and return the response line.
- */
- private ImapResponse parseResponse() throws IOException, MessagingException {
- // We need to destroy the response if we get an exception.
- // So, we first store the response that's being built in responseToDestroy, until it's
- // completely built, at which point we copy it into responseToReturn and null out
- // responseToDestroyt.
- // If responseToDestroy is not null in finally, we destroy it because that means
- // we got an exception somewhere.
- ImapResponse responseToDestroy = null;
- final ImapResponse responseToReturn;
-
- try {
- final int ch = peek();
- if (ch == '+') { // Continuation request
- readByte(); // skip +
- expect(' ');
- responseToDestroy = new ImapResponse(null, true);
-
- // If it's continuation request, we don't really care what's in it.
- responseToDestroy.add(new ImapSimpleString(readUntilEol()));
-
- // Response has successfully been built. Let's return it.
- responseToReturn = responseToDestroy;
- responseToDestroy = null;
- } else {
- // Status response or response data
- final String tag;
- if (ch == '*') {
- tag = null;
- readByte(); // skip *
- expect(' ');
- } else {
- tag = readUntil(' ');
- }
- responseToDestroy = new ImapResponse(tag, false);
-
- final ImapString firstString = parseBareString();
- responseToDestroy.add(firstString);
-
- // parseBareString won't eat a space after the string, so we need to skip it,
- // if exists.
- // If the next char is not ' ', it should be EOL.
- if (peek() == ' ') {
- readByte(); // skip ' '
-
- if (responseToDestroy.isStatusResponse()) { // It's a status response
-
- // Is there a response code?
- final int next = peek();
- if (next == '[') {
- responseToDestroy.add(parseList('[', ']'));
- if (peek() == ' ') { // Skip following space
- readByte();
- }
- }
-
- String rest = readUntilEol();
- if (!TextUtils.isEmpty(rest)) {
- // The rest is free-form text.
- responseToDestroy.add(new ImapSimpleString(rest));
- }
- } else { // It's a response data.
- parseElements(responseToDestroy, '\0');
- }
- } else {
- expect('\r');
- expect('\n');
- }
-
- // Response has successfully been built. Let's return it.
- responseToReturn = responseToDestroy;
- responseToDestroy = null;
- }
- } finally {
- if (responseToDestroy != null) {
- // We get an exception.
- responseToDestroy.destroy();
- }
- }
-
- return responseToReturn;
- }
-
- private ImapElement parseElement() throws IOException, MessagingException {
- final int next = peek();
- switch (next) {
- case '(':
- return parseList('(', ')');
- case '[':
- return parseList('[', ']');
- case '"':
- readByte(); // Skip "
- return new ImapSimpleString(readUntil('"'));
- case '{':
- return parseLiteral();
- case '\r': // CR
- readByte(); // Consume \r
- expect('\n'); // Should be followed by LF.
- return null;
- case '\n': // LF // There shouldn't be a bare LF, but just in case.
- readByte(); // Consume \n
- return null;
- default:
- return parseBareString();
- }
- }
-
- /**
- * Parses an atom.
- *
- * Special case: If an atom contains '[', everything until the next ']' will be considered
- * a part of the atom.
- * (e.g. "BODY[HEADER.FIELDS ("DATE" ...)]" will become a single ImapString)
- *
- * If the value is "NIL", returns an empty string.
- */
- private ImapString parseBareString() throws IOException, MessagingException {
- mParseBareString.setLength(0);
- for (;;) {
- final int ch = peek();
-
- // TODO Can we clean this up? (This condition is from the old parser.)
- if (ch == '(' || ch == ')' || ch == '{' || ch == ' ' ||
- // ']' is not part of atom (it's in resp-specials)
- ch == ']' ||
- // docs claim that flags are \ atom but atom isn't supposed to
- // contain
- // * and some flags contain *
- // ch == '%' || ch == '*' ||
- ch == '%' ||
- // TODO probably should not allow \ and should recognize
- // it as a flag instead
- // ch == '"' || ch == '\' ||
- ch == '"' || (0x00 <= ch && ch <= 0x1f) || ch == 0x7f) {
- if (mParseBareString.length() == 0) {
- throw new MessagingException("Expected string, none found.");
- }
- String s = mParseBareString.toString();
-
- // NIL will be always converted into the empty string.
- if (ImapConstants.NIL.equalsIgnoreCase(s)) {
- return ImapString.EMPTY;
- }
- return new ImapSimpleString(s);
- } else if (ch == '[') {
- // Eat all until next ']'
- mParseBareString.append((char) readByte());
- mParseBareString.append(readUntil(']'));
- mParseBareString.append(']'); // readUntil won't include the end char.
- } else {
- mParseBareString.append((char) readByte());
- }
- }
- }
-
- private void parseElements(ImapList list, char end)
- throws IOException, MessagingException {
- for (;;) {
- for (;;) {
- final int next = peek();
- if (next == end) {
- return;
- }
- if (next != ' ') {
- break;
- }
- // Skip space
- readByte();
- }
- final ImapElement el = parseElement();
- if (el == null) { // EOL
- return;
- }
- list.add(el);
- }
- }
-
- private ImapList parseList(char opening, char closing)
- throws IOException, MessagingException {
- expect(opening);
- final ImapList list = new ImapList();
- parseElements(list, closing);
- expect(closing);
- return list;
- }
-
- private ImapString parseLiteral() throws IOException, MessagingException {
- expect('{');
- final int size;
- try {
- size = Integer.parseInt(readUntil('}'));
- } catch (NumberFormatException nfe) {
- throw new MessagingException("Invalid length in literal");
- }
- expect('\r');
- expect('\n');
- FixedLengthInputStream in = new FixedLengthInputStream(mIn, size);
- if (size > mLiteralKeepInMemoryThreshold) {
- return new ImapTempFileLiteral(in);
- } else {
- return new ImapMemoryLiteral(in);
- }
- }
-}
diff --git a/src/com/android/email/mail/store/imap/ImapSimpleString.java b/src/com/android/email/mail/store/imap/ImapSimpleString.java
deleted file mode 100644
index 190c5237f..000000000
--- a/src/com/android/email/mail/store/imap/ImapSimpleString.java
+++ /dev/null
@@ -1,55 +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.email.mail.store.imap;
-
-import com.android.emailcommon.utility.Utility;
-
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-
-/**
- * Subclass of {@link ImapString} used for non literals.
- */
-public class ImapSimpleString extends ImapString {
- private String mString;
-
- /* package */ ImapSimpleString(String string) {
- mString = (string != null) ? string : "";
- }
-
- @Override
- public void destroy() {
- mString = null;
- super.destroy();
- }
-
- @Override
- public String getString() {
- return mString;
- }
-
- @Override
- public InputStream getAsStream() {
- return new ByteArrayInputStream(Utility.toAscii(mString));
- }
-
- @Override
- public String toString() {
- // Purposefully not return just mString, in order to prevent using it instead of getString.
- return "\"" + mString + "\"";
- }
-}
diff --git a/src/com/android/email/mail/store/imap/ImapString.java b/src/com/android/email/mail/store/imap/ImapString.java
deleted file mode 100644
index b0ee99d84..000000000
--- a/src/com/android/email/mail/store/imap/ImapString.java
+++ /dev/null
@@ -1,187 +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.email.mail.store.imap;
-
-import com.android.emailcommon.Logging;
-
-import android.util.Log;
-
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-
-/**
- * Class represents an IMAP "element" that is not a list.
- *
- * An atom, quoted string, literal, are all represented by this. Values like OK, STATUS are too.
- * Also, this class class may contain more arbitrary value like "BODY[HEADER.FIELDS ("DATE")]".
- * See {@link ImapResponseParser}.
- */
-public abstract class ImapString extends ImapElement {
- private static final byte[] EMPTY_BYTES = new byte[0];
-
- public static final ImapString EMPTY = new ImapString() {
- @Override public void destroy() {
- // Don't call super.destroy().
- // It's a shared object. We don't want the mDestroyed to be set on this.
- }
-
- @Override public String getString() {
- return "";
- }
-
- @Override public InputStream getAsStream() {
- return new ByteArrayInputStream(EMPTY_BYTES);
- }
-
- @Override public String toString() {
- return "";
- }
- };
-
- // This is used only for parsing IMAP's FETCH ENVELOPE command, in which
- // en_US-like date format is used like "01-Jan-2009 11:20:39 -0800", so this should be
- // handled by Locale.US
- private final static SimpleDateFormat DATE_TIME_FORMAT =
- new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss Z", Locale.US);
-
- private boolean mIsInteger;
- private int mParsedInteger;
- private Date mParsedDate;
-
- @Override
- public final boolean isList() {
- return false;
- }
-
- @Override
- public final boolean isString() {
- return true;
- }
-
- /**
- * @return true if and only if the length of the string is larger than 0.
- *
- * Note: IMAP NIL is considered an empty string. See {@link ImapResponseParser
- * #parseBareString}.
- * On the other hand, a quoted/literal string with value NIL (i.e. "NIL" and {3}\r\nNIL) is
- * treated literally.
- */
- public final boolean isEmpty() {
- return getString().length() == 0;
- }
-
- public abstract String getString();
-
- public abstract InputStream getAsStream();
-
- /**
- * @return whether it can be parsed as a number.
- */
- public final boolean isNumber() {
- if (mIsInteger) {
- return true;
- }
- try {
- mParsedInteger = Integer.parseInt(getString());
- mIsInteger = true;
- return true;
- } catch (NumberFormatException e) {
- return false;
- }
- }
-
- /**
- * @return value parsed as a number.
- */
- public final int getNumberOrZero() {
- if (!isNumber()) {
- return 0;
- }
- return mParsedInteger;
- }
-
- /**
- * @return whether it can be parsed as a date using {@link #DATE_TIME_FORMAT}.
- */
- public final boolean isDate() {
- if (mParsedDate != null) {
- return true;
- }
- if (isEmpty()) {
- return false;
- }
- try {
- mParsedDate = DATE_TIME_FORMAT.parse(getString());
- return true;
- } catch (ParseException e) {
- Log.w(Logging.LOG_TAG, getString() + " can't be parsed as a date.");
- return false;
- }
- }
-
- /**
- * @return value it can be parsed as a {@link Date}, or null otherwise.
- */
- public final Date getDateOrNull() {
- if (!isDate()) {
- return null;
- }
- return mParsedDate;
- }
-
- /**
- * @return whether the value case-insensitively equals to {@code s}.
- */
- public final boolean is(String s) {
- if (s == null) {
- return false;
- }
- return getString().equalsIgnoreCase(s);
- }
-
-
- /**
- * @return whether the value case-insensitively starts with {@code s}.
- */
- public final boolean startsWith(String prefix) {
- if (prefix == null) {
- return false;
- }
- final String me = this.getString();
- if (me.length() < prefix.length()) {
- return false;
- }
- return me.substring(0, prefix.length()).equalsIgnoreCase(prefix);
- }
-
- // To force subclasses to implement it.
- @Override
- public abstract String toString();
-
- @Override
- public final boolean equalsForTest(ImapElement that) {
- if (!super.equalsForTest(that)) {
- return false;
- }
- ImapString thatString = (ImapString) that;
- return getString().equals(thatString.getString());
- }
-}
diff --git a/src/com/android/email/mail/store/imap/ImapTempFileLiteral.java b/src/com/android/email/mail/store/imap/ImapTempFileLiteral.java
deleted file mode 100644
index eda1b568e..000000000
--- a/src/com/android/email/mail/store/imap/ImapTempFileLiteral.java
+++ /dev/null
@@ -1,124 +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.email.mail.store.imap;
-
-import android.util.Log;
-
-import com.android.email.FixedLengthInputStream;
-import com.android.emailcommon.Logging;
-import com.android.emailcommon.TempDirectory;
-import com.android.emailcommon.utility.Utility;
-
-import org.apache.commons.io.IOUtils;
-
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- * Subclass of {@link ImapString} used for literals backed by a temp file.
- */
-public class ImapTempFileLiteral extends ImapString {
- /* package for test */ final File mFile;
-
- /** Size is purely for toString() */
- private final int mSize;
-
- /* package */ ImapTempFileLiteral(FixedLengthInputStream stream) throws IOException {
- mSize = stream.getLength();
- mFile = File.createTempFile("imap", ".tmp", TempDirectory.getTempDirectory());
-
- // Unfortunately, we can't really use deleteOnExit(), because temp filenames are random
- // so it'd simply cause a memory leak.
- // deleteOnExit() simply adds filenames to a static list and the list will never shrink.
- // mFile.deleteOnExit();
- OutputStream out = new FileOutputStream(mFile);
- IOUtils.copy(stream, out);
- out.close();
- }
-
- /**
- * Make sure we delete the temp file.
- *
- * We should always be calling {@link ImapResponse#destroy()}, but it's here as a last resort.
- */
- @Override
- protected void finalize() throws Throwable {
- try {
- destroy();
- } finally {
- super.finalize();
- }
- }
-
- @Override
- public InputStream getAsStream() {
- checkNotDestroyed();
- try {
- return new FileInputStream(mFile);
- } catch (FileNotFoundException e) {
- // It's probably possible if we're low on storage and the system clears the cache dir.
- Log.w(Logging.LOG_TAG, "ImapTempFileLiteral: Temp file not found");
-
- // Return 0 byte stream as a dummy...
- return new ByteArrayInputStream(new byte[0]);
- }
- }
-
- @Override
- public String getString() {
- checkNotDestroyed();
- try {
- byte[] bytes = IOUtils.toByteArray(getAsStream());
- // Prevent crash from OOM; we've seen this, but only rarely and not reproducibly
- if (bytes.length > ImapResponseParser.LITERAL_KEEP_IN_MEMORY_THRESHOLD) {
- throw new IOException();
- }
- return Utility.fromAscii(bytes);
- } catch (IOException e) {
- Log.w(Logging.LOG_TAG, "ImapTempFileLiteral: Error while reading temp file", e);
- return "";
- }
- }
-
- @Override
- public void destroy() {
- try {
- if (!isDestroyed() && mFile.exists()) {
- mFile.delete();
- }
- } catch (RuntimeException re) {
- // Just log and ignore.
- Log.w(Logging.LOG_TAG, "Failed to remove temp file: " + re.getMessage());
- }
- super.destroy();
- }
-
- @Override
- public String toString() {
- return String.format("{%d byte literal(file)}", mSize);
- }
-
- public boolean tempFileExistsForTest() {
- return mFile.exists();
- }
-}
diff --git a/src/com/android/email/mail/store/imap/ImapUtility.java b/src/com/android/email/mail/store/imap/ImapUtility.java
deleted file mode 100644
index dc7e98e96..000000000
--- a/src/com/android/email/mail/store/imap/ImapUtility.java
+++ /dev/null
@@ -1,127 +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.email.mail.store.imap;
-
-import com.android.emailcommon.Logging;
-
-import android.util.Log;
-
-import java.util.ArrayList;
-
-/**
- * Utility methods for use with IMAP.
- */
-public class ImapUtility {
- /**
- * Apply quoting rules per IMAP RFC,
- * quoted = DQUOTE *QUOTED-CHAR DQUOTE
- * QUOTED-CHAR = / "\" quoted-specials
- * quoted-specials = DQUOTE / "\"
- *
- * This is used primarily for IMAP login, but might be useful elsewhere.
- *
- * NOTE: Not very efficient - you may wish to preflight this, or perhaps it should check
- * for trouble chars before calling the replace functions.
- *
- * @param s The string to be quoted.
- * @return A copy of the string, having undergone quoting as described above
- */
- public static String imapQuoted(String s) {
-
- // First, quote any backslashes by replacing \ with \\
- // regex Pattern: \\ (Java string const = \\\\)
- // Substitute: \\\\ (Java string const = \\\\\\\\)
- String result = s.replaceAll("\\\\", "\\\\\\\\");
-
- // Then, quote any double-quotes by replacing " with \"
- // regex Pattern: " (Java string const = \")
- // Substitute: \\" (Java string const = \\\\\")
- result = result.replaceAll("\"", "\\\\\"");
-
- // return string with quotes around it
- return "\"" + result + "\"";
- }
-
- /**
- * Gets all of the values in a sequence set per RFC 3501. Any ranges are expanded into a
- * list of individual numbers. If the set is invalid, an empty array is returned.
- *
- * sequence-number = nz-number / "*"
- * sequence-range = sequence-number ":" sequence-number
- * sequence-set = (sequence-number / sequence-range) *("," sequence-set)
- *
- */
- public static String[] getImapSequenceValues(String set) {
- ArrayList list = new ArrayList();
- if (set != null) {
- String[] setItems = set.split(",");
- for (String item : setItems) {
- if (item.indexOf(':') == -1) {
- // simple item
- try {
- Integer.parseInt(item); // Don't need the value; just ensure it's valid
- list.add(item);
- } catch (NumberFormatException e) {
- Log.d(Logging.LOG_TAG, "Invalid UID value", e);
- }
- } else {
- // range
- for (String rangeItem : getImapRangeValues(item)) {
- list.add(rangeItem);
- }
- }
- }
- }
- String[] stringList = new String[list.size()];
- return list.toArray(stringList);
- }
-
- /**
- * Expand the given number range into a list of individual numbers. If the range is not valid,
- * an empty array is returned.
- *
- * sequence-number = nz-number / "*"
- * sequence-range = sequence-number ":" sequence-number
- * sequence-set = (sequence-number / sequence-range) *("," sequence-set)
- *
- */
- public static String[] getImapRangeValues(String range) {
- ArrayList list = new ArrayList();
- try {
- if (range != null) {
- int colonPos = range.indexOf(':');
- if (colonPos > 0) {
- int first = Integer.parseInt(range.substring(0, colonPos));
- int second = Integer.parseInt(range.substring(colonPos + 1));
- if (first < second) {
- for (int i = first; i <= second; i++) {
- list.add(Integer.toString(i));
- }
- } else {
- for (int i = first; i >= second; i--) {
- list.add(Integer.toString(i));
- }
- }
- }
- }
- } catch (NumberFormatException e) {
- Log.d(Logging.LOG_TAG, "Invalid range value", e);
- }
- String[] stringList = new String[list.size()];
- return list.toArray(stringList);
- }
-}
diff --git a/src/com/android/email/provider/AttachmentProvider.java b/src/com/android/email/provider/AttachmentProvider.java
index 71955851e..25945e1d3 100644
--- a/src/com/android/email/provider/AttachmentProvider.java
+++ b/src/com/android/email/provider/AttachmentProvider.java
@@ -49,10 +49,10 @@ import java.util.List;
* A simple ContentProvider that allows file access to Email's attachments.
*
* The URI scheme is as follows. For raw file access:
- * content://com.android.email.attachmentprovider/acct#/attach#/RAW
+ * content://com.android.mail.attachmentprovider/acct#/attach#/RAW
*
* And for access to thumbnails:
- * content://com.android.email.attachmentprovider/acct#/attach#/THUMBNAIL/width#/height#
+ * content://com.android.mail.attachmentprovider/acct#/attach#/THUMBNAIL/width#/height#
*
* The on-disk (storage) schema is as follows.
*
diff --git a/src/com/android/email/provider/DBHelper.java b/src/com/android/email/provider/DBHelper.java
index b3cecfd83..f0e44bf92 100644
--- a/src/com/android/email/provider/DBHelper.java
+++ b/src/com/android/email/provider/DBHelper.java
@@ -29,7 +29,6 @@ import android.provider.ContactsContract;
import android.util.Log;
import com.android.email2.ui.MailActivityEmail;
-import com.android.emailcommon.AccountManagerTypes;
import com.android.emailcommon.mail.Address;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.EmailContent;
@@ -586,7 +585,7 @@ public final class DBHelper {
// Versions >= 5 require that data be preserved!
if (oldVersion < 5) {
android.accounts.Account[] accounts = AccountManager.get(mContext)
- .getAccountsByType(AccountManagerTypes.TYPE_EXCHANGE);
+ .getAccountsByType("eas");
for (android.accounts.Account account: accounts) {
AccountManager.get(mContext).removeAccount(account, null, null);
}
@@ -1092,8 +1091,9 @@ public final class DBHelper {
static private void createAccountManagerAccount(Context context, String login,
String password) {
AccountManager accountManager = AccountManager.get(context);
+ // STOPSHIP
android.accounts.Account amAccount =
- new android.accounts.Account(login, AccountManagerTypes.TYPE_POP_IMAP);
+ new android.accounts.Account(login, "com.android.email");
accountManager.addAccountExplicitly(amAccount, password, null);
ContentResolver.setIsSyncable(amAccount, EmailContent.AUTHORITY, 1);
ContentResolver.setSyncAutomatically(amAccount, EmailContent.AUTHORITY, true);
@@ -1135,7 +1135,7 @@ public final class DBHelper {
android.accounts.Account amAccount =
new android.accounts.Account(
accountCursor.getString(V21_ACCOUNT_EMAIL),
- AccountManagerTypes.TYPE_EXCHANGE);
+ "eas");
ContentResolver.setIsSyncable(amAccount, EmailContent.AUTHORITY, 1);
ContentResolver.setSyncAutomatically(amAccount,
EmailContent.AUTHORITY, true);
diff --git a/src/com/android/email/provider/EmailProvider.java b/src/com/android/email/provider/EmailProvider.java
index 040183653..60a0dfd26 100644
--- a/src/com/android/email/provider/EmailProvider.java
+++ b/src/com/android/email/provider/EmailProvider.java
@@ -136,15 +136,6 @@ public class EmailProvider extends ContentProvider {
public static final String EMAIL_ATTACHMENT_MIME_TYPE =
"vnd.android.cursor.item/email-attachment";
- public static final Uri INTEGRITY_CHECK_URI =
- Uri.parse("content://" + EmailContent.AUTHORITY + "/integrityCheck");
- public static final Uri ACCOUNT_BACKUP_URI =
- Uri.parse("content://" + EmailContent.AUTHORITY + "/accountBackup");
- public static final Uri FOLDER_STATUS_URI =
- Uri.parse("content://" + EmailContent.AUTHORITY + "/status");
- public static final Uri FOLDER_REFRESH_URI =
- Uri.parse("content://" + EmailContent.AUTHORITY + "/refresh");
-
/** Appended to the notification URI for delete operations */
public static final String NOTIFICATION_OP_DELETE = "delete";
/** Appended to the notification URI for insert operations */
@@ -310,7 +301,7 @@ public class EmailProvider extends ContentProvider {
null // UI
};
- private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+ private static UriMatcher sURIMatcher = null;
private static final String MAILBOX_PRE_CACHE_SELECTION = MailboxColumns.TYPE + " IN (" +
Mailbox.TYPE_INBOX + "," + Mailbox.TYPE_DRAFTS + "," + Mailbox.TYPE_TRASH + "," +
@@ -341,7 +332,7 @@ public class EmailProvider extends ContentProvider {
private static final String ID_EQUALS = EmailContent.RECORD_ID + "=?";
- private static final ContentValues CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT;
+ private static ContentValues CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT;
private static final ContentValues EMPTY_CONTENT_VALUES = new ContentValues();
public static final String MESSAGE_URI_PARAMETER_MAILBOX_ID = "mailboxId";
@@ -357,130 +348,6 @@ public class EmailProvider extends ContentProvider {
private static final String SWIPE_DELETE = Integer.toString(Swipe.DELETE);
private static final String SWIPE_DISABLED = Integer.toString(Swipe.DISABLED);
- static {
- // Email URI matching table
- UriMatcher matcher = sURIMatcher;
-
- // All accounts
- matcher.addURI(EmailContent.AUTHORITY, "account", ACCOUNT);
- // A specific account
- // insert into this URI causes a mailbox to be added to the account
- matcher.addURI(EmailContent.AUTHORITY, "account/#", ACCOUNT_ID);
- matcher.addURI(EmailContent.AUTHORITY, "account/default", ACCOUNT_DEFAULT_ID);
- matcher.addURI(EmailContent.AUTHORITY, "accountCheck/#", ACCOUNT_CHECK);
-
- // Special URI to reset the new message count. Only update works, and content values
- // will be ignored.
- matcher.addURI(EmailContent.AUTHORITY, "resetNewMessageCount",
- ACCOUNT_RESET_NEW_COUNT);
- matcher.addURI(EmailContent.AUTHORITY, "resetNewMessageCount/#",
- ACCOUNT_RESET_NEW_COUNT_ID);
-
- // All mailboxes
- matcher.addURI(EmailContent.AUTHORITY, "mailbox", MAILBOX);
- // A specific mailbox
- // insert into this URI causes a message to be added to the mailbox
- // ** NOTE For now, the accountKey must be set manually in the values!
- matcher.addURI(EmailContent.AUTHORITY, "mailbox/#", MAILBOX_ID);
- matcher.addURI(EmailContent.AUTHORITY, "mailboxIdFromAccountAndType/#/#",
- MAILBOX_ID_FROM_ACCOUNT_AND_TYPE);
- matcher.addURI(EmailContent.AUTHORITY, "mailboxNotification/#", MAILBOX_NOTIFICATION);
- matcher.addURI(EmailContent.AUTHORITY, "mailboxMostRecentMessage/#",
- MAILBOX_MOST_RECENT_MESSAGE);
-
- // All messages
- matcher.addURI(EmailContent.AUTHORITY, "message", MESSAGE);
- // A specific message
- // insert into this URI causes an attachment to be added to the message
- matcher.addURI(EmailContent.AUTHORITY, "message/#", MESSAGE_ID);
-
- // A specific attachment
- matcher.addURI(EmailContent.AUTHORITY, "attachment", ATTACHMENT);
- // A specific attachment (the header information)
- matcher.addURI(EmailContent.AUTHORITY, "attachment/#", ATTACHMENT_ID);
- // The attachments of a specific message (query only) (insert & delete TBD)
- matcher.addURI(EmailContent.AUTHORITY, "attachment/message/#",
- ATTACHMENTS_MESSAGE_ID);
-
- // All mail bodies
- matcher.addURI(EmailContent.AUTHORITY, "body", BODY);
- // A specific mail body
- matcher.addURI(EmailContent.AUTHORITY, "body/#", BODY_ID);
-
- // All hostauth records
- matcher.addURI(EmailContent.AUTHORITY, "hostauth", HOSTAUTH);
- // A specific hostauth
- matcher.addURI(EmailContent.AUTHORITY, "hostauth/*", HOSTAUTH_ID);
-
- // Atomically a constant value to a particular field of a mailbox/account
- matcher.addURI(EmailContent.AUTHORITY, "mailboxIdAddToField/#",
- MAILBOX_ID_ADD_TO_FIELD);
- matcher.addURI(EmailContent.AUTHORITY, "accountIdAddToField/#",
- ACCOUNT_ID_ADD_TO_FIELD);
-
- /**
- * THIS URI HAS SPECIAL SEMANTICS
- * ITS USE IS INTENDED FOR THE UI APPLICATION TO MARK CHANGES THAT NEED TO BE SYNCED BACK
- * TO A SERVER VIA A SYNC ADAPTER
- */
- matcher.addURI(EmailContent.AUTHORITY, "syncedMessage/#", SYNCED_MESSAGE_ID);
- matcher.addURI(EmailContent.AUTHORITY, "messageBySelection", MESSAGE_SELECTION);
-
- /**
- * THE URIs BELOW THIS POINT ARE INTENDED TO BE USED BY SYNC ADAPTERS ONLY
- * THEY REFER TO DATA CREATED AND MAINTAINED BY CALLS TO THE SYNCED_MESSAGE_ID URI
- * BY THE UI APPLICATION
- */
- // All deleted messages
- matcher.addURI(EmailContent.AUTHORITY, "deletedMessage", DELETED_MESSAGE);
- // A specific deleted message
- matcher.addURI(EmailContent.AUTHORITY, "deletedMessage/#", DELETED_MESSAGE_ID);
-
- // All updated messages
- matcher.addURI(EmailContent.AUTHORITY, "updatedMessage", UPDATED_MESSAGE);
- // A specific updated message
- matcher.addURI(EmailContent.AUTHORITY, "updatedMessage/#", UPDATED_MESSAGE_ID);
-
- CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT = new ContentValues();
- CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT.put(Account.NEW_MESSAGE_COUNT, 0);
-
- matcher.addURI(EmailContent.AUTHORITY, "policy", POLICY);
- matcher.addURI(EmailContent.AUTHORITY, "policy/#", POLICY_ID);
-
- // All quick responses
- matcher.addURI(EmailContent.AUTHORITY, "quickresponse", QUICK_RESPONSE);
- // A specific quick response
- matcher.addURI(EmailContent.AUTHORITY, "quickresponse/#", QUICK_RESPONSE_ID);
- // All quick responses associated with a particular account id
- matcher.addURI(EmailContent.AUTHORITY, "quickresponse/account/#",
- QUICK_RESPONSE_ACCOUNT_ID);
-
- matcher.addURI(EmailContent.AUTHORITY, "uifolders/#", UI_FOLDERS);
- matcher.addURI(EmailContent.AUTHORITY, "uiallfolders/#", UI_ALL_FOLDERS);
- matcher.addURI(EmailContent.AUTHORITY, "uisubfolders/#", UI_SUBFOLDERS);
- matcher.addURI(EmailContent.AUTHORITY, "uimessages/#", UI_MESSAGES);
- matcher.addURI(EmailContent.AUTHORITY, "uimessage/#", UI_MESSAGE);
- matcher.addURI(EmailContent.AUTHORITY, "uisendmail/#", UI_SENDMAIL);
- matcher.addURI(EmailContent.AUTHORITY, "uiundo", UI_UNDO);
- matcher.addURI(EmailContent.AUTHORITY, "uisavedraft/#", UI_SAVEDRAFT);
- matcher.addURI(EmailContent.AUTHORITY, "uiupdatedraft/#", UI_UPDATEDRAFT);
- matcher.addURI(EmailContent.AUTHORITY, "uisenddraft/#", UI_SENDDRAFT);
- matcher.addURI(EmailContent.AUTHORITY, "uirefresh/#", UI_FOLDER_REFRESH);
- matcher.addURI(EmailContent.AUTHORITY, "uifolder/#", UI_FOLDER);
- matcher.addURI(EmailContent.AUTHORITY, "uiaccount/#", UI_ACCOUNT);
- matcher.addURI(EmailContent.AUTHORITY, "uiaccts", UI_ACCTS);
- matcher.addURI(EmailContent.AUTHORITY, "uiattachments/#", UI_ATTACHMENTS);
- matcher.addURI(EmailContent.AUTHORITY, "uiattachment/#", UI_ATTACHMENT);
- matcher.addURI(EmailContent.AUTHORITY, "uisearch/#", UI_SEARCH);
- matcher.addURI(EmailContent.AUTHORITY, "uiaccountdata/#", UI_ACCOUNT_DATA);
- matcher.addURI(EmailContent.AUTHORITY, "uiloadmore/#", UI_FOLDER_LOAD_MORE);
- matcher.addURI(EmailContent.AUTHORITY, "uiconversation/#", UI_CONVERSATION);
- matcher.addURI(EmailContent.AUTHORITY, "uirecentfolders/#", UI_RECENT_FOLDERS);
- matcher.addURI(EmailContent.AUTHORITY, "uidefaultrecentfolders/#",
- UI_DEFAULT_RECENT_FOLDERS);
- matcher.addURI(EmailContent.AUTHORITY, "pickTrashFolder/#", ACCOUNT_PICK_TRASH_FOLDER);
- matcher.addURI(EmailContent.AUTHORITY, "pickSentFolder/#", ACCOUNT_PICK_SENT_FOLDER);
- }
/**
* Wrap the UriMatcher call so we can throw a runtime exception if an unknown Uri is passed in
@@ -497,6 +364,11 @@ public class EmailProvider extends ContentProvider {
return match;
}
+ public static Uri INTEGRITY_CHECK_URI;
+ public static Uri ACCOUNT_BACKUP_URI;
+ public static Uri FOLDER_STATUS_URI;
+ public static Uri FOLDER_REFRESH_URI;
+
private SQLiteDatabase mDatabase;
private SQLiteDatabase mBodyDatabase;
@@ -1171,10 +1043,147 @@ public class EmailProvider extends ContentProvider {
return resultUri;
}
- @Override
- public boolean onCreate() {
- MailActivityEmail.setServicesEnabledAsync(getContext());
- checkDatabases();
+ @Override
+ public boolean onCreate() {
+ Context context = getContext();
+ EmailContent.init(context);
+ if (INTEGRITY_CHECK_URI == null) {
+ INTEGRITY_CHECK_URI = Uri.parse("content://" + EmailContent.AUTHORITY +
+ "/integrityCheck");
+ ACCOUNT_BACKUP_URI =
+ Uri.parse("content://" + EmailContent.AUTHORITY + "/accountBackup");
+ FOLDER_STATUS_URI =
+ Uri.parse("content://" + EmailContent.AUTHORITY + "/status");
+ FOLDER_REFRESH_URI =
+ Uri.parse("content://" + EmailContent.AUTHORITY + "/refresh");
+ }
+ MailActivityEmail.setServicesEnabledAsync(context);
+ checkDatabases();
+ if (sURIMatcher == null) {
+ sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+ // Email URI matching table
+ UriMatcher matcher = sURIMatcher;
+
+ // All accounts
+ matcher.addURI(EmailContent.AUTHORITY, "account", ACCOUNT);
+ // A specific account
+ // insert into this URI causes a mailbox to be added to the account
+ matcher.addURI(EmailContent.AUTHORITY, "account/#", ACCOUNT_ID);
+ matcher.addURI(EmailContent.AUTHORITY, "account/default", ACCOUNT_DEFAULT_ID);
+ matcher.addURI(EmailContent.AUTHORITY, "accountCheck/#", ACCOUNT_CHECK);
+
+ // Special URI to reset the new message count. Only update works, and content values
+ // will be ignored.
+ matcher.addURI(EmailContent.AUTHORITY, "resetNewMessageCount",
+ ACCOUNT_RESET_NEW_COUNT);
+ matcher.addURI(EmailContent.AUTHORITY, "resetNewMessageCount/#",
+ ACCOUNT_RESET_NEW_COUNT_ID);
+
+ // All mailboxes
+ matcher.addURI(EmailContent.AUTHORITY, "mailbox", MAILBOX);
+ // A specific mailbox
+ // insert into this URI causes a message to be added to the mailbox
+ // ** NOTE For now, the accountKey must be set manually in the values!
+ matcher.addURI(EmailContent.AUTHORITY, "mailbox/#", MAILBOX_ID);
+ matcher.addURI(EmailContent.AUTHORITY, "mailboxIdFromAccountAndType/#/#",
+ MAILBOX_ID_FROM_ACCOUNT_AND_TYPE);
+ matcher.addURI(EmailContent.AUTHORITY, "mailboxNotification/#", MAILBOX_NOTIFICATION);
+ matcher.addURI(EmailContent.AUTHORITY, "mailboxMostRecentMessage/#",
+ MAILBOX_MOST_RECENT_MESSAGE);
+
+ // All messages
+ matcher.addURI(EmailContent.AUTHORITY, "message", MESSAGE);
+ // A specific message
+ // insert into this URI causes an attachment to be added to the message
+ matcher.addURI(EmailContent.AUTHORITY, "message/#", MESSAGE_ID);
+
+ // A specific attachment
+ matcher.addURI(EmailContent.AUTHORITY, "attachment", ATTACHMENT);
+ // A specific attachment (the header information)
+ matcher.addURI(EmailContent.AUTHORITY, "attachment/#", ATTACHMENT_ID);
+ // The attachments of a specific message (query only) (insert & delete TBD)
+ matcher.addURI(EmailContent.AUTHORITY, "attachment/message/#",
+ ATTACHMENTS_MESSAGE_ID);
+
+ // All mail bodies
+ matcher.addURI(EmailContent.AUTHORITY, "body", BODY);
+ // A specific mail body
+ matcher.addURI(EmailContent.AUTHORITY, "body/#", BODY_ID);
+
+ // All hostauth records
+ matcher.addURI(EmailContent.AUTHORITY, "hostauth", HOSTAUTH);
+ // A specific hostauth
+ matcher.addURI(EmailContent.AUTHORITY, "hostauth/*", HOSTAUTH_ID);
+
+ // Atomically a constant value to a particular field of a mailbox/account
+ matcher.addURI(EmailContent.AUTHORITY, "mailboxIdAddToField/#",
+ MAILBOX_ID_ADD_TO_FIELD);
+ matcher.addURI(EmailContent.AUTHORITY, "accountIdAddToField/#",
+ ACCOUNT_ID_ADD_TO_FIELD);
+
+ /**
+ * THIS URI HAS SPECIAL SEMANTICS
+ * ITS USE IS INTENDED FOR THE UI TO MARK CHANGES THAT NEED TO BE SYNCED BACK
+ * TO A SERVER VIA A SYNC ADAPTER
+ */
+ matcher.addURI(EmailContent.AUTHORITY, "syncedMessage/#", SYNCED_MESSAGE_ID);
+ matcher.addURI(EmailContent.AUTHORITY, "messageBySelection", MESSAGE_SELECTION);
+
+ /**
+ * THE URIs BELOW THIS POINT ARE INTENDED TO BE USED BY SYNC ADAPTERS ONLY
+ * THEY REFER TO DATA CREATED AND MAINTAINED BY CALLS TO THE SYNCED_MESSAGE_ID URI
+ * BY THE UI APPLICATION
+ */
+ // All deleted messages
+ matcher.addURI(EmailContent.AUTHORITY, "deletedMessage", DELETED_MESSAGE);
+ // A specific deleted message
+ matcher.addURI(EmailContent.AUTHORITY, "deletedMessage/#", DELETED_MESSAGE_ID);
+
+ // All updated messages
+ matcher.addURI(EmailContent.AUTHORITY, "updatedMessage", UPDATED_MESSAGE);
+ // A specific updated message
+ matcher.addURI(EmailContent.AUTHORITY, "updatedMessage/#", UPDATED_MESSAGE_ID);
+
+ CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT = new ContentValues();
+ CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT.put(Account.NEW_MESSAGE_COUNT, 0);
+
+ matcher.addURI(EmailContent.AUTHORITY, "policy", POLICY);
+ matcher.addURI(EmailContent.AUTHORITY, "policy/#", POLICY_ID);
+
+ // All quick responses
+ matcher.addURI(EmailContent.AUTHORITY, "quickresponse", QUICK_RESPONSE);
+ // A specific quick response
+ matcher.addURI(EmailContent.AUTHORITY, "quickresponse/#", QUICK_RESPONSE_ID);
+ // All quick responses associated with a particular account id
+ matcher.addURI(EmailContent.AUTHORITY, "quickresponse/account/#",
+ QUICK_RESPONSE_ACCOUNT_ID);
+
+ matcher.addURI(EmailContent.AUTHORITY, "uifolders/#", UI_FOLDERS);
+ matcher.addURI(EmailContent.AUTHORITY, "uiallfolders/#", UI_ALL_FOLDERS);
+ matcher.addURI(EmailContent.AUTHORITY, "uisubfolders/#", UI_SUBFOLDERS);
+ matcher.addURI(EmailContent.AUTHORITY, "uimessages/#", UI_MESSAGES);
+ matcher.addURI(EmailContent.AUTHORITY, "uimessage/#", UI_MESSAGE);
+ matcher.addURI(EmailContent.AUTHORITY, "uisendmail/#", UI_SENDMAIL);
+ matcher.addURI(EmailContent.AUTHORITY, "uiundo", UI_UNDO);
+ matcher.addURI(EmailContent.AUTHORITY, "uisavedraft/#", UI_SAVEDRAFT);
+ matcher.addURI(EmailContent.AUTHORITY, "uiupdatedraft/#", UI_UPDATEDRAFT);
+ matcher.addURI(EmailContent.AUTHORITY, "uisenddraft/#", UI_SENDDRAFT);
+ matcher.addURI(EmailContent.AUTHORITY, "uirefresh/#", UI_FOLDER_REFRESH);
+ matcher.addURI(EmailContent.AUTHORITY, "uifolder/#", UI_FOLDER);
+ matcher.addURI(EmailContent.AUTHORITY, "uiaccount/#", UI_ACCOUNT);
+ matcher.addURI(EmailContent.AUTHORITY, "uiaccts", UI_ACCTS);
+ matcher.addURI(EmailContent.AUTHORITY, "uiattachments/#", UI_ATTACHMENTS);
+ matcher.addURI(EmailContent.AUTHORITY, "uiattachment/#", UI_ATTACHMENT);
+ matcher.addURI(EmailContent.AUTHORITY, "uisearch/#", UI_SEARCH);
+ matcher.addURI(EmailContent.AUTHORITY, "uiaccountdata/#", UI_ACCOUNT_DATA);
+ matcher.addURI(EmailContent.AUTHORITY, "uiloadmore/#", UI_FOLDER_LOAD_MORE);
+ matcher.addURI(EmailContent.AUTHORITY, "uiconversation/#", UI_CONVERSATION);
+ matcher.addURI(EmailContent.AUTHORITY, "uirecentfolders/#", UI_RECENT_FOLDERS);
+ matcher.addURI(EmailContent.AUTHORITY, "uidefaultrecentfolders/#",
+ UI_DEFAULT_RECENT_FOLDERS);
+ matcher.addURI(EmailContent.AUTHORITY, "pickTrashFolder/#", ACCOUNT_PICK_TRASH_FOLDER);
+ matcher.addURI(EmailContent.AUTHORITY, "pickSentFolder/#", ACCOUNT_PICK_SENT_FOLDER);
+ }
return false;
}
@@ -2121,28 +2130,34 @@ outer:
* Mapping of UIProvider columns to EmailProvider columns for the message list (called the
* conversation list in UnifiedEmail)
*/
- private static final ProjectionMap sMessageListMap = ProjectionMap.builder()
- .add(BaseColumns._ID, MessageColumns.ID)
- .add(UIProvider.ConversationColumns.URI, uriWithId("uimessage"))
- .add(UIProvider.ConversationColumns.MESSAGE_LIST_URI, uriWithId("uimessage"))
- .add(UIProvider.ConversationColumns.SUBJECT, MessageColumns.SUBJECT)
- .add(UIProvider.ConversationColumns.SNIPPET, MessageColumns.SNIPPET)
- .add(UIProvider.ConversationColumns.CONVERSATION_INFO, null)
- .add(UIProvider.ConversationColumns.DATE_RECEIVED_MS, MessageColumns.TIMESTAMP)
- .add(UIProvider.ConversationColumns.HAS_ATTACHMENTS, MessageColumns.FLAG_ATTACHMENT)
- .add(UIProvider.ConversationColumns.NUM_MESSAGES, "1")
- .add(UIProvider.ConversationColumns.NUM_DRAFTS, "0")
- .add(UIProvider.ConversationColumns.SENDING_STATE,
- Integer.toString(ConversationSendingState.OTHER))
- .add(UIProvider.ConversationColumns.PRIORITY, Integer.toString(ConversationPriority.LOW))
- .add(UIProvider.ConversationColumns.READ, MessageColumns.FLAG_READ)
- .add(UIProvider.ConversationColumns.STARRED, MessageColumns.FLAG_FAVORITE)
- .add(UIProvider.ConversationColumns.FLAGS, CONVERSATION_FLAGS)
- .add(UIProvider.ConversationColumns.ACCOUNT_URI,
- "'content://" + EmailContent.AUTHORITY + "/uiaccount/' || "
- + MessageColumns.ACCOUNT_KEY)
- .add(UIProvider.ConversationColumns.SENDER_INFO, MessageColumns.FROM_LIST)
- .build();
+ private ProjectionMap getMessageListMap() {
+ if (sMessageListMap == null) {
+ sMessageListMap = ProjectionMap.builder()
+ .add(BaseColumns._ID, MessageColumns.ID)
+ .add(UIProvider.ConversationColumns.URI, uriWithId("uimessage"))
+ .add(UIProvider.ConversationColumns.MESSAGE_LIST_URI, uriWithId("uimessage"))
+ .add(UIProvider.ConversationColumns.SUBJECT, MessageColumns.SUBJECT)
+ .add(UIProvider.ConversationColumns.SNIPPET, MessageColumns.SNIPPET)
+ .add(UIProvider.ConversationColumns.CONVERSATION_INFO, null)
+ .add(UIProvider.ConversationColumns.DATE_RECEIVED_MS, MessageColumns.TIMESTAMP)
+ .add(UIProvider.ConversationColumns.HAS_ATTACHMENTS, MessageColumns.FLAG_ATTACHMENT)
+ .add(UIProvider.ConversationColumns.NUM_MESSAGES, "1")
+ .add(UIProvider.ConversationColumns.NUM_DRAFTS, "0")
+ .add(UIProvider.ConversationColumns.SENDING_STATE,
+ Integer.toString(ConversationSendingState.OTHER))
+ .add(UIProvider.ConversationColumns.PRIORITY,
+ Integer.toString(ConversationPriority.LOW))
+ .add(UIProvider.ConversationColumns.READ, MessageColumns.FLAG_READ)
+ .add(UIProvider.ConversationColumns.STARRED, MessageColumns.FLAG_FAVORITE)
+ .add(UIProvider.ConversationColumns.FLAGS, CONVERSATION_FLAGS)
+ .add(UIProvider.ConversationColumns.ACCOUNT_URI,
+ uriWithColumn("uiaccount", MessageColumns.ACCOUNT_KEY))
+ .add(UIProvider.ConversationColumns.SENDER_INFO, MessageColumns.FROM_LIST)
+ .build();
+ }
+ return sMessageListMap;
+ }
+ private static ProjectionMap sMessageListMap;
/**
* Generate UIProvider draft type; note the test for "reply all" must come before "reply"
@@ -2167,45 +2182,53 @@ outer:
* Mapping of UIProvider columns to EmailProvider columns for a detailed message view in
* UnifiedEmail
*/
- private static final ProjectionMap sMessageViewMap = ProjectionMap.builder()
- .add(BaseColumns._ID, Message.TABLE_NAME + "." + EmailContent.MessageColumns.ID)
- .add(UIProvider.MessageColumns.SERVER_ID, SyncColumns.SERVER_ID)
- .add(UIProvider.MessageColumns.URI, uriWithFQId("uimessage", Message.TABLE_NAME))
- .add(UIProvider.MessageColumns.CONVERSATION_ID,
- uriWithFQId("uimessage", Message.TABLE_NAME))
- .add(UIProvider.MessageColumns.SUBJECT, EmailContent.MessageColumns.SUBJECT)
- .add(UIProvider.MessageColumns.SNIPPET, EmailContent.MessageColumns.SNIPPET)
- .add(UIProvider.MessageColumns.FROM, EmailContent.MessageColumns.FROM_LIST)
- .add(UIProvider.MessageColumns.TO, EmailContent.MessageColumns.TO_LIST)
- .add(UIProvider.MessageColumns.CC, EmailContent.MessageColumns.CC_LIST)
- .add(UIProvider.MessageColumns.BCC, EmailContent.MessageColumns.BCC_LIST)
- .add(UIProvider.MessageColumns.REPLY_TO, EmailContent.MessageColumns.REPLY_TO_LIST)
- .add(UIProvider.MessageColumns.DATE_RECEIVED_MS, EmailContent.MessageColumns.TIMESTAMP)
- .add(UIProvider.MessageColumns.BODY_HTML, Body.HTML_CONTENT)
- .add(UIProvider.MessageColumns.BODY_TEXT, Body.TEXT_CONTENT)
- .add(UIProvider.MessageColumns.REF_MESSAGE_ID, "0")
- .add(UIProvider.MessageColumns.DRAFT_TYPE, NOT_A_DRAFT_STRING)
- .add(UIProvider.MessageColumns.APPEND_REF_MESSAGE_CONTENT, "0")
- .add(UIProvider.MessageColumns.HAS_ATTACHMENTS, EmailContent.MessageColumns.FLAG_ATTACHMENT)
- .add(UIProvider.MessageColumns.ATTACHMENT_LIST_URI,
- uriWithFQId("uiattachments", Message.TABLE_NAME))
- .add(UIProvider.MessageColumns.MESSAGE_FLAGS, MESSAGE_FLAGS)
- .add(UIProvider.MessageColumns.SAVE_MESSAGE_URI,
- uriWithFQId("uiupdatedraft", Message.TABLE_NAME))
- .add(UIProvider.MessageColumns.SEND_MESSAGE_URI,
- uriWithFQId("uisenddraft", Message.TABLE_NAME))
- .add(UIProvider.MessageColumns.DRAFT_TYPE, MESSAGE_DRAFT_TYPE)
- .add(UIProvider.MessageColumns.MESSAGE_ACCOUNT_URI,
- uriWithColumn("account", MessageColumns.ACCOUNT_KEY))
- .add(UIProvider.MessageColumns.STARRED, EmailContent.MessageColumns.FLAG_FAVORITE)
- .add(UIProvider.MessageColumns.READ, EmailContent.MessageColumns.FLAG_READ)
- .add(UIProvider.MessageColumns.SPAM_WARNING_STRING, null)
- .add(UIProvider.MessageColumns.SPAM_WARNING_LEVEL,
- Integer.toString(UIProvider.SpamWarningLevel.NO_WARNING))
- .add(UIProvider.MessageColumns.SPAM_WARNING_LINK_TYPE,
- Integer.toString(UIProvider.SpamWarningLinkType.NO_LINK))
- .add(UIProvider.MessageColumns.VIA_DOMAIN, null)
- .build();
+ private ProjectionMap getMessageViewMap() {
+ if (sMessageViewMap == null) {
+ sMessageViewMap = ProjectionMap.builder()
+ .add(BaseColumns._ID, Message.TABLE_NAME + "." + EmailContent.MessageColumns.ID)
+ .add(UIProvider.MessageColumns.SERVER_ID, SyncColumns.SERVER_ID)
+ .add(UIProvider.MessageColumns.URI, uriWithFQId("uimessage", Message.TABLE_NAME))
+ .add(UIProvider.MessageColumns.CONVERSATION_ID,
+ uriWithFQId("uimessage", Message.TABLE_NAME))
+ .add(UIProvider.MessageColumns.SUBJECT, EmailContent.MessageColumns.SUBJECT)
+ .add(UIProvider.MessageColumns.SNIPPET, EmailContent.MessageColumns.SNIPPET)
+ .add(UIProvider.MessageColumns.FROM, EmailContent.MessageColumns.FROM_LIST)
+ .add(UIProvider.MessageColumns.TO, EmailContent.MessageColumns.TO_LIST)
+ .add(UIProvider.MessageColumns.CC, EmailContent.MessageColumns.CC_LIST)
+ .add(UIProvider.MessageColumns.BCC, EmailContent.MessageColumns.BCC_LIST)
+ .add(UIProvider.MessageColumns.REPLY_TO, EmailContent.MessageColumns.REPLY_TO_LIST)
+ .add(UIProvider.MessageColumns.DATE_RECEIVED_MS,
+ EmailContent.MessageColumns.TIMESTAMP)
+ .add(UIProvider.MessageColumns.BODY_HTML, Body.HTML_CONTENT)
+ .add(UIProvider.MessageColumns.BODY_TEXT, Body.TEXT_CONTENT)
+ .add(UIProvider.MessageColumns.REF_MESSAGE_ID, "0")
+ .add(UIProvider.MessageColumns.DRAFT_TYPE, NOT_A_DRAFT_STRING)
+ .add(UIProvider.MessageColumns.APPEND_REF_MESSAGE_CONTENT, "0")
+ .add(UIProvider.MessageColumns.HAS_ATTACHMENTS,
+ EmailContent.MessageColumns.FLAG_ATTACHMENT)
+ .add(UIProvider.MessageColumns.ATTACHMENT_LIST_URI,
+ uriWithFQId("uiattachments", Message.TABLE_NAME))
+ .add(UIProvider.MessageColumns.MESSAGE_FLAGS, MESSAGE_FLAGS)
+ .add(UIProvider.MessageColumns.SAVE_MESSAGE_URI,
+ uriWithFQId("uiupdatedraft", Message.TABLE_NAME))
+ .add(UIProvider.MessageColumns.SEND_MESSAGE_URI,
+ uriWithFQId("uisenddraft", Message.TABLE_NAME))
+ .add(UIProvider.MessageColumns.DRAFT_TYPE, MESSAGE_DRAFT_TYPE)
+ .add(UIProvider.MessageColumns.MESSAGE_ACCOUNT_URI,
+ uriWithColumn("account", MessageColumns.ACCOUNT_KEY))
+ .add(UIProvider.MessageColumns.STARRED, EmailContent.MessageColumns.FLAG_FAVORITE)
+ .add(UIProvider.MessageColumns.READ, EmailContent.MessageColumns.FLAG_READ)
+ .add(UIProvider.MessageColumns.SPAM_WARNING_STRING, null)
+ .add(UIProvider.MessageColumns.SPAM_WARNING_LEVEL,
+ Integer.toString(UIProvider.SpamWarningLevel.NO_WARNING))
+ .add(UIProvider.MessageColumns.SPAM_WARNING_LINK_TYPE,
+ Integer.toString(UIProvider.SpamWarningLinkType.NO_LINK))
+ .add(UIProvider.MessageColumns.VIA_DOMAIN, null)
+ .build();
+ }
+ return sMessageViewMap;
+ }
+ private static ProjectionMap sMessageViewMap;
/**
* Generate UIProvider folder capabilities from mailbox flags
@@ -2236,50 +2259,62 @@ outer:
+ " WHEN " + Mailbox.TYPE_STARRED + " THEN " + R.drawable.ic_menu_star_holo_light
+ " ELSE -1 END";
- private static final ProjectionMap sFolderListMap = ProjectionMap.builder()
- .add(BaseColumns._ID, MailboxColumns.ID)
- .add(UIProvider.FolderColumns.URI, uriWithId("uifolder"))
- .add(UIProvider.FolderColumns.NAME, "displayName")
- .add(UIProvider.FolderColumns.HAS_CHILDREN,
- MailboxColumns.FLAGS + "&" + Mailbox.FLAG_HAS_CHILDREN)
- .add(UIProvider.FolderColumns.CAPABILITIES, FOLDER_CAPABILITIES)
- .add(UIProvider.FolderColumns.SYNC_WINDOW, "3")
- .add(UIProvider.FolderColumns.CONVERSATION_LIST_URI, uriWithId("uimessages"))
- .add(UIProvider.FolderColumns.CHILD_FOLDERS_LIST_URI, uriWithId("uisubfolders"))
- .add(UIProvider.FolderColumns.UNREAD_COUNT, MailboxColumns.UNREAD_COUNT)
- .add(UIProvider.FolderColumns.TOTAL_COUNT, MailboxColumns.MESSAGE_COUNT)
- .add(UIProvider.FolderColumns.REFRESH_URI, uriWithId("uirefresh"))
- .add(UIProvider.FolderColumns.SYNC_STATUS, MailboxColumns.UI_SYNC_STATUS)
- .add(UIProvider.FolderColumns.LAST_SYNC_RESULT, MailboxColumns.UI_LAST_SYNC_RESULT)
- .add(UIProvider.FolderColumns.TYPE, FOLDER_TYPE)
- .add(UIProvider.FolderColumns.ICON_RES_ID, FOLDER_ICON)
- .add(UIProvider.FolderColumns.HIERARCHICAL_DESC, MailboxColumns.HIERARCHICAL_NAME)
- .build();
+ private ProjectionMap getFolderListMap() {
+ if (sFolderListMap == null) {
+ sFolderListMap = ProjectionMap.builder()
+ .add(BaseColumns._ID, MailboxColumns.ID)
+ .add(UIProvider.FolderColumns.URI, uriWithId("uifolder"))
+ .add(UIProvider.FolderColumns.NAME, "displayName")
+ .add(UIProvider.FolderColumns.HAS_CHILDREN,
+ MailboxColumns.FLAGS + "&" + Mailbox.FLAG_HAS_CHILDREN)
+ .add(UIProvider.FolderColumns.CAPABILITIES, FOLDER_CAPABILITIES)
+ .add(UIProvider.FolderColumns.SYNC_WINDOW, "3")
+ .add(UIProvider.FolderColumns.CONVERSATION_LIST_URI, uriWithId("uimessages"))
+ .add(UIProvider.FolderColumns.CHILD_FOLDERS_LIST_URI, uriWithId("uisubfolders"))
+ .add(UIProvider.FolderColumns.UNREAD_COUNT, MailboxColumns.UNREAD_COUNT)
+ .add(UIProvider.FolderColumns.TOTAL_COUNT, MailboxColumns.MESSAGE_COUNT)
+ .add(UIProvider.FolderColumns.REFRESH_URI, uriWithId("uirefresh"))
+ .add(UIProvider.FolderColumns.SYNC_STATUS, MailboxColumns.UI_SYNC_STATUS)
+ .add(UIProvider.FolderColumns.LAST_SYNC_RESULT, MailboxColumns.UI_LAST_SYNC_RESULT)
+ .add(UIProvider.FolderColumns.TYPE, FOLDER_TYPE)
+ .add(UIProvider.FolderColumns.ICON_RES_ID, FOLDER_ICON)
+ .add(UIProvider.FolderColumns.HIERARCHICAL_DESC, MailboxColumns.HIERARCHICAL_NAME)
+ .build();
+ }
+ return sFolderListMap;
+ }
+ private static ProjectionMap sFolderListMap;
- private static final ProjectionMap sAccountListMap = ProjectionMap.builder()
- .add(BaseColumns._ID, AccountColumns.ID)
- .add(UIProvider.AccountColumns.FOLDER_LIST_URI, uriWithId("uifolders"))
- .add(UIProvider.AccountColumns.FULL_FOLDER_LIST_URI, uriWithId("uiallfolders"))
- .add(UIProvider.AccountColumns.NAME, AccountColumns.DISPLAY_NAME)
- .add(UIProvider.AccountColumns.SAVE_DRAFT_URI, uriWithId("uisavedraft"))
- .add(UIProvider.AccountColumns.SEND_MAIL_URI, uriWithId("uisendmail"))
- .add(UIProvider.AccountColumns.UNDO_URI,
- ("'content://" + UIProvider.AUTHORITY + "/uiundo'"))
- .add(UIProvider.AccountColumns.URI, uriWithId("uiaccount"))
- .add(UIProvider.AccountColumns.SEARCH_URI, uriWithId("uisearch"))
- // TODO: Is provider version used?
- .add(UIProvider.AccountColumns.PROVIDER_VERSION, "1")
- .add(UIProvider.AccountColumns.SYNC_STATUS, "0")
- .add(UIProvider.AccountColumns.RECENT_FOLDER_LIST_URI, uriWithId("uirecentfolders"))
- .add(UIProvider.AccountColumns.DEFAULT_RECENT_FOLDER_LIST_URI,
- uriWithId("uidefaultrecentfolders"))
- .add(UIProvider.AccountColumns.SettingsColumns.SIGNATURE, AccountColumns.SIGNATURE)
- .add(UIProvider.AccountColumns.SettingsColumns.SNAP_HEADERS,
- Integer.toString(UIProvider.SnapHeaderValue.ALWAYS))
- .add(UIProvider.AccountColumns.SettingsColumns.REPLY_BEHAVIOR,
- Integer.toString(UIProvider.DefaultReplyBehavior.REPLY))
- .add(UIProvider.AccountColumns.SettingsColumns.CONFIRM_ARCHIVE, "0")
- .build();
+ private ProjectionMap getAccountListMap() {
+ if (sAccountListMap == null) {
+ sAccountListMap = ProjectionMap.builder()
+ .add(BaseColumns._ID, AccountColumns.ID)
+ .add(UIProvider.AccountColumns.FOLDER_LIST_URI, uriWithId("uifolders"))
+ .add(UIProvider.AccountColumns.FULL_FOLDER_LIST_URI, uriWithId("uiallfolders"))
+ .add(UIProvider.AccountColumns.NAME, AccountColumns.DISPLAY_NAME)
+ .add(UIProvider.AccountColumns.SAVE_DRAFT_URI, uriWithId("uisavedraft"))
+ .add(UIProvider.AccountColumns.SEND_MAIL_URI, uriWithId("uisendmail"))
+ .add(UIProvider.AccountColumns.UNDO_URI,
+ ("'content://" + UIProvider.AUTHORITY + "/uiundo'"))
+ .add(UIProvider.AccountColumns.URI, uriWithId("uiaccount"))
+ .add(UIProvider.AccountColumns.SEARCH_URI, uriWithId("uisearch"))
+ // TODO: Is provider version used?
+ .add(UIProvider.AccountColumns.PROVIDER_VERSION, "1")
+ .add(UIProvider.AccountColumns.SYNC_STATUS, "0")
+ .add(UIProvider.AccountColumns.RECENT_FOLDER_LIST_URI, uriWithId("uirecentfolders"))
+ .add(UIProvider.AccountColumns.DEFAULT_RECENT_FOLDER_LIST_URI,
+ uriWithId("uidefaultrecentfolders"))
+ .add(UIProvider.AccountColumns.SettingsColumns.SIGNATURE, AccountColumns.SIGNATURE)
+ .add(UIProvider.AccountColumns.SettingsColumns.SNAP_HEADERS,
+ Integer.toString(UIProvider.SnapHeaderValue.ALWAYS))
+ .add(UIProvider.AccountColumns.SettingsColumns.REPLY_BEHAVIOR,
+ Integer.toString(UIProvider.DefaultReplyBehavior.REPLY))
+ .add(UIProvider.AccountColumns.SettingsColumns.CONFIRM_ARCHIVE, "0")
+ .build();
+ }
+ return sAccountListMap;
+ }
+ private static ProjectionMap sAccountListMap;
/**
* The "ORDER BY" clause for top level folders
@@ -2298,16 +2333,23 @@ outer:
/**
* Mapping of UIProvider columns to EmailProvider columns for a message's attachments
*/
- private static final ProjectionMap sAttachmentMap = ProjectionMap.builder()
- .add(UIProvider.AttachmentColumns.NAME, AttachmentColumns.FILENAME)
- .add(UIProvider.AttachmentColumns.SIZE, AttachmentColumns.SIZE)
- .add(UIProvider.AttachmentColumns.URI, uriWithId("uiattachment"))
- .add(UIProvider.AttachmentColumns.CONTENT_TYPE, AttachmentColumns.MIME_TYPE)
- .add(UIProvider.AttachmentColumns.STATE, AttachmentColumns.UI_STATE)
- .add(UIProvider.AttachmentColumns.DESTINATION, AttachmentColumns.UI_DESTINATION)
- .add(UIProvider.AttachmentColumns.DOWNLOADED_SIZE, AttachmentColumns.UI_DOWNLOADED_SIZE)
- .add(UIProvider.AttachmentColumns.CONTENT_URI, AttachmentColumns.CONTENT_URI)
- .build();
+ private ProjectionMap getAttachmentMap() {
+ if (sAttachmentMap == null) {
+ sAttachmentMap = ProjectionMap.builder()
+ .add(UIProvider.AttachmentColumns.NAME, AttachmentColumns.FILENAME)
+ .add(UIProvider.AttachmentColumns.SIZE, AttachmentColumns.SIZE)
+ .add(UIProvider.AttachmentColumns.URI, uriWithId("uiattachment"))
+ .add(UIProvider.AttachmentColumns.CONTENT_TYPE, AttachmentColumns.MIME_TYPE)
+ .add(UIProvider.AttachmentColumns.STATE, AttachmentColumns.UI_STATE)
+ .add(UIProvider.AttachmentColumns.DESTINATION, AttachmentColumns.UI_DESTINATION)
+ .add(UIProvider.AttachmentColumns.DOWNLOADED_SIZE,
+ AttachmentColumns.UI_DOWNLOADED_SIZE)
+ .add(UIProvider.AttachmentColumns.CONTENT_URI, AttachmentColumns.CONTENT_URI)
+ .build();
+ }
+ return sAttachmentMap;
+ }
+ private static ProjectionMap sAttachmentMap;
/**
* Generate the SELECT clause using a specified mapping and the original UI projection
@@ -2469,7 +2511,7 @@ outer:
"content://ui.email2.android.com/event/" + msg.mId);
}
}
- StringBuilder sb = genSelect(sMessageViewMap, uiProjection, values);
+ StringBuilder sb = genSelect(getMessageViewMap(), uiProjection, values);
sb.append(" FROM " + Message.TABLE_NAME + "," + Body.TABLE_NAME + " WHERE " +
Body.MESSAGE_KEY + "=" + Message.TABLE_NAME + "." + Message.RECORD_ID + " AND " +
Message.TABLE_NAME + "." + Message.RECORD_ID + "=?");
@@ -2484,7 +2526,7 @@ outer:
* @return the SQLite query to be executed on the EmailProvider database
*/
private String genQueryMailboxMessages(String[] uiProjection) {
- StringBuilder sb = genSelect(sMessageListMap, uiProjection);
+ StringBuilder sb = genSelect(getMessageListMap(), uiProjection);
sb.append(" FROM " + Message.TABLE_NAME + " WHERE " + Message.MAILBOX_KEY + "=? ORDER BY " +
MessageColumns.TIMESTAMP + " DESC");
return sb.toString();
@@ -2501,7 +2543,7 @@ outer:
long mailboxId) {
ContentValues values = new ContentValues();
values.put(UIProvider.ConversationColumns.COLOR, CONVERSATION_COLOR);
- StringBuilder sb = genSelect(sMessageListMap, uiProjection, values);
+ StringBuilder sb = genSelect(getMessageListMap(), uiProjection, values);
if (isCombinedMailbox(mailboxId)) {
switch (getVirtualMailboxType(mailboxId)) {
case Mailbox.TYPE_INBOX:
@@ -2543,7 +2585,7 @@ outer:
* @return the SQLite query to be executed on the EmailProvider database
*/
private String genQueryConversation(String[] uiProjection) {
- StringBuilder sb = genSelect(sMessageListMap, uiProjection);
+ StringBuilder sb = genSelect(getMessageListMap(), uiProjection);
sb.append(" FROM " + Message.TABLE_NAME + " WHERE " + Message.RECORD_ID + "=?");
return sb.toString();
}
@@ -2555,7 +2597,7 @@ outer:
* @return the SQLite query to be executed on the EmailProvider database
*/
private String genQueryAccountMailboxes(String[] uiProjection) {
- StringBuilder sb = genSelect(sFolderListMap, uiProjection);
+ StringBuilder sb = genSelect(getFolderListMap(), uiProjection);
sb.append(" FROM " + Mailbox.TABLE_NAME + " WHERE " + MailboxColumns.ACCOUNT_KEY +
"=? AND " + MailboxColumns.TYPE + " < " + Mailbox.TYPE_NOT_EMAIL +
" AND " + MailboxColumns.PARENT_KEY + " < 0 ORDER BY ");
@@ -2571,7 +2613,7 @@ outer:
* @return the SQLite query to be executed on the EmailProvider database
*/
private String genQueryAccountAllMailboxes(String[] uiProjection) {
- StringBuilder sb = genSelect(sFolderListMap, uiProjection);
+ StringBuilder sb = genSelect(getFolderListMap(), uiProjection);
// Use a derived column to choose either hierarchicalName or displayName
sb.append(", case when " + MailboxColumns.HIERARCHICAL_NAME + " is null then " +
MailboxColumns.DISPLAY_NAME + " else " + MailboxColumns.HIERARCHICAL_NAME +
@@ -2590,7 +2632,7 @@ outer:
* @return the SQLite query to be executed on the EmailProvider database
*/
private String genQueryRecentMailboxes(String[] uiProjection) {
- StringBuilder sb = genSelect(sFolderListMap, uiProjection);
+ StringBuilder sb = genSelect(getFolderListMap(), uiProjection);
sb.append(" FROM " + Mailbox.TABLE_NAME + " WHERE " + MailboxColumns.ACCOUNT_KEY +
"=? AND " + MailboxColumns.TYPE + " < " + Mailbox.TYPE_NOT_EMAIL +
" AND " + MailboxColumns.PARENT_KEY + " < 0 AND " +
@@ -2656,7 +2698,7 @@ outer:
getFolderCapabilities(info, mailbox.mFlags, mailbox.mType, mailboxId));
}
}
- StringBuilder sb = genSelect(sFolderListMap, uiProjection, values);
+ StringBuilder sb = genSelect(getFolderListMap(), uiProjection, values);
sb.append(" FROM " + Mailbox.TABLE_NAME + " WHERE " + MailboxColumns.ID + "=?");
return sb.toString();
}
@@ -2778,7 +2820,7 @@ outer:
values.put(UIProvider.AccountColumns.SettingsColumns.PRIORITY_ARROWS_ENABLED, "0");
}
- final StringBuilder sb = genSelect(sAccountListMap, uiProjection, values);
+ final StringBuilder sb = genSelect(getAccountListMap(), uiProjection, values);
sb.append(" FROM " + Account.TABLE_NAME + " WHERE " + AccountColumns.ID + "=?");
return sb.toString();
}
@@ -3037,7 +3079,7 @@ outer:
*/
private String genQueryAttachments(String[] uiProjection,
List contentTypeQueryParameters) {
- StringBuilder sb = genSelect(sAttachmentMap, uiProjection);
+ StringBuilder sb = genSelect(getAttachmentMap(), uiProjection);
sb.append(" FROM " + Attachment.TABLE_NAME + " WHERE " + AttachmentColumns.MESSAGE_KEY +
" =? ");
@@ -3069,7 +3111,7 @@ outer:
* @return the SQLite query to be executed on the EmailProvider database
*/
private String genQueryAttachment(String[] uiProjection) {
- StringBuilder sb = genSelect(sAttachmentMap, uiProjection);
+ StringBuilder sb = genSelect(getAttachmentMap(), uiProjection);
sb.append(" FROM " + Attachment.TABLE_NAME + " WHERE " + AttachmentColumns.ID + " =? ");
return sb.toString();
}
@@ -3081,7 +3123,7 @@ outer:
* @return the SQLite query to be executed on the EmailProvider database
*/
private String genQuerySubfolders(String[] uiProjection) {
- StringBuilder sb = genSelect(sFolderListMap, uiProjection);
+ StringBuilder sb = genSelect(getFolderListMap(), uiProjection);
sb.append(" FROM " + Mailbox.TABLE_NAME + " WHERE " + MailboxColumns.PARENT_KEY +
" =? ORDER BY ");
sb.append(MAILBOX_ORDER_BY);
@@ -3157,7 +3199,7 @@ outer:
final String[] idAndType = { BaseColumns._ID, UIProvider.FolderColumns.TYPE };
// Sent, Drafts, and Starred are the default recents.
- final StringBuilder sb = genSelect(sFolderListMap, idAndType);
+ final StringBuilder sb = genSelect(getFolderListMap(), idAndType);
sb.append(" FROM " + Mailbox.TABLE_NAME
+ " WHERE " + MailboxColumns.ACCOUNT_KEY + " = " + id
+ " AND "
diff --git a/src/com/android/email/service/EmailBroadcastProcessorService.java b/src/com/android/email/service/EmailBroadcastProcessorService.java
index ecf8569af..c7483e29e 100644
--- a/src/com/android/email/service/EmailBroadcastProcessorService.java
+++ b/src/com/android/email/service/EmailBroadcastProcessorService.java
@@ -203,7 +203,7 @@ public class EmailBroadcastProcessorService extends IntentService {
private void onSystemAccountChanged() {
Log.i(Logging.LOG_TAG, "System accounts updated.");
- MailService.reconcilePopImapAccountsSync(this);
+ MailService.reconcilePopAccountsSync(this);
// Start any remote services
EmailServiceUtils.startRemoteServices(this);
diff --git a/src/com/android/email/service/EmailServiceStub.java b/src/com/android/email/service/EmailServiceStub.java
index fd7a217b0..04d7ce964 100644
--- a/src/com/android/email/service/EmailServiceStub.java
+++ b/src/com/android/email/service/EmailServiceStub.java
@@ -28,11 +28,11 @@ import android.text.TextUtils;
import android.util.Log;
import com.android.email.NotificationController;
+import com.android.email.R;
import com.android.email.mail.Sender;
import com.android.email.mail.Store;
import com.android.email.provider.Utilities;
import com.android.email2.ui.MailActivityEmail;
-import com.android.emailcommon.AccountManagerTypes;
import com.android.emailcommon.Api;
import com.android.emailcommon.Logging;
import com.android.emailcommon.TrafficFlags;
@@ -108,7 +108,7 @@ public abstract class EmailServiceStub extends IEmailService.Stub implements IEm
Account account = Account.restoreAccountWithId(mContext, mailbox.mAccountKey);
if (account == null) return;
android.accounts.Account acct = new android.accounts.Account(account.mEmailAddress,
- AccountManagerTypes.TYPE_POP_IMAP);
+ mContext.getString(R.string.account_manager_type_pop3));
Bundle extras = new Bundle();
extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
extras.putLong(SYNC_EXTRA_MAILBOX_ID, mailboxId);
@@ -450,7 +450,7 @@ public abstract class EmailServiceStub extends IEmailService.Stub implements IEm
@Override
public void deleteAccountPIMData(long accountId) throws RemoteException {
- MailService.reconcilePopImapAccountsSync(mContext);
+ MailService.reconcilePopAccountsSync(mContext);
}
@Override
diff --git a/src/com/android/email/service/EmailServiceUtils.java b/src/com/android/email/service/EmailServiceUtils.java
index ec0850db2..53e3395d2 100644
--- a/src/com/android/email/service/EmailServiceUtils.java
+++ b/src/com/android/email/service/EmailServiceUtils.java
@@ -21,21 +21,29 @@ import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Service;
+import android.content.ComponentName;
+import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.database.Cursor;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.Debug;
import android.os.IBinder;
import android.os.RemoteException;
import android.provider.CalendarContract;
+import android.provider.CalendarContract.Calendars;
+import android.provider.CalendarContract.SyncState;
import android.provider.ContactsContract;
+import android.provider.SyncStateContract;
import android.util.Log;
import com.android.email.R;
@@ -43,8 +51,8 @@ import com.android.emailcommon.Api;
import com.android.emailcommon.Logging;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.EmailContent;
-import com.android.emailcommon.provider.HostAuth;
import com.android.emailcommon.provider.EmailContent.AccountColumns;
+import com.android.emailcommon.provider.HostAuth;
import com.android.emailcommon.service.EmailServiceProxy;
import com.android.emailcommon.service.IEmailService;
import com.android.emailcommon.service.IEmailServiceCallback;
@@ -167,6 +175,7 @@ public class EmailServiceUtils {
public CharSequence[] syncIntervals;
public int defaultSyncInterval;
public String inferPrefix;
+ public boolean requiresAccountUpdate;
public String toString() {
StringBuilder sb = new StringBuilder("Protocol: ");
@@ -187,7 +196,14 @@ public class EmailServiceUtils {
if (info == null) {
Log.w(Logging.LOG_TAG, "Returning NullService for " + protocol);
return new EmailServiceProxy(context, NullService.class, null);
- } else if (info.klass != null) {
+ } else {
+ return getServiceFromInfo(context, callback, info);
+ }
+ }
+
+ public static EmailServiceProxy getServiceFromInfo(Context context,
+ IEmailServiceCallback callback, EmailServiceInfo info) {
+ if (info.klass != null) {
return new EmailServiceProxy(context, info.klass, callback);
} else {
return new EmailServiceProxy(context, info.intentAction, callback);
@@ -229,30 +245,47 @@ public class EmailServiceUtils {
}
}
- /**
- * "Change" the account manager type of the account; this entails deleting the account
- * and adding a new one. We can't call into AccountManager on the UI thread, but we might
- * well be on it (currently no clean way of guaranteeing that we're not).
- *
- * @param context the caller's context
- * @param amAccount the AccountManager account we're changing
- * @param newType the new AccountManager type for this account
- * @param newProtocol the protocol now being used
- */
- private static void updateAccountManagerType(final Context context,
- final android.accounts.Account amAccount, final String newType,
- final String newProtocol) {
- // STOPSHIP There must be a better way
- Thread amThread = new Thread(new Runnable() {
- @Override
- public void run() {
- updateAccountManagerTypeImpl(context, amAccount, newType, newProtocol);
- }});
- amThread.start();
+ private static class UpdateAccountManagerTask extends AsyncTask {
+ private final Context mContext;
+ private final android.accounts.Account mAccount;
+ private final EmailServiceInfo mOldInfo;
+ private final EmailServiceInfo mNewInfo;
+
+ public UpdateAccountManagerTask(Context context, android.accounts.Account amAccount,
+ EmailServiceInfo oldInfo, EmailServiceInfo newInfo) {
+ super();
+ mContext = context;
+ mAccount = amAccount;
+ mOldInfo = oldInfo;
+ mNewInfo = newInfo;
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ updateAccountManagerType(mContext, mAccount, mOldInfo, mNewInfo);
+ return null;
+ }
}
- private static void updateAccountManagerTypeImpl(Context context,
- android.accounts.Account amAccount, String newType, String newProtocol) {
+ private static class DisableComponentsTask extends AsyncTask {
+ private final Context mContext;
+
+ public DisableComponentsTask(Context context) {
+ super();
+ mContext = context;
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ disableComponent(mContext, LegacyEmailAuthenticatorService.class);
+ disableComponent(mContext, LegacyEasAuthenticatorService.class);
+ return null;
+ }
+ }
+
+ private static void updateAccountManagerType(Context context,
+ android.accounts.Account amAccount, EmailServiceInfo oldInfo,
+ EmailServiceInfo newInfo) {
ContentResolver resolver = context.getContentResolver();
Cursor c = resolver.query(Account.CONTENT_URI, Account.CONTENT_PROJECTION,
AccountColumns.EMAIL_ADDRESS + "=?", new String[] { amAccount.name }, null);
@@ -260,7 +293,6 @@ public class EmailServiceUtils {
if (c == null) return;
try {
if (c.moveToNext()) {
- Log.w(Logging.LOG_TAG, "Converting " + amAccount.name + " to " + newProtocol);
// Get the EmailProvider Account/HostAuth
Account account = new Account();
account.restore(c);
@@ -268,6 +300,14 @@ public class EmailServiceUtils {
HostAuth.restoreHostAuthWithId(context, account.mHostAuthKeyRecv);
if (hostAuth == null) return;
+ // Make sure this email address is using the expected protocol; our query to
+ // AccountManager doesn't know which protocol was being used (com.android.email
+ // was used for both pop3 and imap
+ if (!hostAuth.mProtocol.equals(oldInfo.protocol)) {
+ return;
+ }
+ Log.w(Logging.LOG_TAG, "Converting " + amAccount.name + " to " + newInfo.protocol);
+
ContentValues accountValues = new ContentValues();
int oldFlags = account.mFlags;
@@ -280,33 +320,122 @@ public class EmailServiceUtils {
// Change the HostAuth to reference the new protocol; this has to be done before
// trying to create the AccountManager account (below)
ContentValues hostValues = new ContentValues();
- hostValues.put(HostAuth.PROTOCOL, newProtocol);
+ hostValues.put(HostAuth.PROTOCOL, newInfo.protocol);
resolver.update(ContentUris.withAppendedId(HostAuth.CONTENT_URI, hostAuth.mId),
hostValues, null, null);
+ Log.w(Logging.LOG_TAG, "Updated HostAuths");
try {
// Get current settings for the existing AccountManager account
boolean email = ContentResolver.getSyncAutomatically(amAccount,
EmailContent.AUTHORITY);
+ if (!email) {
+ // Try our old provider name
+ email = ContentResolver.getSyncAutomatically(amAccount,
+ "com.android.email.provider");
+ }
boolean contacts = ContentResolver.getSyncAutomatically(amAccount,
ContactsContract.AUTHORITY);
boolean calendar = ContentResolver.getSyncAutomatically(amAccount,
CalendarContract.AUTHORITY);
+ Log.w(Logging.LOG_TAG, "Email: " + email + ", Contacts: " + contacts + "," +
+ " Calendar: " + calendar);
- // Delete the AccountManager account
- AccountManagerFuture> amFuture = AccountManager.get(context)
- .removeAccount(amAccount, null, null);
- finishAccountManagerBlocker(amFuture);
+ // Get sync keys for calendar/contacts
+ String amName = amAccount.name;
+ String oldType = amAccount.type;
+ ContentProviderClient client = context.getContentResolver()
+ .acquireContentProviderClient(CalendarContract.CONTENT_URI);
+ byte[] calendarSyncKey = null;
+ try {
+ calendarSyncKey = SyncStateContract.Helpers.get(client,
+ asCalendarSyncAdapter(SyncState.CONTENT_URI, amName, oldType),
+ new android.accounts.Account(amName, oldType));
+ } catch (RemoteException e) {
+ Log.w(Logging.LOG_TAG, "Get calendar key FAILED");
+ } finally {
+ client.release();
+ }
+ client = context.getContentResolver()
+ .acquireContentProviderClient(ContactsContract.AUTHORITY_URI);
+ byte[] contactsSyncKey = null;
+ try {
+ contactsSyncKey = SyncStateContract.Helpers.get(client,
+ ContactsContract.SyncState.CONTENT_URI,
+ new android.accounts.Account(amName, oldType));
+ } catch (RemoteException e) {
+ Log.w(Logging.LOG_TAG, "Get contacts key FAILED");
+ } finally {
+ client.release();
+ }
+ if (calendarSyncKey != null) {
+ Log.w(Logging.LOG_TAG, "Got calendar key: " + new String(calendarSyncKey));
+ }
+ if (contactsSyncKey != null) {
+ Log.w(Logging.LOG_TAG, "Got contacts key: " + new String(contactsSyncKey));
+ }
// Set up a new AccountManager account with new type and old settings
- amFuture = MailService.setupAccountManagerAccount(context, account, email,
- calendar, contacts, null);
+ AccountManagerFuture> amFuture = MailService.setupAccountManagerAccount(
+ context, account, email, calendar, contacts, null);
finishAccountManagerBlocker(amFuture);
- Log.w(Logging.LOG_TAG, "Conversion complete!");
+ Log.w(Logging.LOG_TAG, "Created new AccountManager account");
+
+ // Delete the AccountManager account
+ amFuture = AccountManager.get(context)
+ .removeAccount(amAccount, null, null);
+ finishAccountManagerBlocker(amFuture);
+ Log.w(Logging.LOG_TAG, "Deleted old AccountManager account");
+
+ // Restore sync keys for contacts/calendar
+ if (calendarSyncKey != null && calendarSyncKey.length != 0) {
+ client = context.getContentResolver()
+ .acquireContentProviderClient(CalendarContract.CONTENT_URI);
+ try {
+ SyncStateContract.Helpers.set(client,
+ asCalendarSyncAdapter(SyncState.CONTENT_URI, amName,
+ newInfo.accountType),
+ new android.accounts.Account(amName, newInfo.accountType),
+ calendarSyncKey);
+ Log.w(Logging.LOG_TAG, "Set calendar key...");
+ } catch (RemoteException e) {
+ Log.w(Logging.LOG_TAG, "Set calendar key FAILED");
+ } finally {
+ client.release();
+ }
+ }
+ if (contactsSyncKey != null && contactsSyncKey.length != 0) {
+ client = context.getContentResolver()
+ .acquireContentProviderClient(ContactsContract.AUTHORITY_URI);
+ try {
+ SyncStateContract.Helpers.set(client,
+ ContactsContract.SyncState.CONTENT_URI,
+ new android.accounts.Account(amName, newInfo.accountType),
+ contactsSyncKey);
+ Log.w(Logging.LOG_TAG, "Set contacts key...");
+ } catch (RemoteException e) {
+ Log.w(Logging.LOG_TAG, "Set contacts key FAILED");
+ }
+ }
+
+ if (oldInfo.requiresAccountUpdate) {
+ EmailServiceProxy service =
+ EmailServiceUtils.getServiceFromInfo(context, null, newInfo);
+ try {
+ service.serviceUpdated(amAccount.name);
+ Log.w(Logging.LOG_TAG, "Updated account settings");
+ } catch (RemoteException e) {
+ // Old settings won't hurt anyone
+ }
+ }
+
+ // That's all folks!
+ Log.w(Logging.LOG_TAG, "Account update completed.");
} finally {
// Clear the incomplete flag on the provider account
accountValues.put(AccountColumns.FLAGS, oldFlags);
resolver.update(accountUri, accountValues, null, null);
+ Log.w(Logging.LOG_TAG, "[Incomplete flag cleared]");
}
}
} finally {
@@ -314,6 +443,14 @@ public class EmailServiceUtils {
}
}
+ private static void disableComponent(Context context, Class> klass) {
+ Log.w(Logging.LOG_TAG, "Disabling legacy authenticator " + klass.getSimpleName());
+ final ComponentName c = new ComponentName(context, klass);
+ context.getPackageManager().setComponentEnabledSetting(c,
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ }
+
/**
* Parse services.xml file to find our available email services
*/
@@ -339,12 +476,14 @@ public class EmailServiceUtils {
throw new IllegalStateException(
"Replacement service not found: " + newProtocol);
}
+ info.requiresAccountUpdate = ta.getBoolean(
+ R.styleable.EmailServiceInfo_requiresAccountUpdate, false);
AccountManager am = AccountManager.get(context);
android.accounts.Account[] amAccounts =
am.getAccountsByType(info.accountType);
for (android.accounts.Account amAccount: amAccounts) {
- updateAccountManagerType(context, amAccount, newInfo.accountType,
- newProtocol);
+ new UpdateAccountManagerTask(context, amAccount, info, newInfo)
+ .executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
}
continue;
}
@@ -407,6 +546,8 @@ public class EmailServiceUtils {
sServiceList.add(info);
}
}
+ // Disable our legacy components
+ new DisableComponentsTask(context).executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
} catch (XmlPullParserException e) {
// ignore
} catch (IOException e) {
@@ -414,6 +555,12 @@ public class EmailServiceUtils {
}
}
+ private static Uri asCalendarSyncAdapter(Uri uri, String account, String accountType) {
+ return uri.buildUpon().appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
+ .appendQueryParameter(Calendars.ACCOUNT_NAME, account)
+ .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build();
+ }
+
/**
* A no-op service that can be returned for non-existent/null protocols
*/
@@ -504,6 +651,10 @@ public class EmailServiceUtils {
public void sendMail(long accountId) throws RemoteException {
}
+ @Override
+ public void serviceUpdated(String emailAddress) throws RemoteException {
+ }
+
@Override
public int getCapabilities(Account acct) throws RemoteException {
return 0;
diff --git a/src/com/android/email/service/Imap2AuthenticatorService.java b/src/com/android/email/service/ImapAuthenticatorService.java
similarity index 91%
rename from src/com/android/email/service/Imap2AuthenticatorService.java
rename to src/com/android/email/service/ImapAuthenticatorService.java
index f19104920..975583da3 100644
--- a/src/com/android/email/service/Imap2AuthenticatorService.java
+++ b/src/com/android/email/service/ImapAuthenticatorService.java
@@ -19,5 +19,5 @@ package com.android.email.service;
/**
* This service needs to be declared separately from the base service
*/
-public class Imap2AuthenticatorService extends AuthenticatorService {
+public class ImapAuthenticatorService extends AuthenticatorService {
}
diff --git a/src/com/android/email/service/ImapService.java b/src/com/android/email/service/ImapService.java
deleted file mode 100644
index a08936f69..000000000
--- a/src/com/android/email/service/ImapService.java
+++ /dev/null
@@ -1,1291 +0,0 @@
-/*
- * Copyright (C) 2012 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.email.service;
-
-import android.app.Service;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.net.TrafficStats;
-import android.net.Uri;
-import android.os.IBinder;
-import android.os.RemoteCallbackList;
-import android.os.RemoteException;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.email.LegacyConversions;
-import com.android.email.NotificationController;
-import com.android.email.mail.Store;
-import com.android.email.provider.Utilities;
-import com.android.email2.ui.MailActivityEmail;
-import com.android.emailcommon.Logging;
-import com.android.emailcommon.TrafficFlags;
-import com.android.emailcommon.internet.MimeUtility;
-import com.android.emailcommon.mail.AuthenticationFailedException;
-import com.android.emailcommon.mail.FetchProfile;
-import com.android.emailcommon.mail.Flag;
-import com.android.emailcommon.mail.Folder;
-import com.android.emailcommon.mail.Folder.FolderType;
-import com.android.emailcommon.mail.Folder.MessageRetrievalListener;
-import com.android.emailcommon.mail.Folder.MessageUpdateCallbacks;
-import com.android.emailcommon.mail.Folder.OpenMode;
-import com.android.emailcommon.mail.Message;
-import com.android.emailcommon.mail.MessagingException;
-import com.android.emailcommon.mail.Part;
-import com.android.emailcommon.provider.Account;
-import com.android.emailcommon.provider.EmailContent;
-import com.android.emailcommon.provider.EmailContent.MailboxColumns;
-import com.android.emailcommon.provider.EmailContent.MessageColumns;
-import com.android.emailcommon.provider.EmailContent.SyncColumns;
-import com.android.emailcommon.provider.Mailbox;
-import com.android.emailcommon.service.EmailServiceCallback;
-import com.android.emailcommon.service.EmailServiceStatus;
-import com.android.emailcommon.service.IEmailServiceCallback;
-import com.android.emailcommon.service.SearchParams;
-import com.android.emailcommon.utility.AttachmentUtilities;
-import com.android.mail.providers.UIProvider.AccountCapabilities;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-
-public class ImapService extends Service {
- private static final String TAG = "ImapService";
- private static final int MAX_SMALL_MESSAGE_SIZE = (25 * 1024);
-
- private static final Flag[] FLAG_LIST_SEEN = new Flag[] { Flag.SEEN };
- private static final Flag[] FLAG_LIST_FLAGGED = new Flag[] { Flag.FLAGGED };
- private static final Flag[] FLAG_LIST_ANSWERED = new Flag[] { Flag.ANSWERED };
-
- /**
- * Simple cache for last search result mailbox by account and serverId, since the most common
- * case will be repeated use of the same mailbox
- */
- private static long mLastSearchAccountKey = Account.NO_ACCOUNT;
- private static String mLastSearchServerId = null;
- private static Mailbox mLastSearchRemoteMailbox = null;
-
- /**
- * Cache search results by account; this allows for "load more" support without having to
- * redo the search (which can be quite slow). SortableMessage is a smallish class, so memory
- * shouldn't be an issue
- */
- private static final HashMap sSearchResults =
- new HashMap();
-
- /**
- * We write this into the serverId field of messages that will never be upsynced.
- */
- private static final String LOCAL_SERVERID_PREFIX = "Local-";
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- return Service.START_STICKY;
- }
-
- // Callbacks as set up via setCallback
- private static final RemoteCallbackList mCallbackList =
- new RemoteCallbackList();
-
- private static final EmailServiceCallback sCallbackProxy =
- new EmailServiceCallback(mCallbackList);
-
- /**
- * Create our EmailService implementation here.
- */
- private final EmailServiceStub mBinder = new EmailServiceStub() {
-
- @Override
- public void setCallback(IEmailServiceCallback cb) throws RemoteException {
- mCallbackList.register(cb);
- }
-
- @Override
- public void loadMore(long messageId) throws RemoteException {
- // We don't do "loadMore" for IMAP messages; the sync should handle this
- }
-
- @Override
- public int searchMessages(long accountId, SearchParams searchParams, long destMailboxId) {
- try {
- return searchMailboxImpl(getApplicationContext(), accountId, searchParams,
- destMailboxId);
- } catch (MessagingException e) {
- }
- return 0;
- }
-
- @Override
- public int getCapabilities(Account acct) throws RemoteException {
- return AccountCapabilities.SYNCABLE_FOLDERS |
- AccountCapabilities.FOLDER_SERVER_SEARCH |
- AccountCapabilities.UNDO;
- }
- };
-
- @Override
- public IBinder onBind(Intent intent) {
- mBinder.init(this, sCallbackProxy);
- return mBinder;
- }
-
- private static void sendMailboxStatus(Mailbox mailbox, int status) {
- sCallbackProxy.syncMailboxStatus(mailbox.mId, status, 0);
- }
-
- /**
- * Start foreground synchronization of the specified folder. This is called by
- * synchronizeMailbox or checkMail.
- * TODO this should use ID's instead of fully-restored objects
- * @param account
- * @param folder
- * @throws MessagingException
- */
- public static void synchronizeMailboxSynchronous(Context context, final Account account,
- final Mailbox folder) throws MessagingException {
- sendMailboxStatus(folder, EmailServiceStatus.IN_PROGRESS);
-
- TrafficStats.setThreadStatsTag(TrafficFlags.getSyncFlags(context, account));
- if ((folder.mFlags & Mailbox.FLAG_HOLDS_MAIL) == 0) {
- sendMailboxStatus(folder, EmailServiceStatus.SUCCESS);
- }
- NotificationController nc = NotificationController.getInstance(context);
- try {
- processPendingActionsSynchronous(context, account);
- synchronizeMailboxGeneric(context, account, folder);
- // Clear authentication notification for this account
- nc.cancelLoginFailedNotification(account.mId);
- sendMailboxStatus(folder, EmailServiceStatus.SUCCESS);
- } catch (MessagingException e) {
- if (Logging.LOGD) {
- Log.v(Logging.LOG_TAG, "synchronizeMailbox", e);
- }
- if (e instanceof AuthenticationFailedException) {
- // Generate authentication notification
- nc.showLoginFailedNotification(account.mId);
- }
- sendMailboxStatus(folder, e.getExceptionType());
- throw e;
- }
- }
-
- /**
- * Lightweight record for the first pass of message sync, where I'm just seeing if
- * the local message requires sync. Later (for messages that need syncing) we'll do a full
- * readout from the DB.
- */
- private static class LocalMessageInfo {
- private static final int COLUMN_ID = 0;
- private static final int COLUMN_FLAG_READ = 1;
- private static final int COLUMN_FLAG_FAVORITE = 2;
- private static final int COLUMN_FLAG_LOADED = 3;
- private static final int COLUMN_SERVER_ID = 4;
- private static final int COLUMN_FLAGS = 7;
- private static final String[] PROJECTION = new String[] {
- EmailContent.RECORD_ID,
- MessageColumns.FLAG_READ, MessageColumns.FLAG_FAVORITE, MessageColumns.FLAG_LOADED,
- SyncColumns.SERVER_ID, MessageColumns.MAILBOX_KEY, MessageColumns.ACCOUNT_KEY,
- MessageColumns.FLAGS
- };
-
- final long mId;
- final boolean mFlagRead;
- final boolean mFlagFavorite;
- final int mFlagLoaded;
- final String mServerId;
- final int mFlags;
-
- public LocalMessageInfo(Cursor c) {
- mId = c.getLong(COLUMN_ID);
- mFlagRead = c.getInt(COLUMN_FLAG_READ) != 0;
- mFlagFavorite = c.getInt(COLUMN_FLAG_FAVORITE) != 0;
- mFlagLoaded = c.getInt(COLUMN_FLAG_LOADED);
- mServerId = c.getString(COLUMN_SERVER_ID);
- mFlags = c.getInt(COLUMN_FLAGS);
- // Note: mailbox key and account key not needed - they are projected for the SELECT
- }
- }
-
- /**
- * Load the structure and body of messages not yet synced
- * @param account the account we're syncing
- * @param remoteFolder the (open) Folder we're working on
- * @param unsyncedMessages an array of Message's we've got headers for
- * @param toMailbox the destination mailbox we're syncing
- * @throws MessagingException
- */
- static void loadUnsyncedMessages(final Context context, final Account account,
- Folder remoteFolder, ArrayList messages, final Mailbox toMailbox)
- throws MessagingException {
-
- FetchProfile fp = new FetchProfile();
- fp.add(FetchProfile.Item.STRUCTURE);
- remoteFolder.fetch(messages.toArray(new Message[messages.size()]), fp, null);
- for (Message message : messages) {
- // Build a list of parts we are interested in. Text parts will be downloaded
- // right now, attachments will be left for later.
- ArrayList viewables = new ArrayList();
- ArrayList attachments = new ArrayList();
- MimeUtility.collectParts(message, viewables, attachments);
- // Download the viewables immediately
- for (Part part : viewables) {
- fp.clear();
- fp.add(part);
- remoteFolder.fetch(new Message[] { message }, fp, null);
- }
- // Store the updated message locally and mark it fully loaded
- Utilities.copyOneMessageToProvider(context, message, account, toMailbox,
- EmailContent.Message.FLAG_LOADED_COMPLETE);
- }
- }
-
- public static void downloadFlagAndEnvelope(final Context context, final Account account,
- final Mailbox mailbox, Folder remoteFolder, ArrayList unsyncedMessages,
- HashMap localMessageMap, final ArrayList unseenMessages)
- throws MessagingException {
- FetchProfile fp = new FetchProfile();
- fp.add(FetchProfile.Item.FLAGS);
- fp.add(FetchProfile.Item.ENVELOPE);
-
- final HashMap localMapCopy;
- if (localMessageMap != null)
- localMapCopy = new HashMap(localMessageMap);
- else {
- localMapCopy = new HashMap();
- }
-
- remoteFolder.fetch(unsyncedMessages.toArray(new Message[0]), fp,
- new MessageRetrievalListener() {
- @Override
- public void messageRetrieved(Message message) {
- try {
- // Determine if the new message was already known (e.g. partial)
- // And create or reload the full message info
- LocalMessageInfo localMessageInfo =
- localMapCopy.get(message.getUid());
- EmailContent.Message localMessage = null;
- if (localMessageInfo == null) {
- localMessage = new EmailContent.Message();
- } else {
- localMessage = EmailContent.Message.restoreMessageWithId(
- context, localMessageInfo.mId);
- }
-
- if (localMessage != null) {
- try {
- // Copy the fields that are available into the message
- LegacyConversions.updateMessageFields(localMessage,
- message, account.mId, mailbox.mId);
- // Commit the message to the local store
- Utilities.saveOrUpdate(localMessage, context);
- // Track the "new" ness of the downloaded message
- if (!message.isSet(Flag.SEEN) && unseenMessages != null) {
- unseenMessages.add(localMessage.mId);
- }
- } catch (MessagingException me) {
- Log.e(Logging.LOG_TAG,
- "Error while copying downloaded message." + me);
- }
-
- }
- }
- catch (Exception e) {
- Log.e(Logging.LOG_TAG,
- "Error while storing downloaded message." + e.toString());
- }
- }
-
- @Override
- public void loadAttachmentProgress(int progress) {
- }
- });
-
- }
-
- /**
- * Synchronizer for IMAP.
- *
- * TODO Break this method up into smaller chunks.
- *
- * @param account the account to sync
- * @param mailbox the mailbox to sync
- * @return results of the sync pass
- * @throws MessagingException
- */
- private static void synchronizeMailboxGeneric(final Context context,
- final Account account, final Mailbox mailbox) throws MessagingException {
-
- /*
- * A list of IDs for messages that were downloaded and did not have the seen flag set.
- * This serves as the "true" new message count reported to the user via notification.
- */
- final ArrayList unseenMessages = new ArrayList();
-
- ContentResolver resolver = context.getContentResolver();
-
- // 0. We do not ever sync DRAFTS or OUTBOX (down or up)
- if (mailbox.mType == Mailbox.TYPE_DRAFTS || mailbox.mType == Mailbox.TYPE_OUTBOX) {
- return;
- }
-
- // 1. Get the message list from the local store and create an index of the uids
-
- Cursor localUidCursor = null;
- HashMap localMessageMap = new HashMap();
-
- try {
- localUidCursor = resolver.query(
- EmailContent.Message.CONTENT_URI,
- LocalMessageInfo.PROJECTION,
- EmailContent.MessageColumns.ACCOUNT_KEY + "=?" +
- " AND " + MessageColumns.MAILBOX_KEY + "=?",
- new String[] {
- String.valueOf(account.mId),
- String.valueOf(mailbox.mId)
- },
- null);
- while (localUidCursor.moveToNext()) {
- LocalMessageInfo info = new LocalMessageInfo(localUidCursor);
- localMessageMap.put(info.mServerId, info);
- }
- } finally {
- if (localUidCursor != null) {
- localUidCursor.close();
- }
- }
-
- // 2. Open the remote folder and create the remote folder if necessary
-
- Store remoteStore = Store.getInstance(account, context);
- // The account might have been deleted
- if (remoteStore == null) return;
- Folder remoteFolder = remoteStore.getFolder(mailbox.mServerId);
-
- /*
- * If the folder is a "special" folder we need to see if it exists
- * on the remote server. It if does not exist we'll try to create it. If we
- * can't create we'll abort. This will happen on every single Pop3 folder as
- * designed and on Imap folders during error conditions. This allows us
- * to treat Pop3 and Imap the same in this code.
- */
- if (mailbox.mType == Mailbox.TYPE_TRASH || mailbox.mType == Mailbox.TYPE_SENT
- || mailbox.mType == Mailbox.TYPE_DRAFTS) {
- if (!remoteFolder.exists()) {
- if (!remoteFolder.create(FolderType.HOLDS_MESSAGES)) {
- return;
- }
- }
- }
-
- // 3, Open the remote folder. This pre-loads certain metadata like message count.
- remoteFolder.open(OpenMode.READ_WRITE);
-
- // 4. Trash any remote messages that are marked as trashed locally.
- // TODO - this comment was here, but no code was here.
-
- // 5. Get the remote message count.
- int remoteMessageCount = remoteFolder.getMessageCount();
- ContentValues values = new ContentValues();
- values.put(MailboxColumns.TOTAL_COUNT, remoteMessageCount);
- mailbox.update(context, values);
-
- // 6. Determine the limit # of messages to download
- int visibleLimit = mailbox.mVisibleLimit;
- if (visibleLimit <= 0) {
- visibleLimit = MailActivityEmail.VISIBLE_LIMIT_DEFAULT;
- }
-
- // 7. Create a list of messages to download
- Message[] remoteMessages = new Message[0];
- final ArrayList unsyncedMessages = new ArrayList();
- HashMap remoteUidMap = new HashMap();
-
- if (remoteMessageCount > 0) {
- /*
- * Message numbers start at 1.
- */
- int remoteStart = Math.max(0, remoteMessageCount - visibleLimit) + 1;
- int remoteEnd = remoteMessageCount;
- remoteMessages = remoteFolder.getMessages(remoteStart, remoteEnd, null);
- // TODO Why are we running through the list twice? Combine w/ for loop below
- for (Message message : remoteMessages) {
- remoteUidMap.put(message.getUid(), message);
- }
-
- /*
- * Get a list of the messages that are in the remote list but not on the
- * local store, or messages that are in the local store but failed to download
- * on the last sync. These are the new messages that we will download.
- * Note, we also skip syncing messages which are flagged as "deleted message" sentinels,
- * because they are locally deleted and we don't need or want the old message from
- * the server.
- */
- for (Message message : remoteMessages) {
- LocalMessageInfo localMessage = localMessageMap.get(message.getUid());
- // localMessage == null -> message has never been created (not even headers)
- // mFlagLoaded = UNLOADED -> message created, but none of body loaded
- // mFlagLoaded = PARTIAL -> message created, a "sane" amt of body has been loaded
- // mFlagLoaded = COMPLETE -> message body has been completely loaded
- // mFlagLoaded = DELETED -> message has been deleted
- // Only the first two of these are "unsynced", so let's retrieve them
- if (localMessage == null ||
- (localMessage.mFlagLoaded == EmailContent.Message.FLAG_LOADED_UNLOADED) ||
- (localMessage.mFlagLoaded == EmailContent.Message.FLAG_LOADED_PARTIAL)) {
- unsyncedMessages.add(message);
- }
- }
- }
-
- // 8. Download basic info about the new/unloaded messages (if any)
- /*
- * Fetch the flags and envelope only of the new messages. This is intended to get us
- * critical data as fast as possible, and then we'll fill in the details.
- */
- if (unsyncedMessages.size() > 0) {
- downloadFlagAndEnvelope(context, account, mailbox, remoteFolder, unsyncedMessages,
- localMessageMap, unseenMessages);
- }
-
- // 9. Refresh the flags for any messages in the local store that we didn't just download.
- FetchProfile fp = new FetchProfile();
- fp.add(FetchProfile.Item.FLAGS);
- remoteFolder.fetch(remoteMessages, fp, null);
- boolean remoteSupportsSeen = false;
- boolean remoteSupportsFlagged = false;
- boolean remoteSupportsAnswered = false;
- for (Flag flag : remoteFolder.getPermanentFlags()) {
- if (flag == Flag.SEEN) {
- remoteSupportsSeen = true;
- }
- if (flag == Flag.FLAGGED) {
- remoteSupportsFlagged = true;
- }
- if (flag == Flag.ANSWERED) {
- remoteSupportsAnswered = true;
- }
- }
- // Update SEEN/FLAGGED/ANSWERED (star) flags (if supported remotely - e.g. not for POP3)
- if (remoteSupportsSeen || remoteSupportsFlagged || remoteSupportsAnswered) {
- for (Message remoteMessage : remoteMessages) {
- LocalMessageInfo localMessageInfo = localMessageMap.get(remoteMessage.getUid());
- if (localMessageInfo == null) {
- continue;
- }
- boolean localSeen = localMessageInfo.mFlagRead;
- boolean remoteSeen = remoteMessage.isSet(Flag.SEEN);
- boolean newSeen = (remoteSupportsSeen && (remoteSeen != localSeen));
- boolean localFlagged = localMessageInfo.mFlagFavorite;
- boolean remoteFlagged = remoteMessage.isSet(Flag.FLAGGED);
- boolean newFlagged = (remoteSupportsFlagged && (localFlagged != remoteFlagged));
- int localFlags = localMessageInfo.mFlags;
- boolean localAnswered = (localFlags & EmailContent.Message.FLAG_REPLIED_TO) != 0;
- boolean remoteAnswered = remoteMessage.isSet(Flag.ANSWERED);
- boolean newAnswered = (remoteSupportsAnswered && (localAnswered != remoteAnswered));
- if (newSeen || newFlagged || newAnswered) {
- Uri uri = ContentUris.withAppendedId(
- EmailContent.Message.CONTENT_URI, localMessageInfo.mId);
- ContentValues updateValues = new ContentValues();
- updateValues.put(MessageColumns.FLAG_READ, remoteSeen);
- updateValues.put(MessageColumns.FLAG_FAVORITE, remoteFlagged);
- if (remoteAnswered) {
- localFlags |= EmailContent.Message.FLAG_REPLIED_TO;
- } else {
- localFlags &= ~EmailContent.Message.FLAG_REPLIED_TO;
- }
- updateValues.put(MessageColumns.FLAGS, localFlags);
- resolver.update(uri, updateValues, null, null);
- }
- }
- }
-
- // 10. Remove any messages that are in the local store but no longer on the remote store.
- HashSet localUidsToDelete = new HashSet(localMessageMap.keySet());
- localUidsToDelete.removeAll(remoteUidMap.keySet());
- for (String uidToDelete : localUidsToDelete) {
- LocalMessageInfo infoToDelete = localMessageMap.get(uidToDelete);
-
- // Delete associated data (attachment files)
- // Attachment & Body records are auto-deleted when we delete the Message record
- AttachmentUtilities.deleteAllAttachmentFiles(context, account.mId,
- infoToDelete.mId);
-
- // Delete the message itself
- Uri uriToDelete = ContentUris.withAppendedId(
- EmailContent.Message.CONTENT_URI, infoToDelete.mId);
- resolver.delete(uriToDelete, null, null);
-
- // Delete extra rows (e.g. synced or deleted)
- Uri syncRowToDelete = ContentUris.withAppendedId(
- EmailContent.Message.UPDATED_CONTENT_URI, infoToDelete.mId);
- resolver.delete(syncRowToDelete, null, null);
- Uri deletERowToDelete = ContentUris.withAppendedId(
- EmailContent.Message.UPDATED_CONTENT_URI, infoToDelete.mId);
- resolver.delete(deletERowToDelete, null, null);
- }
-
- loadUnsyncedMessages(context, account, remoteFolder, unsyncedMessages, mailbox);
-
- // 14. Clean up and report results
- remoteFolder.close(false);
- }
-
- /**
- * Find messages in the updated table that need to be written back to server.
- *
- * Handles:
- * Read/Unread
- * Flagged
- * Append (upload)
- * Move To Trash
- * Empty trash
- * TODO:
- * Move
- *
- * @param account the account to scan for pending actions
- * @throws MessagingException
- */
- private static void processPendingActionsSynchronous(Context context, Account account)
- throws MessagingException {
- TrafficStats.setThreadStatsTag(TrafficFlags.getSyncFlags(context, account));
- String[] accountIdArgs = new String[] { Long.toString(account.mId) };
-
- // Handle deletes first, it's always better to get rid of things first
- processPendingDeletesSynchronous(context, account, accountIdArgs);
-
- // Handle uploads (currently, only to sent messages)
- processPendingUploadsSynchronous(context, account, accountIdArgs);
-
- // Now handle updates / upsyncs
- processPendingUpdatesSynchronous(context, account, accountIdArgs);
- }
-
- /**
- * Get the mailbox corresponding to the remote location of a message; this will normally be
- * the mailbox whose _id is mailboxKey, except for search results, where we must look it up
- * by serverId
- * @param message the message in question
- * @return the mailbox in which the message resides on the server
- */
- private static Mailbox getRemoteMailboxForMessage(Context context,
- EmailContent.Message message) {
- // If this is a search result, use the protocolSearchInfo field to get the server info
- if (!TextUtils.isEmpty(message.mProtocolSearchInfo)) {
- long accountKey = message.mAccountKey;
- String protocolSearchInfo = message.mProtocolSearchInfo;
- if (accountKey == mLastSearchAccountKey &&
- protocolSearchInfo.equals(mLastSearchServerId)) {
- return mLastSearchRemoteMailbox;
- }
- Cursor c = context.getContentResolver().query(Mailbox.CONTENT_URI,
- Mailbox.CONTENT_PROJECTION, Mailbox.PATH_AND_ACCOUNT_SELECTION,
- new String[] {protocolSearchInfo, Long.toString(accountKey)},
- null);
- try {
- if (c.moveToNext()) {
- Mailbox mailbox = new Mailbox();
- mailbox.restore(c);
- mLastSearchAccountKey = accountKey;
- mLastSearchServerId = protocolSearchInfo;
- mLastSearchRemoteMailbox = mailbox;
- return mailbox;
- } else {
- return null;
- }
- } finally {
- c.close();
- }
- } else {
- return Mailbox.restoreMailboxWithId(context, message.mMailboxKey);
- }
- }
-
- /**
- * Scan for messages that are in the Message_Deletes table, look for differences that
- * we can deal with, and do the work.
- *
- * @param account
- * @param resolver
- * @param accountIdArgs
- */
- private static void processPendingDeletesSynchronous(Context context, Account account,
- String[] accountIdArgs) {
- Cursor deletes = context.getContentResolver().query(
- EmailContent.Message.DELETED_CONTENT_URI,
- EmailContent.Message.CONTENT_PROJECTION,
- EmailContent.MessageColumns.ACCOUNT_KEY + "=?", accountIdArgs,
- EmailContent.MessageColumns.MAILBOX_KEY);
- long lastMessageId = -1;
- try {
- // Defer setting up the store until we know we need to access it
- Store remoteStore = null;
- // loop through messages marked as deleted
- while (deletes.moveToNext()) {
- boolean deleteFromTrash = false;
-
- EmailContent.Message oldMessage =
- EmailContent.getContent(deletes, EmailContent.Message.class);
-
- if (oldMessage != null) {
- lastMessageId = oldMessage.mId;
-
- Mailbox mailbox = getRemoteMailboxForMessage(context, oldMessage);
- if (mailbox == null) {
- continue; // Mailbox removed. Move to the next message.
- }
- deleteFromTrash = mailbox.mType == Mailbox.TYPE_TRASH;
-
- // Load the remote store if it will be needed
- if (remoteStore == null && deleteFromTrash) {
- remoteStore = Store.getInstance(account, context);
- }
-
- // Dispatch here for specific change types
- if (deleteFromTrash) {
- // Move message to trash
- processPendingDeleteFromTrash(context, remoteStore, account, mailbox,
- oldMessage);
- }
- }
-
- // Finally, delete the update
- Uri uri = ContentUris.withAppendedId(EmailContent.Message.DELETED_CONTENT_URI,
- oldMessage.mId);
- context.getContentResolver().delete(uri, null, null);
- }
- } catch (MessagingException me) {
- // Presumably an error here is an account connection failure, so there is
- // no point in continuing through the rest of the pending updates.
- if (MailActivityEmail.DEBUG) {
- Log.d(Logging.LOG_TAG, "Unable to process pending delete for id="
- + lastMessageId + ": " + me);
- }
- } finally {
- deletes.close();
- }
- }
-
- /**
- * Scan for messages that are in Sent, and are in need of upload,
- * and send them to the server. "In need of upload" is defined as:
- * serverId == null (no UID has been assigned)
- * or
- * message is in the updated list
- *
- * Note we also look for messages that are moving from drafts->outbox->sent. They never
- * go through "drafts" or "outbox" on the server, so we hang onto these until they can be
- * uploaded directly to the Sent folder.
- *
- * @param account
- * @param resolver
- * @param accountIdArgs
- */
- private static void processPendingUploadsSynchronous(Context context, Account account,
- String[] accountIdArgs) {
- ContentResolver resolver = context.getContentResolver();
- // Find the Sent folder (since that's all we're uploading for now
- Cursor mailboxes = resolver.query(Mailbox.CONTENT_URI, Mailbox.ID_PROJECTION,
- MailboxColumns.ACCOUNT_KEY + "=?"
- + " and " + MailboxColumns.TYPE + "=" + Mailbox.TYPE_SENT,
- accountIdArgs, null);
- long lastMessageId = -1;
- try {
- // Defer setting up the store until we know we need to access it
- Store remoteStore = null;
- while (mailboxes.moveToNext()) {
- long mailboxId = mailboxes.getLong(Mailbox.ID_PROJECTION_COLUMN);
- String[] mailboxKeyArgs = new String[] { Long.toString(mailboxId) };
- // Demand load mailbox
- Mailbox mailbox = null;
-
- // First handle the "new" messages (serverId == null)
- Cursor upsyncs1 = resolver.query(EmailContent.Message.CONTENT_URI,
- EmailContent.Message.ID_PROJECTION,
- EmailContent.Message.MAILBOX_KEY + "=?"
- + " and (" + EmailContent.Message.SERVER_ID + " is null"
- + " or " + EmailContent.Message.SERVER_ID + "=''" + ")",
- mailboxKeyArgs,
- null);
- try {
- while (upsyncs1.moveToNext()) {
- // Load the remote store if it will be needed
- if (remoteStore == null) {
- remoteStore = Store.getInstance(account, context);
- }
- // Load the mailbox if it will be needed
- if (mailbox == null) {
- mailbox = Mailbox.restoreMailboxWithId(context, mailboxId);
- if (mailbox == null) {
- continue; // Mailbox removed. Move to the next message.
- }
- }
- // upsync the message
- long id = upsyncs1.getLong(EmailContent.Message.ID_PROJECTION_COLUMN);
- lastMessageId = id;
- processUploadMessage(context, remoteStore, account, mailbox, id);
- }
- } finally {
- if (upsyncs1 != null) {
- upsyncs1.close();
- }
- }
-
- // Next, handle any updates (e.g. edited in place, although this shouldn't happen)
- Cursor upsyncs2 = resolver.query(EmailContent.Message.UPDATED_CONTENT_URI,
- EmailContent.Message.ID_PROJECTION,
- EmailContent.MessageColumns.MAILBOX_KEY + "=?", mailboxKeyArgs,
- null);
- try {
- while (upsyncs2.moveToNext()) {
- // Load the remote store if it will be needed
- if (remoteStore == null) {
- remoteStore = Store.getInstance(account, context);
- }
- // Load the mailbox if it will be needed
- if (mailbox == null) {
- mailbox = Mailbox.restoreMailboxWithId(context, mailboxId);
- if (mailbox == null) {
- continue; // Mailbox removed. Move to the next message.
- }
- }
- // upsync the message
- long id = upsyncs2.getLong(EmailContent.Message.ID_PROJECTION_COLUMN);
- lastMessageId = id;
- processUploadMessage(context, remoteStore, account, mailbox, id);
- }
- } finally {
- if (upsyncs2 != null) {
- upsyncs2.close();
- }
- }
- }
- } catch (MessagingException me) {
- // Presumably an error here is an account connection failure, so there is
- // no point in continuing through the rest of the pending updates.
- if (MailActivityEmail.DEBUG) {
- Log.d(Logging.LOG_TAG, "Unable to process pending upsync for id="
- + lastMessageId + ": " + me);
- }
- } finally {
- if (mailboxes != null) {
- mailboxes.close();
- }
- }
- }
-
- /**
- * Scan for messages that are in the Message_Updates table, look for differences that
- * we can deal with, and do the work.
- *
- * @param account
- * @param resolver
- * @param accountIdArgs
- */
- private static void processPendingUpdatesSynchronous(Context context, Account account,
- String[] accountIdArgs) {
- ContentResolver resolver = context.getContentResolver();
- Cursor updates = resolver.query(EmailContent.Message.UPDATED_CONTENT_URI,
- EmailContent.Message.CONTENT_PROJECTION,
- EmailContent.MessageColumns.ACCOUNT_KEY + "=?", accountIdArgs,
- EmailContent.MessageColumns.MAILBOX_KEY);
- long lastMessageId = -1;
- try {
- // Defer setting up the store until we know we need to access it
- Store remoteStore = null;
- // Demand load mailbox (note order-by to reduce thrashing here)
- Mailbox mailbox = null;
- // loop through messages marked as needing updates
- while (updates.moveToNext()) {
- boolean changeMoveToTrash = false;
- boolean changeRead = false;
- boolean changeFlagged = false;
- boolean changeMailbox = false;
- boolean changeAnswered = false;
-
- EmailContent.Message oldMessage =
- EmailContent.getContent(updates, EmailContent.Message.class);
- lastMessageId = oldMessage.mId;
- EmailContent.Message newMessage =
- EmailContent.Message.restoreMessageWithId(context, oldMessage.mId);
- if (newMessage != null) {
- mailbox = Mailbox.restoreMailboxWithId(context, newMessage.mMailboxKey);
- if (mailbox == null) {
- continue; // Mailbox removed. Move to the next message.
- }
- if (oldMessage.mMailboxKey != newMessage.mMailboxKey) {
- if (mailbox.mType == Mailbox.TYPE_TRASH) {
- changeMoveToTrash = true;
- } else {
- changeMailbox = true;
- }
- }
- changeRead = oldMessage.mFlagRead != newMessage.mFlagRead;
- changeFlagged = oldMessage.mFlagFavorite != newMessage.mFlagFavorite;
- changeAnswered = (oldMessage.mFlags & EmailContent.Message.FLAG_REPLIED_TO) !=
- (newMessage.mFlags & EmailContent.Message.FLAG_REPLIED_TO);
- }
-
- // Load the remote store if it will be needed
- if (remoteStore == null &&
- (changeMoveToTrash || changeRead || changeFlagged || changeMailbox ||
- changeAnswered)) {
- remoteStore = Store.getInstance(account, context);
- }
-
- // Dispatch here for specific change types
- if (changeMoveToTrash) {
- // Move message to trash
- processPendingMoveToTrash(context, remoteStore, account, mailbox, oldMessage,
- newMessage);
- } else if (changeRead || changeFlagged || changeMailbox || changeAnswered) {
- processPendingDataChange(context, remoteStore, mailbox, changeRead,
- changeFlagged, changeMailbox, changeAnswered, oldMessage, newMessage);
- }
-
- // Finally, delete the update
- Uri uri = ContentUris.withAppendedId(EmailContent.Message.UPDATED_CONTENT_URI,
- oldMessage.mId);
- resolver.delete(uri, null, null);
- }
-
- } catch (MessagingException me) {
- // Presumably an error here is an account connection failure, so there is
- // no point in continuing through the rest of the pending updates.
- if (MailActivityEmail.DEBUG) {
- Log.d(Logging.LOG_TAG, "Unable to process pending update for id="
- + lastMessageId + ": " + me);
- }
- } finally {
- updates.close();
- }
- }
-
- /**
- * Upsync an entire message. This must also unwind whatever triggered it (either by
- * updating the serverId, or by deleting the update record, or it's going to keep happening
- * over and over again.
- *
- * Note: If the message is being uploaded into an unexpected mailbox, we *do not* upload.
- * This is to avoid unnecessary uploads into the trash. Although the caller attempts to select
- * only the Drafts and Sent folders, this can happen when the update record and the current
- * record mismatch. In this case, we let the update record remain, because the filters
- * in processPendingUpdatesSynchronous() will pick it up as a move and handle it (or drop it)
- * appropriately.
- *
- * @param resolver
- * @param remoteStore
- * @param account
- * @param mailbox the actual mailbox
- * @param messageId
- */
- private static void processUploadMessage(Context context, Store remoteStore,
- Account account, Mailbox mailbox, long messageId)
- throws MessagingException {
- EmailContent.Message newMessage =
- EmailContent.Message.restoreMessageWithId(context, messageId);
- boolean deleteUpdate = false;
- if (newMessage == null) {
- deleteUpdate = true;
- Log.d(Logging.LOG_TAG, "Upsync failed for null message, id=" + messageId);
- } else if (mailbox.mType == Mailbox.TYPE_DRAFTS) {
- deleteUpdate = false;
- Log.d(Logging.LOG_TAG, "Upsync skipped for mailbox=drafts, id=" + messageId);
- } else if (mailbox.mType == Mailbox.TYPE_OUTBOX) {
- deleteUpdate = false;
- Log.d(Logging.LOG_TAG, "Upsync skipped for mailbox=outbox, id=" + messageId);
- } else if (mailbox.mType == Mailbox.TYPE_TRASH) {
- deleteUpdate = false;
- Log.d(Logging.LOG_TAG, "Upsync skipped for mailbox=trash, id=" + messageId);
- } else if (newMessage != null && newMessage.mMailboxKey != mailbox.mId) {
- deleteUpdate = false;
- Log.d(Logging.LOG_TAG, "Upsync skipped; mailbox changed, id=" + messageId);
- } else {
-// Log.d(Logging.LOG_TAG, "Upsyc triggered for message id=" + messageId);
-// deleteUpdate = processPendingAppend(context, remoteStore, account, mailbox,
- //newMessage);
- }
- if (deleteUpdate) {
- // Finally, delete the update (if any)
- Uri uri = ContentUris.withAppendedId(
- EmailContent.Message.UPDATED_CONTENT_URI, messageId);
- context.getContentResolver().delete(uri, null, null);
- }
- }
-
- /**
- * Upsync changes to read, flagged, or mailbox
- *
- * @param remoteStore the remote store for this mailbox
- * @param mailbox the mailbox the message is stored in
- * @param changeRead whether the message's read state has changed
- * @param changeFlagged whether the message's flagged state has changed
- * @param changeMailbox whether the message's mailbox has changed
- * @param oldMessage the message in it's pre-change state
- * @param newMessage the current version of the message
- */
- private static void processPendingDataChange(final Context context, Store remoteStore,
- Mailbox mailbox, boolean changeRead, boolean changeFlagged, boolean changeMailbox,
- boolean changeAnswered, EmailContent.Message oldMessage,
- final EmailContent.Message newMessage) throws MessagingException {
- // New mailbox is the mailbox this message WILL be in (same as the one it WAS in if it isn't
- // being moved
- Mailbox newMailbox = mailbox;
- // Mailbox is the original remote mailbox (the one we're acting on)
- mailbox = getRemoteMailboxForMessage(context, oldMessage);
-
- // 0. No remote update if the message is local-only
- if (newMessage.mServerId == null || newMessage.mServerId.equals("")
- || newMessage.mServerId.startsWith(LOCAL_SERVERID_PREFIX) || (mailbox == null)) {
- return;
- }
-
- // 1. No remote update for DRAFTS or OUTBOX
- if (mailbox.mType == Mailbox.TYPE_DRAFTS || mailbox.mType == Mailbox.TYPE_OUTBOX) {
- return;
- }
-
- // 2. Open the remote store & folder
- Folder remoteFolder = remoteStore.getFolder(mailbox.mServerId);
- if (!remoteFolder.exists()) {
- return;
- }
- remoteFolder.open(OpenMode.READ_WRITE);
- if (remoteFolder.getMode() != OpenMode.READ_WRITE) {
- return;
- }
-
- // 3. Finally, apply the changes to the message
- Message remoteMessage = remoteFolder.getMessage(newMessage.mServerId);
- if (remoteMessage == null) {
- return;
- }
- if (MailActivityEmail.DEBUG) {
- Log.d(Logging.LOG_TAG,
- "Update for msg id=" + newMessage.mId
- + " read=" + newMessage.mFlagRead
- + " flagged=" + newMessage.mFlagFavorite
- + " answered="
- + ((newMessage.mFlags & EmailContent.Message.FLAG_REPLIED_TO) != 0)
- + " new mailbox=" + newMessage.mMailboxKey);
- }
- Message[] messages = new Message[] { remoteMessage };
- if (changeRead) {
- remoteFolder.setFlags(messages, FLAG_LIST_SEEN, newMessage.mFlagRead);
- }
- if (changeFlagged) {
- remoteFolder.setFlags(messages, FLAG_LIST_FLAGGED, newMessage.mFlagFavorite);
- }
- if (changeAnswered) {
- remoteFolder.setFlags(messages, FLAG_LIST_ANSWERED,
- (newMessage.mFlags & EmailContent.Message.FLAG_REPLIED_TO) != 0);
- }
- if (changeMailbox) {
- Folder toFolder = remoteStore.getFolder(newMailbox.mServerId);
- if (!remoteFolder.exists()) {
- return;
- }
- // We may need the message id to search for the message in the destination folder
- remoteMessage.setMessageId(newMessage.mMessageId);
- // Copy the message to its new folder
- remoteFolder.copyMessages(messages, toFolder, new MessageUpdateCallbacks() {
- @Override
- public void onMessageUidChange(Message message, String newUid) {
- ContentValues cv = new ContentValues();
- cv.put(EmailContent.Message.SERVER_ID, newUid);
- // We only have one message, so, any updates _must_ be for it. Otherwise,
- // we'd have to cycle through to find the one with the same server ID.
- context.getContentResolver().update(ContentUris.withAppendedId(
- EmailContent.Message.CONTENT_URI, newMessage.mId), cv, null, null);
- }
- @Override
- public void onMessageNotFound(Message message) {
- }
- });
- // Delete the message from the remote source folder
- remoteMessage.setFlag(Flag.DELETED, true);
- remoteFolder.expunge();
- }
- remoteFolder.close(false);
- }
-
- /**
- * Process a pending trash message command.
- *
- * @param remoteStore the remote store we're working in
- * @param account The account in which we are working
- * @param newMailbox The local trash mailbox
- * @param oldMessage The message copy that was saved in the updates shadow table
- * @param newMessage The message that was moved to the mailbox
- */
- private static void processPendingMoveToTrash(final Context context, Store remoteStore,
- Account account, Mailbox newMailbox, EmailContent.Message oldMessage,
- final EmailContent.Message newMessage) throws MessagingException {
-
- // 0. No remote move if the message is local-only
- if (newMessage.mServerId == null || newMessage.mServerId.equals("")
- || newMessage.mServerId.startsWith(LOCAL_SERVERID_PREFIX)) {
- return;
- }
-
- // 1. Escape early if we can't find the local mailbox
- // TODO smaller projection here
- Mailbox oldMailbox = getRemoteMailboxForMessage(context, oldMessage);
- if (oldMailbox == null) {
- // can't find old mailbox, it may have been deleted. just return.
- return;
- }
- // 2. We don't support delete-from-trash here
- if (oldMailbox.mType == Mailbox.TYPE_TRASH) {
- return;
- }
-
- // The rest of this method handles server-side deletion
-
- // 4. Find the remote mailbox (that we deleted from), and open it
- Folder remoteFolder = remoteStore.getFolder(oldMailbox.mServerId);
- if (!remoteFolder.exists()) {
- return;
- }
-
- remoteFolder.open(OpenMode.READ_WRITE);
- if (remoteFolder.getMode() != OpenMode.READ_WRITE) {
- remoteFolder.close(false);
- return;
- }
-
- // 5. Find the remote original message
- Message remoteMessage = remoteFolder.getMessage(oldMessage.mServerId);
- if (remoteMessage == null) {
- remoteFolder.close(false);
- return;
- }
-
- // 6. Find the remote trash folder, and create it if not found
- Folder remoteTrashFolder = remoteStore.getFolder(newMailbox.mServerId);
- if (!remoteTrashFolder.exists()) {
- /*
- * If the remote trash folder doesn't exist we try to create it.
- */
- remoteTrashFolder.create(FolderType.HOLDS_MESSAGES);
- }
-
- // 7. Try to copy the message into the remote trash folder
- // Note, this entire section will be skipped for POP3 because there's no remote trash
- if (remoteTrashFolder.exists()) {
- /*
- * Because remoteTrashFolder may be new, we need to explicitly open it
- */
- remoteTrashFolder.open(OpenMode.READ_WRITE);
- if (remoteTrashFolder.getMode() != OpenMode.READ_WRITE) {
- remoteFolder.close(false);
- remoteTrashFolder.close(false);
- return;
- }
-
- remoteFolder.copyMessages(new Message[] { remoteMessage }, remoteTrashFolder,
- new Folder.MessageUpdateCallbacks() {
- @Override
- public void onMessageUidChange(Message message, String newUid) {
- // update the UID in the local trash folder, because some stores will
- // have to change it when copying to remoteTrashFolder
- ContentValues cv = new ContentValues();
- cv.put(EmailContent.Message.SERVER_ID, newUid);
- context.getContentResolver().update(newMessage.getUri(), cv, null, null);
- }
-
- /**
- * This will be called if the deleted message doesn't exist and can't be
- * deleted (e.g. it was already deleted from the server.) In this case,
- * attempt to delete the local copy as well.
- */
- @Override
- public void onMessageNotFound(Message message) {
- context.getContentResolver().delete(newMessage.getUri(), null, null);
- }
- });
- remoteTrashFolder.close(false);
- }
-
- // 8. Delete the message from the remote source folder
- remoteMessage.setFlag(Flag.DELETED, true);
- remoteFolder.expunge();
- remoteFolder.close(false);
- }
-
- /**
- * Process a pending trash message command.
- *
- * @param remoteStore the remote store we're working in
- * @param account The account in which we are working
- * @param oldMailbox The local trash mailbox
- * @param oldMessage The message that was deleted from the trash
- */
- private static void processPendingDeleteFromTrash(Context context, Store remoteStore,
- Account account, Mailbox oldMailbox, EmailContent.Message oldMessage)
- throws MessagingException {
-
- // 1. We only support delete-from-trash here
- if (oldMailbox.mType != Mailbox.TYPE_TRASH) {
- return;
- }
-
- // 2. Find the remote trash folder (that we are deleting from), and open it
- Folder remoteTrashFolder = remoteStore.getFolder(oldMailbox.mServerId);
- if (!remoteTrashFolder.exists()) {
- return;
- }
-
- remoteTrashFolder.open(OpenMode.READ_WRITE);
- if (remoteTrashFolder.getMode() != OpenMode.READ_WRITE) {
- remoteTrashFolder.close(false);
- return;
- }
-
- // 3. Find the remote original message
- Message remoteMessage = remoteTrashFolder.getMessage(oldMessage.mServerId);
- if (remoteMessage == null) {
- remoteTrashFolder.close(false);
- return;
- }
-
- // 4. Delete the message from the remote trash folder
- remoteMessage.setFlag(Flag.DELETED, true);
- remoteTrashFolder.expunge();
- remoteTrashFolder.close(false);
- }
-
- /**
- * A message and numeric uid that's easily sortable
- */
- private static class SortableMessage {
- private final Message mMessage;
- private final long mUid;
-
- SortableMessage(Message message, long uid) {
- mMessage = message;
- mUid = uid;
- }
- }
-
- private int searchMailboxImpl(final Context context, long accountId, SearchParams searchParams,
- final long destMailboxId) throws MessagingException {
- final Account account = Account.restoreAccountWithId(context, accountId);
- final Mailbox mailbox = Mailbox.restoreMailboxWithId(context, searchParams.mMailboxId);
- final Mailbox destMailbox = Mailbox.restoreMailboxWithId(context, destMailboxId);
- if (account == null || mailbox == null || destMailbox == null) {
- Log.d(Logging.LOG_TAG, "Attempted search for " + searchParams
- + " but account or mailbox information was missing");
- return 0;
- }
-
- // Tell UI that we're loading messages
-
- Store remoteStore = Store.getInstance(account, context);
- Folder remoteFolder = remoteStore.getFolder(mailbox.mServerId);
- remoteFolder.open(OpenMode.READ_WRITE);
-
- SortableMessage[] sortableMessages = new SortableMessage[0];
- if (searchParams.mOffset == 0) {
- // Get the "bare" messages (basically uid)
- Message[] remoteMessages = remoteFolder.getMessages(searchParams, null);
- int remoteCount = remoteMessages.length;
- if (remoteCount > 0) {
- sortableMessages = new SortableMessage[remoteCount];
- int i = 0;
- for (Message msg : remoteMessages) {
- sortableMessages[i++] = new SortableMessage(msg, Long.parseLong(msg.getUid()));
- }
- // Sort the uid's, most recent first
- // Note: Not all servers will be nice and return results in the order of request;
- // those that do will see messages arrive from newest to oldest
- Arrays.sort(sortableMessages, new Comparator() {
- @Override
- public int compare(SortableMessage lhs, SortableMessage rhs) {
- return lhs.mUid > rhs.mUid ? -1 : lhs.mUid < rhs.mUid ? 1 : 0;
- }
- });
- sSearchResults.put(accountId, sortableMessages);
- }
- } else {
- sortableMessages = sSearchResults.get(accountId);
- }
-
- final int numSearchResults = sortableMessages.length;
- final int numToLoad =
- Math.min(numSearchResults - searchParams.mOffset, searchParams.mLimit);
- if (numToLoad <= 0) {
- return 0;
- }
-
- final ArrayList messageList = new ArrayList();
- for (int i = searchParams.mOffset; i < numToLoad + searchParams.mOffset; i++) {
- messageList.add(sortableMessages[i].mMessage);
- }
- // Get everything in one pass, rather than two (as in sync); this starts getting us
- // usable results quickly.
- FetchProfile fp = new FetchProfile();
- fp.add(FetchProfile.Item.FLAGS);
- fp.add(FetchProfile.Item.ENVELOPE);
- fp.add(FetchProfile.Item.STRUCTURE);
- fp.add(FetchProfile.Item.BODY_SANE);
- remoteFolder.fetch(messageList.toArray(new Message[0]), fp,
- new MessageRetrievalListener() {
- @Override
- public void messageRetrieved(Message message) {
- try {
- // Determine if the new message was already known (e.g. partial)
- // And create or reload the full message info
- EmailContent.Message localMessage = new EmailContent.Message();
- try {
- // Copy the fields that are available into the message
- LegacyConversions.updateMessageFields(localMessage,
- message, account.mId, mailbox.mId);
- // Commit the message to the local store
- Utilities.saveOrUpdate(localMessage, context);
- localMessage.mMailboxKey = destMailboxId;
- // We load 50k or so; maybe it's complete, maybe not...
- int flag = EmailContent.Message.FLAG_LOADED_COMPLETE;
- // We store the serverId of the source mailbox into protocolSearchInfo
- // This will be used by loadMessageForView, etc. to use the proper remote
- // folder
- localMessage.mProtocolSearchInfo = mailbox.mServerId;
- if (message.getSize() > Store.FETCH_BODY_SANE_SUGGESTED_SIZE) {
- flag = EmailContent.Message.FLAG_LOADED_PARTIAL;
- }
- Utilities.copyOneMessageToProvider(context, message, localMessage, flag);
- } catch (MessagingException me) {
- Log.e(Logging.LOG_TAG,
- "Error while copying downloaded message." + me);
- }
- } catch (Exception e) {
- Log.e(Logging.LOG_TAG,
- "Error while storing downloaded message." + e.toString());
- }
- }
-
- @Override
- public void loadAttachmentProgress(int progress) {
- }
- });
- return numSearchResults;
- }
-}
\ No newline at end of file
diff --git a/src/com/android/email/service/ImapTempFileLiteral.java b/src/com/android/email/service/ImapTempFileLiteral.java
deleted file mode 100644
index cc1dd5410..000000000
--- a/src/com/android/email/service/ImapTempFileLiteral.java
+++ /dev/null
@@ -1,127 +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.email.service;
-
-import android.util.Log;
-
-import com.android.email.FixedLengthInputStream;
-import com.android.email.mail.store.imap.ImapResponse;
-import com.android.email.mail.store.imap.ImapResponseParser;
-import com.android.email.mail.store.imap.ImapString;
-import com.android.emailcommon.Logging;
-import com.android.emailcommon.TempDirectory;
-import com.android.emailcommon.utility.Utility;
-
-import org.apache.commons.io.IOUtils;
-
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- * Subclass of {@link ImapString} used for literals backed by a temp file.
- */
-public class ImapTempFileLiteral extends ImapString {
- /* package for test */ final File mFile;
-
- /** Size is purely for toString() */
- private final int mSize;
-
- /* package */ ImapTempFileLiteral(FixedLengthInputStream stream) throws IOException {
- mSize = stream.getLength();
- mFile = File.createTempFile("imap", ".tmp", TempDirectory.getTempDirectory());
-
- // Unfortunately, we can't really use deleteOnExit(), because temp filenames are random
- // so it'd simply cause a memory leak.
- // deleteOnExit() simply adds filenames to a static list and the list will never shrink.
- // mFile.deleteOnExit();
- OutputStream out = new FileOutputStream(mFile);
- IOUtils.copy(stream, out);
- out.close();
- }
-
- /**
- * Make sure we delete the temp file.
- *
- * We should always be calling {@link ImapResponse#destroy()}, but it's here as a last resort.
- */
- @Override
- protected void finalize() throws Throwable {
- try {
- destroy();
- } finally {
- super.finalize();
- }
- }
-
- @Override
- public InputStream getAsStream() {
- checkNotDestroyed();
- try {
- return new FileInputStream(mFile);
- } catch (FileNotFoundException e) {
- // It's probably possible if we're low on storage and the system clears the cache dir.
- Log.w(Logging.LOG_TAG, "ImapTempFileLiteral: Temp file not found");
-
- // Return 0 byte stream as a dummy...
- return new ByteArrayInputStream(new byte[0]);
- }
- }
-
- @Override
- public String getString() {
- checkNotDestroyed();
- try {
- byte[] bytes = IOUtils.toByteArray(getAsStream());
- // Prevent crash from OOM; we've seen this, but only rarely and not reproducibly
- if (bytes.length > ImapResponseParser.LITERAL_KEEP_IN_MEMORY_THRESHOLD) {
- throw new IOException();
- }
- return Utility.fromAscii(bytes);
- } catch (IOException e) {
- Log.w(Logging.LOG_TAG, "ImapTempFileLiteral: Error while reading temp file", e);
- return "";
- }
- }
-
- @Override
- public void destroy() {
- try {
- if (!isDestroyed() && mFile.exists()) {
- mFile.delete();
- }
- } catch (RuntimeException re) {
- // Just log and ignore.
- Log.w(Logging.LOG_TAG, "Failed to remove temp file: " + re.getMessage());
- }
- super.destroy();
- }
-
- @Override
- public String toString() {
- return String.format("{%d byte literal(file)}", mSize);
- }
-
- public boolean tempFileExistsForTest() {
- return mFile.exists();
- }
-}
diff --git a/emailcommon/src/com/android/emailcommon/AccountManagerTypes.java b/src/com/android/email/service/LegacyEasAuthenticatorService.java
similarity index 68%
rename from emailcommon/src/com/android/emailcommon/AccountManagerTypes.java
rename to src/com/android/email/service/LegacyEasAuthenticatorService.java
index 4ccd480a5..d90497c4c 100644
--- a/emailcommon/src/com/android/emailcommon/AccountManagerTypes.java
+++ b/src/com/android/email/service/LegacyEasAuthenticatorService.java
@@ -1,6 +1,5 @@
/*
- /*
- * Copyright (C) 2011 The Android Open Source Project
+ * 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.
@@ -15,9 +14,10 @@
* limitations under the License.
*/
-package com.android.emailcommon;
+package com.android.email.service;
-public class AccountManagerTypes {
- public static final String TYPE_EXCHANGE = "com.android.exchange";
- public static final String TYPE_POP_IMAP = "com.android.email";
+/**
+ * This service needs to be declared separately from the base service
+ */
+public class LegacyEasAuthenticatorService extends AuthenticatorService {
}
diff --git a/src/com/android/email/service/LegacyEmailAuthenticatorService.java b/src/com/android/email/service/LegacyEmailAuthenticatorService.java
new file mode 100644
index 000000000..c5b56444d
--- /dev/null
+++ b/src/com/android/email/service/LegacyEmailAuthenticatorService.java
@@ -0,0 +1,23 @@
+/*
+ * 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.email.service;
+
+/**
+ * This service needs to be declared separately from the base service
+ */
+public class LegacyEmailAuthenticatorService extends AuthenticatorService {
+}
diff --git a/src/com/android/email/service/MailService.java b/src/com/android/email/service/MailService.java
index cc99789aa..d5c11ea26 100644
--- a/src/com/android/email/service/MailService.java
+++ b/src/com/android/email/service/MailService.java
@@ -26,11 +26,11 @@ import android.database.Cursor;
import android.os.Bundle;
import android.os.IBinder;
+import com.android.email.R;
import com.android.email.SingleRunningTask;
import com.android.email.provider.AccountReconciler;
import com.android.email.service.EmailServiceUtils.EmailServiceInfo;
import com.android.email2.ui.MailActivityEmail;
-import com.android.emailcommon.AccountManagerTypes;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.HostAuth;
import com.android.emailcommon.utility.EmailAsyncTask;
@@ -51,7 +51,7 @@ public class MailService extends Service {
EmailAsyncTask.runAsyncParallel(new Runnable() {
@Override
public void run() {
- reconcilePopImapAccountsSync(MailService.this);
+ reconcilePopAccountsSync(MailService.this);
}
});
@@ -70,7 +70,7 @@ public class MailService extends Service {
return null;
}
- public static ArrayList getPopImapAccountList(Context context) {
+ public static ArrayList getPopAccountList(Context context) {
ArrayList providerAccounts = new ArrayList();
Cursor c = context.getContentResolver().query(Account.CONTENT_URI, Account.ID_PROJECTION,
null, null, null);
@@ -79,7 +79,8 @@ public class MailService extends Service {
long accountId = c.getLong(Account.CONTENT_ID_COLUMN);
String protocol = Account.getProtocol(context, accountId);
EmailServiceInfo info = EmailServiceUtils.getServiceInfo(context, protocol);
- if ((info != null) && info.accountType.equals(AccountManagerTypes.TYPE_POP_IMAP)) {
+ if ((info != null) && info.accountType.equals(
+ context.getString(R.string.account_manager_type_pop3))) {
Account account = Account.restoreAccountWithId(context, accountId);
if (account != null) {
providerAccounts.add(account);
@@ -92,13 +93,14 @@ public class MailService extends Service {
return providerAccounts;
}
- private static final SingleRunningTask sReconcilePopImapAccountsSyncExecutor =
+ private static final SingleRunningTask sReconcilePopAccountsSyncExecutor =
new SingleRunningTask("ReconcilePopImapAccountsSync") {
@Override
protected void runInternal(Context context) {
android.accounts.Account[] accountManagerAccounts = AccountManager.get(context)
- .getAccountsByType(AccountManagerTypes.TYPE_POP_IMAP);
- ArrayList providerAccounts = getPopImapAccountList(context);
+ .getAccountsByType(
+ context.getString(R.string.account_manager_type_pop3));
+ ArrayList providerAccounts = getPopAccountList(context);
MailService.reconcileAccountsWithAccountManager(context, providerAccounts,
accountManagerAccounts, context);
@@ -108,20 +110,8 @@ public class MailService extends Service {
/**
* Reconcile POP/IMAP accounts.
*/
- public static void reconcilePopImapAccountsSync(Context context) {
- sReconcilePopImapAccountsSyncExecutor.run(context);
- }
-
- /**
- * Determines whether or not POP/IMAP accounts need reconciling or not. This is a safe operation
- * to perform on the UI thread.
- */
- public static boolean hasMismatchInPopImapAccounts(Context context) {
- android.accounts.Account[] accountManagerAccounts = AccountManager.get(context)
- .getAccountsByType(AccountManagerTypes.TYPE_POP_IMAP);
- ArrayList providerAccounts = getPopImapAccountList(context);
- return AccountReconciler.accountsNeedReconciling(
- context, providerAccounts, accountManagerAccounts);
+ public static void reconcilePopAccountsSync(Context context) {
+ sReconcilePopAccountsSyncExecutor.run(context);
}
/**
diff --git a/src/com/android/email/service/PopImapAuthenticatorService.java b/src/com/android/email/service/Pop3AuthenticatorService.java
similarity index 91%
rename from src/com/android/email/service/PopImapAuthenticatorService.java
rename to src/com/android/email/service/Pop3AuthenticatorService.java
index b829be1f3..f3076ee6b 100644
--- a/src/com/android/email/service/PopImapAuthenticatorService.java
+++ b/src/com/android/email/service/Pop3AuthenticatorService.java
@@ -19,5 +19,5 @@ package com.android.email.service;
/**
* This service needs to be declared separately from the base service
*/
-public class PopImapAuthenticatorService extends AuthenticatorService {
+public class Pop3AuthenticatorService extends AuthenticatorService {
}
diff --git a/src/com/android/email/service/Pop3Service.java b/src/com/android/email/service/Pop3Service.java
index 0561d666a..600694445 100644
--- a/src/com/android/email/service/Pop3Service.java
+++ b/src/com/android/email/service/Pop3Service.java
@@ -105,6 +105,11 @@ public class Pop3Service extends Service {
// We load attachments during a sync
startSync(inboxId, true);
}
+
+ @Override
+ public void serviceUpdated(String emailAddress) throws RemoteException {
+ // Not required for POP3
+ }
};
@Override
diff --git a/src/com/android/email/service/PopImapSyncAdapterService.java b/src/com/android/email/service/Pop3SyncAdapterService.java
similarity index 87%
rename from src/com/android/email/service/PopImapSyncAdapterService.java
rename to src/com/android/email/service/Pop3SyncAdapterService.java
index 9a1f185e6..8f2cc8559 100644
--- a/src/com/android/email/service/PopImapSyncAdapterService.java
+++ b/src/com/android/email/service/Pop3SyncAdapterService.java
@@ -38,18 +38,17 @@ import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.EmailContent.AccountColumns;
import com.android.emailcommon.provider.EmailContent.Message;
-import com.android.emailcommon.provider.HostAuth;
import com.android.emailcommon.provider.Mailbox;
import com.android.emailcommon.service.EmailServiceProxy;
import java.util.ArrayList;
-public class PopImapSyncAdapterService extends Service {
- private static final String TAG = "PopImapSyncAdapterService";
+public class Pop3SyncAdapterService extends Service {
+ private static final String TAG = "Pop3SyncAdapterService";
private static SyncAdapterImpl sSyncAdapter = null;
private static final Object sSyncAdapterLock = new Object();
- public PopImapSyncAdapterService() {
+ public Pop3SyncAdapterService() {
super();
}
@@ -65,7 +64,7 @@ public class PopImapSyncAdapterService extends Service {
public void onPerformSync(android.accounts.Account account, Bundle extras,
String authority, ContentProviderClient provider, SyncResult syncResult) {
try {
- PopImapSyncAdapterService.performSync(mContext, account, extras,
+ Pop3SyncAdapterService.performSync(mContext, account, extras,
authority, provider, syncResult);
} catch (OperationCanceledException e) {
}
@@ -87,25 +86,6 @@ public class PopImapSyncAdapterService extends Service {
return sSyncAdapter.getSyncAdapterBinder();
}
- /**
- * @return whether or not this mailbox retrieves its data from the server (as opposed to just
- * a local mailbox that is never synced).
- */
- public static boolean loadsFromServer(Mailbox m, String protocol) {
- if (HostAuth.LEGACY_SCHEME_IMAP.equals(protocol)) {
- // TODO: actually use a sync flag when creating the mailboxes. Right now we use an
- // approximation for IMAP.
- return m.mType != Mailbox.TYPE_DRAFTS
- && m.mType != Mailbox.TYPE_OUTBOX
- && m.mType != Mailbox.TYPE_SEARCH;
-
- } else if (HostAuth.LEGACY_SCHEME_POP3.equals(protocol)) {
- return Mailbox.TYPE_INBOX == m.mType;
- }
-
- return false;
- }
-
private static void sync(Context context, long mailboxId, SyncResult syncResult,
boolean uiRefresh) {
TempDirectory.setTempDirectory(context);
@@ -114,8 +94,7 @@ public class PopImapSyncAdapterService extends Service {
Account account = Account.restoreAccountWithId(context, mailbox.mAccountKey);
if (account == null) return;
ContentResolver resolver = context.getContentResolver();
- String protocol = account.getProtocol(context);
- if ((mailbox.mType != Mailbox.TYPE_OUTBOX) && !loadsFromServer(mailbox, protocol)) {
+ if ((mailbox.mType != Mailbox.TYPE_OUTBOX) && (mailbox.mType != Mailbox.TYPE_INBOX)) {
// This is an update to a message in a non-syncing mailbox; delete this from the
// updates table and return
resolver.delete(Message.UPDATED_CONTENT_URI, Message.MAILBOX_KEY + "=?",
@@ -134,8 +113,6 @@ public class PopImapSyncAdapterService extends Service {
try {
if (mailbox.mType == Mailbox.TYPE_OUTBOX) {
EmailServiceStub.sendMailImpl(context, account.mId);
- } else if (protocol.equals(HostAuth.LEGACY_SCHEME_IMAP)) {
- ImapService.synchronizeMailboxSynchronous(context, account, mailbox);
} else {
Pop3Service.synchronizeMailboxSynchronous(context, account, mailbox);
}
diff --git a/src/com/android/mail/providers/protos/boot/AccountReceiver.java b/src/com/android/mail/providers/protos/boot/AccountReceiver.java
index 16eed27d3..08b29401a 100644
--- a/src/com/android/mail/providers/protos/boot/AccountReceiver.java
+++ b/src/com/android/mail/providers/protos/boot/AccountReceiver.java
@@ -16,6 +16,7 @@
package com.android.mail.providers.protos.boot;
+import com.android.emailcommon.provider.EmailContent;
import com.android.mail.providers.MailAppProvider;
import android.content.BroadcastReceiver;
@@ -30,12 +31,9 @@ public class AccountReceiver extends BroadcastReceiver {
public static final String ACTION_PROVIDER_CREATED
= "com.android.email2.providers.protos.boot.intent.ACTION_PROVIDER_CREATED";
- private static final Uri ACCOUNTS_URI =
- Uri.parse("content://com.android.email.provider/uiaccts");
-
-
@Override
public void onReceive(Context context, Intent intent) {
- MailAppProvider.addAccountsForUriAsync(ACCOUNTS_URI);
+ EmailContent.init(context);
+ MailAppProvider.addAccountsForUriAsync(Uri.parse(EmailContent.CONTENT_URI + "/uiaccts"));
}
}
diff --git a/tests/src/com/android/emailcommon/utility/UtilityMediumTests.java b/tests/src/com/android/emailcommon/utility/UtilityMediumTests.java
index 96d2a8f0b..46ea4d78e 100644
--- a/tests/src/com/android/emailcommon/utility/UtilityMediumTests.java
+++ b/tests/src/com/android/emailcommon/utility/UtilityMediumTests.java
@@ -134,12 +134,12 @@ public class UtilityMediumTests extends ProviderTestCase2 {
public void testBuildLimitOneUri() {
// EmailProvider supports "?limit="
- assertEquals(Uri.parse("content://com.android.email.provider?limit=1"),
- Utility.buildLimitOneUri(Uri.parse("content://com.android.email.provider")));
+ assertEquals(Uri.parse("content://com.android.mail.provider?limit=1"),
+ Utility.buildLimitOneUri(Uri.parse("content://com.android.mail.provider")));
// Others don't -- so don't add it.
- assertEquals(Uri.parse("content://com.android.email.attachmentprovider"),
- Utility.buildLimitOneUri(Uri.parse("content://com.android.email.attachmentprovider"
+ assertEquals(Uri.parse("content://com.android.mail.attachmentprovider"),
+ Utility.buildLimitOneUri(Uri.parse("content://com.android.mail.attachmentprovider"
)));
assertEquals(Uri.parse("content://gmail-ls/android@gmail.com"),
Utility.buildLimitOneUri(Uri.parse("content://gmail-ls/android@gmail.com"