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