package com.android.exchange.eas; import android.content.ContentValues; import android.content.Context; import android.content.SyncResult; import com.android.emailcommon.Logging; import com.android.emailcommon.provider.Account; import com.android.emailcommon.provider.Mailbox; import com.android.emailcommon.service.SearchParams; import com.android.exchange.CommandStatusException; import com.android.exchange.Eas; import com.android.exchange.EasResponse; import com.android.exchange.adapter.Serializer; import com.android.exchange.adapter.Tags; import com.android.exchange.adapter.SearchParser; import com.android.mail.providers.UIProvider; import com.android.mail.utils.LogUtils; import org.apache.http.HttpEntity; import org.apache.http.entity.ByteArrayEntity; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; public class EasSearch extends EasOperation { public final static int RESULT_NO_MESSAGES = 0; public final static int RESULT_OK = 1; public final static int RESULT_EMPTY_RESPONSE = 2; // The shortest search query we'll accept // TODO Check with UX whether this is correct private static final int MIN_QUERY_LENGTH = 3; // The largest number of results we'll ask for per server request private static final int MAX_SEARCH_RESULTS = 100; final SearchParams mSearchParams; final long mDestMailboxId; int mTotalResults; Mailbox mSearchMailbox; public EasSearch(final Context context, final Account account, final SearchParams searchParams, final long destMailboxId) { super(context, account); mSearchParams = searchParams; mDestMailboxId = destMailboxId; } public int getTotalResults() { return mTotalResults; } @Override protected String getCommand() { return "Search"; } @Override protected HttpEntity getRequestEntity() throws IOException { // Sanity check for arguments final int offset = mSearchParams.mOffset; final int limit = mSearchParams.mLimit; final String filter = mSearchParams.mFilter; if (limit < 0 || limit > MAX_SEARCH_RESULTS || offset < 0) { return null; } // TODO Should this be checked in UI? Are there guidelines for minimums? if (filter == null || filter.length() < MIN_QUERY_LENGTH) { LogUtils.w(LOG_TAG, "filter too short"); return null; } int res = 0; mSearchMailbox = Mailbox.restoreMailboxWithId(mContext, mDestMailboxId); // Sanity check; account might have been deleted? if (mSearchMailbox == null) { LogUtils.i(LOG_TAG, "search mailbox ceased to exist"); return null; } try { // Set the status of this mailbox to indicate query final ContentValues statusValues = new ContentValues(1); statusValues.put(Mailbox.UI_SYNC_STATUS, UIProvider.SyncStatus.LIVE_QUERY); mSearchMailbox.update(mContext, statusValues); final Serializer s = new Serializer(); s.start(Tags.SEARCH_SEARCH).start(Tags.SEARCH_STORE); s.data(Tags.SEARCH_NAME, "Mailbox"); s.start(Tags.SEARCH_QUERY).start(Tags.SEARCH_AND); s.data(Tags.SYNC_CLASS, "Email"); // If this isn't an inbox search, then include the collection id final Mailbox inbox = Mailbox.restoreMailboxOfType(mContext, mAccount.mId, Mailbox.TYPE_INBOX); if (inbox == null) { LogUtils.i(LOG_TAG, "Inbox ceased to exist"); return null; } if (mSearchParams.mMailboxId != inbox.mId) { s.data(Tags.SYNC_COLLECTION_ID, inbox.mServerId); } s.data(Tags.SEARCH_FREE_TEXT, filter); // Add the date window if appropriate if (mSearchParams.mStartDate != null) { s.start(Tags.SEARCH_GREATER_THAN); s.tag(Tags.EMAIL_DATE_RECEIVED); s.data(Tags.SEARCH_VALUE, Eas.DATE_FORMAT.format(mSearchParams.mStartDate)); s.end(); // SEARCH_GREATER_THAN } if (mSearchParams.mEndDate != null) { s.start(Tags.SEARCH_LESS_THAN); s.tag(Tags.EMAIL_DATE_RECEIVED); s.data(Tags.SEARCH_VALUE, Eas.DATE_FORMAT.format(mSearchParams.mEndDate)); s.end(); // SEARCH_LESS_THAN } s.end().end(); // SEARCH_AND, SEARCH_QUERY s.start(Tags.SEARCH_OPTIONS); if (offset == 0) { s.tag(Tags.SEARCH_REBUILD_RESULTS); } if (mSearchParams.mIncludeChildren) { s.tag(Tags.SEARCH_DEEP_TRAVERSAL); } // Range is sent in the form first-last (e.g. 0-9) s.data(Tags.SEARCH_RANGE, offset + "-" + (offset + limit - 1)); s.start(Tags.BASE_BODY_PREFERENCE); s.data(Tags.BASE_TYPE, Eas.BODY_PREFERENCE_HTML); s.data(Tags.BASE_TRUNCATION_SIZE, "20000"); s.end(); // BASE_BODY_PREFERENCE s.end().end().end().done(); // SEARCH_OPTIONS, SEARCH_STORE, SEARCH_SEARCH return makeEntity(s); } catch (IOException e) { LogUtils.d(LOG_TAG, e, "Search exception"); } LogUtils.i(LOG_TAG, "end returning null"); return null; } @Override protected int handleResponse(final EasResponse response) throws IOException, CommandStatusException { if (response.isEmpty()) { return RESULT_EMPTY_RESPONSE; } final InputStream is = response.getInputStream(); try { final Mailbox searchMailbox = Mailbox.restoreMailboxWithId(mContext, mDestMailboxId); final SearchParser sp = new SearchParser(mContext, mContext.getContentResolver(), is, searchMailbox, mAccount, mSearchParams.mFilter); sp.parse(); mTotalResults = sp.getTotalResults(); } finally { is.close(); } return RESULT_OK; } protected void onRequestComplete() { if (mSearchMailbox != null) { // TODO: Handle error states final ContentValues statusValues = new ContentValues(2); statusValues.put(Mailbox.UI_SYNC_STATUS, UIProvider.SyncStatus.NO_SYNC); statusValues.put(Mailbox.SYNC_TIME, System.currentTimeMillis()); mSearchMailbox.update(mContext, statusValues); } } }