1 package com.android.exchange.eas; 2 3 import android.content.ContentValues; 4 import android.content.Context; 5 import android.content.SyncResult; 6 7 import com.android.emailcommon.Logging; 8 import com.android.emailcommon.provider.Mailbox; 9 import com.android.emailcommon.service.SearchParams; 10 import com.android.exchange.CommandStatusException; 11 import com.android.exchange.Eas; 12 import com.android.exchange.EasResponse; 13 import com.android.exchange.adapter.Serializer; 14 import com.android.exchange.adapter.Tags; 15 import com.android.exchange.adapter.SearchParser; 16 import com.android.mail.providers.UIProvider; 17 import com.android.mail.utils.LogUtils; 18 19 import org.apache.http.HttpEntity; 20 import org.apache.http.entity.ByteArrayEntity; 21 22 import java.io.BufferedOutputStream; 23 import java.io.ByteArrayOutputStream; 24 import java.io.IOException; 25 import java.io.InputStream; 26 27 public class EasSearch extends EasOperation { 28 29 public final static int RESULT_NO_MESSAGES = 0; 30 public final static int RESULT_OK = 1; 31 public final static int RESULT_EMPTY_RESPONSE = 2; 32 33 // The shortest search query we'll accept 34 // TODO Check with UX whether this is correct 35 private static final int MIN_QUERY_LENGTH = 3; 36 // The largest number of results we'll ask for per server request 37 private static final int MAX_SEARCH_RESULTS = 100; 38 39 final SearchParams mSearchParams; 40 final long mDestMailboxId; 41 int mTotalResults; 42 43 public EasSearch(final Context context, final long accountId, final SearchParams searchParams, 44 final long destMailboxId) { 45 super(context, accountId); 46 mSearchParams = searchParams; 47 mDestMailboxId = destMailboxId; 48 } 49 50 public int getTotalResults() { 51 return mTotalResults; 52 } 53 54 @Override 55 protected String getCommand() { 56 return "Search"; 57 } 58 59 @Override 60 protected HttpEntity getRequestEntity() throws IOException { 61 // Sanity check for arguments 62 final int offset = mSearchParams.mOffset; 63 final int limit = mSearchParams.mLimit; 64 final String filter = mSearchParams.mFilter; 65 if (limit < 0 || limit > MAX_SEARCH_RESULTS || offset < 0) { 66 return null; 67 } 68 // TODO Should this be checked in UI? Are there guidelines for minimums? 69 if (filter == null || filter.length() < MIN_QUERY_LENGTH) { 70 LogUtils.w(LOG_TAG, "filter too short"); 71 return null; 72 } 73 74 int res = 0; 75 final Mailbox searchMailbox = Mailbox.restoreMailboxWithId(mContext, mDestMailboxId); 76 // Sanity check; account might have been deleted? 77 if (searchMailbox == null) { 78 LogUtils.i(LOG_TAG, "search mailbox ceased to exist"); 79 return null; 80 } 81 final ContentValues statusValues = new ContentValues(2); 82 try { 83 // Set the status of this mailbox to indicate query 84 statusValues.put(Mailbox.UI_SYNC_STATUS, UIProvider.SyncStatus.LIVE_QUERY); 85 searchMailbox.update(mContext, statusValues); 86 87 final Serializer s = new Serializer(); 88 s.start(Tags.SEARCH_SEARCH).start(Tags.SEARCH_STORE); 89 s.data(Tags.SEARCH_NAME, "Mailbox"); 90 s.start(Tags.SEARCH_QUERY).start(Tags.SEARCH_AND); 91 s.data(Tags.SYNC_CLASS, "Email"); 92 93 // If this isn't an inbox search, then include the collection id 94 final Mailbox inbox = 95 Mailbox.restoreMailboxOfType(mContext, mAccount.mId, Mailbox.TYPE_INBOX); 96 if (inbox == null) { 97 LogUtils.i(LOG_TAG, "Inbox ceased to exist"); 98 return null; 99 } 100 if (mSearchParams.mMailboxId != inbox.mId) { 101 s.data(Tags.SYNC_COLLECTION_ID, inbox.mServerId); 102 } 103 s.data(Tags.SEARCH_FREE_TEXT, filter); 104 105 // Add the date window if appropriate 106 if (mSearchParams.mStartDate != null) { 107 s.start(Tags.SEARCH_GREATER_THAN); 108 s.tag(Tags.EMAIL_DATE_RECEIVED); 109 s.data(Tags.SEARCH_VALUE, Eas.DATE_FORMAT.format(mSearchParams.mStartDate)); 110 s.end(); // SEARCH_GREATER_THAN 111 } 112 if (mSearchParams.mEndDate != null) { 113 s.start(Tags.SEARCH_LESS_THAN); 114 s.tag(Tags.EMAIL_DATE_RECEIVED); 115 s.data(Tags.SEARCH_VALUE, Eas.DATE_FORMAT.format(mSearchParams.mEndDate)); 116 s.end(); // SEARCH_LESS_THAN 117 } 118 s.end().end(); // SEARCH_AND, SEARCH_QUERY 119 s.start(Tags.SEARCH_OPTIONS); 120 if (offset == 0) { 121 s.tag(Tags.SEARCH_REBUILD_RESULTS); 122 } 123 if (mSearchParams.mIncludeChildren) { 124 s.tag(Tags.SEARCH_DEEP_TRAVERSAL); 125 } 126 // Range is sent in the form first-last (e.g. 0-9) 127 s.data(Tags.SEARCH_RANGE, offset + "-" + (offset + limit - 1)); 128 s.start(Tags.BASE_BODY_PREFERENCE); 129 s.data(Tags.BASE_TYPE, Eas.BODY_PREFERENCE_HTML); 130 s.data(Tags.BASE_TRUNCATION_SIZE, "20000"); 131 s.end(); // BASE_BODY_PREFERENCE 132 s.end().end().end().done(); // SEARCH_OPTIONS, SEARCH_STORE, SEARCH_SEARCH 133 return makeEntity(s); 134 } catch (IOException e) { 135 LogUtils.d(LOG_TAG, e, "Search exception"); 136 } finally { 137 // TODO: Handle error states 138 // Set the status of this mailbox to indicate query over 139 statusValues.put(Mailbox.SYNC_TIME, System.currentTimeMillis()); 140 statusValues.put(Mailbox.UI_SYNC_STATUS, UIProvider.SyncStatus.NO_SYNC); 141 searchMailbox.update(mContext, statusValues); 142 } 143 LogUtils.i(LOG_TAG, "end returning null"); 144 return null; 145 } 146 147 @Override 148 protected int handleResponse(final EasResponse response) 149 throws IOException, CommandStatusException { 150 if (response.isEmpty()) { 151 return RESULT_EMPTY_RESPONSE; 152 } 153 final InputStream is = response.getInputStream(); 154 try { 155 final Mailbox searchMailbox = Mailbox.restoreMailboxWithId(mContext, mDestMailboxId); 156 final SearchParser sp = new SearchParser(mContext, mContext.getContentResolver(), 157 is, searchMailbox, mAccount, mSearchParams.mFilter); 158 sp.parse(); 159 mTotalResults = sp.getTotalResults(); 160 } finally { 161 is.close(); 162 } 163 return RESULT_OK; 164 } 165 } 166