Support "Load More" for IMAP search

Change-Id: I47e5fd1fab421026ee52af72ba56338c69e9b467
This commit is contained in:
Marc Blank 2011-06-17 11:43:32 -07:00
parent ac11e3bb99
commit ed1dc9ee72
4 changed files with 140 additions and 85 deletions

View File

@ -390,7 +390,6 @@ public class Mailbox extends EmailContent implements SyncColumns, MailboxColumns
}
switch (getMailboxType(context, mailboxId)) {
case -1: // not found
case TYPE_SEARCH:
case TYPE_DRAFTS:
case TYPE_OUTBOX:
return false;

View File

@ -16,15 +16,15 @@
package com.android.emailcommon.service;
import com.android.emailcommon.provider.Mailbox;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.emailcommon.provider.Mailbox;
public class SearchParams implements Parcelable {
public static final long ALL_MAILBOXES = Mailbox.NO_MAILBOX;
private static final int DEFAULT_LIMIT = 20;
private static final int DEFAULT_LIMIT = 10; // Need input on what this number should be
private static final int DEFAULT_OFFSET = 0;
// The id of the mailbox to be searched; if -1, all mailboxes MUST be searched

View File

@ -62,6 +62,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.concurrent.ConcurrentHashMap;
@ -392,8 +393,7 @@ public class Controller {
IEmailService service = getServiceForAccount(accountId);
if (service != null) {
// Service implementation
try {
try {
service.startSync(mailboxId, userRequest);
} catch (RemoteException e) {
// TODO Change exception handling to be consistent with however this method
@ -409,7 +409,8 @@ public class Controller {
Account.restoreAccountWithId(mProviderContext, accountId);
Mailbox mailbox =
Mailbox.restoreMailboxWithId(mProviderContext, mailboxId);
if (account == null || mailbox == null) {
if (account == null || mailbox == null ||
mailbox.mType == Mailbox.TYPE_SEARCH) {
return;
}
mLegacyController.synchronizeMailbox(account, mailbox, mLegacyListener);
@ -669,12 +670,20 @@ public class Controller {
* @param mailboxId the mailbox
*/
public void loadMoreMessages(final long mailboxId) {
Utility.runAsync(new Runnable() {
EmailAsyncTask.runAsyncParallel(new Runnable() {
public void run() {
Mailbox mailbox = Mailbox.restoreMailboxWithId(mProviderContext, mailboxId);
if (mailbox == null) {
return;
}
if (mailbox.mType == Mailbox.TYPE_SEARCH) {
try {
searchMore(mailbox.mAccountKey);
} catch (MessagingException e) {
// Nothing to be done
}
return;
}
Account account = Account.restoreAccountWithId(mProviderContext,
mailbox.mAccountKey);
if (account == null) {
@ -886,6 +895,16 @@ public class Controller {
});
}
private static final HashMap<Long, SearchParams> sSearchParamsMap =
new HashMap<Long, SearchParams>();
public void searchMore(long accountId) throws MessagingException {
SearchParams params = sSearchParamsMap.get(accountId);
if (params == null) return;
params.mOffset += params.mLimit;
searchMessages(accountId, params);
}
/**
* Search for messages on the (IMAP) server; do not call this on the UI thread!
* @param accountId the id of the account to be searched
@ -898,7 +917,8 @@ public class Controller {
Mailbox searchMailbox = getSearchMailbox(accountId);
if (searchMailbox == null) return;
final long searchMailboxId = searchMailbox.mId;
// Save this away (per account)
sSearchParamsMap.put(accountId, searchParams);
IEmailService service = getServiceForAccount(accountId);
if (service != null) {
// Service implementation
@ -918,18 +938,20 @@ public class Controller {
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);
if (searchParams.mOffset == 0) {
// 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);

View File

@ -108,6 +108,14 @@ public class MessagingController implements Runnable {
*/
private static final String LOCAL_SERVERID_PREFIX = "Local-";
/**
* 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<Long, SortableMessage[]> sSearchResults =
new HashMap<Long, SortableMessage[]>();
private static final ContentValues PRUNE_ATTACHMENT_CV = new ContentValues();
static {
PRUNE_ATTACHMENT_CV.putNull(AttachmentColumns.CONTENT_URI);
@ -572,8 +580,19 @@ public class MessagingController implements Runnable {
}
}
public void searchMailbox(long accountId, SearchParams searchParams, final long destMailboxId)
public void searchMailbox(long accountId, SearchParams searchParams, long destMailboxId)
throws MessagingException {
try {
searchMailboxImpl(accountId, searchParams, destMailboxId);
} finally {
// Tell UI that we're done loading any search results (no harm calling this even if we
// encountered an error or never sent a "started" message)
mListeners.synchronizeMailboxFinished(accountId, destMailboxId, 0, 0, null);
}
}
private void searchMailboxImpl(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);
@ -583,77 +602,92 @@ public class MessagingController implements Runnable {
return;
}
// Tell UI that we're loading messages
mListeners.synchronizeMailboxStarted(accountId, destMailbox.mId);
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;
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()));
}
});
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);
// 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<SortableMessage>() {
@Override
public int compare(SortableMessage lhs, SortableMessage rhs) {
return lhs.mUid > rhs.mUid ? -1 : lhs.mUid < rhs.mUid ? 1 : 0;
}
});
sSearchResults.put(accountId, sortableMessages);
}
// 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());
}
}
} else {
sortableMessages = sSearchResults.get(accountId);
}
@Override
public void loadAttachmentProgress(int progress) {
int numSearchResults = sortableMessages.length;
int numToLoad = Math.min(numSearchResults - searchParams.mOffset, searchParams.mLimit);
if (numToLoad <= 0) {
return;
}
final ArrayList<Message> messageList = new ArrayList<Message>();
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() {
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.