First implementation of IMAP search
* Broke up synchronizeMailboxGeneric into three pieces; it's still horrible, but this at least stops my eyes from bleeding * Remove unused method/tests from Folder interface Change-Id: Ib4d979536be657137cf70ca535cf429d707be41b
This commit is contained in:
parent
1579b7864a
commit
627bc6ed57
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package com.android.emailcommon.mail;
|
package com.android.emailcommon.mail;
|
||||||
|
|
||||||
|
import com.android.emailcommon.service.SearchParams;
|
||||||
|
|
||||||
|
|
||||||
public abstract class Folder {
|
public abstract class Folder {
|
||||||
public enum OpenMode {
|
public enum OpenMode {
|
||||||
@ -107,21 +109,21 @@ public abstract class Folder {
|
|||||||
|
|
||||||
public abstract Message getMessage(String uid) throws MessagingException;
|
public abstract Message getMessage(String uid) throws MessagingException;
|
||||||
|
|
||||||
public abstract Message[] getMessages(int start, int end, MessageRetrievalListener listener)
|
|
||||||
throws MessagingException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches the given list of messages. The specified listener is notified as
|
* Fetches the given list of messages. The specified listener is notified as
|
||||||
* each fetch completes. Messages are downloaded as (as) lightweight (as
|
* each fetch completes. Messages are downloaded as (as) lightweight (as
|
||||||
* possible) objects to be filled in with later requests. In most cases this
|
* possible) objects to be filled in with later requests. In most cases this
|
||||||
* means that only the UID is downloaded.
|
* means that only the UID is downloaded.
|
||||||
*/
|
*/
|
||||||
public abstract Message[] getMessages(MessageRetrievalListener listener)
|
public abstract Message[] getMessages(int start, int end, MessageRetrievalListener listener)
|
||||||
|
throws MessagingException;
|
||||||
|
|
||||||
|
public abstract Message[] getMessages(SearchParams params,MessageRetrievalListener listener)
|
||||||
throws MessagingException;
|
throws MessagingException;
|
||||||
|
|
||||||
public abstract Message[] getMessages(String[] uids, MessageRetrievalListener listener)
|
public abstract Message[] getMessages(String[] uids, MessageRetrievalListener listener)
|
||||||
throws MessagingException;
|
throws MessagingException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a set of messages based on the state of the flags.
|
* Return a set of messages based on the state of the flags.
|
||||||
* Note: Not typically implemented in remote stores, so not abstract.
|
* Note: Not typically implemented in remote stores, so not abstract.
|
||||||
|
@ -17,11 +17,6 @@
|
|||||||
|
|
||||||
package com.android.emailcommon.provider;
|
package com.android.emailcommon.provider;
|
||||||
|
|
||||||
import com.android.emailcommon.Logging;
|
|
||||||
import com.android.emailcommon.provider.EmailContent.MailboxColumns;
|
|
||||||
import com.android.emailcommon.provider.EmailContent.SyncColumns;
|
|
||||||
import com.android.emailcommon.utility.Utility;
|
|
||||||
|
|
||||||
import android.content.ContentUris;
|
import android.content.ContentUris;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -31,6 +26,11 @@ import android.os.Parcel;
|
|||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.android.emailcommon.Logging;
|
||||||
|
import com.android.emailcommon.provider.EmailContent.MailboxColumns;
|
||||||
|
import com.android.emailcommon.provider.EmailContent.SyncColumns;
|
||||||
|
import com.android.emailcommon.utility.Utility;
|
||||||
|
|
||||||
public class Mailbox extends EmailContent implements SyncColumns, MailboxColumns, Parcelable {
|
public class Mailbox extends EmailContent implements SyncColumns, MailboxColumns, Parcelable {
|
||||||
public static final String TABLE_NAME = "Mailbox";
|
public static final String TABLE_NAME = "Mailbox";
|
||||||
@SuppressWarnings("hiding")
|
@SuppressWarnings("hiding")
|
||||||
@ -390,6 +390,7 @@ public class Mailbox extends EmailContent implements SyncColumns, MailboxColumns
|
|||||||
}
|
}
|
||||||
switch (getMailboxType(context, mailboxId)) {
|
switch (getMailboxType(context, mailboxId)) {
|
||||||
case -1: // not found
|
case -1: // not found
|
||||||
|
case TYPE_SEARCH:
|
||||||
case TYPE_DRAFTS:
|
case TYPE_DRAFTS:
|
||||||
case TYPE_OUTBOX:
|
case TYPE_OUTBOX:
|
||||||
return false;
|
return false;
|
||||||
|
@ -886,21 +886,50 @@ public class Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search for messages on the server; see {@Link EmailServiceProxy#searchMessages(long, long,
|
* Search for messages on the (IMAP) server; do not call this on the UI thread!
|
||||||
* boolean, String, int, int, long)} for a complete description of this method's arguments.
|
* @param accountId the id of the account to be searched
|
||||||
|
* @param searchParams the parameters for this search
|
||||||
|
* @throws MessagingException
|
||||||
*/
|
*/
|
||||||
public void searchMessages(final long accountId, final SearchParams searchParams,
|
public void searchMessages(final long accountId, final SearchParams searchParams)
|
||||||
final long destMailboxId) {
|
throws MessagingException {
|
||||||
|
// Find/create our search mailbox
|
||||||
|
Mailbox searchMailbox = getSearchMailbox(accountId);
|
||||||
|
if (searchMailbox == null) return;
|
||||||
|
final long searchMailboxId = searchMailbox.mId;
|
||||||
|
|
||||||
IEmailService service = getServiceForAccount(accountId);
|
IEmailService service = getServiceForAccount(accountId);
|
||||||
if (service != null) {
|
if (service != null) {
|
||||||
// Service implementation
|
// Service implementation
|
||||||
try {
|
try {
|
||||||
service.searchMessages(accountId, searchParams, destMailboxId);
|
service.searchMessages(accountId, searchParams, searchMailboxId);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
// TODO Change exception handling to be consistent with however this method
|
// TODO Change exception handling to be consistent with however this method
|
||||||
// is implemented for other protocols
|
// is implemented for other protocols
|
||||||
Log.e("searchMessages", "RemoteException", e);
|
Log.e("searchMessages", "RemoteException", e);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// This is the actual mailbox we'll be searching
|
||||||
|
Mailbox actualMailbox = Mailbox.restoreMailboxWithId(mContext, searchParams.mMailboxId);
|
||||||
|
if (actualMailbox == null) return;
|
||||||
|
|
||||||
|
// Delete existing contents of search mailbox
|
||||||
|
ContentResolver resolver = mContext.getContentResolver();
|
||||||
|
resolver.delete(Message.CONTENT_URI, Message.MAILBOX_KEY + "=" + searchMailboxId,
|
||||||
|
null);
|
||||||
|
ContentValues cv = new ContentValues();
|
||||||
|
// For now, use the actual query as the name of the mailbox
|
||||||
|
cv.put(Mailbox.DISPLAY_NAME, searchParams.mFilter);
|
||||||
|
// But use the server id of the actual mailbox we're searching; this allows full
|
||||||
|
// message loading to work normally (clever, huh?)
|
||||||
|
cv.put(MailboxColumns.SERVER_ID, actualMailbox.mServerId);
|
||||||
|
resolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, searchMailboxId), cv,
|
||||||
|
null, null);
|
||||||
|
// Do the search
|
||||||
|
if (Email.DEBUG) {
|
||||||
|
Log.d(Logging.LOG_TAG, "Search: " + searchParams.mFilter);
|
||||||
|
}
|
||||||
|
mLegacyController.searchMailbox(accountId, searchParams, searchMailboxId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,15 @@
|
|||||||
|
|
||||||
package com.android.email;
|
package com.android.email;
|
||||||
|
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.content.ContentUris;
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Process;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import com.android.email.mail.Sender;
|
import com.android.email.mail.Sender;
|
||||||
import com.android.email.mail.Store;
|
import com.android.email.mail.Store;
|
||||||
import com.android.emailcommon.Logging;
|
import com.android.emailcommon.Logging;
|
||||||
@ -42,21 +51,15 @@ import com.android.emailcommon.provider.EmailContent.MailboxColumns;
|
|||||||
import com.android.emailcommon.provider.EmailContent.MessageColumns;
|
import com.android.emailcommon.provider.EmailContent.MessageColumns;
|
||||||
import com.android.emailcommon.provider.EmailContent.SyncColumns;
|
import com.android.emailcommon.provider.EmailContent.SyncColumns;
|
||||||
import com.android.emailcommon.provider.Mailbox;
|
import com.android.emailcommon.provider.Mailbox;
|
||||||
|
import com.android.emailcommon.service.SearchParams;
|
||||||
import com.android.emailcommon.utility.AttachmentUtilities;
|
import com.android.emailcommon.utility.AttachmentUtilities;
|
||||||
import com.android.emailcommon.utility.ConversionUtilities;
|
import com.android.emailcommon.utility.ConversionUtilities;
|
||||||
import com.android.emailcommon.utility.Utility;
|
import com.android.emailcommon.utility.Utility;
|
||||||
|
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.ContentUris;
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Process;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@ -397,18 +400,268 @@ public class MessagingController implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
void loadUnsyncedMessages(final Account account, Folder remoteFolder,
|
||||||
|
ArrayList<Message> unsyncedMessages, final Mailbox toMailbox)
|
||||||
|
throws MessagingException {
|
||||||
|
|
||||||
|
// 1. Divide the unsynced messages into small & large (by size)
|
||||||
|
|
||||||
|
// TODO doing this work here (synchronously) is problematic because it prevents the UI
|
||||||
|
// from affecting the order (e.g. download a message because the user requested it.) Much
|
||||||
|
// of this logic should move out to a different sync loop that attempts to update small
|
||||||
|
// groups of messages at a time, as a background task. However, we can't just return
|
||||||
|
// (yet) because POP messages don't have an envelope yet....
|
||||||
|
|
||||||
|
ArrayList<Message> largeMessages = new ArrayList<Message>();
|
||||||
|
ArrayList<Message> smallMessages = new ArrayList<Message>();
|
||||||
|
for (Message message : unsyncedMessages) {
|
||||||
|
if (message.getSize() > (MAX_SMALL_MESSAGE_SIZE)) {
|
||||||
|
largeMessages.add(message);
|
||||||
|
} else {
|
||||||
|
smallMessages.add(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Download small messages
|
||||||
|
|
||||||
|
// TODO Problems with this implementation. 1. For IMAP, where we get a real envelope,
|
||||||
|
// this is going to be inefficient and duplicate work we've already done. 2. It's going
|
||||||
|
// back to the DB for a local message that we already had (and discarded).
|
||||||
|
|
||||||
|
// For small messages, we specify "body", which returns everything (incl. attachments)
|
||||||
|
FetchProfile fp = new FetchProfile();
|
||||||
|
fp.add(FetchProfile.Item.BODY);
|
||||||
|
remoteFolder.fetch(smallMessages.toArray(new Message[smallMessages.size()]), fp,
|
||||||
|
new MessageRetrievalListener() {
|
||||||
|
public void messageRetrieved(Message message) {
|
||||||
|
// Store the updated message locally and mark it fully loaded
|
||||||
|
copyOneMessageToProvider(message, account, toMailbox,
|
||||||
|
EmailContent.Message.FLAG_LOADED_COMPLETE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadAttachmentProgress(int progress) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. Download large messages. We ask the server to give us the message structure,
|
||||||
|
// but not all of the attachments.
|
||||||
|
fp.clear();
|
||||||
|
fp.add(FetchProfile.Item.STRUCTURE);
|
||||||
|
remoteFolder.fetch(largeMessages.toArray(new Message[largeMessages.size()]), fp, null);
|
||||||
|
for (Message message : largeMessages) {
|
||||||
|
if (message.getBody() == null) {
|
||||||
|
// POP doesn't support STRUCTURE mode, so we'll just do a partial download
|
||||||
|
// (hopefully enough to see some/all of the body) and mark the message for
|
||||||
|
// further download.
|
||||||
|
fp.clear();
|
||||||
|
fp.add(FetchProfile.Item.BODY_SANE);
|
||||||
|
// TODO a good optimization here would be to make sure that all Stores set
|
||||||
|
// the proper size after this fetch and compare the before and after size. If
|
||||||
|
// they equal we can mark this SYNCHRONIZED instead of PARTIALLY_SYNCHRONIZED
|
||||||
|
remoteFolder.fetch(new Message[] { message }, fp, null);
|
||||||
|
|
||||||
|
// Store the partially-loaded message and mark it partially loaded
|
||||||
|
copyOneMessageToProvider(message, account, toMailbox,
|
||||||
|
EmailContent.Message.FLAG_LOADED_PARTIAL);
|
||||||
|
} else {
|
||||||
|
// We have a structure to deal with, from which
|
||||||
|
// we can pull down the parts we want to actually store.
|
||||||
|
// Build a list of parts we are interested in. Text parts will be downloaded
|
||||||
|
// right now, attachments will be left for later.
|
||||||
|
ArrayList<Part> viewables = new ArrayList<Part>();
|
||||||
|
ArrayList<Part> attachments = new ArrayList<Part>();
|
||||||
|
MimeUtility.collectParts(message, viewables, attachments);
|
||||||
|
// Download the viewables immediately
|
||||||
|
for (Part part : viewables) {
|
||||||
|
fp.clear();
|
||||||
|
fp.add(part);
|
||||||
|
// TODO what happens if the network connection dies? We've got partial
|
||||||
|
// messages with incorrect status stored.
|
||||||
|
remoteFolder.fetch(new Message[] { message }, fp, null);
|
||||||
|
}
|
||||||
|
// Store the updated message locally and mark it fully loaded
|
||||||
|
copyOneMessageToProvider(message, account, toMailbox,
|
||||||
|
EmailContent.Message.FLAG_LOADED_COMPLETE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void downloadFlagAndEnvelope(final Account account, final Mailbox mailbox,
|
||||||
|
Folder remoteFolder, ArrayList<Message> unsyncedMessages,
|
||||||
|
HashMap<String, LocalMessageInfo> localMessageMap, final ArrayList<Long> unseenMessages)
|
||||||
|
throws MessagingException {
|
||||||
|
FetchProfile fp = new FetchProfile();
|
||||||
|
fp.add(FetchProfile.Item.FLAGS);
|
||||||
|
fp.add(FetchProfile.Item.ENVELOPE);
|
||||||
|
|
||||||
|
final HashMap<String, LocalMessageInfo> localMapCopy;
|
||||||
|
if (localMessageMap != null)
|
||||||
|
localMapCopy = new HashMap<String, LocalMessageInfo>(localMessageMap);
|
||||||
|
else {
|
||||||
|
localMapCopy = new HashMap<String, LocalMessageInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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(
|
||||||
|
mContext, 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
|
||||||
|
saveOrUpdate(localMessage, mContext);
|
||||||
|
// 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) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void searchMailbox(long accountId, SearchParams searchParams, final long destMailboxId)
|
||||||
|
throws MessagingException {
|
||||||
|
final Account account = Account.restoreAccountWithId(mContext, accountId);
|
||||||
|
final Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, searchParams.mMailboxId);
|
||||||
|
final Mailbox destMailbox = Mailbox.restoreMailboxWithId(mContext, destMailboxId);
|
||||||
|
if (account == null || mailbox == null || destMailbox == null) return;
|
||||||
|
|
||||||
|
Store remoteStore = Store.getInstance(account, mContext, null);
|
||||||
|
Folder remoteFolder = remoteStore.getFolder(mailbox.mServerId);
|
||||||
|
remoteFolder.open(OpenMode.READ_WRITE, null);
|
||||||
|
|
||||||
|
// Get the "bare" messages (basically uid)
|
||||||
|
Message[] remoteMessages = remoteFolder.getMessages(searchParams, null);
|
||||||
|
int remoteCount = remoteMessages.length;
|
||||||
|
if (remoteCount > 0) {
|
||||||
|
SortableMessage[] 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 we request them;
|
||||||
|
// those that do will see messages arrive from newest to oldest (i.e. the "right" order)
|
||||||
|
Arrays.sort(sortableMessages, new Comparator<SortableMessage>() {
|
||||||
|
@Override
|
||||||
|
public int compare(SortableMessage lhs, SortableMessage rhs) {
|
||||||
|
return lhs.mUid > rhs.mUid ? -1 : lhs.mUid < rhs.mUid ? 1 : 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
final ArrayList<Message> messageList = new ArrayList<Message>();
|
||||||
|
// Now create a list sized for our visible limit and fill it in
|
||||||
|
int messageListSize = Math.min(remoteCount, Email.VISIBLE_LIMIT_DEFAULT);
|
||||||
|
for (i = 0; i < messageListSize; 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() {
|
||||||
|
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
|
||||||
|
saveOrUpdate(localMessage, mContext);
|
||||||
|
localMessage.mMailboxKey = destMailboxId;
|
||||||
|
// We load 50k or so; maybe it's complete, maybe not...
|
||||||
|
int flag = EmailContent.Message.FLAG_LOADED_COMPLETE;
|
||||||
|
if (message.getSize() > Store.FETCH_BODY_SANE_SUGGESTED_SIZE) {
|
||||||
|
flag = EmailContent.Message.FLAG_LOADED_PARTIAL;
|
||||||
|
}
|
||||||
|
copyOneMessageToProvider(message, localMessage, flag, mContext);
|
||||||
|
} 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) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic synchronizer - used for POP3 and IMAP.
|
* Generic synchronizer - used for POP3 and IMAP.
|
||||||
*
|
*
|
||||||
* TODO Break this method up into smaller chunks.
|
* TODO Break this method up into smaller chunks.
|
||||||
*
|
*
|
||||||
* @param account the account to sync
|
* @param account the account to sync
|
||||||
* @param folder the mailbox to sync
|
* @param mailbox the mailbox to sync
|
||||||
* @return results of the sync pass
|
* @return results of the sync pass
|
||||||
* @throws MessagingException
|
* @throws MessagingException
|
||||||
*/
|
*/
|
||||||
private SyncResults synchronizeMailboxGeneric(
|
private SyncResults synchronizeMailboxGeneric(final Account account, final Mailbox mailbox)
|
||||||
final Account account, final Mailbox folder)
|
|
||||||
throws MessagingException {
|
throws MessagingException {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -421,8 +674,8 @@ public class MessagingController implements Runnable {
|
|||||||
ContentResolver resolver = mContext.getContentResolver();
|
ContentResolver resolver = mContext.getContentResolver();
|
||||||
|
|
||||||
// 0. We do not ever sync DRAFTS or OUTBOX (down or up)
|
// 0. We do not ever sync DRAFTS or OUTBOX (down or up)
|
||||||
if (folder.mType == Mailbox.TYPE_DRAFTS || folder.mType == Mailbox.TYPE_OUTBOX) {
|
if (mailbox.mType == Mailbox.TYPE_DRAFTS || mailbox.mType == Mailbox.TYPE_OUTBOX) {
|
||||||
int totalMessages = EmailContent.count(mContext, folder.getUri(), null, null);
|
int totalMessages = EmailContent.count(mContext, mailbox.getUri(), null, null);
|
||||||
return new SyncResults(totalMessages, unseenMessages);
|
return new SyncResults(totalMessages, unseenMessages);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -439,7 +692,7 @@ public class MessagingController implements Runnable {
|
|||||||
" AND " + MessageColumns.MAILBOX_KEY + "=?",
|
" AND " + MessageColumns.MAILBOX_KEY + "=?",
|
||||||
new String[] {
|
new String[] {
|
||||||
String.valueOf(account.mId),
|
String.valueOf(account.mId),
|
||||||
String.valueOf(folder.mId)
|
String.valueOf(mailbox.mId)
|
||||||
},
|
},
|
||||||
null);
|
null);
|
||||||
while (localUidCursor.moveToNext()) {
|
while (localUidCursor.moveToNext()) {
|
||||||
@ -452,20 +705,10 @@ public class MessagingController implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1a. Count the unread messages before changing anything
|
|
||||||
int localUnreadCount = EmailContent.count(mContext, EmailContent.Message.CONTENT_URI,
|
|
||||||
EmailContent.MessageColumns.ACCOUNT_KEY + "=?" +
|
|
||||||
" AND " + MessageColumns.MAILBOX_KEY + "=?" +
|
|
||||||
" AND " + MessageColumns.FLAG_READ + "=0",
|
|
||||||
new String[] {
|
|
||||||
String.valueOf(account.mId),
|
|
||||||
String.valueOf(folder.mId)
|
|
||||||
});
|
|
||||||
|
|
||||||
// 2. Open the remote folder and create the remote folder if necessary
|
// 2. Open the remote folder and create the remote folder if necessary
|
||||||
|
|
||||||
Store remoteStore = Store.getInstance(account, mContext, null);
|
Store remoteStore = Store.getInstance(account, mContext, null);
|
||||||
Folder remoteFolder = remoteStore.getFolder(folder.mServerId);
|
Folder remoteFolder = remoteStore.getFolder(mailbox.mServerId);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the folder is a "special" folder we need to see if it exists
|
* If the folder is a "special" folder we need to see if it exists
|
||||||
@ -474,8 +717,8 @@ public class MessagingController implements Runnable {
|
|||||||
* designed and on Imap folders during error conditions. This allows us
|
* designed and on Imap folders during error conditions. This allows us
|
||||||
* to treat Pop3 and Imap the same in this code.
|
* to treat Pop3 and Imap the same in this code.
|
||||||
*/
|
*/
|
||||||
if (folder.mType == Mailbox.TYPE_TRASH || folder.mType == Mailbox.TYPE_SENT
|
if (mailbox.mType == Mailbox.TYPE_TRASH || mailbox.mType == Mailbox.TYPE_SENT
|
||||||
|| folder.mType == Mailbox.TYPE_DRAFTS) {
|
|| mailbox.mType == Mailbox.TYPE_DRAFTS) {
|
||||||
if (!remoteFolder.exists()) {
|
if (!remoteFolder.exists()) {
|
||||||
if (!remoteFolder.create(FolderType.HOLDS_MESSAGES)) {
|
if (!remoteFolder.create(FolderType.HOLDS_MESSAGES)) {
|
||||||
return new SyncResults(0, unseenMessages);
|
return new SyncResults(0, unseenMessages);
|
||||||
@ -493,7 +736,7 @@ public class MessagingController implements Runnable {
|
|||||||
int remoteMessageCount = remoteFolder.getMessageCount();
|
int remoteMessageCount = remoteFolder.getMessageCount();
|
||||||
|
|
||||||
// 6. Determine the limit # of messages to download
|
// 6. Determine the limit # of messages to download
|
||||||
int visibleLimit = folder.mVisibleLimit;
|
int visibleLimit = mailbox.mVisibleLimit;
|
||||||
if (visibleLimit <= 0) {
|
if (visibleLimit <= 0) {
|
||||||
visibleLimit = Email.VISIBLE_LIMIT_DEFAULT;
|
visibleLimit = Email.VISIBLE_LIMIT_DEFAULT;
|
||||||
}
|
}
|
||||||
@ -548,56 +791,8 @@ public class MessagingController implements Runnable {
|
|||||||
* critical data as fast as possible, and then we'll fill in the details.
|
* critical data as fast as possible, and then we'll fill in the details.
|
||||||
*/
|
*/
|
||||||
if (unsyncedMessages.size() > 0) {
|
if (unsyncedMessages.size() > 0) {
|
||||||
FetchProfile fp = new FetchProfile();
|
downloadFlagAndEnvelope(account, mailbox, remoteFolder, unsyncedMessages,
|
||||||
fp.add(FetchProfile.Item.FLAGS);
|
localMessageMap, unseenMessages);
|
||||||
fp.add(FetchProfile.Item.ENVELOPE);
|
|
||||||
final HashMap<String, LocalMessageInfo> localMapCopy =
|
|
||||||
new HashMap<String, LocalMessageInfo>(localMessageMap);
|
|
||||||
|
|
||||||
remoteFolder.fetch(unsyncedMessages.toArray(new Message[0]), fp,
|
|
||||||
new MessageRetrievalListener() {
|
|
||||||
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(
|
|
||||||
mContext, localMessageInfo.mId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (localMessage != null) {
|
|
||||||
try {
|
|
||||||
// Copy the fields that are available into the message
|
|
||||||
LegacyConversions.updateMessageFields(localMessage,
|
|
||||||
message, account.mId, folder.mId);
|
|
||||||
// Commit the message to the local store
|
|
||||||
saveOrUpdate(localMessage, mContext);
|
|
||||||
// Track the "new" ness of the downloaded message
|
|
||||||
if (!message.isSet(Flag.SEEN)) {
|
|
||||||
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) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 9. Refresh the flags for any messages in the local store that we didn't just download.
|
// 9. Refresh the flags for any messages in the local store that we didn't just download.
|
||||||
@ -663,87 +858,7 @@ public class MessagingController implements Runnable {
|
|||||||
resolver.delete(deletERowToDelete, null, null);
|
resolver.delete(deletERowToDelete, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 11. Divide the unsynced messages into small & large (by size)
|
loadUnsyncedMessages(account, remoteFolder, unsyncedMessages, mailbox);
|
||||||
|
|
||||||
// TODO doing this work here (synchronously) is problematic because it prevents the UI
|
|
||||||
// from affecting the order (e.g. download a message because the user requested it.) Much
|
|
||||||
// of this logic should move out to a different sync loop that attempts to update small
|
|
||||||
// groups of messages at a time, as a background task. However, we can't just return
|
|
||||||
// (yet) because POP messages don't have an envelope yet....
|
|
||||||
|
|
||||||
ArrayList<Message> largeMessages = new ArrayList<Message>();
|
|
||||||
ArrayList<Message> smallMessages = new ArrayList<Message>();
|
|
||||||
for (Message message : unsyncedMessages) {
|
|
||||||
if (message.getSize() > (MAX_SMALL_MESSAGE_SIZE)) {
|
|
||||||
largeMessages.add(message);
|
|
||||||
} else {
|
|
||||||
smallMessages.add(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 12. Download small messages
|
|
||||||
|
|
||||||
// TODO Problems with this implementation. 1. For IMAP, where we get a real envelope,
|
|
||||||
// this is going to be inefficient and duplicate work we've already done. 2. It's going
|
|
||||||
// back to the DB for a local message that we already had (and discarded).
|
|
||||||
|
|
||||||
// For small messages, we specify "body", which returns everything (incl. attachments)
|
|
||||||
fp = new FetchProfile();
|
|
||||||
fp.add(FetchProfile.Item.BODY);
|
|
||||||
remoteFolder.fetch(smallMessages.toArray(new Message[smallMessages.size()]), fp,
|
|
||||||
new MessageRetrievalListener() {
|
|
||||||
public void messageRetrieved(Message message) {
|
|
||||||
// Store the updated message locally and mark it fully loaded
|
|
||||||
copyOneMessageToProvider(message, account, folder,
|
|
||||||
EmailContent.Message.FLAG_LOADED_COMPLETE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadAttachmentProgress(int progress) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 13. Download large messages. We ask the server to give us the message structure,
|
|
||||||
// but not all of the attachments.
|
|
||||||
fp.clear();
|
|
||||||
fp.add(FetchProfile.Item.STRUCTURE);
|
|
||||||
remoteFolder.fetch(largeMessages.toArray(new Message[largeMessages.size()]), fp, null);
|
|
||||||
for (Message message : largeMessages) {
|
|
||||||
if (message.getBody() == null) {
|
|
||||||
// POP doesn't support STRUCTURE mode, so we'll just do a partial download
|
|
||||||
// (hopefully enough to see some/all of the body) and mark the message for
|
|
||||||
// further download.
|
|
||||||
fp.clear();
|
|
||||||
fp.add(FetchProfile.Item.BODY_SANE);
|
|
||||||
// TODO a good optimization here would be to make sure that all Stores set
|
|
||||||
// the proper size after this fetch and compare the before and after size. If
|
|
||||||
// they equal we can mark this SYNCHRONIZED instead of PARTIALLY_SYNCHRONIZED
|
|
||||||
remoteFolder.fetch(new Message[] { message }, fp, null);
|
|
||||||
|
|
||||||
// Store the partially-loaded message and mark it partially loaded
|
|
||||||
copyOneMessageToProvider(message, account, folder,
|
|
||||||
EmailContent.Message.FLAG_LOADED_PARTIAL);
|
|
||||||
} else {
|
|
||||||
// We have a structure to deal with, from which
|
|
||||||
// we can pull down the parts we want to actually store.
|
|
||||||
// Build a list of parts we are interested in. Text parts will be downloaded
|
|
||||||
// right now, attachments will be left for later.
|
|
||||||
ArrayList<Part> viewables = new ArrayList<Part>();
|
|
||||||
ArrayList<Part> attachments = new ArrayList<Part>();
|
|
||||||
MimeUtility.collectParts(message, viewables, attachments);
|
|
||||||
// Download the viewables immediately
|
|
||||||
for (Part part : viewables) {
|
|
||||||
fp.clear();
|
|
||||||
fp.add(part);
|
|
||||||
// TODO what happens if the network connection dies? We've got partial
|
|
||||||
// messages with incorrect status stored.
|
|
||||||
remoteFolder.fetch(new Message[] { message }, fp, null);
|
|
||||||
}
|
|
||||||
// Store the updated message locally and mark it fully loaded
|
|
||||||
copyOneMessageToProvider(message, account, folder,
|
|
||||||
EmailContent.Message.FLAG_LOADED_COMPLETE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 14. Clean up and report results
|
// 14. Clean up and report results
|
||||||
remoteFolder.close(false);
|
remoteFolder.close(false);
|
||||||
|
@ -16,25 +16,10 @@
|
|||||||
|
|
||||||
package com.android.email.activity;
|
package com.android.email.activity;
|
||||||
|
|
||||||
import com.android.email.Controller;
|
|
||||||
import com.android.email.ControllerResultUiThreadWrapper;
|
|
||||||
import com.android.email.Email;
|
|
||||||
import com.android.email.MessagingExceptionStrings;
|
|
||||||
import com.android.email.R;
|
|
||||||
import com.android.emailcommon.Logging;
|
|
||||||
import com.android.emailcommon.mail.MessagingException;
|
|
||||||
import com.android.emailcommon.provider.Account;
|
|
||||||
import com.android.emailcommon.provider.EmailContent.MailboxColumns;
|
|
||||||
import com.android.emailcommon.provider.EmailContent.Message;
|
|
||||||
import com.android.emailcommon.provider.Mailbox;
|
|
||||||
import com.android.emailcommon.service.SearchParams;
|
|
||||||
import com.android.emailcommon.utility.EmailAsyncTask;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.ContentUris;
|
import android.content.ContentUris;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -49,6 +34,19 @@ import android.view.MenuItem;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.android.email.Controller;
|
||||||
|
import com.android.email.ControllerResultUiThreadWrapper;
|
||||||
|
import com.android.email.Email;
|
||||||
|
import com.android.email.MessagingExceptionStrings;
|
||||||
|
import com.android.email.R;
|
||||||
|
import com.android.emailcommon.Logging;
|
||||||
|
import com.android.emailcommon.mail.MessagingException;
|
||||||
|
import com.android.emailcommon.provider.Account;
|
||||||
|
import com.android.emailcommon.provider.EmailContent.MailboxColumns;
|
||||||
|
import com.android.emailcommon.provider.Mailbox;
|
||||||
|
import com.android.emailcommon.service.SearchParams;
|
||||||
|
import com.android.emailcommon.utility.EmailAsyncTask;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -264,32 +262,19 @@ public class EmailActivity extends Activity implements View.OnClickListener, Fra
|
|||||||
// Switch to search mailbox
|
// Switch to search mailbox
|
||||||
// TODO How to handle search from within the search mailbox??
|
// TODO How to handle search from within the search mailbox??
|
||||||
final Controller controller = Controller.getInstance(mContext);
|
final Controller controller = Controller.getInstance(mContext);
|
||||||
final Mailbox searchMailbox = controller.getSearchMailbox(accountId);
|
|
||||||
if (searchMailbox == null) return;
|
|
||||||
|
|
||||||
// Delete contents, add a placeholder
|
|
||||||
ContentResolver resolver = mContext.getContentResolver();
|
|
||||||
resolver.delete(Message.CONTENT_URI, Message.MAILBOX_KEY + "=" + searchMailbox.mId,
|
|
||||||
null);
|
|
||||||
ContentValues cv = new ContentValues();
|
|
||||||
cv.put(Mailbox.DISPLAY_NAME, queryString);
|
|
||||||
resolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, searchMailbox.mId), cv,
|
|
||||||
null, null);
|
|
||||||
Message msg = new Message();
|
|
||||||
msg.mMailboxKey = searchMailbox.mId;
|
|
||||||
msg.mAccountKey = accountId;
|
|
||||||
msg.mDisplayName = "Searching for " + queryString;
|
|
||||||
msg.mTimeStamp = Long.MAX_VALUE; // Sort on top
|
|
||||||
msg.save(mContext);
|
|
||||||
|
|
||||||
startActivity(createOpenMessageIntent(EmailActivity.this,
|
|
||||||
accountId, searchMailbox.mId, msg.mId));
|
|
||||||
EmailAsyncTask.runAsyncParallel(new Runnable() {
|
EmailAsyncTask.runAsyncParallel(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
SearchParams searchSpec = new SearchParams(SearchParams.ALL_MAILBOXES,
|
// TODO Use the proper parameters (a mailbox id for IMAP, a mailbox id or
|
||||||
queryString);
|
// SearchParams.ALL_MAILBOXES for eas
|
||||||
controller.searchMessages(accountId, searchSpec, searchMailbox.mId);
|
// We assume IMAP here for testing
|
||||||
|
SearchParams searchSpec = new SearchParams(mailboxId, queryString);
|
||||||
|
try {
|
||||||
|
controller.searchMessages(accountId, searchSpec);
|
||||||
|
} catch (MessagingException e) {
|
||||||
|
// TODO What to do??
|
||||||
|
}
|
||||||
}});
|
}});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -367,14 +352,28 @@ public class EmailActivity extends Activity implements View.OnClickListener, Fra
|
|||||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||||
// STOPSHIP Temporary sync options UI
|
// STOPSHIP Temporary sync options UI
|
||||||
boolean isEas = false;
|
boolean isEas = false;
|
||||||
|
boolean canSearch = false;
|
||||||
|
|
||||||
long accountId = mUIController.getActualAccountId();
|
long accountId = mUIController.getActualAccountId();
|
||||||
if (accountId > 0) {
|
if (accountId > 0) {
|
||||||
// Move database operations out of the UI thread
|
// Move database operations out of the UI thread
|
||||||
isEas = "eas".equals(Account.getProtocol(mContext, accountId));
|
if ("eas".equals(Account.getProtocol(mContext, accountId))) {
|
||||||
|
isEas = true;
|
||||||
|
Account account = Account.restoreAccountWithId(mContext, accountId);
|
||||||
|
if (account != null) {
|
||||||
|
// We should set a flag in the account indicating ability to handle search
|
||||||
|
String protocolVersion = account.mProtocolVersion;
|
||||||
|
if (Double.parseDouble(protocolVersion) >= 12.0) {
|
||||||
|
canSearch = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ("imap".equals(Account.getProtocol(mContext, accountId))) {
|
||||||
|
canSearch = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should use an isSyncable call to prevent drafts/outbox from allowing this
|
// Should use an isSyncable call to prevent drafts/outbox from allowing this
|
||||||
|
menu.findItem(R.id.search).setVisible(canSearch);
|
||||||
menu.findItem(R.id.sync_lookback).setVisible(isEas);
|
menu.findItem(R.id.sync_lookback).setVisible(isEas);
|
||||||
menu.findItem(R.id.sync_frequency).setVisible(isEas);
|
menu.findItem(R.id.sync_frequency).setVisible(isEas);
|
||||||
|
|
||||||
|
@ -16,16 +16,6 @@
|
|||||||
|
|
||||||
package com.android.email.activity;
|
package com.android.email.activity;
|
||||||
|
|
||||||
import com.android.email.Email;
|
|
||||||
import com.android.email.R;
|
|
||||||
import com.android.email.RefreshManager;
|
|
||||||
import com.android.email.activity.setup.AccountSettings;
|
|
||||||
import com.android.emailcommon.Logging;
|
|
||||||
import com.android.emailcommon.provider.Account;
|
|
||||||
import com.android.emailcommon.provider.EmailContent.Message;
|
|
||||||
import com.android.emailcommon.provider.Mailbox;
|
|
||||||
import com.android.emailcommon.utility.EmailAsyncTask;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
import android.app.FragmentManager;
|
import android.app.FragmentManager;
|
||||||
@ -36,6 +26,16 @@ import android.view.Menu;
|
|||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
||||||
|
import com.android.email.Email;
|
||||||
|
import com.android.email.R;
|
||||||
|
import com.android.email.RefreshManager;
|
||||||
|
import com.android.email.activity.setup.AccountSettings;
|
||||||
|
import com.android.emailcommon.Logging;
|
||||||
|
import com.android.emailcommon.provider.Account;
|
||||||
|
import com.android.emailcommon.provider.EmailContent.Message;
|
||||||
|
import com.android.emailcommon.provider.Mailbox;
|
||||||
|
import com.android.emailcommon.utility.EmailAsyncTask;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -619,7 +619,8 @@ abstract class UIControllerBase implements MailboxListFragment.Callback,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Should use an isSearchable call to prevent search on inappropriate accounts/boxes
|
// Should use an isSearchable call to prevent search on inappropriate accounts/boxes
|
||||||
menu.findItem(R.id.search).setVisible(canSearch);
|
// STOPSHIP Figure out where the "canSearch" test belongs
|
||||||
|
menu.findItem(R.id.search).setVisible(true); //canSearch);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,7 @@ import com.android.emailcommon.mail.Message;
|
|||||||
import com.android.emailcommon.mail.MessagingException;
|
import com.android.emailcommon.mail.MessagingException;
|
||||||
import com.android.emailcommon.mail.Part;
|
import com.android.emailcommon.mail.Part;
|
||||||
import com.android.emailcommon.provider.Mailbox;
|
import com.android.emailcommon.provider.Mailbox;
|
||||||
|
import com.android.emailcommon.service.SearchParams;
|
||||||
import com.android.emailcommon.utility.Utility;
|
import com.android.emailcommon.utility.Utility;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -404,6 +405,30 @@ class ImapFolder extends Folder {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve messages based on search parameters. We search FROM, TO, CC, SUBJECT, and BODY
|
||||||
|
* We send: SEARCH OR FROM "foo" (OR TO "foo" (OR CC "foo" (OR SUBJECT "foo" BODY "foo")))
|
||||||
|
* TODO: Properly quote the filter
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Message[] getMessages(SearchParams params, MessageRetrievalListener listener)
|
||||||
|
throws MessagingException {
|
||||||
|
String filter = params.mFilter;
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("OR FROM \"");
|
||||||
|
sb.append(filter);
|
||||||
|
sb.append("\" (OR TO \"");
|
||||||
|
sb.append(filter);
|
||||||
|
sb.append("\" (OR CC \"");
|
||||||
|
sb.append(filter);
|
||||||
|
sb.append("\" (OR SUBJECT \"");
|
||||||
|
sb.append(filter);
|
||||||
|
sb.append("\" BODY \"");
|
||||||
|
sb.append(filter);
|
||||||
|
sb.append("\")))");
|
||||||
|
return getMessagesInternal(searchForUids(sb.toString()), listener);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Message[] getMessages(int start, int end, MessageRetrievalListener listener)
|
public Message[] getMessages(int start, int end, MessageRetrievalListener listener)
|
||||||
throws MessagingException {
|
throws MessagingException {
|
||||||
@ -414,11 +439,6 @@ class ImapFolder extends Folder {
|
|||||||
searchForUids(String.format("%d:%d NOT DELETED", start, end)), listener);
|
searchForUids(String.format("%d:%d NOT DELETED", start, end)), listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Message[] getMessages(MessageRetrievalListener listener) throws MessagingException {
|
|
||||||
return getMessages(null, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Message[] getMessages(String[] uids, MessageRetrievalListener listener)
|
public Message[] getMessages(String[] uids, MessageRetrievalListener listener)
|
||||||
throws MessagingException {
|
throws MessagingException {
|
||||||
@ -576,11 +596,12 @@ class ImapFolder extends Folder {
|
|||||||
}
|
}
|
||||||
if (fp.contains(FetchProfile.Item.BODY)
|
if (fp.contains(FetchProfile.Item.BODY)
|
||||||
|| fp.contains(FetchProfile.Item.BODY_SANE)) {
|
|| fp.contains(FetchProfile.Item.BODY_SANE)) {
|
||||||
// Body is keyed by "BODY[...".
|
// Body is keyed by "BODY[]...".
|
||||||
// TOOD Should we accept "RFC822" as well??
|
// Previously used "BODY[..." but this can be confused with "BODY[HEADER..."
|
||||||
// The old code didn't really check the key, so it accepted any literal
|
// TODO Should we accept "RFC822" as well??
|
||||||
// that first appeared.
|
ImapString body = fetchList.getKeyedStringOrEmpty("BODY[]", true);
|
||||||
ImapString body = fetchList.getKeyedStringOrEmpty("BODY[", true);
|
String bodyText = body.getString();
|
||||||
|
Log.v(Logging.LOG_TAG, bodyText);
|
||||||
InputStream bodyStream = body.getAsStream();
|
InputStream bodyStream = body.getAsStream();
|
||||||
message.parse(bodyStream);
|
message.parse(bodyStream);
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,10 @@
|
|||||||
|
|
||||||
package com.android.email.mail.store;
|
package com.android.email.mail.store;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import com.android.email.Email;
|
import com.android.email.Email;
|
||||||
import com.android.email.mail.Store;
|
import com.android.email.mail.Store;
|
||||||
import com.android.email.mail.Transport;
|
import com.android.email.mail.Transport;
|
||||||
@ -26,20 +30,17 @@ import com.android.emailcommon.mail.AuthenticationFailedException;
|
|||||||
import com.android.emailcommon.mail.FetchProfile;
|
import com.android.emailcommon.mail.FetchProfile;
|
||||||
import com.android.emailcommon.mail.Flag;
|
import com.android.emailcommon.mail.Flag;
|
||||||
import com.android.emailcommon.mail.Folder;
|
import com.android.emailcommon.mail.Folder;
|
||||||
|
import com.android.emailcommon.mail.Folder.OpenMode;
|
||||||
import com.android.emailcommon.mail.Message;
|
import com.android.emailcommon.mail.Message;
|
||||||
import com.android.emailcommon.mail.MessagingException;
|
import com.android.emailcommon.mail.MessagingException;
|
||||||
import com.android.emailcommon.mail.Folder.OpenMode;
|
|
||||||
import com.android.emailcommon.provider.Account;
|
import com.android.emailcommon.provider.Account;
|
||||||
import com.android.emailcommon.provider.HostAuth;
|
import com.android.emailcommon.provider.HostAuth;
|
||||||
import com.android.emailcommon.provider.Mailbox;
|
import com.android.emailcommon.provider.Mailbox;
|
||||||
import com.android.emailcommon.service.EmailServiceProxy;
|
import com.android.emailcommon.service.EmailServiceProxy;
|
||||||
|
import com.android.emailcommon.service.SearchParams;
|
||||||
import com.android.emailcommon.utility.LoggingInputStream;
|
import com.android.emailcommon.utility.LoggingInputStream;
|
||||||
import com.android.emailcommon.utility.Utility;
|
import com.android.emailcommon.utility.Utility;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -607,12 +608,6 @@ public class Pop3Store extends Store {
|
|||||||
mUidToMsgNumMap.put(message.getUid(), msgNum);
|
mUidToMsgNumMap.put(message.getUid(), msgNum);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Message[] getMessages(MessageRetrievalListener listener) {
|
|
||||||
throw new UnsupportedOperationException(
|
|
||||||
"Pop3Folder.getMessage(MessageRetrievalListener)");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Message[] getMessages(String[] uids, MessageRetrievalListener listener) {
|
public Message[] getMessages(String[] uids, MessageRetrievalListener listener) {
|
||||||
throw new UnsupportedOperationException(
|
throw new UnsupportedOperationException(
|
||||||
@ -947,6 +942,12 @@ public class Pop3Store extends Store {
|
|||||||
public Message createMessage(String uid) {
|
public Message createMessage(String uid) {
|
||||||
return new Pop3Message(uid, this);
|
return new Pop3Message(uid, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Message[] getMessages(SearchParams params, MessageRetrievalListener listener)
|
||||||
|
throws MessagingException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Pop3Message extends MimeMessage {
|
public static class Pop3Message extends MimeMessage {
|
||||||
|
@ -16,6 +16,16 @@
|
|||||||
|
|
||||||
package com.android.email.mail.store;
|
package com.android.email.mail.store;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.ContextWrapper;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.pm.PackageManager.NameNotFoundException;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.test.InstrumentationTestCase;
|
||||||
|
import android.test.MoreAsserts;
|
||||||
|
import android.test.suitebuilder.annotation.SmallTest;
|
||||||
|
|
||||||
import com.android.email.DBTestHelper;
|
import com.android.email.DBTestHelper;
|
||||||
import com.android.email.MockSharedPreferences;
|
import com.android.email.MockSharedPreferences;
|
||||||
import com.android.email.MockVendorPolicy;
|
import com.android.email.MockVendorPolicy;
|
||||||
@ -49,16 +59,6 @@ import com.android.emailcommon.utility.Utility;
|
|||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.ContextWrapper;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.content.pm.PackageManager.NameNotFoundException;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.test.InstrumentationTestCase;
|
|
||||||
import android.test.MoreAsserts;
|
|
||||||
import android.test.suitebuilder.annotation.SmallTest;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
@ -1979,25 +1979,6 @@ public class ImapStoreUnitTests extends InstrumentationTestCase {
|
|||||||
mFolder.getMessages(new String[] {}, null));
|
mFolder.getMessages(new String[] {}, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Test for getMessages(MessageRetrievalListener), which is the same as
|
|
||||||
* getMessages(String[] uids, MessageRetrievalListener) where uids == null.
|
|
||||||
*/
|
|
||||||
public void testGetMessages3() throws Exception {
|
|
||||||
MockTransport mock = openAndInjectMockTransport();
|
|
||||||
setupOpenFolder(mock);
|
|
||||||
mFolder.open(OpenMode.READ_WRITE, null);
|
|
||||||
|
|
||||||
mock.expect(
|
|
||||||
getNextTag(false) + " UID SEARCH 1:\\* NOT DELETED",
|
|
||||||
new String[] {
|
|
||||||
"* sEARCH 3 4 5",
|
|
||||||
getNextTag(true) + " OK success"
|
|
||||||
});
|
|
||||||
checkMessageUids(new String[] {"3", "4", "5"},
|
|
||||||
mFolder.getMessages(null));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void checkMessageUids(String[] expectedUids, Message[] actualMessages) {
|
private static void checkMessageUids(String[] expectedUids, Message[] actualMessages) {
|
||||||
ArrayList<String> list = new ArrayList<String>();
|
ArrayList<String> list = new ArrayList<String>();
|
||||||
for (Message m : actualMessages) {
|
for (Message m : actualMessages) {
|
||||||
|
@ -16,6 +16,9 @@
|
|||||||
|
|
||||||
package com.android.email.mail.store;
|
package com.android.email.mail.store;
|
||||||
|
|
||||||
|
import android.test.InstrumentationTestCase;
|
||||||
|
import android.test.suitebuilder.annotation.SmallTest;
|
||||||
|
|
||||||
import com.android.email.mail.Transport;
|
import com.android.email.mail.Transport;
|
||||||
import com.android.email.mail.transport.MockTransport;
|
import com.android.email.mail.transport.MockTransport;
|
||||||
import com.android.emailcommon.TempDirectory;
|
import com.android.emailcommon.TempDirectory;
|
||||||
@ -32,9 +35,6 @@ import com.android.emailcommon.mail.MessagingException;
|
|||||||
import com.android.emailcommon.provider.Account;
|
import com.android.emailcommon.provider.Account;
|
||||||
import com.android.emailcommon.provider.HostAuth;
|
import com.android.emailcommon.provider.HostAuth;
|
||||||
|
|
||||||
import android.test.InstrumentationTestCase;
|
|
||||||
import android.test.suitebuilder.annotation.SmallTest;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a series of unit tests for the POP3 Store class. These tests must be locally
|
* This is a series of unit tests for the POP3 Store class. These tests must be locally
|
||||||
* complete - no server(s) required.
|
* complete - no server(s) required.
|
||||||
@ -283,22 +283,6 @@ public class Pop3StoreUnitTests extends InstrumentationTestCase {
|
|||||||
// getUnreadMessageCount() always returns -1
|
// getUnreadMessageCount() always returns -1
|
||||||
assertEquals(-1, mFolder.getUnreadMessageCount());
|
assertEquals(-1, mFolder.getUnreadMessageCount());
|
||||||
|
|
||||||
// getMessages(MessageRetrievalListener listener) is unsupported
|
|
||||||
try {
|
|
||||||
mFolder.getMessages(null);
|
|
||||||
fail("Exception not thrown by getMessages()");
|
|
||||||
} catch (UnsupportedOperationException e) {
|
|
||||||
// expected - succeed
|
|
||||||
}
|
|
||||||
|
|
||||||
// getMessages(String[] uids, MessageRetrievalListener listener) is unsupported
|
|
||||||
try {
|
|
||||||
mFolder.getMessages(null, null);
|
|
||||||
fail("Exception not thrown by getMessages()");
|
|
||||||
} catch (UnsupportedOperationException e) {
|
|
||||||
// expected - succeed
|
|
||||||
}
|
|
||||||
|
|
||||||
// getPermanentFlags() returns { Flag.DELETED }
|
// getPermanentFlags() returns { Flag.DELETED }
|
||||||
Flag[] flags = mFolder.getPermanentFlags();
|
Flag[] flags = mFolder.getPermanentFlags();
|
||||||
assertEquals(1, flags.length);
|
assertEquals(1, flags.length);
|
||||||
|
@ -16,10 +16,8 @@
|
|||||||
|
|
||||||
package com.android.emailcommon.mail;
|
package com.android.emailcommon.mail;
|
||||||
|
|
||||||
import com.android.emailcommon.mail.FetchProfile;
|
import com.android.emailcommon.service.SearchParams;
|
||||||
import com.android.emailcommon.mail.Flag;
|
|
||||||
import com.android.emailcommon.mail.Folder;
|
|
||||||
import com.android.emailcommon.mail.Message;
|
|
||||||
|
|
||||||
public class MockFolder extends Folder {
|
public class MockFolder extends Folder {
|
||||||
|
|
||||||
@ -79,11 +77,6 @@ public class MockFolder extends Folder {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Message[] getMessages(MessageRetrievalListener listener) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Message[] getMessages(String[] uids, MessageRetrievalListener listener) {
|
public Message[] getMessages(String[] uids, MessageRetrievalListener listener) {
|
||||||
return null;
|
return null;
|
||||||
@ -127,4 +120,10 @@ public class MockFolder extends Folder {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Message[] getMessages(SearchParams params, MessageRetrievalListener listener)
|
||||||
|
throws MessagingException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user